Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ $(MODULES_PATH)/emlkmeans.mpy:
$(MODULES_PATH)/eml_iir_q15.mpy:
make -C src/eml_iir_q15/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean dist

$(MODULES_PATH)/emlearn_arrayutils.mpy:
make -C src/emlearn_arrayutils/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean dist

emltrees.results: $(MODULES_PATH)/emltrees.mpy
MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_trees.py

Expand All @@ -52,6 +55,9 @@ emlkmeans.results: $(MODULES_PATH)/emlkmeans.mpy
eml_iir_q15.results: $(MODULES_PATH)/eml_iir_q15.mpy
MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_iir_q15.py

emlearn_arrayutils.results: $(MODULES_PATH)/emlearn_arrayutils.mpy
MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_arrayutils.py

.PHONY: clean

clean:
Expand All @@ -68,8 +74,8 @@ release:
zip -r $(RELEASE_NAME).zip $(RELEASE_NAME)
#cp $(RELEASE_NAME).zip emlearn-micropython-latest.zip

check: emltrees.results emlneighbors.results emliir.results eml_iir_q15.results emlfft.results emlkmeans.results tinymaix_cnn.results
check: emltrees.results emlneighbors.results emliir.results eml_iir_q15.results emlfft.results emlkmeans.results emlearn_arrayutils.results tinymaix_cnn.results

dist: $(MODULES_PATH)/emltrees.mpy $(MODULES_PATH)/emlneighbors.mpy $(MODULES_PATH)/emliir.mpy $(MODULES_PATH)/eml_iir_q15.mpy $(MODULES_PATH)/emlfft.mpy $(MODULES_PATH)/emlkmeans.mpy $(MODULES_PATH)/tinymaix_cnn.mpy
dist: $(MODULES_PATH)/emltrees.mpy $(MODULES_PATH)/emlneighbors.mpy $(MODULES_PATH)/emliir.mpy $(MODULES_PATH)/eml_iir_q15.mpy $(MODULES_PATH)/emlfft.mpy $(MODULES_PATH)/emlkmeans.mpy $(MODULES_PATH)/emlearn_arrayutils.mpy $(MODULES_PATH)/tinymaix_cnn.mpy


1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ It can be combined with feature preprocessing, including neural networks to addr
- [xor_trees](./examples/xor_trees/). A "Hello World", using RandomForest.
- [mnist_cnn](./examples/mnist_cnn/). Basic image classification, using Convolutional Neural Network.
- [har_trees](./examples/har_trees/). Accelerometer-based Human Activity Recognition, using Random Forest
- [soundlevel_iir](./examples/har_trees/). Sound Level Meter, using Infinite Impulse Response (IIR) filters.

## Prerequisites

Expand Down
80 changes: 80 additions & 0 deletions examples/soundlevel_iir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

# Sound level meter using Infinite Impulse Response (IIR) filters

This is a sound level meter implemented in MicroPython with
emlearn-micropython.
It implements the standard processing typically used in a
sound level meter used for noise measurements:
A frequency weighting and Fast (125ms) or Slow (1second)
time integration.
It then computes the soundlevel in decibels.
When using Fast integration, this measurement is known as LAF.
Or with Slow integration time, known as LAS.

## Hardware requirements

The device must have an `I2S` microphone,
and support the `machine.I2S` MicroPython module.
It has been tested on an ESP32 device, namely the LilyGo T-Camera Mic v1.6.

## Notes on measurement correctness

NOTE: In order to have reasonable output values,
the microphone sensitivity must be correctly specified.
Ideally you also check/calibrate wrt to a know good sound level meter.

NOTE: There is no compensation for non-linear frequency responses in microphone.

## Install requirements

Make sure to have Python 3.10+ installed.

Make sure to have the Unix port of MicroPython 1.23 setup.
On Windows you can use Windows Subsystem for Linux (WSL), or Docker.

Install the dependencies of this example:
```console
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

## Running on host

```console
curl -o emliir.mpy https://github.com/emlearn/emlearn-micropython/raw/refs/heads/gh-pages/builds/master/x64_6.3/emliir.mpy
curl -o emlearn_arrayutils.mpy https://github.com/emlearn/emlearn-micropython/raw/refs/heads/gh-pages/builds/master/x64_6.3/emlearn_arrayutils.mpy

micropython soundlevel_test.py
```

## Running on device

!Make sure you have it running successfully on host first.

Flash your device with a standard MicroPython firmware,
from the MicroPython.org downloads page.

Download native modules.
```console

