Add per-meter MQTT value scale + expose device topic/scale in Meters UI
The water-softener totaliser publishes litres but the water category is m3 — without a conversion factor its readings would land in the DB 1000x too high. Also, mqtt_topic was only settable via direct SQL since the Meters form never exposed it.
This commit is contained in:
parent
1243b73a04
commit
f5b0b01ff1
5 changed files with 42 additions and 12 deletions
|
|
@ -47,6 +47,12 @@ export async function initDb() {
|
|||
ALTER TABLE meters ADD COLUMN IF NOT EXISTS mqtt_topic TEXT;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS meters_mqtt_topic_uidx ON meters (mqtt_topic) WHERE mqtt_topic IS NOT NULL;
|
||||
|
||||
-- multiplied onto a raw device-published value before it's stored, so a
|
||||
-- device publishing in a different unit than the category's billed unit
|
||||
-- (e.g. the water-softener totaliser publishes litres, category is m3)
|
||||
-- lands correctly with no per-category special-casing. 1 = no conversion.
|
||||
ALTER TABLE meters ADD COLUMN IF NOT EXISTS mqtt_value_scale NUMERIC(12,6) NOT NULL DEFAULT 1;
|
||||
|
||||
-- gas meters read raw volume off the dial (m3 on newer metric meters, ft3 on
|
||||
-- older imperial ones like ours), never kWh directly — these two columns let
|
||||
-- cost-calc.js convert a volume reading to billed kWh using the same
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const BROKER_RECONNECT_BACKOFF_MS = [30, 60, 120, 300, 600, 1800].map(s => s * 1
|
|||
let client = null
|
||||
let connecting = false
|
||||
let reconnectAttempt = 0
|
||||
let topicMap = new Map() // mqtt_topic -> meter_id
|
||||
let topicMap = new Map() // mqtt_topic -> { meterId, scale }
|
||||
let subscribedTopics = new Set()
|
||||
|
||||
async function getCredentials() {
|
||||
|
|
@ -31,9 +31,9 @@ async function getCredentials() {
|
|||
|
||||
export async function refreshTopicMap() {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT id, mqtt_topic FROM meters WHERE mqtt_topic IS NOT NULL AND active = TRUE`
|
||||
`SELECT id, mqtt_topic, mqtt_value_scale FROM meters WHERE mqtt_topic IS NOT NULL AND active = TRUE`
|
||||
)
|
||||
topicMap = new Map(rows.map(r => [r.mqtt_topic, r.id]))
|
||||
topicMap = new Map(rows.map(r => [r.mqtt_topic, { meterId: r.id, scale: Number(r.mqtt_value_scale) }]))
|
||||
if (client?.connected) subscribeToKnownTopics()
|
||||
}
|
||||
|
||||
|
|
@ -47,11 +47,12 @@ function subscribeToKnownTopics() {
|
|||
}
|
||||
|
||||
function handleMessage(topic, message) {
|
||||
const meterId = topicMap.get(topic)
|
||||
if (!meterId) return
|
||||
const value = Number(message.toString().trim())
|
||||
if (!Number.isFinite(value)) return
|
||||
pool.query(`INSERT INTO meter_telemetry_raw (meter_id, value) VALUES ($1, $2)`, [meterId, value])
|
||||
const entry = topicMap.get(topic)
|
||||
if (!entry) return
|
||||
const raw = Number(message.toString().trim())
|
||||
if (!Number.isFinite(raw)) return
|
||||
const value = raw * entry.scale
|
||||
pool.query(`INSERT INTO meter_telemetry_raw (meter_id, value) VALUES ($1, $2)`, [entry.meterId, value])
|
||||
.catch(err => console.error('[mqtt] telemetry insert failed:', err.message))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,11 +93,12 @@ export async function meterRoutes(app, opts) {
|
|||
if (!b.name || !b.category_id) return reply.status(400).send({ error: 'name and category_id required' })
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO meters (category_id, parent_meter_id, name, location, serial_number, install_date, notes, mqtt_topic,
|
||||
gas_volume_unit, gas_calorific_value)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) RETURNING *`,
|
||||
mqtt_value_scale, gas_volume_unit, gas_calorific_value)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *`,
|
||||
[
|
||||
b.category_id, b.parent_meter_id || null, b.name, b.location || null, b.serial_number || null,
|
||||
b.install_date || null, b.notes || null, b.mqtt_topic || null,
|
||||
b.mqtt_value_scale || 1,
|
||||
b.gas_volume_unit || null, b.gas_calorific_value || null,
|
||||
]
|
||||
)
|
||||
|
|
@ -116,8 +117,8 @@ export async function meterRoutes(app, opts) {
|
|||
const { rows } = await pool.query(
|
||||
`UPDATE meters SET category_id = $1, parent_meter_id = $2, name = $3, location = $4,
|
||||
serial_number = $5, install_date = $6, notes = $7, active = $8, mqtt_topic = $9,
|
||||
gas_volume_unit = $10, gas_calorific_value = $11
|
||||
WHERE id = $12 RETURNING *`,
|
||||
mqtt_value_scale = $10, gas_volume_unit = $11, gas_calorific_value = $12
|
||||
WHERE id = $13 RETURNING *`,
|
||||
[
|
||||
b.category_id ?? m.category_id,
|
||||
b.parent_meter_id !== undefined ? b.parent_meter_id : m.parent_meter_id,
|
||||
|
|
@ -128,6 +129,7 @@ export async function meterRoutes(app, opts) {
|
|||
b.notes !== undefined ? b.notes : m.notes,
|
||||
b.active ?? m.active,
|
||||
b.mqtt_topic !== undefined ? b.mqtt_topic : m.mqtt_topic,
|
||||
b.mqtt_value_scale !== undefined ? b.mqtt_value_scale : m.mqtt_value_scale,
|
||||
b.gas_volume_unit !== undefined ? b.gas_volume_unit : m.gas_volume_unit,
|
||||
b.gas_calorific_value !== undefined ? b.gas_calorific_value : m.gas_calorific_value,
|
||||
req.params.id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue