feat(fusion_iot): live DS18B20 poller for Pi-side — first real tank reading in Odoo
Phase B kickoff — Pi hardware is wired up and posting readings to Odoo via /fp/iot/ingest every 30 seconds. No more simulations; this is real tank-temperature data. New files: - `pi/fp_iot_poller.py` — tiny systemd daemon. Reads every DS18B20 under /sys/bus/w1/devices/28-* (kernel CRC-validated) and POSTs a batch to /fp/iot/ingest with the shared-secret token. Handles transient network failures by logging + retrying on the next 30-second tick. Config in /etc/fp-iot/poller.conf (ODOO_URL, INGEST_TOKEN, INTERVAL_SECONDS). - `pi/fp-iot-poller.service` — systemd unit with hardened sandbox (NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, ReadOnlyPaths=/sys/bus/w1 /etc/fp-iot). Auto-starts on boot, restarts on failure. - `scripts/fp_iot_setup_live_sensor.py` — one-shot entech initialiser: rotates the ingest token to a real random secret, picks a test tank + temperature parameter, creates the fp.tank.sensor record for serial 28-000000b276e4 with 15-35°C alert thresholds sized for bench testing. Verified end-to-end: Pi reads probe → posts to Odoo → reading lands in fp.tank.reading within 1s. 5 consecutive readings at 30s cadence show smooth temperature trend (probe cooling from 27.25°C to 26.06°C after being handled). in_spec flag correct, sensor cache (last_reading_value / _at / _in_spec) updates on every reading. Not yet done — Phase B continued: - Repackaged iot_drivers path (full Odoo IoT integration vs this simple HTTP path) — this poller is the minimal viable pilot. - Multi-probe (scalable to N probes per Pi; code already supports, just need more hardware). - Graduate to the proper iot.device + iot.box Odoo registration flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
fusion_iot/scripts/fp_iot_setup_live_sensor.py
Normal file
79
fusion_iot/scripts/fp_iot_setup_live_sensor.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""One-shot setup for the live Pi probe.
|
||||
|
||||
Does three things:
|
||||
1. Rotates fusion_plating_iot.ingest_token to a real random secret.
|
||||
2. Picks (or creates) a test tank + temperature parameter.
|
||||
3. Creates the fp.tank.sensor record mapped to the real probe serial
|
||||
(28-000000b276e4), with plating-realistic alert thresholds.
|
||||
|
||||
Run:
|
||||
cat fp_iot_setup_live_sensor.py | odoo shell -c /etc/odoo/odoo.conf -d admin --no-http
|
||||
|
||||
Prints the new token at the end — copy it into the Pi poller script.
|
||||
"""
|
||||
import secrets
|
||||
|
||||
env = env # noqa — odoo shell
|
||||
|
||||
SERIAL = '28-000000b276e4'
|
||||
SENSOR_NAME = 'Pi IoT pilot — DS18B20 #1'
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 1. Rotate token
|
||||
# -----------------------------------------------------------
|
||||
new_token = f'fp-iot-{secrets.token_urlsafe(24)}'
|
||||
env['ir.config_parameter'].sudo().set_param(
|
||||
'fusion_plating_iot.ingest_token', new_token,
|
||||
)
|
||||
print(f'\n>>> Token rotated to: {new_token}')
|
||||
print(' (use this in the Pi poller X-FP-IOT-Token header)\n')
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 2. Pick / create test tank + temperature parameter
|
||||
# -----------------------------------------------------------
|
||||
tank = env['fusion.plating.tank'].search(
|
||||
[('code', '=', 'SMOKE-TEST')], limit=1,
|
||||
) or env['fusion.plating.tank'].search([], limit=1)
|
||||
if not tank:
|
||||
facility = env['fusion.plating.facility'].search([], limit=1)
|
||||
tank = env['fusion.plating.tank'].create({
|
||||
'name': 'IoT Pilot Tank',
|
||||
'code': 'IOT-PILOT-01',
|
||||
'facility_id': facility.id if facility else False,
|
||||
})
|
||||
print(f'Tank: {tank.name} (id={tank.id}, code={tank.code})')
|
||||
|
||||
param = env['fusion.plating.bath.parameter'].search(
|
||||
[('parameter_type', '=', 'temperature')], limit=1,
|
||||
) or env['fusion.plating.bath.parameter'].search([], limit=1)
|
||||
print(f'Parameter: {param.name} '
|
||||
f'(target range {param.target_min}..{param.target_max} {param.uom or ""})')
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 3. Create / update fp.tank.sensor
|
||||
# -----------------------------------------------------------
|
||||
# Alert thresholds chosen for bench testing — 15°C to 35°C will stay
|
||||
# in-spec at room temp, goes out if you squeeze the probe.
|
||||
sensor = env['fp.tank.sensor'].search([('device_serial', '=', SERIAL)], limit=1)
|
||||
vals = {
|
||||
'name': SENSOR_NAME,
|
||||
'device_serial': SERIAL,
|
||||
'device_kind': 'ds18b20',
|
||||
'tank_id': tank.id,
|
||||
'parameter_id': param.id,
|
||||
'alert_min_override': 15.0,
|
||||
'alert_max_override': 35.0,
|
||||
'alert_on_out_of_spec': True,
|
||||
}
|
||||
if sensor:
|
||||
sensor.write(vals)
|
||||
print(f'Updated existing sensor id={sensor.id}')
|
||||
else:
|
||||
sensor = env['fp.tank.sensor'].create(vals)
|
||||
print(f'Created sensor id={sensor.id}')
|
||||
print(f' alert range: {sensor.alert_min_override}°C .. {sensor.alert_max_override}°C')
|
||||
|
||||
env.cr.commit()
|
||||
print('\n✓ Setup committed.\n')
|
||||
print('Next step — deploy the Pi poller with token:')
|
||||
print(f' {new_token}')
|
||||
Reference in New Issue
Block a user