```

```console
mpremote cp device/emliir.mpy :
mpremote cp device/emlearn_arrayutils.mpy :
mpremote cp soundlevel.py :
mpremote run soundlevel_run.py
```

## Running with live camera input

This example requires hardware with SSD1306 screen,
in addition to an I2S microphone.
It has been tested on Lilygo T-Camera Mic v1.6.
By adapting the pins, it will probably also work on Lilygo T-Camera S3 v1.6.

```
mpremote run soundlevel_screen.py
```

13 changes: 13 additions & 0 deletions examples/soundlevel_iir/color_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

import gc
from machine import I2C, Pin

from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD

# LiLyGo T-Camera Mic
i2c = I2C(scl=Pin(22), sda=Pin(21))

oled_width = 128
oled_height = 64
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(oled_width, oled_height, i2c)
95 changes: 95 additions & 0 deletions examples/soundlevel_iir/iot_blynk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@


import time
import requests

class BlynkClient():
"""
Ref:
https://docs.blynk.io/en/blynk.cloud/device-https-api/upload-set-of-data-with-timestamps-api

"""

def __init__(self,
token,
hostname='blynk.cloud',
protocol='https',
):

self._telemetry_url = protocol + '://' + hostname + '/external/api/batch/update?token=' + token


def post_telemetry(self, values : list[dict[str, float]]):
"""
Send multiple telemetry values.
The 'time' key should be in Unix milliseconds.
"""
stream_values = {}

# Blynk HTTP API currently cannot send multiple timestamped values on multiple streams
# so we shuffle the data into being organized per-stream
for datapoint in values:
if 'time' in datapoint.keys():
t = datapoint['time']
for key, value in datapoint.items():
if key == 'time':
continue
if key not in stream_values.keys():
stream_values[key] = []
stream_values[key].append((t, value))
else:
print('WARNING: ignored datapoint without time')

# NOTE: if no timestamps are provided (and there are multiple values)
# then regular batch update would be better, since it could be done in 1 request
# https://docs.blynk.io/en/blynk.cloud/device-https-api/update-multiple-datastreams-api

for key, datapoints in stream_values.items():
self.post_timestamped_values(key, datapoints)

def post_timestamped_values(self, pin : str, values : list[tuple[int, float]]):
"""
Post multiple values from different times, for 1 stream
Each entry in values must be a tuple with (timestamp, value)
"""

payload = values
url = self._telemetry_url+f'&pin={pin}'
r = requests.post(url, json=payload)
assert r.status_code == 200, (r.status_code, r.content)

def unix_time_seconds():
timestamp = time.time()
epoch_year = time.gmtime(0)[0]

if epoch_year == 2020:
# seconds between 2000 (MicroPython epoch) and 1970 (Unix epoch)
epoch_difference = 946684800
timestamp = timestamp + epoch_difference
elif epoch_year == 1970:
pass
else:
raise ValueError('Unknown epoch year')

return float(timestamp)

def main():
# bc3ab311-4e92-11ef-b45a-8f71ad378839
BLYNK_AUTH_TOKEN = 'Cxvp01Mvo2-A8er9mGRWLfHnPcTNvaTP'
api = BlynkClient(token=BLYNK_AUTH_TOKEN)

t = int(unix_time_seconds() * 1000)

values = []
for s in range(0, 60, 10):
v = {'time': t-(s*1000), 'V1': 78.0+s}
values.append(v)

api.post_telemetry(values)
print(values)
print('Posted telemetry')

if __name__ == '__main__':
main()


71 changes: 71 additions & 0 deletions examples/soundlevel_iir/iot_thingsboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

import time
import requests

class ThingsBoard():
"""
Ref:
https://thingsboard.io/docs/reference/http-api/
"""

def __init__(self,
token,
hostname='thingsboard.cloud',
protocol='https',
):


self._telemetry_url = protocol + '://' + hostname + '/api/v1/' + token + '/telemetry'

def post_telemetry(self, values : list[dict]):
"""
Send multiple telemetry values.
The 'time' key should be in Unix milliseconds.
"""

def encode_one(d):
o = { 'values': {} }
if 'time' in d:
o['ts'] = d['time']
for k, v in d.items():
if k == 'time':
continue
o['values'][k] = v
return o

payload = [ encode_one(v) for v in values ]


r = requests.post(self._telemetry_url, json=payload)
assert r.status_code == 200, (r.status_code, r.content)

def unix_time_seconds():
timestamp = time.time()
epoch_year = time.gmtime(0)[0]

if epoch_year == 2020:
# seconds between 2000 (MicroPython epoch) and 1970 (Unix epoch)
epoch_difference = 946684800
timestamp = timestamp + epoch_difference
elif epoch_year == 1970:
pass
else:
raise ValueError('Unknown epoch year')

return float(timestamp)

# bc3ab311-4e92-11ef-b45a-8f71ad378839
ACCESS_TOKEN = '7AiV0dXRPWKrxrLcI4wO'
api = ThingsBoard(token=ACCESS_TOKEN)

t = int(unix_time_seconds() * 1000)

values = []
for s in range(0, 60, 10):
v = {'time': t-(s*1000), 'db2': 78.0+s, 'hex': 'ABCEDFE123122312452231DFABCEDF'}
values.append(v)

api.post_telemetry(values)
print(values)
print('Posted telemetry')

Loading
Loading