Skip to content

Commit f6356bd

Browse files
authored
Merge pull request #16 from emlearn/array-map-linear
Add arrayutils with linear_map function
2 parents 23a0d81 + 5ce3a03 commit f6356bd

File tree

11 files changed

+944
-2
lines changed

11 files changed

+944
-2
lines changed

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ $(MODULES_PATH)/emlkmeans.mpy:
3131
$(MODULES_PATH)/eml_iir_q15.mpy:
3232
make -C src/eml_iir_q15/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean dist
3333

34+
$(MODULES_PATH)/emlearn_arrayutils.mpy:
35+
make -C src/emlearn_arrayutils/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean dist
36+
3437
emltrees.results: $(MODULES_PATH)/emltrees.mpy
3538
MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_trees.py
3639

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

58+
emlearn_arrayutils.results: $(MODULES_PATH)/emlearn_arrayutils.mpy
59+
MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_arrayutils.py
60+
5561
.PHONY: clean
5662

5763
clean:
@@ -68,8 +74,8 @@ release:
6874
zip -r $(RELEASE_NAME).zip $(RELEASE_NAME)
6975
#cp $(RELEASE_NAME).zip emlearn-micropython-latest.zip
7076

71-
check: emltrees.results emlneighbors.results emliir.results eml_iir_q15.results emlfft.results emlkmeans.results tinymaix_cnn.results
77+
check: emltrees.results emlneighbors.results emliir.results eml_iir_q15.results emlfft.results emlkmeans.results emlearn_arrayutils.results tinymaix_cnn.results
7278

73-
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
79+
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
7480

7581

README.md

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

4344
## Prerequisites
4445

