Appearance
Getting started
This chapter installs the daemon, declares one sensor, and confirms that the value it publishes is the one the boat should see.
Prerequisites
On the Brain:
- An MQTT broker on
127.0.0.1:1883. The daemon connects at startup and exits if the broker refuses; systemd restarts it 30 seconds later. The unit orders itselfAfter=mosquitto.service. - A CAN interface carrying the MUXEN bus, normally
can0, unless the daemon is going to be run with--mqtt. - The boat's deployment file,
/etc/muxen/deploy.json, readable by themuxenuser. The daemon runs as that user and reads only thesensorsarray out of the file.
For --mqtt mode, a service publishing the device/… topics is required. The package carries Recommends: muxen-boat (>= 5.0.0) for that reason.
Install
sh
sudo apt install muxen-sensorsThe package installs the daemon /usr/bin/muxen-sensors and the unit /usr/lib/systemd/system/muxen-sensors.service. It depends on muxen-systemd, which provides the muxen-deploy.target the unit attaches to.
Nothing needs enabling by hand — the package's generated maintainer scripts enable and start the unit. Confirm it:
sh
systemctl is-enabled muxen-sensors
systemctl status muxen-sensorsThe unit is PartOf=muxen-deploy.target and WantedBy the same target, so a MUXEN deployment restarts it along with everything else that depends on the configuration file.
What the unit runs
ExecStart=/usr/bin/muxen-sensors --interface can0 --sensors ${MUXEN_DEPLOY}with these defaults, overridable with a drop-in (systemctl edit muxen-sensors) — except the CAN interface, which is literal in ExecStart and in the unit's device dependency:
| Variable | Default |
|---|---|
MUXEN_DEPLOY | /etc/muxen/deploy.json |
Both arguments are mandatory to the daemon: it refuses to start without --sensors, and without --interface unless --mqtt is given. An empty override in a drop-in is therefore a startup failure, not a default.
--mqtt is not reachable through an environment variable. Running the daemon from MQTT rather than CAN needs a drop-in that replaces ExecStart — see Reference.
Declare a first sensor
Sensors live in the sensors array of /etc/muxen/deploy.json. The minimum entry names the variable, says where to read it, and says what the result is:
json
{
"sensors": [
{
"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]
}
]
}Read from the top:
namebecomes the topicapp/sensor/FreshWaterPortside. It may not contain/,#,+or a space. Theapp/sensorpart is--prefix; an install migrating from the oldervariable/prefix setsMUXEN_TOPIC_PREFIXin a drop-in and readsvariable/FreshWaterPortsideinstead.inputpoints at analogue channel 0 of the Bloc 8 (function 1) at instance 1, and says the sender never produces less than 0 or more than 5. Readings outside that are clamped to it.scaleType+polynomialconvert the reading:0 + 50 × input, so 5 gives 250.outputlabels the result and bounds it to 0…250 litres.
The daemon reads this file once, at startup, so a change needs a restart:
sh
sudo systemctl restart muxen-sensorsIn normal operation that happens by itself. muxen-systemd — a dependency of this package — watches /etc/muxen/deploy.json and restarts muxen-deploy.target when it is modified, and this unit is PartOf that target.
Verify
1. The daemon loaded the file.
sh
journalctl -u muxen-sensors -n 40A healthy start prints its configuration, then the list of variables, then the dispatcher table:
config: verbose = 0
config: sensors = /etc/muxen/deploy.json
config: data source = can
config: can interface = can0
sensors: 1 variables
sensors: FreshWaterPortside
can: dispatcher table size: 1
can: canId canMask canDlc name
can: 0x002041 0xFFFFFF 7 FreshWaterPortside
main: mqtt connected
can: can0 is UP (state = ERROR_ACTIVE, attempt = 0)sensors: N variables is the count that survived parsing. If it is lower than the number of entries in the file, a line above it names each one that failed.
2. The value is being published.
sh
mosquitto_sub -h 127.0.0.1 -t 'app/sensor/#' -vOne message per variable per second — on variable/# instead, if this install still pins the old prefix. config: topic prefix = … in the startup log says which one. The payload arrives compact, on one line; it is shown indented here, as --json-pretty would publish it:
json
{
"name": "FreshWaterPortside",
"data": {
"value": 143.5,
"unit": "obix:units/liter",
"min": 0.0,
"max": 250.0
},
"input": {
"functionCode": 1,
"instance": 1,
"channel": 6,
"value": 2.87,
"min": 0.0,
"max": 5.0
},
"metadata": {
"rxdate": "2026-08-16T09:14:22.310Z",
"rxTimestamp": 1786000462,
"expireAfterSec": 30
}
}input.value is what the device reported; data.value is what the calibration made of it. Having both on the same message is what makes a wrong gauge diagnosable without a bus analyser.
Nothing is published until the first frame arrives. A sensor that has never received anything stays silent — it does not publish a zero.
3. The reading is the right one. Compare input.value against the device's own topic, if muxen-boat is running:
sh
mosquitto_sub -h 127.0.0.1 -t 'device/1/1/io' -vThe two must agree. If they do and data.value is still wrong, the calibration is the problem, not the wiring — see Calibration and filtering.
Run it by hand at commissioning
The verbose mode prints the full interpretation of every entry, which is the fastest way to check that a file says what its author meant:
sh
sudo -u muxen /usr/bin/muxen-sensors -v -i can0 -s /etc/muxen/deploy.jsonsensors: 1 variables
sensors: FreshWaterPortside
sensors: ├╴Input
sensors: │ ├╴DeviceType = 1
sensors: │ ├╴DeviceInstance = 1
sensors: │ ├╴DeviceChannel = 6
sensors: │ ├╴Min = 0.000000
sensors: │ └╴Max = 5.000000
sensors: ├╴Output
sensors: │ ├╴Unit = obix:units/liter
sensors: │ ├╴Min = 0.000000
sensors: │ └╴Max = 250.000000
sensors: └╴Scale = polynomial
sensors: ├╴Order 0 = 0.000000
sensors: ├╴Order 1 = 50.000000
sensors: ├╴Order 2 = 0.000000
sensors: ├╴Order 3 = 0.000000
sensors: └╴Order 4 = 0.000000Stop the service first, or the two instances compete for the same MQTT client identifier. -vv adds a trace of every frame and every MQTT message, which on a live bus is a firehose.
Where to go next
- Every key of a sensor entry — Configuration
- Choosing and measuring a curve, and smoothing a tank — Calibration and filtering
- When the number is wrong — Troubleshooting
