Skip to content

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 sensors key is a startup failure.
  • Everything else in the file — including a root version key — 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
  }
}
KeyRequiredMeaning
nameyesthe variable name, and the topic it publishes on
inputyeswhich device channel to read, and its expected range
outputyesthe unit and the range of the result
scaleTypenothe curve to apply; identity when absent
polynomial / linear / stepwith the matching scaleTypethe curve's coefficients or points
liquidnopassed 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.

KeyTypeDefaultMeaning
deviceTypeinteger0, which is refusedthe MUXEN function code of the source device
deviceInstanceinteger0which unit of that function
deviceChannelstringchannel 0 of the devicethe channel within the device's frame
minnumber−∞readings below this are raised to it
maxnumber+∞readings above this are lowered to it

deviceType and deviceChannel ​

Only three functions can feed a variable, and each accepts its own channel names:

deviceTypeDeviceAccepted deviceChannel
1Power output Bloc 8digitalInput0 … digitalInput5, analogInput0 … analogInput2
3Power source interconnectionsource0 … source3
17Generic I/OdigitalInput0, 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/input1 and its analogue inputs as ana0/ana1 on its own MQTT topic; here they are digitalInput0/digitalInput1 and analogInput0/analogInput1.
  • deviceChannel is optional, and its absence is not neutral. An entry without it reads channel 0, which is digitalInput0 on a Bloc 8, source0 on an interconnection, and relay0 on 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.

KeyTypeDefaultMeaning
unitstringabsentcopied verbatim into the payload; omitted from the payload when unset
minnumber−∞results below this are raised to it
maxnumber+∞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.

scaleTypeCompanion keyShape
identitynonethe reading is published unchanged
polynomialpolynomialup to 5 coefficients, lowest order first
linearlinearup to 32 measured points, interpolated between
stepstepup 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 filterPeriod is 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:

  1. 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.
  2. A partial load is reported: sensor: 6 of 7 sensors loaded.
  3. An empty result is fatal. If no entry survives, the daemon prints sensor: no usable sensor out of N and exits rather than sit there active (running) with nothing to publish.
  4. 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.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.