feat(iot): Sub 7 — per-sensor polling interval + rate-limit + entech seed

Per-sensor override on fp.tank.sensor.poll_interval_minutes with a
company-wide default (res.company.x_fc_default_poll_interval_minutes,
default 30) exposed in Settings → Fusion Plating → IoT. Single
lookup helper _fp_effective_poll_interval_minutes keeps downstream
call sites simple. Read-only poll_interval_display Char ("30 min
(default)" / "15 min (override)") keeps units unambiguous per user
request.

Ingest endpoint /fp/iot/ingest drops readings that arrive inside a
sensor's effective interval, returning {accepted, skipped} so the Pi
agent can log it. Pi-side interval stays its own concern.

Post-init hook seeds 5 small tanks + 20 big tanks (10 active, 10
inactive) with 1 temperature + 1 pH sensor each → 25 tanks, 50
sensors. Idempotent (keyed by tank.code, with_context(active_test=
False)). Opt-in via ir.config_parameter
fusion_plating_iot.seed_entech_tanks = '1' so a fresh install
elsewhere doesn't auto-seed. Flag set on entech today; 27 tanks / 52
sensors now live (2 pilot + 25 seeded).

Smoke on entech: 14/14 assertions pass including idempotency and
rate-limit conditions.

fusion_plating_iot → 19.0.2.0.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-22 23:29:08 -04:00
parent def9c801fa
commit a7fd39d6f3
11 changed files with 358 additions and 5 deletions

View File

@@ -30,6 +30,8 @@
<field name="last_reading_at"/>
<field name="last_reading_in_spec" widget="boolean_toggle"/>
<field name="reading_count"/>
<field name="poll_interval_minutes" optional="show"/>
<field name="poll_interval_display" optional="show"/>
<field name="active" column_invisible="1"/>
</list>
</field>
@@ -110,6 +112,22 @@
<field name="last_reading_in_spec" readonly="1" widget="boolean_toggle"/>
</group>
</group>
<group string="IoT Polling (Sub 7)">
<group>
<field name="poll_interval_minutes"/>
<field name="poll_interval_display" readonly="1"/>
</group>
<group>
<div class="text-muted">
<i class="fa fa-info-circle me-1"/>
Leave the interval blank to inherit the
company-wide default (configured under
Settings → Fusion Plating → IoT). Readings
arriving faster than the effective interval
are dropped by the ingest endpoint.
</div>
</group>
</group>
</sheet>
</form>
</field>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Part of the Fusion Plating product family.
Sub 7 — IoT default polling interval setting.
-->
<odoo>
<record id="res_config_settings_view_form_fp_iot" model="ir.ui.view">
<field name="name">res.config.settings.view.form.fp.iot</field>
<field name="model">res.config.settings</field>
<field name="inherit_id"
ref="fusion_plating.res_config_settings_view_form_fp_core"/>
<field name="arch" type="xml">
<xpath expr="//app[@name='fusion_plating']" position="inside">
<block title="IoT"
name="fp_iot_settings"
help="Defaults applied to physical sensors wired into
Fusion Plating tanks. Per-sensor overrides live
on each fp.tank.sensor record.">
<setting id="fp_iot_default_poll_interval"
string="Default Polling Interval"
help="How often (in minutes) readings from a
tank sensor are stored when the sensor
itself does not specify its own interval.
The ingest endpoint drops readings that
arrive faster than the effective interval,
so a Pi agent can freely poll more often
without bloating the database.">
<field name="x_fc_default_poll_interval_minutes"/>
<span class="text-muted ms-2">minutes</span>
</setting>
</block>
</xpath>
</field>
</record>
</odoo>