examples/soundlevel_iir/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
# Sound level meter using Infinite Impulse Response (IIR) filters
3+
4+
This is a sound level meter implemented in MicroPython with
5+
emlearn-micropython.
6+
It implements the standard processing typically used in a
7+
sound level meter used for noise measurements:
8+
A frequency weighting and Fast (125ms) or Slow (1second)
9+
time integration.
10+
It then computes the soundlevel in decibels.
11+
When using Fast integration, this measurement is known as LAF.
12+
Or with Slow integration time, known as LAS.
13+
14+
## Hardware requirements
15+
16+
The device must have an `I2S` microphone,
17+
and support the `machine.I2S` MicroPython module.
18+
It has been tested on an ESP32 device, namely the LilyGo T-Camera Mic v1.6.
19+
20+
## Notes on measurement correctness
21+
22+
NOTE: In order to have reasonable output values,
23+
the microphone sensitivity must be correctly specified.
24+
Ideally you also check/calibrate wrt to a know good sound level meter.
25+
26+
NOTE: There is no compensation for non-linear frequency responses in microphone.
27+
28+
## Install requirements
29+
30+
Make sure to have Python 3.10+ installed.
31+
32+
Make sure to have the Unix port of MicroPython 1.23 setup.
33+
On Windows you can use Windows Subsystem for Linux (WSL), or Docker.
34+
35+
Install the dependencies of this example:
36+
```console
37+
python -m venv venv
38+
source venv/bin/activate
39+
pip install -r requirements.txt
40+
```
41+
42+
## Running on host
43+
44+
```console
45+
curl -o emliir.mpy https://github.com/emlearn/emlearn-micropython/raw/refs/heads/gh-pages/builds/master/x64_6.3/emliir.mpy
46+
curl -o emlearn_arrayutils.mpy https://github.com/emlearn/emlearn-micropython/raw/refs/heads/gh-pages/builds/master/x64_6.3/emlearn_arrayutils.mpy
47+
48+
micropython soundlevel_test.py
49+
```
50+
51+
## Running on device
52+
53+
!Make sure you have it running successfully on host first.
54+
55+
Flash your device with a standard MicroPython firmware,
56+
from the MicroPython.org downloads page.
57+
58+
Download native modules.
59+
```console
60+
61+
```
62+
63+
```console
64+
mpremote cp device/emliir.mpy :
65+
mpremote cp device/emlearn_arrayutils.mpy :
66+
mpremote cp soundlevel.py :
67+
mpremote run soundlevel_run.py
68+
```
69+
70+
## Running with live camera input
71+
72+
This example requires hardware with SSD1306 screen,
73+
in addition to an I2S microphone.
74+
It has been tested on Lilygo T-Camera Mic v1.6.
75+
By adapting the pins, it will probably also work on Lilygo T-Camera S3 v1.6.
76+
77+
```
78+
mpremote run soundlevel_screen.py
79+
```
80+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
import gc
3+
from machine import I2C, Pin
4+
5+
from drivers.ssd1306.ssd1306 import SSD1306_I2C as SSD
6+
7+
# LiLyGo T-Camera Mic
8+
i2c = I2C(scl=Pin(22), sda=Pin(21))
9+
10+
oled_width = 128
11+
oled_height = 64
12+
gc.collect() # Precaution before instantiating framebuf
13+
ssd = SSD(oled_width, oled_height, i2c)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
3+
import time
4+
import requests
5+
6+
class BlynkClient():
7+
"""
8+
Ref:
9+
https://docs.blynk.io/en/blynk.cloud/device-https-api/upload-set-of-data-with-timestamps-api
10+
11+
"""
12+
13+
def __init__(self,
14+
token,
15+
hostname='blynk.cloud',
16+
protocol='https',
17+
):
18+
19+
self._telemetry_url = protocol + '://' + hostname + '/external/api/batch/update?token=' + token
20+
21+
22+
def post_telemetry(self, values : list[dict[str, float]]):
23+
"""
24+
Send multiple telemetry values.
25+
The 'time' key should be in Unix milliseconds.
26+
"""
27+
stream_values = {}
28+
29+
# Blynk HTTP API currently cannot send multiple timestamped values on multiple streams
30+
# so we shuffle the data into being organized per-stream
31+
for datapoint in values:
32+
if 'time' in datapoint.keys():
33+
t = datapoint['time']
34+
for key, value in datapoint.items():
35+
if key == 'time':
36+
continue
37+
if key not in stream_values.keys():
38+
stream_values[key] = []
39+
stream_values[key].append((t, value))
40+
else:
41+
print('WARNING: ignored datapoint without time')
42+
43+
# NOTE: if no timestamps are provided (and there are multiple values)
44+
# then regular batch update would be better, since it could be done in 1 request
45+
# https://docs.blynk.io/en/blynk.cloud/device-https-api/update-multiple-datastreams-api
46+
47+
for key, datapoints in stream_values.items():
48+
self.post_timestamped_values(key, datapoints)
49+
50+
def post_timestamped_values(self, pin : str, values : list[tuple[int, float]]):
51+
"""
52+
Post multiple values from different times, for 1 stream
53+
Each entry in values must be a tuple with (timestamp, value)
54+
"""
55+
56+
payload = values
57+
url = self._telemetry_url+f'&pin={pin}'
58+
r = requests.post(url, json=payload)
59+
assert r.status_code == 200, (r.status_code, r.content)
60+
61+
def unix_time_seconds():
62+
timestamp = time.time()
63+
epoch_year = time.gmtime(0)[0]
64+
65+
if epoch_year == 2020:
66+
# seconds between 2000 (MicroPython epoch) and 1970 (Unix epoch)
67+
epoch_difference = 946684800
68+
timestamp = timestamp + epoch_difference
69+
elif epoch_year == 1970:
70+
pass
71+
else:
72+
raise ValueError('Unknown epoch year')
73+
74+
return float(timestamp)
75+
76+
def main():
77+
# bc3ab311-4e92-11ef-b45a-8f71ad378839
78+
BLYNK_AUTH_TOKEN = 'Cxvp01Mvo2-A8er9mGRWLfHnPcTNvaTP'
79+
api = BlynkClient(token=BLYNK_AUTH_TOKEN)
80+
81+
t = int(unix_time_seconds() * 1000)
82+
83+
values = []
84+
for s in range(0, 60, 10):
85+
v = {'time': t-(s*1000), 'V1': 78.0+s}
86+
values.append(v)
87+
88+
api.post_telemetry(values)
89+
print(values)
90+
print('Posted telemetry')
91+
92+
if __name__ == '__main__':
93+
main()
94+
95+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
import time
3+
import requests
4+
5+
class ThingsBoard():
6+
"""
7+
Ref:
8+
https://thingsboard.io/docs/reference/http-api/
9+
"""
10+
11+
def __init__(self,
12+
token,
13+
hostname='thingsboard.cloud',
14+
protocol='https',
15+
):
16+
17+
18+
self._telemetry_url = protocol + '://' + hostname + '/api/v1/' + token + '/telemetry'
19+
20+
def post_telemetry(self, values : list[dict]):
21+
"""
22+
Send multiple telemetry values.
23+
The 'time' key should be in Unix milliseconds.
24+
"""
25+
26+
def encode_one(d):
27+
o = { 'values': {} }
28+
if 'time' in d:
29+
o['ts'] = d['time']
30+
for k, v in d.items():
31+
if k == 'time':
32+
continue
33+
o['values'][k] = v
34+
return o
35+
36+
payload = [ encode_one(v) for v in values ]
37+
38+
39+
r = requests.post(self._telemetry_url, json=payload)
40+
assert r.status_code == 200, (r.status_code, r.content)
41+
42+
def unix_time_seconds():
43+
timestamp = time.time()
44+
epoch_year = time.gmtime(0)[0]
45+
46+
if epoch_year == 2020:
47+
# seconds between 2000 (MicroPython epoch) and 1970 (Unix epoch)
48+
epoch_difference = 946684800
49+
timestamp = timestamp + epoch_difference
50+
elif epoch_year == 1970:
51+
pass
52+
else:
53+
raise ValueError('Unknown epoch year')
54+
55+
return float(timestamp)
56+
57+
# bc3ab311-4e92-11ef-b45a-8f71ad378839
58+
ACCESS_TOKEN = '7AiV0dXRPWKrxrLcI4wO'
59+
api = ThingsBoard(token=ACCESS_TOKEN)
60+
61+
t = int(unix_time_seconds() * 1000)
62+
63+
values = []
64+
for s in range(0, 60, 10):
65+
v = {'time': t-(s*1000), 'db2': 78.0+s, 'hex': 'ABCEDFE123122312452231DFABCEDF'}
66+
values.append(v)
67+
68+
api.post_telemetry(values)
69+
print(values)
70+
print('Posted telemetry')
71+

0 commit comments

Comments
 (0)