fusion_plating_sensors had broader scope (sensor_type taxonomy,
dashboards, location flexibility) but its core logic was ALL
scaffolding — alert rules stored thresholds with zero side effects,
measurement create just filled a name sequence, the HTTP endpoint
required user-auth session cookies. Meanwhile fusion_plating_iot had
the actual working alerting: in-spec checks, quality-hold auto-raise
with excursion dedupe, setpoint + deviation, token-auth ingest for
headless hardware. Plus 563 real readings from the pilot Pi.
Right unification: keep fusion_plating_iot (working) as the base,
port the valuable structural bits from fusion_plating_sensors, demolish
the latter entirely.
**Ported to fusion_plating_iot:**
- `fp.sensor.type` — taxonomy model with 8 seeded types (Temperature,
pH, Conductivity, Level, Pressure, Flow, Concentration, Switch).
Richer than the device_kind Selection; hardware-independent (one
"Temperature" type covers DS18B20 / PT100 / thermocouple).
- `fp.sensor.dashboard` — named grouping of sensors with
out-of-spec count. Simple but useful ("ENP Line 1 — all tanks")
without the broken alert-rule complexity.
- Extended `fp.tank.sensor`:
* `uuid` (stable logical ID, survives hardware swaps)
* `sensor_type_id` (link to the taxonomy above)
* `work_center_id`, `facility_id`, `location_name` — alternatives to
tank_id so probes can live on ovens, ambient air, effluent pipes
without faking a "tank"
* `effective_location` computed — picks the first non-empty of the
four location fields for display
**Post-install hook** backfills UUID + default sensor_type on existing
live sensors. Verified on the 2 pilot sensors: both got UUIDs, both
auto-assigned the Temperature type via device_kind=ds18b20 mapping.
**Deleted** (all of fusion_plating_sensors, 1205 LOC):
- fp.sensor (replaced by fp.tank.sensor with added fields)
- fp.sensor.measurement (replaced by fp.tank.reading)
- fp.sensor.alert.rule (replaced by inline alert_min/max + working hold)
- /fp/sensor/measure controller (replaced by /fp/iot/ingest)
- fp.sensor.measure.wizard (not needed — Odoo's normal create form works)
- The "Sensors" submenu hierarchy (Dashboards/All Sensors/Measurements/
Sensor Types) that created the dup menus the user reported
**Menu now**: Plating → Operations → Sensors
- Dashboards (fp.sensor.dashboard)
- Sensors (fp.tank.sensor — renamed from "Tank Sensors" since
it supports non-tank locations now)
- Readings (fp.tank.reading)
- Sensor Types (fp.sensor.type)
No data loss: all 591 Pi readings preserved (up from 563 earlier as
the live poller kept running throughout the refactor). Brief 503 on
the Pi during the Odoo module-update restart; poller auto-retried on
the next 30s tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
"""Post-install hook — backfill new fields on existing live sensors.
|
|
|
|
Runs once on every install/upgrade. Idempotent: checks before writing
|
|
so re-runs don't overwrite user-edited values.
|
|
|
|
What it does:
|
|
1. Populates `uuid` on any fp.tank.sensor record that doesn't have one
|
|
(for sensors created BEFORE the uuid field existed — the create
|
|
override only covers new records).
|
|
2. Sets a default `sensor_type_id` on sensors that don't have one yet,
|
|
inferring from `device_kind` (DS18B20 / PT100 / PT1000 → Temperature,
|
|
pH → pH probe, etc.).
|
|
"""
|
|
import logging
|
|
import uuid as _uuid
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def post_init_hook(env):
|
|
_backfill_uuids(env)
|
|
_backfill_sensor_types(env)
|
|
|
|
|
|
def _backfill_uuids(env):
|
|
Sensor = env['fp.tank.sensor']
|
|
missing = Sensor.search([('uuid', '=', False)])
|
|
if not missing:
|
|
return
|
|
for s in missing:
|
|
s.sudo().write({'uuid': _uuid.uuid4().hex})
|
|
_logger.info('fp.tank.sensor: populated UUID on %d existing records',
|
|
len(missing))
|
|
|
|
|
|
def _backfill_sensor_types(env):
|
|
Sensor = env['fp.tank.sensor']
|
|
Type = env['fp.sensor.type']
|
|
|
|
# Map device_kind → sensor-type code. Falls back to 'temperature' for
|
|
# the rare case someone set device_kind='other' on a probe that IS
|
|
# temperature (common on the pilot).
|
|
kind_to_code = {
|
|
'ds18b20': 'temperature',
|
|
'pt100': 'temperature',
|
|
'pt1000': 'temperature',
|
|
'ph': 'ph',
|
|
'conductivity': 'conductivity',
|
|
'level': 'level',
|
|
}
|
|
|
|
missing = Sensor.search([('sensor_type_id', '=', False)])
|
|
if not missing:
|
|
return
|
|
|
|
# Resolve the types once up front
|
|
type_cache = {}
|
|
for code in set(kind_to_code.values()):
|
|
t = Type.search([('code', '=', code)], limit=1)
|
|
if t:
|
|
type_cache[code] = t.id
|
|
|
|
updated = 0
|
|
for s in missing:
|
|
code = kind_to_code.get(s.device_kind)
|
|
# Unmapped (device_kind='other') → try temperature as the most
|
|
# common fallback. Admin can correct in the UI.
|
|
type_id = type_cache.get(code) or type_cache.get('temperature')
|
|
if type_id:
|
|
s.sudo().write({'sensor_type_id': type_id})
|
|
updated += 1
|
|
_logger.info('fp.tank.sensor: set default sensor_type_id on %d records',
|
|
updated)
|