Troubleshooting
Startup errors
A wrong setting stops the collector with exit status 78. Under systemd the service then stays
failed instead of restarting: systemctl status sunspec-collector shows status=78, and
journalctl -u sunspec-collector -n 20 shows the error. Fix the setting, then run
systemctl restart sunspec-collector. Other startup errors exit with status 1, and systemd keeps
retrying every 5 s.
| Error | Cause | Fix |
|---|---|---|
load config failed ... unknown field | Misspelled or removed key in the config file | Compare with the reference. |
load config failed ... SUNSPEC_X="..." is invalid | Unparsable environment value, including inline comments in an env file | Fix the value; put comments on their own lines. |
load config failed ... read config file | --config / SUNSPEC_CONFIG path missing or unreadable | Check the path and permissions (mode 0644 for systemd). |
config validation failed | Value out of range | The message names the key and the allowed values. |
config validation failed ... kafka.acks must be all (or -1) while kafka.enable_idempotence is true | acks of 0 or 1 with idempotence on (the default) | Set kafka.enable_idempotence = false or acks = "all". |
config validation failed ... modbus.max_batch_size must be between 1 and 125 | Batch size above the Modbus limit | Use a value from 1 to 125. |
config validation failed ... kafka.brokers must be non-empty when set | SUNSPEC_KAFKA_BROKERS= with no value | Set the brokers, or comment the line out to run without Kafka (records are then only logged). |
kafka publisher init failed | librdkafka rejected a setting when the producer was created | The message gives librdkafka's reason. |
bind metrics port <port> | The port is in use (often a second collector) or not permitted | Stop the other process or change metrics.port / SUNSPEC_METRICS_PORT. |
buffer init failed | Buffer path not writable | systemd: keep the buffer under /var/lib/sunspec-collector. Docker: mount a volume at /app/data and keep buffer.path relative or under /app/data. |
SUNSPEC_LOG_FORMAT must be text or json | Unsupported log format | Use text or json. |
Serial devices through a converter
See Serial devices through a converter.
Probing a device
See Probing a device.
A device is not polled
- Check that the collector knows the device:
curl localhost:9090/deviceslists every supervised device with its state and latest readings;sunspec_devices_known,devices_totalin/healthz, and astarting device poller device=host/unitlog line say the same. In subnet mode the host must accept a TCP connection on the Modbus port withindiscovery.per_host_timeout_ms. - Check
sunspec_device_up{device="host/unit"}. A value of 0 means the device is not being read successfully; ifsunspec_device_last_success_timestamp_secondshas no series for it, it has not been read successfully since the collector started. Ifsunspec_poller_restarts_totalkeeps increasing, its sessions keep failing;sunspec_poll_errors_total{kind}shows whether connects or reads fail. - Find the reason in the logs:
journalctl -u sunspec-collector | grep 'device session ended', or probe the device directly withcollector-app --probe host:unit(see Probing a device).
error= in device session ended | Likely cause |
|---|---|
could not resolve host:port | Hostname does not resolve. |
connect to ... failed / request timed out after ...ms | Wrong host or port, firewall, device offline, or poller.request_timeout_ms too short. Test with nc -zv <host> <port>. |
no SunSpec register map at base addresses [...] | The SunS marker was not found at any base. The device may not be SunSpec, may use another base address (set sunspec.base_address), or the unit ID is wrong. (last answer: ...) gives the device's last Modbus exception. |
connection closed by the device | The device or gateway closed the TCP connection. Many devices accept only one Modbus TCP client at a time, or close idle connections; check for another client polling the same device. |
SunSpec model chain is malformed: ... | The model headers do not form a valid chain ending in 0xFFFF. |
10 consecutive poll cycles failed | Reads keep failing after discovery; check sunspec_poll_errors_total{kind}. Devices that reject large reads need a lower modbus.max_batch_size. |
no successful read for ... | Stall detection; see Stalls. |
none of the discovered models [...] is selected for polling | include_models / exclude_models (global or on the device entry) leave nothing to poll on this device. The list shows the models it has. |
If discovery reports (last answer: Gateway target device failed to respond), or the
modbus read failed lines before a session ends show that exception, a gateway answered but has no
device at that unit ID. Check the unit IDs. Each failed attempt before a retry is logged at debug
(RUST_LOG=info,modbus_client=debug).
Failed sessions are retried with exponential backoff capped at 60 s, so a device that comes back online is picked up within about a minute without restarting the collector.
No records in Kafka
- Check that
kafka.brokers/SUNSPEC_KAFKA_BROKERSis set. Without it, records are only logged at debug level (record published (log sink)withRUST_LOG=info,avro_kafka=debug) and then removed from the buffer. - Check
sunspec_uplink_errors_totaland therecords not delivered; they stay bufferedlog line. Rising errors mean the brokers are unreachable or are rejecting the records (wrong address, missing topic with auto-creation disabled, ACLs, or a security mismatch).Kafka broker connection failedgives librdkafka's reason, naming the broker; it is logged at warn at most once per 5 minutes, and every attempt at debug (RUST_LOG=info,avro_kafka=debug).records delivered againmarks the end of an outage. Authentication problems show up asKafka client errorwithSASL authentication errororSSL handshake failed. Checkkafka.security_protocol, the SASL mechanism and credentials, and thatkafka.ssl_ca_locationholds the CA that signed the broker certificate. - Check
sunspec_buffer_oldest_age_secondsandsunspec_buffer_rows. If they grow whilesunspec_uplink_records_totalis flat, nothing is being delivered. - If records arrive but consumers fail to read them, the consumers may still expect the old format; see Upgrade notes.
The buffer keeps growing
The buffer grows when samples are written faster than they are delivered. Compare:
rate(sunspec_buffer_writes_total[5m]) # samples in
rate(sunspec_uplink_records_total[5m]) # records out
- Kafka down or rejecting records: see the previous section. The buffer holds the data until
retention starts dropping it (
sunspec_buffer_dropped_total{reason="max_rows"|"max_age"}). - Kafka up but slow: check
sunspec_uplink_publish_seconds. Each batch waits for the producer's 50 ms linger plus the broker round trip, so a largerbuffer.batch_sizeraises throughput. - More data than expected: every polled model of every device produces one row per poll interval. Read slow-changing models less often or skip unneeded ones with a polling profile; see also Sizing.
Stalls
sunspec_poller_stalls_total increases when a session goes poller.stall_timeout_ms (default
max(10 × poll interval, 30 s)) without a successful read. This usually means every read of the
device timed out or failed for that long, for example when a device keeps its TCP connection open
but stops answering. The session is ended and retried with backoff. The modbus read failed lines
for the device show the errors. Occasional stalls point to the device, the gateway or the network;
a device that stalls repeatedly also triggers the SunspecDeviceRestarting alert.
If /healthz returns 503, the ingest task or the uplink itself has stopped checking in. That is
a collector problem rather than a device problem; the systemd watchdog restarts the service. Keep
the logs from before the restart.
Samples dropped with reason="channel_full"
The ingest task could not take a sample within one poll interval, usually because SQLite writes are
slow (slow or failing storage). Check disk health and latency. runtime.channel_capacity absorbs
short bursts but does not fix slow storage.
Poll cycles take longer than the poll interval
Compare sunspec_poll_duration_seconds with poller.poll_interval_ms. When a cycle overruns, the
next tick is skipped, so samples arrive less often than configured. Increase the poll interval, or
reduce per-request latency (lower poller.request_timeout_ms, fewer retries).
"database is locked"
Two collectors are using the same buffer file, or a manual sqlite3 session holds a write lock.
Run one collector per buffer file and follow
Buffer Maintenance for manual work.