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:
jtricerolph 2026-07-28 17:08:22 +00:00
parent 1243b73a04
commit f5b0b01ff1
5 changed files with 42 additions and 12 deletions

View file

@ -10,6 +10,7 @@ const emptyForm = {
id: 0, category_id: 0, parent_meter_id: '' as number | '', name: '', location: '',
serial_number: '', install_date: '', notes: '', active: true,
gas_volume_unit: '' as 'm3' | 'ft3' | '', gas_calorific_value: '' as number | '',
mqtt_topic: '', mqtt_value_scale: 1 as number | '',
}
export default function Meters() {
@ -54,6 +55,7 @@ export default function Meters() {
install_date: m.install_date ? m.install_date.slice(0, 10) : '', notes: m.notes || '',
active: m.active,
gas_volume_unit: m.gas_volume_unit || '', gas_calorific_value: m.gas_calorific_value != null ? Number(m.gas_calorific_value) : '',
mqtt_topic: m.mqtt_topic || '', mqtt_value_scale: m.mqtt_value_scale != null ? Number(m.mqtt_value_scale) : 1,
})
setImageFile(null)
}
@ -76,6 +78,8 @@ export default function Meters() {
active: form.active,
gas_volume_unit: isGas && form.gas_volume_unit ? form.gas_volume_unit : null,
gas_calorific_value: isGas && form.gas_calorific_value !== '' ? form.gas_calorific_value : null,
mqtt_topic: form.mqtt_topic.trim() || null,
mqtt_value_scale: form.mqtt_value_scale === '' ? 1 : form.mqtt_value_scale,
}
const saved = form.id ? await api.updateMeter(form.id, body) : await api.createMeter(body)
if (imageFile) await api.uploadMeterImage(saved.id, imageFile)
@ -220,6 +224,21 @@ export default function Meters() {
</div>
)}
<div className="field-row">
<div className="field">
<label>MQTT topic (device feed)</label>
<input type="text" value={form.mqtt_topic} onChange={e => setForm({ ...form, mqtt_topic: e.target.value })}
placeholder="e.g. utilities/water-softener/total_usage_l" />
<div className="field-hint">Leave blank for a manually-read meter. If set, readings are ingested automatically from the shared MQTT broker.</div>
</div>
<div className="field">
<label>Device value scale</label>
<input type="number" step="0.000001" value={form.mqtt_value_scale}
onChange={e => setForm({ ...form, mqtt_value_scale: e.target.value === '' ? '' : parseFloat(e.target.value) })} />
<div className="field-hint">Multiplied onto the raw published value, e.g. 0.001 if the device publishes litres but this meter's unit is m³.</div>
</div>
</div>
<div className="field-row">
<div className="field">
<label>Parent meter (sub-metering)</label>

View file

@ -40,6 +40,8 @@ export interface Meter {
reading_unit_label: string
gas_volume_unit: 'm3' | 'ft3' | null
gas_calorific_value: number | null
mqtt_topic: string | null
mqtt_value_scale: number
parent_meter_id: number | null
parent_name: string | null
name: string