Appearance
Troubleshooting
This chapter is organised by what you see. The daemon has no control socket and no status command: everything it knows, it says in the journal at startup or publishes on MQTT, so those two are the whole toolbox.
sh
journalctl -u muxen-energy -n 100 # the startup log, and any errors
journalctl -u muxen-energy -f # follow it
mosquitto_sub -h 127.0.0.1 -t 'app/energy/#' -v # the published summaries
mosquitto_sub -h 127.0.0.1 -t 'app/energy/info' -v # version, online, data source
systemctl status muxen-energyNo app/energy/… topic at all
Nothing is published for any zone.
| Likely cause | What to check | Fix |
|---|---|---|
| The daemon is not running | systemctl status muxen-energy | see the service restarts every 30 seconds below |
| It is running but built no zones | zones: 0 zones in the journal | nothing in the configuration binds a device or enables a sensor — see Energy zones |
| The broker refused the connection | no mqtt: connected line | the daemon exits when it cannot connect at startup; check the broker |
Zones exist only because a device or a variable claims one. An EnergyZoneName<N> setting on its own creates nothing, and a zone with neither devices nor variables is skipped rather than published — so a setting for a zone that no device joined never produces a stray topic.
The service restarts every 30 seconds
Restart=always with RestartSec=30, so a daemon that fails at startup loops on that period. The journal names the reason on each attempt:
| Message | Cause | Fix |
|---|---|---|
config: configuration file is missing | no --energy argument | the unit supplies it from MUXEN_DEPLOY; check a drop-in has not overridden it to an empty value |
config: select a CAN interface, or the MQTT source | neither --interface nor --mqtt | the unit passes --interface can0; check a drop-in has not replaced ExecStart |
zone: failed to parse the JSON | the file is missing, unreadable, not valid JSON, or its root is not a JSON object | validate the deployment file |
init: Failed to load the sensors configuration file | follows the line above | as above |
init: Failed to create dispatchers | the handler tables could not be built | rare; report it |
no message, exits after config: lines | the MQTT connection or the CAN socket failed | see the next two entries |
The first two exit with status 1 and print the usage block. The rest exit with status 255.
The daemon exits right after printing its configuration
Two silent failures land here, and which one it is depends on whether the journal shows a mqtt: connected line:
- No
mqtt: connected— the broker is not reachable on127.0.0.1:1883. The daemon does not wait for it. Checksystemctl status mosquitto. mqtt: connectedand then nothing — the CAN interface named by--interfacedoes not exist. Checkip link show can0. The unit is bound tosys-subsystem-net-devices-can0.device, so on a Brain whose bus is notcan0both the binding andExecStartneed a drop-in.
A zone publishes, but every number is zero
The topic is there, the timestamp advances, and all the currents are 0.
Every device and variable is dropped from the sum if it has not reported within five seconds, so this is what an entire zone going quiet looks like.
- Is the bus alive?
candump can0for a few seconds. Nothing at all means the problem is upstream of this daemon. - Did the daemon subscribe to anything? The journal prints
can: dispatcher table size: Nat startup.0means no device of a supported type was bound to any zone. - Is the daemon reading the source you think?
config: data source =sayscanormqtt. In--mqttmode the CAN table is empty by design, and the daemon depends entirely on another service publishingdevice/…topics. - Are the instances right? The CAN table lists one row per subscription with the exact frame ID it matches. A device configured at the wrong
instanceproduces a subscription that never fires, and looks exactly like a silent device.
One device contributes nothing, the rest of the zone is fine
| Likely cause | How to confirm |
|---|---|
| It is a function the daemon does not consume | can: not supported device type <N> at startup, with <N> its function code |
| It is bound to a different zone | the -v zone tree lists each device under its zone |
Its BindToParc is a JSON number, not a string | the value is ignored and the device silently lands in zone 1 |
Its instance differs from the one on the bus | compare the CAN table's frame IDs against candump |
| It reports less often than every five seconds | it will flicker in and out of the sum |
A device may also be missing from the tables entirely: an entry in devices[] whose function or instance is absent, or is not an integer, is skipped without a message.
Everything ended up in "Zone 1"
Devices with no BindToParc parameter — or with a negative one — join zone 1. On a boat where the parameter was never set, that is every device in the file, and zone 1 becomes a meaningless mixture while the other zones stay empty.
The -v zone tree shows it immediately. The fix is in the deployment file, one BindToParc per device.
A zone is called "Zone 3" instead of its real name
No setting named EnergyZoneName3 was found. Check the settings[] section for the exact spelling — the key is the literal string EnergyZoneName with the zone number appended, and the name is taken from the setting's value field.
socValid is false
Two causes, and the journal cannot tell them apart:
- One of the batteries in the zone reports a state of charge of 255, the "not available" value. A single such module invalidates the whole zone.
- No battery reported within five seconds. The
socfield then still holds0, which is exactly why the flag exists — otherwise a silent bus would publish a plausible flat bank.
Check mosquitto_sub -t 'device/5/+/life' -v (or candump can0) to see whether the batteries are talking and what state of charge they send.
autonomy is 0 while the bank is clearly discharging
In decreasing order of likelihood:
capacityis 0. No battery in the zone carries aCapacityparameter, so the remaining capacity is zero and so is the estimate. This is the common one. Check the payload'scapacityfield.socis 0, so remaining capacity is zero even with a capacity configured. Look atsocValid.- The filtered current is not negative. The estimate uses the filtered value, not the instantaneous one; a bank that has only just started discharging is still averaging in the previous minute.
- The daemon restarted less than a filter period ago. The history starts at zero and needs 20 to 60 seconds to fill.
chargingTime is 0 under the mirror-image conditions.
autonomy looks too pessimistic
It is meant to. The filter drops the extremes and then weights the remaining samples towards the most-discharging end, so a bank with an intermittent heavy load reports a shorter time than the arithmetic mean would give. Raising --filter-period lengthens the window — up to 60 seconds — but does not remove the bias.
dischargeOther is large
Not a fault. It is the boat's uninstrumented consumption: everything the configured equipment does not account for. Bloc 8 outputs and interconnections also land there, because they are collected but not yet published in their own right.
It becomes a symptom when it changes without the installation changing, or when it is large on a boat you believe to be fully instrumented — in which case a source or a load is missing from the zone, or is bound to the wrong one.
A converter's current appears on the wrong side
Check the -v zone tree: each converter prints Connected to: output or Connected to: input under the zone it belongs to.
BindToParcselects the output side, read from thelifeframe.InputBindToParcselects the input side, read from thestateframe.
A converter with no BindToParc at all lands in zone 1 and is read on its input side there, which is rarely what is wanted.
If the side is right but the sign is not, the device is sending a convention the daemon does not expect; capture the frame and report it.
The numbers on the screen are frozen
The app/energy/<zone> topics are retained. A stopped daemon leaves its last payload on the broker, and a screen that reads the retained message shows perfectly plausible figures forever.
sh
mosquitto_sub -h 127.0.0.1 -t 'app/energy/#' -v -W 3If the payload arrives once and never repeats, the daemon is not publishing. Compare metadata.rxdate against the current time.
A decommissioned zone still shows up
Same cause, opposite direction: removing a zone from the configuration stops the daemon publishing it, but does not remove the retained message already on the broker. Clear it explicitly:
sh
mosquitto_pub -h 127.0.0.1 -t app/energy/7 -r -n--filter-period did not take the value I gave
The value is clamped to 20…60 seconds, and the daemon says so:
config: filter period set to 60 secondsAn unparseable value is refused outright with config: invalid filter period '<value>' and the daemon exits with status 1.
FAQ
Why does the energy page say "Zone 2" instead of "Starboard bank"? The name comes from a setting in the boat's configuration file called EnergyZoneName2. If it is missing, the daemon falls back to the zone number. An installer needs to add it and restart the service.
Why is the remaining time shorter than my own calculation? On purpose. The estimate leans on the periods when the bank was discharging hardest, so it errs short rather than long. A number that promises more time than the boat has is worse than one that promises less.
Why does the remaining time disappear when I switch a load on? It does not disappear — it goes to zero because the filtered battery current has crossed into charging, at which point the daemon publishes chargingTime instead. Only one of the two is ever non-zero.
The batteries are fine, so why does the screen show no state of charge? One battery in that zone is reporting "not available" for its state of charge, which invalidates the figure for the whole zone. It is usually a module that has just been replaced or has not finished initialising.
What is "other" on the energy page? Everything on the boat that is not individually metered: cabin lighting, fridges, the autopilot, the electronics. The daemon works it out from the difference between what the known sources and loads do and what the battery actually does, so the page always adds up.
Do I have to restart anything after changing the configuration? The daemon reads the file only at startup. Changing /etc/muxen/deploy.json restarts the MUXEN deployment target, which restarts the daemon, so in normal use it happens by itself. Editing the file by another route needs systemctl restart muxen-energy.
Can it work without a CAN bus? Yes — --mqtt takes the same device data from the MQTT device/… topics instead of the bus. Sensor variables always come from MQTT regardless.
Why is there no zone 0? Devices never land in zone 0: an unbound device falls back to zone 1. Only a sensor variable can declare energy.zone: 0.
Why does an AC zone show 0 W? powerUsed is a current multiplied by the zone voltage, and the voltage comes from a battery. A zone with no battery has no voltage, so no power — the currents are still published.
Tips
At commissioning, work through the zone tree. Run the daemon by hand once with -v and read the tree it prints. Every device you expect should be there, under the right zone, with the right function name; each converter should show the side you intended. It is faster than diagnosing the published payload afterwards.
Set Capacity on every battery before anything else. Without it a zone publishes currents but no remaining time, which is the single most common commissioning complaint.
Record dischargeOther at handover. On a settled boat it is nearly constant. Knowing its normal value turns it into a diagnostic: a step change with no change to the installation means something new is drawing, or a metered load has stopped reporting.
Give the estimate a minute after a restart. The filter history starts empty, so the first 20 to 60 seconds of autonomy mean nothing.
Do not raise --filter-period to smooth a noisy display. The window is bounded at 60 seconds and the bias is by design; a display that flickers is better fixed by the screen's own smoothing.
Avoid leaving devices unbound. They land in zone 1 and either pollute it or fill the journal with not supported device type lines that mask the ones that matter.
Clear retained topics when you remove a zone, otherwise the old summary stays on the broker for the next screen that subscribes.
Check the clock. Every payload carries expireAfterSec: 5 and a timestamp; consumers that compare it against a Brain whose clock has jumped will treat live data as stale.
-vv on a live bus is a firehose. It dumps every CAN frame and every MQTT message the daemon sees — which is all of them, since it subscribes to #. Use it against a quiet bus or for a few seconds at a time.
