refactor(fusion_iot): port sensor taxonomy + dashboards, retire fusion_plating_sensors
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>
This commit is contained in:
@@ -3,27 +3,39 @@
|
||||
Copyright 2026 Nexa Systems Inc.
|
||||
License OPL-1
|
||||
|
||||
Surface IoT sensors + readings under the existing Plating >
|
||||
Operations menu. Not a top-level app — sensors are an extension
|
||||
of bath/tank management, not a separate concern.
|
||||
Single "Sensors" section under Plating > Operations. Consolidates
|
||||
all sensor management into one place (formerly split across an
|
||||
obsolete fusion_plating_sensors module).
|
||||
-->
|
||||
<odoo>
|
||||
|
||||
<menuitem id="menu_fp_iot_root"
|
||||
name="Sensors & Readings"
|
||||
name="Sensors"
|
||||
parent="fusion_plating.menu_fp_operations"
|
||||
sequence="55"/>
|
||||
|
||||
<menuitem id="menu_fp_tank_sensor"
|
||||
name="Tank Sensors"
|
||||
<menuitem id="menu_fp_sensor_dashboard"
|
||||
name="Dashboards"
|
||||
parent="menu_fp_iot_root"
|
||||
action="action_fp_tank_sensor"
|
||||
action="action_fp_sensor_dashboard"
|
||||
sequence="10"/>
|
||||
|
||||
<menuitem id="menu_fp_tank_reading"
|
||||
name="Sensor Readings"
|
||||
<menuitem id="menu_fp_tank_sensor"
|
||||
name="Sensors"
|
||||
parent="menu_fp_iot_root"
|
||||
action="action_fp_tank_reading"
|
||||
action="action_fp_tank_sensor"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem id="menu_fp_tank_reading"
|
||||
name="Readings"
|
||||
parent="menu_fp_iot_root"
|
||||
action="action_fp_tank_reading"
|
||||
sequence="30"/>
|
||||
|
||||
<menuitem id="menu_fp_sensor_type"
|
||||
name="Sensor Types"
|
||||
parent="menu_fp_iot_root"
|
||||
action="action_fp_sensor_type"
|
||||
sequence="40"/>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="fp_sensor_dashboard_list" model="ir.ui.view">
|
||||
<field name="name">fp.sensor.dashboard.list</field>
|
||||
<field name="model">fp.sensor.dashboard</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Sensor Dashboards"
|
||||
decoration-danger="out_of_spec_count > 0">
|
||||
<field name="name"/>
|
||||
<field name="description" optional="show"/>
|
||||
<field name="sensor_count"/>
|
||||
<field name="out_of_spec_count"/>
|
||||
<field name="color" widget="color_picker" optional="show"/>
|
||||
<field name="active" widget="boolean_toggle" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_sensor_dashboard_form" model="ir.ui.view">
|
||||
<field name="name">fp.sensor.dashboard.form</field>
|
||||
<field name="model">fp.sensor.dashboard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sensor Dashboard">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_sensors" type="object"
|
||||
class="oe_stat_button" icon="fa-microchip">
|
||||
<field name="sensor_count" widget="statinfo"
|
||||
string="Sensors"/>
|
||||
</button>
|
||||
<button name="action_view_recent_readings" type="object"
|
||||
class="oe_stat_button" icon="fa-line-chart">
|
||||
<field name="out_of_spec_count" widget="statinfo"
|
||||
string="Out of Spec"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" placeholder="e.g. ENP Line 1"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="description" placeholder="What this dashboard shows…"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Sensors">
|
||||
<field name="sensor_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="sensor_type_id"/>
|
||||
<field name="effective_location"/>
|
||||
<field name="last_reading_display"/>
|
||||
<field name="last_reading_display_unit"/>
|
||||
<field name="last_reading_in_spec" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_sensor_dashboard" model="ir.actions.act_window">
|
||||
<field name="name">Dashboards</field>
|
||||
<field name="res_model">fp.sensor.dashboard</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
60
fusion_iot/fusion_plating_iot/views/fp_sensor_type_views.xml
Normal file
60
fusion_iot/fusion_plating_iot/views/fp_sensor_type_views.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="fp_sensor_type_list" model="ir.ui.view">
|
||||
<field name="name">fp.sensor.type.list</field>
|
||||
<field name="model">fp.sensor.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Sensor Types" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="measurement_type"/>
|
||||
<field name="default_uom"/>
|
||||
<field name="icon" optional="hide"/>
|
||||
<field name="sensor_count"/>
|
||||
<field name="active" widget="boolean_toggle" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="fp_sensor_type_form" model="ir.ui.view">
|
||||
<field name="name">fp.sensor.type.form</field>
|
||||
<field name="model">fp.sensor.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sensor Type">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_sensors" type="object"
|
||||
class="oe_stat_button" icon="fa-microchip">
|
||||
<field name="sensor_count" widget="statinfo"
|
||||
string="Sensors"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" placeholder="e.g. Temperature"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
<field name="measurement_type"/>
|
||||
<field name="default_uom"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="sequence"/>
|
||||
<field name="icon"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fp_sensor_type" model="ir.actions.act_window">
|
||||
<field name="name">Sensor Types</field>
|
||||
<field name="res_model">fp.sensor.type</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -10,14 +10,19 @@
|
||||
<field name="name">fp.tank.sensor.list</field>
|
||||
<field name="model">fp.tank.sensor</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Tank Sensors" decoration-danger="not last_reading_in_spec and last_reading_at"
|
||||
<list string="Sensors" decoration-danger="not last_reading_in_spec and last_reading_at"
|
||||
decoration-muted="not active">
|
||||
<field name="name"/>
|
||||
<field name="device_kind"/>
|
||||
<field name="tank_id"/>
|
||||
<field name="bath_id" optional="show"/>
|
||||
<field name="sensor_type_id" optional="show"/>
|
||||
<field name="device_kind" optional="show"/>
|
||||
<field name="effective_location" string="Location" optional="show"/>
|
||||
<field name="tank_id" optional="hide"/>
|
||||
<field name="work_center_id" optional="hide"/>
|
||||
<field name="facility_id" optional="hide"/>
|
||||
<field name="bath_id" optional="hide"/>
|
||||
<field name="parameter_id"/>
|
||||
<field name="device_serial" optional="show"/>
|
||||
<field name="uuid" optional="hide"/>
|
||||
<field name="iot_device_id" optional="hide"/>
|
||||
<field name="last_reading_display"/>
|
||||
<field name="last_reading_display_unit"/>
|
||||
@@ -55,18 +60,30 @@
|
||||
<h1><field name="name" placeholder="e.g. Tank 3 — ENP temp"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Identity">
|
||||
<field name="sensor_type_id" options="{'no_create': True}"/>
|
||||
<field name="uuid" readonly="1"/>
|
||||
<field name="parameter_id" options="{'no_create': True}"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Hardware">
|
||||
<field name="device_kind"/>
|
||||
<field name="device_serial" placeholder="28-abc123def456"/>
|
||||
<field name="iot_device_id"
|
||||
options="{'no_create': True}"
|
||||
help="Optional — the iot.device auto-registered by the Pi proxy."/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Location">
|
||||
</group>
|
||||
<group string="Location — fill in ONE (first one set wins for display)">
|
||||
<group>
|
||||
<field name="tank_id" options="{'no_create': True}"/>
|
||||
<field name="bath_id" options="{'no_create': True}"/>
|
||||
<field name="parameter_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="work_center_id" options="{'no_create': True}"/>
|
||||
<field name="facility_id" options="{'no_create': True}"/>
|
||||
<field name="location_name"/>
|
||||
<field name="effective_location" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Setpoint & Alerting">
|
||||
|
||||
Reference in New Issue
Block a user