diff --git a/boot_manager.py b/boot_manager.py index c0ac318..323dd43 100644 --- a/boot_manager.py +++ b/boot_manager.py @@ -290,7 +290,7 @@ def run(): if caps.get('has_nfc'): from provisioning_nfc import run_provisioning_nfc _show(['PROVISIONING', 'NFC mode', '', 'Tap writer badge', '']) - cfg = run_provisioning_nfc(_nfc) + cfg = run_provisioning_nfc() else: from provisioning_server import run_provisioning wlan_sta = network.WLAN(network.STA_IF) @@ -335,11 +335,11 @@ def run(): if node_type == 'sensor': from node_sensor import run_sensor - run_sensor(cfg, caps) + run_sensor(cfg, caps, checkin_result['token']) elif node_type == 'relay': from node_relay import run_relay - run_relay(cfg, caps) + run_relay(cfg, caps, checkin_result['token']) else: print('[operational] unknown node_type:', node_type) diff --git a/config_example.json b/config_example.json index 7fa4605..fbb52f4 100644 --- a/config_example.json +++ b/config_example.json @@ -13,7 +13,7 @@ "node_type": "sensor", "node_label": "node-01", "identity_iot_url": "https://api.yourhost.com/API/V1/identity_layer/identity_iot/", - "data_destination_url": "", + "data_destination_url": "https://api.parta.app/API/V1/identity_iot/", "pinout": { "i2c0_sda": 4, "i2c0_scl": 5, diff --git a/node_relay.py b/node_relay.py index 4f6c79c..74f90e8 100644 --- a/node_relay.py +++ b/node_relay.py @@ -7,7 +7,7 @@ import utime -def run_relay(cfg, caps): +def run_relay(cfg, caps, jwt_token=''): """Main loop for relay node type. Called from boot_manager.""" print('[relay] starting — relay:', caps.get('has_relay')) diff --git a/node_sensor.py b/node_sensor.py index a19db80..f422bdf 100644 --- a/node_sensor.py +++ b/node_sensor.py @@ -5,16 +5,140 @@ # ============================================================+ import utime +import ujson +import machine + +try: + import uurequests as requests +except ImportError: + import urequests as requests + +from identity_iot_aes import aes_cbc_encrypt, aes_cbc_decrypt -def run_sensor(cfg, caps): - """Main loop for sensor node type. Called from boot_manager.""" - print('[sensor] starting — sensor:', caps.get('has_sensor')) +def _read_internal_temp(): + sensor = machine.ADC(4) + reading = sensor.read_u16() + voltage = reading * 3.3 / 65535 + temp_c = 27 - (voltage - 0.706) / 0.001721 + return round(temp_c, 2) - # TODO: read DHT22 / other sensor - # TODO: POST readings to backend via AES channel - # TODO: polling interval from cfg + +def _read_dht22(dht_sensor): + try: + dht_sensor.measure() + return { + 'temperature': dht_sensor.temperature(), + 'humidity': dht_sensor.humidity(), + } + except Exception as e: + print('[sensor] DHT22 read error:', e) + return None + + +def _show_reading(reading, label): + try: + import boot_manager + temp = reading.get('temperature', reading.get('temp_internal', '--')) + hum = reading.get('humidity') + boot_manager._show([ + 'Identity IoT', + label or 'sensor', + 'Temp: ' + str(temp) + 'C', + 'Hum: ' + (str(hum) + '%' if hum is not None else '--'), + '', + ]) + except Exception as e: + print('[sensor] display error:', e) + + +def _post_reading(cfg, key, iv, payload, jwt_token=''): + url = cfg.get('data_destination_url', '') + if not url: + print('[sensor] no data_destination_url configured') + return False + try: + body = ujson.dumps({'data': aes_cbc_encrypt(ujson.dumps(payload), key, iv)}) + resp = requests.post( + url, + headers={ + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + jwt_token, + }, + data=body, + timeout=10, + ) + raw = resp.text + resp.close() + outer = ujson.loads(raw) + clear = aes_cbc_decrypt(outer['data'], key, iv) + result = ujson.loads(clear) + if result.get('status') == 'ok': + print('[sensor] reading posted ok') + return True + print('[sensor] post error:', result.get('code', 'unknown')) + return False + except Exception as e: + print('[sensor] post failed:', e) + return False + + +def run_sensor(cfg, caps, jwt_token=''): + print('[sensor] starting — has_sensor:', caps.get('has_sensor')) + + key = cfg['comm_key'].encode() if isinstance(cfg['comm_key'], str) else cfg['comm_key'] + iv = cfg['comm_iv'].encode() if isinstance(cfg['comm_iv'], str) else cfg['comm_iv'] + + poll_interval = int(cfg.get('poll_interval', 60)) + temp_min = cfg.get('temp_min') + temp_max = cfg.get('temp_max') + node_id = cfg.get('node_id', '') + tenant_id = cfg.get('tenant_id', '') + label = cfg.get('node_label', '') + + dht_sensor = None + if caps.get('has_sensor'): + try: + import dht + from machine import Pin + dht_pin = cfg.get('pinout', {}).get('dht_pin', 15) + dht_sensor = dht.DHT22(Pin(dht_pin)) + except Exception as e: + print('[sensor] DHT22 init error:', e) while True: - print('[sensor] polling...') - utime.sleep(10) + reading = {} + reading['temp_internal'] = _read_internal_temp() + + if dht_sensor: + dht_data = _read_dht22(dht_sensor) + if dht_data: + reading['temperature'] = dht_data['temperature'] + reading['humidity'] = dht_data['humidity'] + + print('[sensor] reading:', reading) + _show_reading(reading, label) + + should_post = True + if temp_min is not None or temp_max is not None: + temp = reading.get('temperature', reading.get('temp_internal')) + in_range = True + if temp_min is not None and temp < float(temp_min): + in_range = False + if temp_max is not None and temp > float(temp_max): + in_range = False + if in_range: + should_post = False + + if should_post: + payload = { + 'request': 'iot_node_reading', + 'node_id': node_id, + 'tenant_id': tenant_id, + 'label': label, + 'reading': reading, + 'ts': utime.time(), + } + _post_reading(cfg, key, iv, payload, jwt_token) + + utime.sleep(poll_interval)