Appearance
Configuration
Everything the daemon does is decided by one array in the boat's deployment file. This chapter takes a sensor entry apart, key by key.
For an owner, the short version: each tank, each temperature probe and each shunt on the boat has one entry here, and that entry is where its name, its capacity and its calibration live. Changing a tank's capacity on the screen means changing it here.
The file
The daemon is given a path with --sensors; the systemd unit passes /etc/muxen/deploy.json. Out of that file it reads exactly one key:
json
{
"sensors": [ … ]
}- A file that cannot be opened, is not valid JSON, or has no
sensorskey is a startup failure. - Everything else in the file — including a root
versionkey — is ignored by this daemon. Other MUXEN services read their own sections of the same file. - Keys inside an entry that this daemon does not know are ignored too, so an entry may legitimately carry configuration meant for another service.
An entry
json
{
"name": "FreshWaterPortside",
"input": {
"deviceType": 1,
"deviceInstance": 1,
"deviceChannel": "analogInput0",
"min": 0.0,
"max": 5.0
},
"output": {
"unit": "obix:units/liter",
"min": 0,
"max": 250
},
"scaleType": "polynomial",
"polynomial": [0, 50],
"liquid": {
"filterPeriod": 60
}
}| Key | Required | Meaning |
|---|---|---|
name | yes | the variable name, and the topic it publishes on |
input | yes | which device channel to read, and its expected range |
output | yes | the unit and the range of the result |
scaleType | no | the curve to apply; identity when absent |
polynomial / linear / step | with the matching scaleType | the curve's coefficients or points |
liquid | no | passed through to consumers; filterPeriod inside it also smooths the value |
name
The name is the topic. "FreshWaterPortside" publishes on app/sensor/FreshWaterPortside, and it is also the name that appears in the journal, in the dispatcher table and in the payload's name field. Only the last element comes from here — the app/sensor in front of it is --prefix, and belongs to the install rather than to the sensor.
A name containing /, #, + or a space is refused and the entry is skipped — those are MQTT topic and wildcard characters, and a sensor carrying one would either publish on an unintended topic or match subscriptions it should not.
Nothing checks that two entries have different names. Two entries sharing one publish to the same topic, alternately, once a second each.
input
Which channel of which device to listen to.
| Key | Type | Default | Meaning |
|---|---|---|---|
deviceType | integer | 0, which is refused | the MUXEN function code of the source device |
deviceInstance | integer | 0 | which unit of that function |
deviceChannel | string | channel 0 of the device | the channel within the device's frame |
min | number | −∞ | readings below this are raised to it |
max | number | +∞ | readings above this are lowered to it |
deviceType and deviceChannel
Only three functions can feed a variable, and each accepts its own channel names:
deviceType | Device | Accepted deviceChannel |
|---|---|---|
| 1 | Power output Bloc 8 | digitalInput0 … digitalInput5, analogInput0 … analogInput2 |
| 3 | Power source interconnection | source0 … source3 |
| 17 | Generic I/O | digitalInput0, digitalInput1, relay0, relay1, analogInput0, analogInput1 |
Any other deviceType — including the 0 you get by omitting the key — is refused with input.deviceType (N) is not supported, and the entry is skipped. A deviceChannel that is not in the list for the declared device is refused the same way.
deviceType and deviceInstance are the function code and the instance of the device on the bus, 6 bits each: outside 0–63 they are refused with input.deviceType must be 0-63 or input.deviceInstance must be 0-63, in a file as in a calibration request. Earlier versions cut them to 8 bits, so deviceType 257 passed as 1 and deviceInstance 256 calibrated instance 0, and an instance of 64 or more was listened for on a topic the bus never produces while its request reached another device.
Two things are worth knowing about the names:
- They are this daemon's vocabulary, not the device's. A Generic I/O device publishes its digital inputs as
input0/input1and its analogue inputs asana0/ana1on its own MQTT topic; here they aredigitalInput0/digitalInput1andanalogInput0/analogInput1. deviceChannelis optional, and its absence is not neutral. An entry without it reads channel 0, which isdigitalInput0on a Bloc 8,source0on an interconnection, andrelay0on a Generic I/O. Declare it explicitly.
min and max
These bound the sender, not the tank. They are applied to the incoming reading before any curve is evaluated, and their purpose is to stop an out-of-range reading — a disconnected probe, a short — from being pushed through the curve and published as a plausible-looking number.
Both default to infinity, meaning no clamping at all. Set them to the range the sender physically produces.
The payload publishes input.value unclamped, alongside the limits it was held to, so a message shows both what arrived and what was made of it. A reading outside the declared limits is therefore visible rather than hidden.
output
What the result is.
| Key | Type | Default | Meaning |
|---|---|---|---|
unit | string | absent | copied verbatim into the payload; omitted from the payload when unset |
min | number | −∞ | results below this are raised to it |
max | number | +∞ | results above this are lowered to it |
unit is not interpreted. Whatever string is written here is what consumers receive; the example configurations use oBIX unit URIs such as obix:units/liter and obix:units/ampere.
min and max bound the published value and are the tank's real limits: a 250-litre tank gets "max": 250, and no calibration error can then make the gauge read 400. They are also what a screen can use to draw a percentage, since both are carried in the payload — but only when they are set. Leave them out and the payload has no min/max at all.
scaleType and the curve
Four curves are available, covered in full — with worked examples — in Calibration and filtering.
scaleType | Companion key | Shape |
|---|---|---|
identity | none | the reading is published unchanged |
polynomial | polynomial | up to 5 coefficients, lowest order first |
linear | linear | up to 32 measured points, interpolated between |
step | step | up to 32 measured points, held until the next one |
scaleType is optional and defaults to identity. A value that is not one of the four is refused. Declaring a scaleType without its companion key is refused as well.
liquid
An optional object, and the one place where the configuration reaches past this daemon:
json
"liquid": {
"filterPeriod": 60
}- The whole object is republished in the payload under
liquid, untouched, whenever it is not empty. Consumers that need to know a tank's type, its location or its capacity read it from there. - Only
filterPeriodis interpreted here. Its presence enables the smoothing filter for this variable; its value is the number of one-second samples the filter keeps, clamped to 12…900. A value outside that range is silently brought inside it.
Without filterPeriod, no filter exists and the value published is the value computed from the last frame. See Calibration and filtering for what the filter does to it.
How a file is loaded
The daemon parses the array once, at startup, and is tolerant by entry but not overall:
- Each entry is validated on its own. One that fails prints the reason and then
sensor: failed to load sensors[N], ignoring..., and the rest of the file continues to load. - A partial load is reported:
sensor: 6 of 7 sensors loaded. - An empty result is fatal. If no entry survives, the daemon prints
sensor: no usable sensor out of Nand exits rather than sit thereactive (running)with nothing to publish. - The surviving list is sorted by device function then instance. That is the order in the journal and in the dispatcher table; it has no effect on what is published.
Because a bad entry is skipped rather than fatal, a typo removes one gauge from the boat and leaves everything else working. The count line in the journal is what tells you it happened.
