Rusty SunSpec Collector

Rusty SunSpec Collector is an edge service written in Rust. It finds SunSpec devices (inverters, meters, batteries) on Modbus TCP, walks each device's SunSpec model chain, polls every model on a fixed schedule and decodes the points at the edge using the official SunSpec model definitions. Samples are buffered in SQLite on local disk and published to Kafka as Avro single-object records keyed by device.

Delivery is at-least-once: a buffered sample is deleted only after Kafka acknowledges it. The buffer is bounded by row count and age, so during a very long outage the oldest samples are dropped (and counted) instead of growing the buffer without limit.

The project is a Cargo workspace of nine crates, including sunspec-sim, a SunSpec Modbus TCP simulator used by the tests and the Docker Compose stack.

flowchart LR
    subgraph field["Field"]
        devices["<b>SunSpec devices</b><br/>inverters · meters<br/>batteries"]
        serial["<b>RS-485 devices</b><br/>behind a serial<br/>converter"]
    end

    subgraph app["collector-app"]
        pollers["<b>Device pollers</b><br/>one per device<br/>discover · poll · decode"]
        buffer[("<b>SQLite buffer</b><br/>batched writes<br/>retention")]
        uplink["<b>Uplink</b><br/>Avro records<br/>at-least-once"]
        api["<b>HTTP</b><br/>/metrics · /healthz<br/>/devices · /models"]
        pollers -- "bounded<br/>channel" --> buffer --> uplink
        pollers -.-> api
    end

    devices -- "Modbus TCP" --> pollers
    serial -- "RTU over TCP" --> pollers
    uplink -- "keyed by<br/>device" --> kafka[("<b>Kafka</b>")]
    api -.-> observers["Prometheus<br/>Grafana · curl"]

    classDef external stroke-dasharray: 5 4
    class devices,serial,kafka,observers external

How data flows

  1. Device set. Either a static list of hosts (discovery.static_devices) or a subnet scan (discovery.subnet, rescanned every discovery.rescan_interval_s). Each device is identified by host and Modbus unit ID, so every unit behind a gateway is polled.
  2. Per-device poller. Each device runs in its own supervised task. A session connects, finds the SunS marker (configured base address first, then 40000, 0 and 50000), reads the model headers one at a time until the end marker, and then reads and decodes every model once per poll interval (or less often, for models given a slower interval in a polling profile). A failed or stalled session is restarted with exponential backoff, capped at 60 s.
  3. Channel and ingest. Samples go through a bounded channel to the ingest task, which writes them to SQLite in transactions of up to 256 rows. If the channel stays full for a poll interval, the sample is dropped and counted; polling never blocks.
  4. Buffer. SQLite in WAL mode. A retention task runs every 10 s and drops rows older than buffer.max_age_s (7 days) or beyond buffer.max_rows (1,000,000).
  5. Uplink. The uplink reads the oldest rows in batches, publishes one Kafka record per sample and deletes only the rows Kafka acknowledged. Undelivered rows stay and are retried first.
  6. Shutdown. On SIGINT or SIGTERM the pollers stop, ingest writes everything still queued, and the uplink keeps draining for up to buffer.shutdown_drain_timeout_ms (5 s). Rows that are still buffered are sent after the next start.

Each Kafka record is keyed by host/unit_id and carries the device, model ID and name, the read time, the decoded points and the raw registers. Consumers can deduplicate on (device_key, model_id, collected_at_ms). The record format is described in the README and the Avro schema; collector-app --print-schema prints the schema.


Quick start

Try it in a few minutes with simulated devices: see Quick start.

Where to go next


Known limitations

  • Kafka authentication is limited to SASL PLAIN and SCRAM (no Kerberos or OAuth).
  • No Schema Registry integration; records use fingerprint-based single-object encoding.
  • Point units and descriptions are not in each record; they are in the model catalog (collector-app --print-models, /models).
  • No configuration reload.
  • Delivery is at-least-once, so duplicates are possible after a crash.

License

Dual-licensed under MIT or Apache-2.0.

Quick start

Docker Compose (no hardware needed)

docker compose up --build

This starts Kafka, a simulator with two units, the collector and Prometheus. Then:

curl localhost:9090/healthz          # 200 with a JSON body while the pipeline is running
curl -s localhost:9090/metrics | grep '^sunspec_'

Prometheus is on http://localhost:9091; Kafka is reachable from the host at localhost:29092 (topic sunspec.telemetry). docker compose down -v stops the stack and removes its volumes.

Local build

Prerequisites: Rust 1.88+, cmake and a C toolchain, pkg-config, and the OpenSSL and libcurl development headers (libssl-dev and libcurl4-openssl-dev on Debian and Ubuntu). librdkafka is built from source; SQLite and zstd are bundled.

cargo build --workspace
cargo test --workspace

# Terminal 1: simulated devices
cargo run -p sunspec-sim -- --bind 127.0.0.1:1502 --units 1,2

# Terminal 2: the collector (no Kafka configured, so each record's key and size is logged
# at debug level instead of being published)
SUNSPEC_STATIC_DEVICES=127.0.0.1:1,127.0.0.1:2 SUNSPEC_PORT=1502 \
  RUST_LOG=info,avro_kafka=debug cargo run -p collector-app

Set SUNSPEC_KAFKA_BROKERS to publish to a broker, for example localhost:29092 while the Compose stack is running.

Deployment

Deployment options

OptionUse it forBuffer location
Docker imageGateways and servers that run containers/app/data/sunspec-buffer.sqlite in a volume
Docker Compose stackLocal testing with Kafka, a simulator and Prometheuscollector-data volume
systemd serviceBare-metal or VM Linux hosts/var/lib/sunspec-collector/buffer.sqlite

Run one collector per buffer file. Two processes on one SQLite file read the same rows, so records are published twice, and they can fail with "database is locked".

Docker

Images

Releases publish multi-arch (linux/amd64, linux/arm64) images to GHCR:

docker pull ghcr.io/imrans110/sunspec-collector:latest     # or a version, e.g. :0.5.0
docker pull ghcr.io/imrans110/sunspec-simulator:latest

To build them from source instead:

docker build -t sunspec-collector .                                 # collector (default target)
docker build --target simulator -t sunspec-simulator .              # SunSpec device simulator

The examples below use the local tag sunspec-collector; substitute ghcr.io/imrans110/sunspec-collector:<version> for a published image. The Dockerfile builds both binaries in rust:1.88-slim-bookworm and copies them into debian:bookworm-slim. The collector image:

  • runs as the non-root user collector;
  • runs in the working directory /app/data, so the default relative buffer path is /app/data/sunspec-buffer.sqlite. Mount a volume at /app/data. The image does not set SUNSPEC_BUFFER_PATH, so buffer.path from a config file applies; keep it relative or under /app/data so the buffer stays in the volume;
  • exposes port 9090;
  • has a HEALTHCHECK that runs curl -fsS "http://localhost:${SUNSPEC_METRICS_PORT:-9090}/healthz" every 30 s (3 s timeout, 20 s start period, 3 retries). It follows SUNSPEC_METRICS_PORT, but not metrics.port from a config file, so set the port with the environment variable in a container.

Run

docker run -d \
  --name sunspec-collector \
  --restart unless-stopped \
  --stop-timeout 30 \
  -p 9090:9090 \
  -e SUNSPEC_SUBNET=192.168.1.0/24 \
  -e SUNSPEC_KAFKA_BROKERS=kafka:9092 \
  -v sunspec-data:/app/data \
  sunspec-collector

--stop-timeout 30 gives the graceful shutdown the same 30 s the systemd unit allows. Docker's default is 10 s; see Graceful shutdown.

With an environment file:

docker run -d --name sunspec-collector -p 9090:9090 \
  --env-file collector.env \
  -v sunspec-data:/app/data \
  sunspec-collector

Write one VAR=value per line in environment files and put comments on their own lines. Docker's --env-file and systemd's EnvironmentFile= both keep text after # on a value line as part of the value, and the collector rejects a value such as 9090 # metrics port at startup.

To use a config file, mount it and point SUNSPEC_CONFIG at it (the image has no entrypoint, so arguments after the image name replace the command):

docker run -d --name sunspec-collector -p 9090:9090 \
  -v "$(pwd)/collector.toml:/app/collector.toml:ro" \
  -e SUNSPEC_CONFIG=/app/collector.toml \
  -v sunspec-data:/app/data \
  sunspec-collector

Check, inspect, stop

docker inspect --format='{{.State.Health.Status}}' sunspec-collector
docker logs -f sunspec-collector
docker stop sunspec-collector && docker rm sunspec-collector

Docker marks a container unhealthy when the health check fails but does not restart it. The --restart policy applies only when the process exits.

Docker Compose (local stack)

docker compose up --build starts:

ServiceImageNotes
kafkaapache/kafka:3.9.1 (KRaft)Plaintext from the host at localhost:29092; SASL/PLAIN listener on kafka:9094 inside the network
simulatorbuilt from the simulator targetUnits 1 and 2 on port 1502
collectorbuilt from the collector targetPolls simulator:1 and simulator:2; publishes over SASL/PLAIN with its password from a Docker secret; port 9090; data in volume collector-data
prometheusprom/prometheus:v3.5.0http://localhost:9091, scraping the collector via docker/prometheus.yml
curl localhost:9090/healthz
curl -s localhost:9090/metrics | grep '^sunspec_'
docker compose down -v        # stop and remove the volumes, including the buffer

The collector service has no stop_grace_period, so docker compose stop allows the default 10 s.

systemd

The unit file is sunspec-collector.service and the environment file template is sunspec-collector.env.

From a release

On x86_64 or aarch64 Linux with glibc 2.18 or newer and systemd 235 or newer (Debian 10+, Ubuntu 18.04+, RHEL 8+, Raspberry Pi OS 10+):

curl -fsSL https://github.com/imrans110/rusty-sunspec-collector/releases/latest/download/install.sh | sudo sh
sudoedit /etc/sunspec-collector.env        # devices, Kafka brokers
sudo systemctl enable --now sunspec-collector

To run without Kafka (records are then only logged), comment out the SUNSPEC_KAFKA_BROKERS line; an empty value is a configuration error.

install.sh downloads sunspec-collector-<version>-<target>.tar.gz and SHA256SUMS from the release, checks the archive's checksum, and installs:

PathNotes
/usr/local/bin/collector-app, /usr/local/bin/sunspec-simReplaced atomically, so a running service keeps its old binary until restarted
/etc/systemd/system/sunspec-collector.serviceReplaced on upgrade; a locally edited copy is saved as .service.bak. Put local changes in a drop-in (systemctl edit sunspec-collector)
/etc/sunspec-collector.envInstalled only if missing (mode 0600, since it may hold Kafka passwords); upgrades keep your settings
/usr/local/share/doc/sunspec-collector/README, CHANGELOG, licenses, config.example.toml and the shipped environment file

It runs systemctl daemon-reload but does not start or restart the service; after an upgrade, run sudo systemctl restart sunspec-collector. SUNSPEC_VERSION=0.5.0 installs a specific release. To read the script first: curl -fsSLO <url>, read install.sh, then sudo sh install.sh. From 0.5.0 the release files and images are attested, so a downloaded install.sh, archive or SHA256SUMS can be checked with gh attestation verify first; see Verifying a release.

To install from a downloaded archive by hand, use the archive's files in the steps below instead of building.

From source

# 1. Build the release binary (see Building & Packaging) and install it
cargo build --release -p collector-app
sudo install -m 0755 target/release/collector-app /usr/local/bin/collector-app

# 2. Install the unit and the environment file, then edit the environment file
sudo install -m 0644 docs/sunspec-collector.service /etc/systemd/system/sunspec-collector.service
sudo install -m 0644 docs/sunspec-collector.env /etc/sunspec-collector.env
sudoedit /etc/sunspec-collector.env

# 3. Optional: a config file, enabled with SUNSPEC_CONFIG=/etc/sunspec-collector.toml
#    in the environment file
sudo install -m 0644 docs/config.example.toml /etc/sunspec-collector.toml

# 4. Start it
sudo systemctl daemon-reload
sudo systemctl enable --now sunspec-collector
systemctl status sunspec-collector

Do not create /var/lib/sunspec-collector yourself; systemd creates and owns it (see below).

Keep SUNSPEC_BUFFER_PATH=/var/lib/sunspec-collector/buffer.sqlite in the environment file. The unit sets no working directory and the file system is read-only to the service except for its state directory, so the default relative buffer path fails with buffer init failed.

The service reads the config file as an unprivileged dynamic user, so the file must be world-readable (mode 0644) and must not be under /home (ProtectHome=true). systemd reads the environment file itself, before dropping privileges.

What the unit does

SettingEffect
Type=notifysystemd marks the service started once the collector has opened the buffer and started its tasks (READY=1).
WatchdogSec=30The collector pings the watchdog every 15 s, but only while the pipeline is healthy (the same check as /healthz). If ingest or the uplink stops checking in for 120 s, the pings stop and systemd stops the service within 30 s; Restart=always starts it again. The log shows pipeline unhealthy; withholding systemd watchdog ping.
Restart=always, RestartSec=5Restarts 5 s after any exit except a configuration error.
RestartPreventExitStatus=78A wrong setting exits with 78 (EX_CONFIG) and is not restarted: the service stays failed (status=78 in systemctl status) with the error in the journal. Fix the setting, then systemctl restart sunspec-collector.
TimeoutStopSec=30Time allowed for the graceful stop after SIGTERM before SIGKILL.
DynamicUser=yesRuns as a transient unprivileged user; no account needs to be created.
StateDirectory=sunspec-collector, StateDirectoryMode=0750systemd creates /var/lib/sunspec-collector, owned by the service user, and keeps it across restarts and reboots. With DynamicUser=yes the data is stored in /var/lib/private/sunspec-collector and /var/lib/sunspec-collector is a symlink to it. This is the only writable location.
EnvironmentFile=-/etc/sunspec-collector.envLoads the environment file; the leading - means a missing file is not an error.
LimitNOFILE=4096File descriptor limit.
HardeningNoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups, RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX, RestrictNamespaces, LockPersonality, MemoryDenyWriteExecute, SystemCallArchitectures=native.

ExecStart passes no arguments. Use SUNSPEC_CONFIG in the environment file, or a drop-in (systemctl edit sunspec-collector), to change how the binary is started.

Logs

journalctl -u sunspec-collector -n 200 --no-pager     # recent logs
journalctl -u sunspec-collector -f                    # follow
journalctl -u sunspec-collector -p warning --no-pager # warnings and errors only

Graceful shutdown

SIGINT (Ctrl-C) and SIGTERM (sent by systemctl stop and docker stop) start a graceful stop:

  1. The log shows shutdown signal received. Device pollers stop; reads in progress are abandoned. Pollers get 10 s to finish before they are aborted.
  2. The ingest task writes every sample still queued in the channel to SQLite, then logs ingest stopped.
  3. The uplink finishes the batch it is publishing, then keeps draining the buffer for up to buffer.shutdown_drain_timeout_ms (default 5 s; 0 skips this step). If time runs out, it logs final uplink drain timed out; remaining rows stay buffered for the next start.
  4. Retention, the HTTP server and the watchdog stop. The log shows collector stopped and the process exits with status 0.

Rows left in the buffer are published after the next start.

A stop normally takes a few seconds. The worst case is roughly the 10 s poller grace, plus one in-flight batch (up to kafka.timeout_ms), plus buffer.shutdown_drain_timeout_ms. The systemd unit allows 30 s (TimeoutStopSec). Docker allows 10 s unless you pass --stop-timeout (or set stop_grace_period in Compose). If you raise buffer.shutdown_drain_timeout_ms, raise these limits too.

If the process is killed instead (SIGKILL, power loss):

  • rows already committed to SQLite are kept and sent after the restart;
  • samples still in the channel, not yet written, are lost;
  • rows that Kafka acknowledged but that were not yet deleted are sent again, so consumers see duplicates. See Delivery semantics.

Logs

Logs go to standard output (journald under systemd, docker logs in Docker). Set the level with RUST_LOG (default info) and the format with SUNSPEC_LOG_FORMAT=text|json; see Logging. At info, steady-state operation logs almost nothing: per-cycle and per-batch messages are at debug level.

Messages worth knowing:

MessageLevelMeaning
starting device pollerinfoA device was added (device=host/unit).
discovered SunSpec modelsinfoSession started; shows the base address and model IDs found.
device session endedwarnA session failed; error gives the reason, retry_in_ms the backoff.
modbus read failedwarnA model could not be read after the client's retries (poller_actor). Each failed attempt is logged at debug by modbus_client.
modbus connectedinfoThe client opened a connection: a shared client's first read, or a reconnect after a transport error or timeout.
telemetry channel full, sample droppedwarnIngest is not keeping up.
no devices foundwarnThe device set is empty; subnet mode retries after rescan_interval_s.
records not delivered; they stay bufferedwarnKafka did not acknowledge some records.
Kafka broker connection failed; repeats are logged at debug for 5 minuteswarnThe Kafka client could not connect to a broker; reason names it. Logged at warn at most once per 5 minutes.
records delivered againinfoKafka accepted records after failed attempts (failed_attempts); a backlog drains from here.
Kafka client errorerrorlibrdkafka reported another error, for example a failed authentication.
buffer write failed; samples droppedwarnSQLite write failed; samples lost.
buffer retention: dropped rows past max agewarnRetention deleted rows older than buffer.max_age_s.
buffer retention: dropped oldest rows over max rowswarnRetention deleted rows beyond buffer.max_rows.
corrupt buffered sample, discarding / sample cannot be encoded, discardingwarnA row could never be published and was deleted.
pipeline unhealthy; withholding systemd watchdog pingwarnIngest or uplink stopped checking in.
final uplink drain timed out; ...warnShutdown left rows in the buffer.

librdkafka's own log lines use the librdkafka target. Its connection failures while a broker is down (for example Connect to ipv4#... failed) are at debug, since the outage warning above covers them.

Securing the Kafka connection

The collector supports TLS, mutual TLS and SASL (PLAIN, SCRAM-SHA-256, SCRAM-SHA-512); the keys are listed in Configuration. Use sasl_ssl or ssl whenever the traffic leaves the site: sasl_plaintext sends the password and the telemetry unencrypted, and the collector logs a warning when it is used.

SCRAM over TLS, with the password in a file:

[kafka]
brokers = "kafka-1.example.com:9093,kafka-2.example.com:9093"
security_protocol = "sasl_ssl"
ssl_ca_location = "/etc/sunspec-collector/kafka-ca.pem"
sasl_mechanism = "SCRAM-SHA-512"
sasl_username = "site-0042"
sasl_password_file = "/run/credentials/sunspec-collector.service/kafka-password"

Mutual TLS instead of SASL: set security_protocol = "ssl", ssl_ca_location, ssl_certificate_location and ssl_key_location (plus ssl_key_password_file if the key is encrypted).

Keeping secrets out of config and environment files

Prefer the *_password_file settings over putting passwords in the config file or environment:

  • systemd: store the password root-only on the host and pass it as a credential. Add to the unit (or a drop-in from systemctl edit sunspec-collector):

    [Service]
    LoadCredential=kafka-password:/etc/sunspec-collector/kafka-password
    Environment=SUNSPEC_KAFKA_SASL_PASSWORD_FILE=/run/credentials/sunspec-collector.service/kafka-password
    

    The file is readable only by the service, which also works with DynamicUser=yes.

  • Docker / Compose: mount the password as a secret and point the _FILE variable at it, as the local stack does:

    services:
      collector:
        environment:
          SUNSPEC_KAFKA_SASL_PASSWORD_FILE: /run/secrets/kafka_sasl_password
        secrets:
          - kafka_sasl_password
    secrets:
      kafka_sasl_password:
        file: ./kafka-sasl-password
    

    The secret file must be readable by the container user (collector).

The compose stack's password (docker/dev-secrets/) is a development-only value.

Rotating credentials

Certificates, keys and passwords are read when the collector starts. After rotating them, restart the service (systemctl restart sunspec-collector or docker compose restart collector). Unsent samples stay in the buffer and are published after the restart.

Serial devices through a converter

Many inverters and meters only have an RS-485 port. A serial-to-Ethernet converter in transparent mode (often called "TCP server", "raw" or "transparent"; not "Modbus TCP gateway") passes Modbus RTU frames between a TCP connection and the bus unchanged. Set the framing to rtu and list each unit on the bus:

[modbus]
port = 8899                       # the converter's TCP port

[[discovery.static_devices]]
ip = "192.168.1.60"               # the converter
unit_id = 1
framing = "rtu"

[[discovery.static_devices]]
ip = "192.168.1.60"
unit_id = 2
framing = "rtu"

([modbus] framing = "rtu" applies it to every device instead, and SUNSPEC_MODBUS_FRAMING=rtu does the same from the environment.)

  • RTU has no transaction IDs, so only one request may be on the bus at a time. All units on the same converter (host and port) share one TCP connection, and their requests run one after another. Per-device poll intervals and model settings may differ, but request_timeout_ms and max_batch_size must match; validation says so if they do not.
  • Serial buses are slow: 125 registers at 9600 baud take roughly 300 ms. Raise poller.request_timeout_ms and, if the converter buffers little, lower modbus.max_batch_size. A slow or absent unit delays the others, because they wait for the bus.
  • A unit that does not answer times out. The connection is then reopened (a late reply could otherwise be read as the answer to the next request), so an absent unit on a shared converter causes reconnects for its neighbours; remove units that are not on the bus.
  • If the converter is in "Modbus TCP gateway" mode instead, it translates Modbus TCP to RTU itself: keep the default framing = "tcp".
  • collector-app --probe uses the same framing, so it works through the converter too.

Native serial ports (a USB RS-485 adapter on the gateway) are not supported yet.

Polling profiles

By default every discovered model is read on every poll cycle. Models that rarely change, such as common (1), nameplate (120) and settings (121), can be read less often, and models you do not need can be skipped. Both cut Modbus traffic and Kafka volume.

[poller]
poll_interval_ms = 1000
exclude_models = [123]            # never read the immediate controls model

[poller.model_intervals_s]        # model ID = seconds
1 = 3600                          # common: hourly
120 = 3600                        # nameplate: hourly
121 = 600                         # settings: every 10 minutes

The same in the environment (for the systemd environment file or Docker):

SUNSPEC_MODEL_INTERVALS_S=1=3600,120=3600,121=600
SUNSPEC_EXCLUDE_MODELS=123

How the schedule works:

  • Every polled model is read on the first cycle of a session, so a device's identity and settings are published as soon as it connects. After a successful read, a model with an interval is read again on the first cycle at least that long later. A failed read is retried on the next cycle.
  • Intervals are counted in whole poll cycles: with poll_interval_ms = 1000, 121 = 600 reads model 121 every 600th cycle.
  • A new session (after a reconnect, a stall or repeated failures) reads every model again, so a slow model can be read more often than its interval while a device is unstable.
  • A cycle in which nothing is due does not contact the device and does not count towards the stall timeout.
  • include_models and exclude_models filter the discovered chain. Listing a model that a device does not have is not an error. If nothing on a device is selected, its session ends with none of the discovered models [...] is selected for polling and is retried with the usual backoff; the chain is rediscovered each time, so a firmware update that adds a model is noticed.
  • Discovery still reads every model header. The discovered SunSpec models log line lists the whole chain (models), the models that are polled (polled) and the slow ones (slow).

Per-device settings

A [[discovery.static_devices]] entry in a config file can override these settings for that device:

KeyOverridesNotes
poll_interval_mspoller.poll_interval_ms≥ 1. The device's default stall timeout follows it.
request_timeout_mspoller.request_timeout_ms≥ 1.
base_addresssunspec.base_addressProbed first; the standard bases follow.
max_batch_sizemodbus.max_batch_size1–125.
framingmodbus.framingtcp or rtu.
include_modelspoller.include_modelsReplaces the global list.
exclude_modelspoller.exclude_modelsReplaces the global list; [] clears it for this device.
model_intervals_spoller.model_intervals_sMerged model by model; the device's entries win.
[poller.model_intervals_s]
1 = 3600
120 = 3600

[[discovery.static_devices]]
ip = "192.168.1.30"               # a slow meter behind a serial gateway
unit_id = 2
poll_interval_ms = 5000
request_timeout_ms = 3000
max_batch_size = 60

[[discovery.static_devices]]
ip = "192.168.1.40"               # an inverter at unit 126 with MPPT data every 10 s
unit_id = 126
include_models = [1, 120, 160]
model_intervals_s = { 1 = 86400, 160 = 10 }   # merged: 1 daily, 120 hourly, 160 every 10 s
  • Per-device settings exist only in the config file. SUNSPEC_STATIC_DEVICES replaces the file's device list together with these settings, and logs a warning when it does.
  • Devices found by a subnet scan use the global settings.
  • After its overrides are applied, a device's include_models and exclude_models may not share a model; the error names the device.

Probing a device

collector-app --probe HOST[:UNIT] reads one device's SunSpec map once and prints it as JSON: the base address, every model's header address and length, its registers, and the decoded points. It uses the same settings as the service (--config, SUNSPEC_* variables, and the device's static-device overrides), so it shows what the collector would read, and it does not start the service.

collector-app --probe 192.168.1.50:126 > dump.json
SUNSPEC_PORT=1502 collector-app --probe inverter.local          # unit defaults to 1

The JSON goes to standard output and a one-line summary to standard error, for example 192.168.1.50/126: SunSpec map at 40000, models 1, 103, 120, 160. A model that cannot be read is kept in the dump with its error, and named in a warning. Serial numbers (SN) and network addresses (MAC, IPv4, IPv6) in each model's top level are blanked and listed under redacted; --keep-identifiers keeps them.

Use it when commissioning a device, when a device is not polled (it shows whether the SunSpec map is found and every model reads), and for device reports. sunspec-sim --fixture dump.json serves the dump again, so a reported problem can be reproduced without the hardware (see fixtures/devices/README.md).

Verifying a release

From 0.5.0, each release's archives, install.sh and SHA256SUMS, and both images, carry SLSA build provenance: a statement of the workflow run, commit and tag that built them, signed with a short-lived Sigstore certificate issued to release.yml and recorded in Sigstore's public transparency log. GitHub artifact attestations are free for public repositories. To check a download with the GitHub CLI:

gh attestation verify sunspec-collector-0.5.0-x86_64-unknown-linux-gnu.tar.gz \
  --repo imrans110/rusty-sunspec-collector \
  --signer-workflow imrans110/rusty-sunspec-collector/.github/workflows/release.yml \
  --source-ref refs/tags/v0.5.0
gh attestation verify oci://ghcr.io/imrans110/sunspec-collector:0.5.0 \
  --repo imrans110/rusty-sunspec-collector

gh finds the attestation by the file's or image's digest. The release also has the signed bundle, sunspec-collector-<version>.intoto.jsonl; --bundle <file> verifies against it instead of looking the attestation up on GitHub. Verifying SHA256SUMS once covers every archive it lists, so install.sh's checksum check then ties the installed archive to the attested build.

Monitoring and alerts

How to scrape the collector with Prometheus and which alerts to start with. Every metric is described in Metrics.


Scrape configuration

scrape_configs:
  - job_name: sunspec-collector
    static_configs:
      - targets: ["collector-host:9090"]

The Compose stack uses the same configuration in docker/prometheus.yml.


groups:
  - name: sunspec-collector
    rules:
      - alert: SunspecCollectorDown
        expr: up{job="sunspec-collector"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Prometheus cannot scrape the collector on {{ $labels.instance }}"

      - alert: SunspecDeviceDown
        expr: sunspec_device_up == 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Device {{ $labels.device }} has not been polled successfully for 5 minutes"

      # Devices whose sessions keep failing, including ones that flap between up and down.
      - alert: SunspecDeviceRestarting
        expr: increase(sunspec_poller_restarts_total[15m]) >= 5
        labels:
          severity: warning
        annotations:
          summary: "Device {{ $labels.device }} sessions keep failing"

      - alert: SunspecPollerStalled
        expr: increase(sunspec_poller_stalls_total[15m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Device {{ $labels.device }} stalled and was restarted"

      - alert: SunspecSamplesDropped
        expr: increase(sunspec_samples_dropped_total[15m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Samples dropped before the buffer ({{ $labels.reason }})"

      - alert: SunspecUplinkErrors
        expr: increase(sunspec_uplink_errors_total[10m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Kafka is not acknowledging records from {{ $labels.instance }}"

      # Rows are waiting but nothing was delivered in the last 5 minutes (also fires when
      # nothing has been delivered since startup, when the counter does not exist yet).
      - alert: SunspecUplinkStalled
        expr: (sunspec_buffer_rows > 0) unless (rate(sunspec_uplink_records_total[5m]) > 0)
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "No records delivered to Kafka from {{ $labels.instance }}"

      - alert: SunspecBufferBacklog
        expr: sunspec_buffer_oldest_age_seconds > 600
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Oldest buffered row is {{ $value | humanizeDuration }} old"

      - alert: SunspecBufferDataLoss
        expr: increase(sunspec_buffer_dropped_total[1h]) > 0
        labels:
          severity: critical
        annotations:
          summary: "Buffered data dropped without being published ({{ $labels.reason }})"

To alert on /healthz itself, probe it with the Prometheus blackbox exporter (an http_2xx module) and alert on the probe:

      - alert: SunspecCollectorUnhealthy
        expr: probe_success{job="sunspec-healthz"} == 0
        for: 5m
        labels:
          severity: critical

Buffer Maintenance

The collector writes every sample to a local SQLite database before it publishes anything to Kafka. This page describes what is in that database, how rows move through it, how to size it, and how to inspect and maintain it safely.

Settings referred to here are described in Configuration; metrics are in Metrics and alerts in Monitoring and alerts.


What the buffer is for

Pollers produce samples on a fixed schedule whether or not Kafka is reachable. The buffer decouples the two:

pollers → channel → ingest → SQLite buffer → uplink → Kafka
  • Ingest writes samples to SQLite in transactions of up to 256 rows: samples that arrive within 100 ms of the first one share one transaction and one fsync.
  • Uplink reads the oldest rows, publishes them, and deletes them once Kafka has acknowledged them.
  • Retention runs every 10 s and keeps the buffer within buffer.max_rows and buffer.max_age_s.

During a Kafka or network outage the rows wait on disk and are sent when Kafka is reachable again. The buffer also survives collector restarts.


Where it lives

DeploymentPathNotes
Local runsunspec-buffer.sqliteDefault; relative to the working directory.
Docker image/app/data/sunspec-buffer.sqliteThe default relative path, resolved against the image's working directory /app/data. Mount a volume at /app/data; the Compose stack uses the volume collector-data (listed by docker volume ls with the project name as prefix). A relative buffer.path or SUNSPEC_BUFFER_PATH also lands in /app/data.
systemd/var/lib/sunspec-collector/buffer.sqliteSet in /etc/sunspec-collector.env. With DynamicUser=yes, /var/lib/sunspec-collector is a symlink to /var/lib/private/sunspec-collector, which only root can enter.

The path is set with buffer.path / SUNSPEC_BUFFER_PATH. The file is created if it does not exist, and the table and index are created at startup.

SQLite keeps three files side by side, named after the database file (here the systemd name buffer.sqlite; in Docker by default sunspec-buffer.sqlite, sunspec-buffer.sqlite-wal and sunspec-buffer.sqlite-shm):

FileContents
buffer.sqliteThe database.
buffer.sqlite-walWrite-ahead log. Holds recently committed transactions that have not yet been copied into the main file.
buffer.sqlite-shmShared-memory index for the WAL.

Treat the three files as one unit: copy, move or delete them together, and only while the collector is stopped.


Schema

CREATE TABLE telemetry_queue (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    topic      TEXT    NOT NULL,
    payload    BLOB    NOT NULL,
    created_at INTEGER NOT NULL
);
CREATE INDEX idx_created_at ON telemetry_queue(created_at);
ColumnContents
idIncreasing row ID. The uplink publishes in id order, oldest first.
topicKafka topic configured when the row was written. The uplink publishes every row to the topic configured now, so after changing kafka.topic the backlog goes to the new topic.
payloadThe sample as UTF-8 JSON (stored as a BLOB). It is converted to Avro only when published.
created_atTime the row was written, in Unix milliseconds. Retention and sunspec_buffer_oldest_age_seconds use it. All rows from one ingest transaction share the same value.

A payload looks like this (registers and points shortened):

{
  "device": {"ip": "192.168.1.20", "unit_id": 1},
  "model_id": 103,
  "model_name": "inverter_three_phase",
  "start": 40070,
  "registers": [103, 50, 769, 0, 0, 0, 65534, ...],
  "collected_at_ms": 1792212700844,
  "points": [["A", 7.69], ["AphA", 0.0], ..., ["PhVphA", 230.3], ["W", 5309.0], ["Hz", 50.02], ["St", 4]]
}
  • start is the register address of the model header; registers are the raw values, including the two header registers.
  • collected_at_ms is the time the model was read. It differs from created_at, which is when the row was written.
  • points are the decoded points as [name, value] pairs in definition order. A value is a number, a string or null (not implemented). Models without a bundled definition have an empty list. Rows written by earlier versions have no points field and are published with an empty points map.

SQLite settings

  • WAL journal mode. Readers and the writer do not block each other, so the uplink can read while ingest writes, and a sqlite3 session can read while the collector runs. Writes (ingest inserts, uplink and retention deletes) still take turns.
  • synchronous=NORMAL, set on every connection. A committed transaction survives a crash or kill of the collector process. After a power loss or an operating-system crash, the most recent transactions can be rolled back; the database itself stays consistent.
  • SQLite checkpoints the WAL into the main file automatically, by default every 1000 pages (about 4 MB with the default 4 KB page size), and reuses the WAL file afterwards.

Delivery semantics

Delivery is at-least-once.

  1. The uplink reads up to buffer.batch_size rows, oldest first.
  2. Each row becomes one Kafka record, keyed by device_key (host/unit). All records of the batch are handed to the producer, then the uplink waits for the acknowledgements.
  3. Rows that Kafka acknowledged are deleted.
  4. Rows that Kafka did not acknowledge within kafka.timeout_ms stay in the buffer. They are still the oldest rows, so they are retried first. Failed records count in sunspec_uplink_errors_total. After a batch in which nothing was delivered, the uplink backs off (1 s, doubling, up to 30 s). If the records timed out while broker connections were failing, it asks the brokers every 2 s during the backoff whether they answer, and ends the backoff as soon as they do.
  5. Rows that can never be published (the payload is not valid JSON, or the sample cannot be encoded as Avro) are deleted, logged, and counted in sunspec_buffer_dropped_total{reason="corrupt"}, so they cannot block the queue.

While a backlog exists, the uplink drains batch after batch without pausing. It waits buffer.drain_interval_ms only after a batch that was not full.

Duplicates are possible. If the process dies after Kafka acknowledged a batch but before the rows were deleted, or if the delete fails (logged as deleting published rows failed; they may be sent again), those rows are sent again. Consumers can deduplicate on (device_key, model_id, collected_at_ms).

Records are sent oldest first, but a record that fails and is retried can arrive after newer records. Consumers should order by collected_at_ms, not by arrival.

Without kafka.brokers, the uplink hands records to a log-only sink, which counts as delivered: rows are deleted and nothing is kept.


Retention

The retention task runs at startup and then every 10 s:

  1. Deletes rows whose created_at is older than buffer.max_age_s (default 604800 s, 7 days). Counted in sunspec_buffer_dropped_total{reason="max_age"}.
  2. Deletes the oldest rows beyond buffer.max_rows (default 1,000,000). Counted in sunspec_buffer_dropped_total{reason="max_rows"}.
  3. Updates sunspec_buffer_rows and sunspec_buffer_oldest_age_seconds.

Both deletions log a warning with the number of rows dropped. Rows dropped by retention are lost.

Because retention runs every 10 s, the buffer can briefly exceed max_rows by up to 10 s of writes. Because it runs at startup, rows older than max_age_s are deleted when a collector starts after a long stop, before the uplink can send them.

Samples that never reach the buffer are counted separately: sunspec_samples_dropped_total{reason="channel_full"} (ingest too slow) and sunspec_buffer_dropped_total{reason="write_error"} (an SQLite write failed, for example on a full disk).


Sizing

Rows per second

Every polled model of every device produces one row per poll interval, and a model with a polling profile interval one row per interval:

rows per second = devices × (models polled every cycle ÷ poll interval in seconds
                             + Σ 1 ÷ slow model interval in seconds)

The discovered SunSpec models log line lists each device's models, the ones it polls and the slow ones. For example, a device with models 1, 103, 120, 121 and 160, polled every second with 1 and 120 hourly and 121 every 10 minutes, produces about 2 rows per second instead of 5.

Bytes per row

Row size depends on the models. As a rough upper estimate:

payload bytes ≈ 150 + 5 × registers + 15 × decoded points
bytes on disk ≈ payload bytes + about 120 (row header, topic, timestamp, index entry)

For reference, with the simulator's models 1, 103, 120 and 160 the payloads were 436–718 bytes (about 580 on average) and the database file grew by about 0.7 KB per row. Rows from real devices can be larger, because values with more digits take more bytes in JSON. To measure your own rows, see Inspecting the buffer.

Example

10 inverters × 4 models, polled every second, at about 0.7 KB per row:

Value
Rows per second10 × 4 ÷ 1 = 40
Rows per hour144,000 (about 100 MB)
Rows per day3,456,000 (about 2.4 GB)
Outage covered by the default max_rows (1,000,000)1,000,000 ÷ 40 ≈ 25,000 s ≈ 7 hours, about 0.7 GB
max_rows for a 24-hour outage40 × 86,400 ≈ 3,500,000, about 2.4 GB

With these numbers the default max_rows is reached long before the default max_age_s of 7 days. Pick the outage you want to ride out, then:

max_rows   ≥ rows per second × outage seconds
disk space ≥ max_rows × bytes on disk + headroom for the WAL and for VACUUM (see below)

The database file does not shrink when rows are deleted. SQLite reuses the freed pages, so after a large backlog has drained the file keeps its largest size until it is vacuumed.


Inspecting the buffer

Use the sqlite3 command-line tool (apt install sqlite3 on Debian and Ubuntu). The Docker image does not include it.

While the collector is running, open the database read-only and keep sessions short. A session left open holds a read snapshot that stops SQLite from checkpointing the WAL, so the WAL file grows. -readonly fails with unable to open database file when the -wal and -shm files do not exist; in that case stop the collector and follow Safe maintenance.

systemd (root is needed to enter /var/lib/private):

sudo sqlite3 -readonly /var/lib/sunspec-collector/buffer.sqlite

Docker, from a throwaway container that mounts the same volume:

docker run --rm -it -v sunspec-data:/data debian:bookworm-slim sh -c \
  'apt-get update -qq && apt-get install -y -qq sqlite3 >/dev/null && sqlite3 -readonly /data/sunspec-buffer.sqlite'

The examples use the Docker default file name. If you set buffer.path or SUNSPEC_BUFFER_PATH (for example /app/data/buffer.sqlite), use the matching name under /data.

The queries below can be typed at the sqlite3> prompt, or passed on standard input:

sudo sqlite3 -readonly /var/lib/sunspec-collector/buffer.sqlite <<'SQL'
SELECT COUNT(*) FROM telemetry_queue;
SQL

Rows waiting:

SELECT COUNT(*) AS rows FROM telemetry_queue;

Age of the oldest row (the same value as sunspec_buffer_oldest_age_seconds):

SELECT datetime(MIN(created_at) / 1000, 'unixepoch') AS oldest_written_utc,
       round((CAST(strftime('%s', 'now') AS INTEGER) * 1000 - MIN(created_at)) / 1000.0, 1) AS oldest_age_s
FROM telemetry_queue;

Rows per device and model:

SELECT json_extract(CAST(payload AS TEXT), '$.device.ip') || '/' ||
       json_extract(CAST(payload AS TEXT), '$.device.unit_id') AS device,
       json_extract(CAST(payload AS TEXT), '$.model_id') AS model_id,
       COUNT(*) AS rows
FROM telemetry_queue
GROUP BY device, model_id
ORDER BY device, model_id;

The oldest rows, and one payload:

SELECT id, datetime(created_at / 1000, 'unixepoch') AS written_utc, length(payload) AS bytes
FROM telemetry_queue ORDER BY id LIMIT 5;

SELECT CAST(payload AS TEXT) FROM telemetry_queue ORDER BY id LIMIT 1;

Average row size and file usage, for sizing:

SELECT COUNT(*) AS rows, round(avg(length(payload))) AS avg_payload_bytes FROM telemetry_queue;

SELECT page_count * page_size AS file_bytes, freelist_count * page_size AS free_bytes
FROM pragma_page_count(), pragma_page_size(), pragma_freelist_count();

CAST(payload AS TEXT) makes the JSON functions treat the BLOB as text JSON on every SQLite version.


Safe maintenance

Stop the collector before changing the buffer. Reading is safe while it runs; anything that writes (VACUUM, DELETE, replacing the file) should be done with the service stopped.

sudo systemctl stop sunspec-collector     # systemd
docker stop sunspec-collector             # Docker

Commands run as root can leave a root-owned -wal or -shm file behind, which the service user then cannot open. Each procedure below ends by giving the files back to the owner of the directory.

Reclaim disk space (VACUUM)

Routine vacuuming is not needed: SQLite reuses freed pages. Vacuum when a large backlog has drained and you want the disk space back. VACUUM rewrites the whole database and needs free space of about twice the database size.

Do not vacuum while the collector is running. VACUUM holds an exclusive lock for its whole run; the collector's writes wait for a few seconds and then fail, and the samples in them are dropped (reason="write_error").

systemd:

sudo systemctl stop sunspec-collector
sudo sqlite3 /var/lib/sunspec-collector/buffer.sqlite 'VACUUM; PRAGMA wal_checkpoint(TRUNCATE);'
sudo sh -c 'chown --reference=/var/lib/sunspec-collector /var/lib/sunspec-collector/*'
sudo systemctl start sunspec-collector

Docker:

docker stop sunspec-collector
docker run --rm -v sunspec-data:/data debian:bookworm-slim sh -c '
  apt-get update -qq && apt-get install -y -qq sqlite3 >/dev/null &&
  sqlite3 /data/sunspec-buffer.sqlite "VACUUM; PRAGMA wal_checkpoint(TRUNCATE);" &&
  chown --reference=/data /data/*'
docker start sunspec-collector

Earlier versions of this guide suggested a weekly systemd timer that vacuumed the live database. Remove such a timer, or make it stop the service first, for the reason above.

Discard part of the backlog

Deleted rows are never published. With the collector stopped:

-- Rows written more than 24 hours ago
DELETE FROM telemetry_queue WHERE created_at < (CAST(strftime('%s', 'now') AS INTEGER) - 86400) * 1000;

-- All rows of one device
DELETE FROM telemetry_queue
WHERE json_extract(CAST(payload AS TEXT), '$.device.ip') = '192.168.1.20'
  AND json_extract(CAST(payload AS TEXT), '$.device.unit_id') = 1;

Then restore ownership as shown above and start the collector.

Delete the buffer

Deleting the buffer loses every sample that has not been published. With the collector stopped, delete the database file and its -wal and -shm files together (for example buffer.sqlite, buffer.sqlite-wal and buffer.sqlite-shm); the collector creates an empty buffer at the next start. Never delete only the -wal file: it can hold committed rows that are not yet in the main file.

Back up

A buffer backup is rarely needed, since its rows are meant to be short-lived. If you need one:

  • While running: use SQLite's online backup, which produces a consistent copy:

    sudo sqlite3 -readonly /var/lib/sunspec-collector/buffer.sqlite ".backup '/root/buffer-backup.sqlite'"
    
  • While stopped: copy all three files.

Restoring a backup (collector stopped, old files removed, ownership restored) re-sends every row in it, including rows that were delivered after the backup was taken. Consumers see duplicates.


Recovery after a long outage

While Kafka is unreachable:

  • the uplink retries the oldest batch with backoff (1 s doubling to 30 s), and each attempt waits up to kafka.timeout_ms for acknowledgements. sunspec_uplink_errors_total increases with each attempt, and each attempt logs records not delivered; they stay buffered. librdkafka's reconnect attempts are logged at warn at most once per 5 minutes (Kafka broker connection failed);
  • polling and ingest carry on, so sunspec_buffer_rows and sunspec_buffer_oldest_age_seconds rise;
  • once the limits are reached, retention drops the oldest rows (sunspec_buffer_dropped_total{reason="max_rows"|"max_age"});
  • /healthz stays 200, because the pipeline itself is working.

When Kafka is reachable again:

  1. The uplink asks the brokers every 2 s whether they are back, so it starts draining within about 2 s of them answering rather than waiting out the rest of a 30 s backoff, and logs records delivered again. It then drains back to back while the batches come back full. It pauses for buffer.drain_interval_ms only once a batch is not full, that is, when the backlog is gone.

  2. Rows are sent oldest first, so the newest data arrives last. Consumers receive records with old collected_at_ms values for a while.

  3. Watch sunspec_buffer_rows and sunspec_buffer_oldest_age_seconds fall. The drain only makes progress while delivery is faster than intake:

    rate(sunspec_uplink_records_total[5m]) - rate(sunspec_buffer_writes_total[5m])
    

    The remaining time in seconds is roughly:

    sunspec_buffer_rows
      / (rate(sunspec_uplink_records_total[5m]) - rate(sunspec_buffer_writes_total[5m]))
    
  4. To drain faster, raise buffer.batch_size (for example to 500 or 1000) and restart the collector; buffered rows are kept across the restart. Each batch waits for the producer's 50 ms linger plus the broker round trip, so larger batches mean fewer waits. One batch is held in memory at a time.

  5. Data that retention dropped during the outage is gone; sunspec_buffer_dropped_total shows how many rows.

  6. After the backlog has drained, the database file keeps its largest size. Vacuum it if you need the disk space back.

Do not delete the buffer to catch up faster unless you intend to discard the backlog.

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.

ErrorCauseFix
load config failed ... unknown fieldMisspelled or removed key in the config fileCompare with the reference.
load config failed ... SUNSPEC_X="..." is invalidUnparsable environment value, including inline comments in an env fileFix the value; put comments on their own lines.
load config failed ... read config file--config / SUNSPEC_CONFIG path missing or unreadableCheck the path and permissions (mode 0644 for systemd).
config validation failedValue out of rangeThe message names the key and the allowed values.
config validation failed ... kafka.acks must be all (or -1) while kafka.enable_idempotence is trueacks 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 125Batch size above the Modbus limitUse a value from 1 to 125.
config validation failed ... kafka.brokers must be non-empty when setSUNSPEC_KAFKA_BROKERS= with no valueSet the brokers, or comment the line out to run without Kafka (records are then only logged).
kafka publisher init failedlibrdkafka rejected a setting when the producer was createdThe message gives librdkafka's reason.
bind metrics port <port>The port is in use (often a second collector) or not permittedStop the other process or change metrics.port / SUNSPEC_METRICS_PORT.
buffer init failedBuffer path not writablesystemd: 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 jsonUnsupported log formatUse 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

  1. Check that the collector knows the device: curl localhost:9090/devices lists every supervised device with its state and latest readings; sunspec_devices_known, devices_total in /healthz, and a starting device poller device=host/unit log line say the same. In subnet mode the host must accept a TCP connection on the Modbus port within discovery.per_host_timeout_ms.
  2. Check sunspec_device_up{device="host/unit"}. A value of 0 means the device is not being read successfully; if sunspec_device_last_success_timestamp_seconds has no series for it, it has not been read successfully since the collector started. If sunspec_poller_restarts_total keeps increasing, its sessions keep failing; sunspec_poll_errors_total{kind} shows whether connects or reads fail.
  3. Find the reason in the logs: journalctl -u sunspec-collector | grep 'device session ended', or probe the device directly with collector-app --probe host:unit (see Probing a device).
error= in device session endedLikely cause
could not resolve host:portHostname does not resolve.
connect to ... failed / request timed out after ...msWrong 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 deviceThe 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 failedReads 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 pollinginclude_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

  1. Check that kafka.brokers / SUNSPEC_KAFKA_BROKERS is set. Without it, records are only logged at debug level (record published (log sink) with RUST_LOG=info,avro_kafka=debug) and then removed from the buffer.
  2. Check sunspec_uplink_errors_total and the records not delivered; they stay buffered log 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 failed gives 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 again marks the end of an outage. Authentication problems show up as Kafka client error with SASL authentication error or SSL handshake failed. Check kafka.security_protocol, the SASL mechanism and credentials, and that kafka.ssl_ca_location holds the CA that signed the broker certificate.
  3. Check sunspec_buffer_oldest_age_seconds and sunspec_buffer_rows. If they grow while sunspec_uplink_records_total is flat, nothing is being delivered.
  4. 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 larger buffer.batch_size raises 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.

Upgrade notes for this release

This release contains breaking changes. Read all of this section before upgrading.

Kafka record format v2 (breaking)

Previous formatThis release (v2)
RecordsOne Avro Object Container File per message, mixing devicesOne record per sample
KeyNonedevice_key, e.g. 192.168.1.20/1
ValueContainer file with the schema embedded in every messageAvro single-object encoding: bytes C3 01, the schema's 8-byte CRC-64-AVRO (Rabin) fingerprint (little-endian), then the Avro binary datum
HeadersNonecontent-type: application/avro; encoding=single-object, schema-fingerprint: <16 hex chars>
SchemaRaw registers onlycom.rusty.sunspec.v2.SunspecSample: decoded points and raw registers
model_nameProject names, e.g. three_phase_inverterOfficial SunSpec names, e.g. inverter_three_phase

collector-app --print-schema prints the v2 schema; Rust consumers can use avro_kafka::decode. Consumers must be updated before or together with the collector. To avoid mixing formats on one topic, consider publishing v2 to a new topic (kafka.topic).

Schema 2.1: labels

Schema 2.1 adds a labels map (enum values and set bitfield bits by name) with an empty default. Avro readers resolve old and new records with either schema, but the schema's fingerprint changes. Consumers that look up the writer schema by fingerprint need the 2.1 schema (collector-app --print-schema) before they read records from this version.

Rows buffered by the previous version are published in the v2 format after the upgrade, but with an empty points map and the old model names. To avoid this, let the old version drain its buffer (buffer_size at 0) before you upgrade.

Expect more records per device than before. Models beyond the first 200 registers are now found by walking the model chain, and every unit ID behind a gateway is polled (previously only one unit per host was).

Metric renames (breaking)

Update dashboards and alerts:

Old metricNew metricChange
buffer_enqueue_successsunspec_buffer_writes_totalRenamed.
buffer_enqueue_errorsunspec_buffer_dropped_total{reason="write_error"}Now one counter with a reason label.
buffer_sizesunspec_buffer_rowsRenamed; updated every 10 s.
uplink_messages_sentsunspec_uplink_records_totalbatch_size label removed. Counts acknowledged records.
uplink_publish_errorsunspec_uplink_errors_totalCounts records, not failed batches.
uplink_publish_latencysunspec_uplink_publish_secondsHistogram with buckets.
poller_success{ip}sunspec_poll_success_total{device}Label ip replaced by device (host/unit).
poller_error{ip,type}sunspec_poll_errors_total{device,kind}kind gives the error class. Channel-full drops moved to sunspec_samples_dropped_total{reason="channel_full"}.

New metrics: sunspec_poll_duration_seconds, sunspec_device_up, sunspec_device_last_success_timestamp_seconds, sunspec_poller_restarts_total, sunspec_poller_stalls_total, sunspec_devices_known, sunspec_samples_dropped_total, sunspec_buffer_dropped_total, sunspec_buffer_oldest_age_seconds.

Strict configuration (breaking)

Unknown keys, unparsable environment values, invalid static devices, unsupported kafka.compression / kafka.acks values, kafka.acks of 0 or 1 with idempotence enabled, and modbus.max_batch_size above 125 now stop the collector at startup instead of being ignored or adjusted. Before upgrading, compare your config file with the reference and check your environment file for inline comments and empty values.

Deprecated settings

Accepted, ignored, and logged with a warning:

  • sunspec.discovery_register_count / SUNSPEC_DISCOVERY_REG_COUNT: models are found by walking the model chain.
  • modbus.timeout_ms / SUNSPEC_MODBUS_TIMEOUT_MS: use poller.request_timeout_ms / SUNSPEC_REQUEST_TIMEOUT_MS.

Deployment changes

  • systemd: the unit now runs as a dynamic unprivileged user with StateDirectory=sunspec-collector, Type=notify with a health-gated watchdog, and TimeoutStopSec=30. Install the new unit file and run systemctl daemon-reload. The previous guide had you create /var/lib/sunspec-collector as root; after the first start with the new unit, check that the log shows buffer initialized and no write errors.
  • Docker: the HEALTHCHECK now calls /healthz instead of /metrics, on the port in SUNSPEC_METRICS_PORT (default 9090). The image's working directory is /app/data, so without SUNSPEC_BUFFER_PATH or buffer.path the buffer is /app/data/sunspec-buffer.sqlite. Deployments that set SUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite, as the previous guide and Compose file did, keep using that file.
  • A metrics port that cannot be bound now stops the collector at startup instead of leaving it running without /metrics and /healthz.
  • SIGTERM now takes the graceful shutdown path.

Upgrade procedure

  1. Update Kafka consumers to read v2 records (or prepare a new topic).
  2. Let the old collector drain its buffer, then stop it.
  3. Install the new binary or image, the new unit file and an updated config / environment file.
  4. Start the collector and check /healthz, sunspec_device_up and sunspec_uplink_records_total.
  5. Update dashboards and alert rules to the new metric names.

Configuration

The collector reads its settings from built-in defaults, an optional config file (TOML or JSON) and SUNSPEC_* environment variables. Every setting has a default, so an empty configuration is valid: it scans 192.168.1.0/24 on port 502 and, with no Kafka brokers set, only logs records.

Two example files ship with the documentation:


Load order

Values are resolved in this order; a later source overrides an earlier one:

1. Built-in defaults
2. Config file (TOML or JSON)
3. Environment variables (SUNSPEC_*)

A typical deployment keeps a config file per site and overrides a few values per host or container with environment variables.

Choosing the config file

collector-app --config /etc/sunspec-collector.toml
collector-app --config=/etc/sunspec-collector.toml
SUNSPEC_CONFIG=/etc/sunspec-collector.toml collector-app

--config takes precedence over SUNSPEC_CONFIG. A file whose name ends in .json is parsed as JSON; any other file is parsed as TOML. With neither option set, no file is read and only the defaults and environment variables apply. A file that is named but cannot be read is an error.


Strict validation

Configuration errors stop the collector at startup with exit status 78 (EX_CONFIG). The systemd unit does not restart on that status (RestartPreventExitStatus=78), so a bad setting leaves the service failed, with the error in the journal, instead of restarting it every few seconds. Nothing falls back to a default silently. The following are errors:

  • Unknown keys or sections in the config file, and values of the wrong type (for example a string where a number is expected, or unit_id = 300).
  • A SUNSPEC_* variable whose value cannot be parsed. The error names the variable.
  • An invalid static device entry (empty host, host containing whitespace, unit ID outside 0–255).
  • kafka.compression or kafka.acks values outside the supported lists, and kafka.acks of 0 or 1 while kafka.enable_idempotence is true (the default).
  • Out-of-range values: zero ports, intervals, timeouts and sizes where at least 1 is required, modbus.max_batch_size outside 1–125, an invalid CIDR subnet, an empty unit ID list, an invalid Kafka topic name, an empty broker list.
  • Model settings (see Polling profiles): a model ID outside 1–65534, an empty include_models, a model in both include_models and exclude_models, a model_intervals_s entry of 0, and a static device listed twice.
  • A framing other than tcp or rtu, and static devices behind the same RTU converter (same host and port, framing = "rtu") with different request_timeout_ms or max_batch_size: they share one connection.

A misspelled key in the file:

[poller]
poll_intervall_ms = 1000
Error: load config failed

Caused by:
    0: parse toml config /etc/sunspec-collector.toml
    1: TOML parse error at line 2, column 1
         |
       2 | poll_intervall_ms = 1000
         | ^^^^^^^^^^^^^^^^^
       unknown field `poll_intervall_ms`, expected one of `poll_interval_ms`, `request_timeout_ms`, `jitter_ms`, `stall_timeout_ms`, `include_models`, `exclude_models`, `model_intervals_s`

An unparsable environment variable:

$ SUNSPEC_POLL_INTERVAL_MS=1s collector-app
Error: load config failed

Caused by:
    SUNSPEC_POLL_INTERVAL_MS="1s" is invalid: invalid digit found in string

A value that parses but is not allowed:

$ SUNSPEC_KAFKA_COMPRESSION=brotli collector-app
Error: config validation failed

Caused by:
    kafka.compression must be one of none, gzip, snappy, lz4, zstd, got "brotli"

load config failed covers reading and parsing (file syntax, unknown keys, types, environment values, static devices). config validation failed covers range and value checks on the merged result.

Settings are also checked against each other. An idempotent producer needs acks=all, so this is rejected before the Kafka client is created:

$ SUNSPEC_KAFKA_ACKS=1 collector-app
Error: config validation failed

Caused by:
    kafka.acks must be all (or -1) while kafka.enable_idempotence is true

Two startup errors come after validation: kafka publisher init failed if librdkafka rejects a setting when the producer is created, and bind metrics port <port> if the HTTP port cannot be bound (see [metrics]).

Empty environment variables

A variable that is set to an empty string counts as set. SUNSPEC_POLL_INTERVAL_MS= is a parse error and SUNSPEC_KAFKA_BROKERS= fails validation. Comment out variables you do not want to set. The exceptions are lists: an empty SUNSPEC_STATIC_DEVICES is an empty list, which selects subnet mode; an empty SUNSPEC_INCLUDE_MODELS means every model; and an empty SUNSPEC_EXCLUDE_MODELS or SUNSPEC_MODEL_INTERVALS_S means none.

Deprecated keys

These keys are still accepted so that existing files and environments keep loading. They have no effect; the collector logs a warning at startup when one is set.

File keyEnv varReplacement
sunspec.discovery_register_countSUNSPEC_DISCOVERY_REG_COUNTNone. Models are found by walking the SunSpec model chain one header at a time.
modbus.timeout_msSUNSPEC_MODBUS_TIMEOUT_MSpoller.request_timeout_ms / SUNSPEC_REQUEST_TIMEOUT_MS, which sets the connect and read timeout for device sessions.

Reference

Durations are integers in the unit given by the key name (_ms, _s). "≥ 1" means 0 is rejected. A dash in the env var column means the setting can only be set in the config file.

[discovery]

The device set comes from either a static list or a subnet scan. When static_devices is non-empty, no scan runs and subnet, max_concurrency, per_host_timeout_ms, unit_ids and rescan_interval_s are not used (they are still validated).

File keyEnv varDefaultNotes
discovery.subnetSUNSPEC_SUBNET192.168.1.0/24IPv4 CIDR, prefix 0–32. Every host address is probed with a TCP connect on the Modbus port; network and broadcast addresses are skipped for prefixes up to /30.
discovery.portSUNSPEC_PORT502Modbus TCP port for probing and polling, 1–65535. Same setting as modbus.port.
discovery.max_concurrency—64Probes in flight at once during a scan. ≥ 1.
discovery.per_host_timeout_ms—200TCP connect timeout for each probe. ≥ 1. Raise it on high-latency links.
discovery.unit_idsSUNSPEC_DISCOVERY_UNIT_IDS[1]Unit IDs polled on every host the scan finds; one device per host and unit ID. Each 0–255, at least one. Env form: comma list, e.g. 1,2,3.
discovery.rescan_interval_sSUNSPEC_RESCAN_INTERVAL_S600Time between scans. New hosts get pollers; hosts that disappear keep theirs and are retried. ≥ 1.
discovery.static_devicesSUNSPEC_STATIC_DEVICESemptyFixed device list; see Static devices. Started once at startup.

[poller]

File keyEnv varDefaultNotes
poller.poll_interval_msSUNSPEC_POLL_INTERVAL_MS1000Time between poll cycles of one device. A cycle reads every polled model that is due, which by default is every discovered model. If a cycle overruns, missed ticks are skipped. ≥ 1.
poller.request_timeout_msSUNSPEC_REQUEST_TIMEOUT_MS1000TCP connect timeout and per-request Modbus read timeout for device sessions. ≥ 1.
poller.jitter_msSUNSPEC_JITTER_MS0Each device's schedule starts after a fixed, device-specific offset between 0 and this value, so devices are not all read at the same moment. 0 = no offset.
poller.stall_timeout_msSUNSPEC_STALL_TIMEOUT_MSmax(10 × poll interval, 30000)A session with no successful read for this long is ended and restarted (counted in sunspec_poller_stalls_total). Cycles in which no model is due do not count. ≥ 1 when set.
poller.include_modelsSUNSPEC_INCLUDE_MODELSunset (every model)Poll only these models, e.g. [1, 103, 160]; the rest of the chain is discovered but never read. Model IDs 1–65534, at least one. Env form: comma list; empty means every model.
poller.exclude_modelsSUNSPEC_EXCLUDE_MODELSemptyNever poll these models. A model cannot also be in include_models. Env form: comma list.
poller.model_intervals_sSUNSPEC_MODEL_INTERVALS_SemptyRead these models at most once per interval instead of every cycle: a table of model ID = seconds (≥ 1). Env form: 1=3600,120=3600. See Polling profiles.

A sample that cannot be queued for ingest within one poll interval is dropped and counted in sunspec_samples_dropped_total{reason="channel_full"}, so a slow buffer never blocks polling.

A session also ends after 10 consecutive poll cycles in which at least one read failed. Ended sessions are retried with the backoff described under runtime.respawn_delay_ms.

[modbus]

File keyEnv varDefaultNotes
modbus.portSUNSPEC_PORT502Same setting as discovery.port. If both are in the file, modbus.port wins; SUNSPEC_PORT overrides both.
modbus.max_batch_sizeSUNSPEC_MAX_BATCH_SIZE125Most registers per read request; longer models are split into several requests. 1–125 (125 is the Modbus protocol limit); other values are rejected with modbus.max_batch_size must be between 1 and 125 (Modbus protocol limit). Lower it for devices that reject large reads.
modbus.timeout_msSUNSPEC_MODBUS_TIMEOUT_MS—Deprecated, ignored with a warning. Use poller.request_timeout_ms.
modbus.retry_count—2Retries per request after the first attempt. Timeouts, transport and connect errors are retried; Modbus exceptions are not, except "server device busy". 0 = no retries.
modbus.retry_backoff_ms—100Delay before the first retry; doubles on each further retry. ≥ 1.
modbus.retry_max_backoff_ms—2000Upper bound for the retry delay. ≥ 1.
modbus.inter_read_delay_ms—unsetPause between the requests of one split read, for devices that need it. ≥ 1 when set.
modbus.framingSUNSPEC_MODBUS_FRAMINGtcptcp for Modbus TCP, or rtu for Modbus RTU frames over TCP, as a transparent serial-to-Ethernet converter in front of an RS-485 bus passes them. Units on the same host and port then share one connection; see Serial devices through a converter.

With the defaults, one request to an unresponsive device takes up to three timeouts plus 300 ms of backoff before it fails. Keep poller.request_timeout_ms × (retry_count + 1) well below the poll interval if you want failed cycles to be detected quickly.

After a transport error or timeout the client drops the connection and reconnects on the next read. A Modbus exception keeps the connection.

[sunspec]

File keyEnv varDefaultNotes
sunspec.base_addressSUNSPEC_BASE_ADDRESS40000Register address probed first for the SunS marker. The other standard bases (40000, 0, 50000) are probed after it. 0–65535.
sunspec.discovery_register_countSUNSPEC_DISCOVERY_REG_COUNT—Deprecated, ignored with a warning.

Models are rediscovered every time a device session starts, so a firmware change that alters the model chain is picked up after the next reconnect.

[buffer]

See Buffer Maintenance for how the buffer works and how to size it.

File keyEnv varDefaultNotes
buffer.pathSUNSPEC_BUFFER_PATHsunspec-buffer.sqliteSQLite file, created if missing. A relative path is relative to the working directory. The Docker image's working directory is /app/data, so the default there is /app/data/sunspec-buffer.sqlite; the systemd env file sets /var/lib/sunspec-collector/buffer.sqlite.
buffer.batch_sizeSUNSPEC_BUFFER_BATCH_SIZE100Rows read and published per uplink batch. ≥ 1.
buffer.drain_interval_msSUNSPEC_BUFFER_DRAIN_MS500Pause before the next batch once the backlog is empty (the last batch was not full). Also the minimum delay after a failed batch. ≥ 1.
buffer.max_rowsSUNSPEC_BUFFER_MAX_ROWS1000000Retention: the oldest rows beyond this count are deleted. Checked every 10 s. ≥ 1.
buffer.max_age_sSUNSPEC_BUFFER_MAX_AGE_S604800 (7 days)Retention: rows written longer ago than this are deleted. Checked every 10 s. ≥ 1.
buffer.shutdown_drain_timeout_msSUNSPEC_SHUTDOWN_DRAIN_TIMEOUT_MS5000On stop, keep publishing buffered rows for up to this long. 0 = stop at once.

In Docker, the image runs in /app/data, the directory to mount a volume on. The image does not set SUNSPEC_BUFFER_PATH, so buffer.path from a config file applies. A relative buffer.path lands in /app/data; an absolute path outside /app/data is not in the volume and is lost when the container is removed.

[kafka]

File keyEnv varDefaultNotes
kafka.brokersSUNSPEC_KAFKA_BROKERSunsetComma-separated bootstrap servers, e.g. kafka-1:9092,kafka-2:9092. Must be non-empty when set. Unset means no Kafka: each record is encoded, logged at debug level, and removed from the buffer as if delivered.
kafka.topicSUNSPEC_KAFKA_TOPICsunspec.telemetry1–249 characters from A-Z a-z 0-9 . _ -, not . or ...
kafka.client_idSUNSPEC_KAFKA_CLIENT_IDsunspec-collectorlibrdkafka client.id.
kafka.acksSUNSPEC_KAFKA_ACKSallOne of 0, 1, -1, all. 0 and 1 require enable_idempotence = false; otherwise validation fails.
kafka.compressionSUNSPEC_KAFKA_COMPRESSIONzstdOne of none, gzip, snappy, lz4, zstd.
kafka.timeout_msSUNSPEC_KAFKA_TIMEOUT_MS5000librdkafka message.timeout.ms: how long the producer tries to deliver a record before it is reported as undelivered. Undelivered rows stay in the buffer. ≥ 1.
kafka.enable_idempotenceSUNSPEC_KAFKA_IDEMPOTENCEtruelibrdkafka enable.idempotence. The env value must be exactly true or false.

The producer also sets linger.ms=50; this is not configurable.

Connection security (TLS and SASL)

File keyEnv varDefaultNotes
kafka.security_protocolSUNSPEC_KAFKA_SECURITY_PROTOCOLplaintextOne of plaintext, ssl, sasl_plaintext, sasl_ssl (lowercase). sasl_plaintext logs a warning: credentials and data are not encrypted.
kafka.ssl_ca_locationSUNSPEC_KAFKA_SSL_CA_LOCATIONsystem CAsPEM file or directory used to verify the broker. Must exist.
kafka.ssl_certificate_locationSUNSPEC_KAFKA_SSL_CERTIFICATE_LOCATIONunsetClient certificate (PEM) for mutual TLS. Must exist; set together with the key.
kafka.ssl_key_locationSUNSPEC_KAFKA_SSL_KEY_LOCATIONunsetClient private key (PEM) for mutual TLS. Must exist; set together with the certificate.
kafka.ssl_key_password / kafka.ssl_key_password_fileSUNSPEC_KAFKA_SSL_KEY_PASSWORD / SUNSPEC_KAFKA_SSL_KEY_PASSWORD_FILEunsetPassphrase of an encrypted private key, given directly or as a file.
kafka.sasl_mechanismSUNSPEC_KAFKA_SASL_MECHANISMunsetPLAIN, SCRAM-SHA-256 or SCRAM-SHA-512. Required with a sasl_* protocol.
kafka.sasl_usernameSUNSPEC_KAFKA_SASL_USERNAMEunsetRequired with a sasl_* protocol.
kafka.sasl_password / kafka.sasl_password_fileSUNSPEC_KAFKA_SASL_PASSWORD / SUNSPEC_KAFKA_SASL_PASSWORD_FILEunsetRequired with a sasl_* protocol. Prefer the _file form.

Rules checked at startup:

  • ssl_* settings need ssl or sasl_ssl; sasl_* settings need sasl_plaintext or sasl_ssl. Setting them with another protocol is an error, so a half-applied security configuration cannot silently fall back to plaintext.
  • *_file settings read a file that contains only the secret (a trailing newline is removed). Setting both a secret and its _file form is an error, as is an empty file.
  • Secrets never appear in logs: they are redacted in debug output.
  • librdkafka loads the CA and client certificate when the producer is created, so an unreadable or invalid PEM file stops the collector at startup (kafka publisher init failed).

GSSAPI (Kerberos) and OAUTHBEARER are not supported. Certificates are read at startup; restart the collector after rotating them (the buffer keeps unsent data across the restart). See Securing the Kafka connection for deployment examples.

[metrics]

File keyEnv varDefaultNotes
metrics.portSUNSPEC_METRICS_PORT9090Port for /metrics, /healthz and /devices, bound on all IPv4 interfaces (0.0.0.0). 0 lets the OS pick a free port; the chosen address is logged. If the port cannot be bound (for example, it is in use), the collector exits at startup with bind metrics port <port>.

The Docker image's HEALTHCHECK calls http://localhost:${SUNSPEC_METRICS_PORT:-9090}/healthz, so it follows the port when you set it with SUNSPEC_METRICS_PORT. If you set metrics.port in a config file instead, or use 0, the health check still probes 9090 and fails; set the port with the environment variable in containers.

[runtime]

File keyEnv varDefaultNotes
runtime.channel_capacitySUNSPEC_CHANNEL_CAPACITY256Samples that can wait between the pollers and the ingest task. ≥ 1.
runtime.respawn_delay_msSUNSPEC_RESPAWN_DELAY_MS1000Base delay before a failed device session is retried. The delay doubles with each consecutive failure, is capped at 60 s, and gets up to 10 % device-specific jitter. It resets after a session that read successfully. ≥ 1.

Environment-only settings

Env varDefaultNotes
SUNSPEC_CONFIGunsetPath of the config file, if --config is not given.
RUST_LOGinfoLog filter; see Logging.
SUNSPEC_LOG_FORMATtexttext or json; see Logging.

Static devices

A static device is a host and a Modbus unit ID. Each entry becomes one supervised poller, keyed host/unit (for example 192.168.1.20/1). That key is used in logs, in the device metric label and as the Kafka record key.

In a config file, use an array of tables (TOML) or an array of objects (JSON). unit_id is optional and defaults to 1:

[[discovery.static_devices]]
ip = "192.168.1.20"

[[discovery.static_devices]]
ip = "modbus-gw.example.net"
unit_id = 3

In the environment, use a comma-separated list of host[:unit]:

SUNSPEC_STATIC_DEVICES=192.168.1.20,192.168.1.21:1,modbus-gw.example.net:3

Rules for both forms:

  • The host is an IPv4 address or a hostname. Hostnames are resolved on every connect, and each resolved address is tried in turn.
  • The unit ID is 0–255 and defaults to 1.
  • The host must not be empty or contain whitespace.
  • In the environment form, spaces around entries and empty entries are ignored. The host and unit are split at the last :, so this form cannot express an IPv6 address literal.

Gateways expose several units on one host. List each unit as its own entry:

SUNSPEC_STATIC_DEVICES=192.168.1.100:1,192.168.1.100:2,192.168.1.100:3

discovery.unit_ids / SUNSPEC_DISCOVERY_UNIT_IDS apply only to hosts found by a subnet scan, not to static entries.

Each host and unit ID may be listed only once. Entries in a config file can also carry per-device settings.


Polling profiles

See Polling profiles, including per-device settings.

Logging

Logging is configured with environment variables only.

VariableValuesNotes
RUST_LOGtracing filter directivesDefault info. Examples: warn, info,poller_actor=debug, info,modbus_client=debug,avro_kafka=debug. If unset or unparsable, info is used. Prefer targets to a bare debug, which also turns on the libraries' debug output (Avro schema parsing, every SQL statement).
SUNSPEC_LOG_FORMATtext, jsonDefault text. json writes one JSON object per line for log shippers. Any other value is a startup error.

Logs go to standard output. ANSI colours are used only when standard output is a terminal.

Steady-state operation is quiet at info: per-cycle and per-batch messages are logged at debug. For a closer look at the collector itself, RUST_LOG=info,collector_app=debug,poller_actor=debug reads well. Useful debug targets:

  • poller_actor: poll cycle timings, and each base address discovery tries without finding a SunSpec map.
  • modbus_client: every read, and every failed attempt before a retry (the poller logs the read that finally failed at warn).
  • collector_app::uplink: every uplink batch.
  • avro_kafka: records handled by the log-only sink when no brokers are set, and every failed broker connection. At info a failed connection is logged at warn at most once per 5 minutes ("Kafka broker connection failed"), and the uplink logs "records delivered again" when an outage ends.
  • librdkafka: the Kafka client library's own log lines.
  • discovery: every probe during a subnet scan.

Command-line options

See Command-line options.

Complete examples

Both examples below pass strict validation.

TOML

# /etc/sunspec-collector.toml
# A non-empty static_devices list selects static mode, so the subnet scan
# settings below are validated but not used.

[discovery]
subnet = "192.168.1.0/24"
port = 502                        # also sets modbus.port
max_concurrency = 64
per_host_timeout_ms = 200
unit_ids = [1]
rescan_interval_s = 600

[[discovery.static_devices]]
ip = "192.168.1.20"               # unit_id defaults to 1

[[discovery.static_devices]]
ip = "modbus-gw.example.net"
unit_id = 2
poll_interval_ms = 2000           # per-device settings: see Polling profiles
max_batch_size = 60

[poller]
poll_interval_ms = 1000
request_timeout_ms = 1000
jitter_ms = 250
stall_timeout_ms = 30000
exclude_models = [123]

[poller.model_intervals_s]
1 = 3600
120 = 3600
121 = 600

[modbus]
max_batch_size = 125
retry_count = 2
retry_backoff_ms = 100
retry_max_backoff_ms = 2000
# inter_read_delay_ms = 5         # unset by default: no pause between split reads

[sunspec]
base_address = 40000

[buffer]
path = "/var/lib/sunspec-collector/buffer.sqlite"
batch_size = 100
drain_interval_ms = 500
max_rows = 1000000
max_age_s = 604800
shutdown_drain_timeout_ms = 5000

[kafka]
brokers = "kafka-1.example.net:9092,kafka-2.example.net:9092"
topic = "sunspec.telemetry"
client_id = "sunspec-collector"
acks = "all"
compression = "zstd"
timeout_ms = 5000
enable_idempotence = true

[metrics]
port = 9090

[runtime]
channel_capacity = 256
respawn_delay_ms = 1000

JSON

Subnet mode, scanning two unit IDs per host, with a slower poll interval and a larger uplink batch:

{
  "discovery": {
    "subnet": "10.20.0.0/24",
    "port": 502,
    "max_concurrency": 64,
    "per_host_timeout_ms": 200,
    "unit_ids": [1, 2],
    "rescan_interval_s": 600
  },
  "poller": {
    "poll_interval_ms": 5000,
    "request_timeout_ms": 1500,
    "jitter_ms": 1000,
    "model_intervals_s": { "1": 3600, "120": 3600, "121": 600 }
  },
  "modbus": {
    "max_batch_size": 100,
    "retry_count": 2,
    "retry_backoff_ms": 100,
    "retry_max_backoff_ms": 2000,
    "inter_read_delay_ms": 10
  },
  "sunspec": {
    "base_address": 40000
  },
  "buffer": {
    "path": "/var/lib/sunspec-collector/buffer.sqlite",
    "batch_size": 500,
    "drain_interval_ms": 500,
    "max_rows": 2000000,
    "max_age_s": 172800,
    "shutdown_drain_timeout_ms": 5000
  },
  "kafka": {
    "brokers": "kafka.example.net:9092",
    "topic": "site-a.sunspec.telemetry",
    "client_id": "sunspec-collector-site-a",
    "acks": "all",
    "compression": "zstd",
    "timeout_ms": 10000,
    "enable_idempotence": true
  },
  "metrics": {
    "port": 9090
  },
  "runtime": {
    "channel_capacity": 512,
    "respawn_delay_ms": 1000
  }
}

Save it with a .json extension so it is parsed as JSON.


Common setups

No Kafka (records are only logged)

SUNSPEC_STATIC_DEVICES=192.168.1.20:1 RUST_LOG=info,avro_kafka=debug collector-app

With the systemd unit, comment out the SUNSPEC_KAFKA_BROKERS line in /etc/sunspec-collector.env; an empty value fails validation.

Subnet scan with Kafka

SUNSPEC_SUBNET=10.0.0.0/24 \
SUNSPEC_KAFKA_BROKERS=kafka.internal:9092 \
SUNSPEC_KAFKA_TOPIC=solar.telemetry \
collector-app

Docker with a config file

Mount the file and point SUNSPEC_CONFIG at it (or pass --config /app/collector.toml after the image name, since the image's entrypoint is /app/collector-app):

docker run -d --name sunspec-collector \
  -p 9090:9090 \
  -v "$(pwd)/collector.toml:/app/collector.toml:ro" \
  -v sunspec-data:/app/data \
  -e SUNSPEC_CONFIG=/app/collector.toml \
  sunspec-collector

Simulator on a non-standard port

cargo run -p sunspec-sim -- --bind 127.0.0.1:1502 --units 1,2
SUNSPEC_STATIC_DEVICES=127.0.0.1:1,127.0.0.1:2 SUNSPEC_PORT=1502 cargo run -p collector-app

Command-line options

OptionEffect
--config PATH, --config=PATHRead this config file. Overrides SUNSPEC_CONFIG.
--print-schemaPrint the Avro schema of the published records as JSON and exit. No configuration is loaded.
--print-modelsPrint the metadata of every bundled SunSpec model (points with type, units, label, description, and enum and bitfield symbols) as JSON and exit.
--probe HOST[:UNIT]Read one device's SunSpec map once, print it as JSON and exit; see Probing a device. Uses the port, timeouts, base address and static-device overrides from the configuration.
--keep-identifiersWith --probe: keep serial numbers and network addresses instead of blanking them.
--version, -VPrint collector-app <version> and exit.
--help, -hPrint a short usage summary and exit.

Other arguments are ignored.


Running the binary

collector-app [--config PATH] [--print-schema]
ArgumentEffect
--config PATH (or --config=PATH)Load this TOML or JSON file (.json is parsed as JSON, anything else as TOML). SUNSPEC_CONFIG does the same.
--print-schemaPrint the Avro schema of the published records to stdout and exit, without reading any configuration

Other arguments are ignored. Without a config file the collector starts from the compiled defaults and SUNSPEC_* environment variables, which override file values when both are used:

SUNSPEC_STATIC_DEVICES=192.168.1.20:1,192.168.1.21:1 \
SUNSPEC_KAFKA_BROKERS=kafka.example.internal:9092 \
  ./collector-app

Without SUNSPEC_KAFKA_BROKERS (or kafka.brokers) nothing is sent to Kafka: records are only logged at debug level. Every setting is described in Configuration.

  • Logging: RUST_LOG sets the filter (default info, for example info,poller_actor=debug); SUNSPEC_LOG_FORMAT=json writes one JSON object per line (text is the default; any other value fails at startup). Colours are used only when stdout is a terminal.
  • Files: the buffer is sunspec-buffer.sqlite in the working directory by default, with SQLite's -wal and -shm files next to it. In the Docker image the working directory is /app/data, so the default is /app/data/sunspec-buffer.sqlite; the example systemd environment file sets /var/lib/sunspec-collector/buffer.sqlite.
  • Ports: /metrics and /healthz on metrics.port (default 9090). If the port cannot be bound, the collector exits at startup with an error containing bind metrics port <port>.
  • Signals: SIGINT and SIGTERM start a graceful shutdown.
  • Exit status: 0 after a graceful shutdown. Startup errors are printed to stderr, and the status says whether trying again can help:
    • 78 (EX_CONFIG): a setting is wrong: configuration that does not load or validate, an invalid SUNSPEC_LOG_FORMAT, or Kafka settings the producer rejects (such as an unreadable CA file). The systemd unit does not restart on this status (RestartPreventExitStatus=78).
    • 1: anything else, such as a metrics port that cannot be bound or a buffer that cannot be opened. These can pass, so the unit restarts the service.

HTTP endpoints

All four endpoints are served on metrics.port (default 9090) on all IPv4 interfaces.

/metrics

Prometheus text format. Every metric has HELP text. Metrics ending in _seconds are histograms with buckets at 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5 and 10 seconds.

curl -s http://localhost:9090/metrics | grep '^sunspec_'

/healthz

Returns 200 while both the ingest task and the uplink have checked in within the last 120 s, and 503 otherwise. The body is the same JSON in both cases:

{"healthy":true,"ingest_age_s":0.7,"uplink_age_s":0.3,"devices_up":2,"devices_total":2}
FieldMeaning
healthytrue when both ages are below 120 s.
ingest_age_sSeconds since the ingest task last checked in (at least every 5 s); null before the first check-in.
uplink_age_sSeconds since the uplink last checked in (at least every 30 s plus the time of one publish attempt); null before the first check-in.
devices_upDevices whose current session has read successfully.
devices_totalDevices being supervised.

Device outages do not make the collector unhealthy, because restarting the collector would not bring a device back. A Kafka outage does not either: the uplink keeps checking in while it backs off. Use the metrics and alerts below to detect both.

The Docker HEALTHCHECK calls this endpoint, and the systemd watchdog uses the same check.

If metrics.port cannot be bound (for example, it is already in use), the collector exits at startup with Error: bind metrics port <port> and the operating-system error.

/devices

The live view of every supervised device: its state, what its latest session discovered, and the latest decoded reading of each polled model. Use it when commissioning ("is it reading my inverter, and are the values right?") or to see data without a Kafka consumer.

curl -s localhost:9090/devices                     # every device
curl -s localhost:9090/devices/192.168.1.50/126    # one device (404 if it is not supervised)
{"devices": [{
  "device": "192.168.1.50/126", "host": "192.168.1.50", "unit_id": 126,
  "up": true, "last_success_ms": 1792250000123, "restarts": 0,
  "base_address": 40000, "models": [1, 103, 120, 160], "polled_models": [1, 103, 160],
  "readings": [
    {"model_id": 103, "model_name": "inverter_three_phase", "collected_at_ms": 1792250000123,
     "age_s": 0.4, "points": {"A": 7.98, "PhVphA": 230.3, "W": 5509.0, "Hz": 50.02, "St": 4}}
  ]
}]}
FieldMeaning
up, last_success_ms, restartsThe same state as sunspec_device_up, sunspec_device_last_success_timestamp_seconds and sunspec_poller_restarts_total. last_success_ms is null before the first successful read.
base_address, modelsWhere the SunS marker was found and the model chain, from the latest discovery; null and empty until a session has discovered the device.
polled_modelsThe models the polling profile selects.
readingsThe latest successful read of each polled model, in device order: decoded points in definition order (scale factors applied, "not implemented" values null), labels (enum values and set bitfield bits by name, as in records), and how old the read is. A model on a slow interval shows its last read until the next one.

Only the latest reading of each model is kept, so memory does not grow. A device that has never answered is listed with empty models and readings.

The endpoint exposes device data (readings, models, addresses) to anyone who can reach metrics.port, like /metrics. Keep the port on a management network or behind a firewall.

/models

The bundled SunSpec model definitions, for describing what records and /devices contain:

curl -s localhost:9090/models          # {"models": [{"id": 1, "name": "common", "label": "Common"}, ...]}
curl -s localhost:9090/models/103      # one model: every point with type, units, label, description, symbols

Point names match records, with [*] for a repeating group's index (module[*].DCA). An unknown ID returns 404. collector-app --print-models prints the same metadata for every model, for consumers that load it once.

Metrics reference

MetricTypeLabelsMeaning
sunspec_poll_success_totalcounterdeviceSuccessful model reads.
sunspec_poll_errors_totalcounterdevice, kindFailed Modbus operations: connects at session start, reads during model discovery, and model reads during polling. kind: exception, timeout, transport, connect, address.
sunspec_poll_duration_secondshistogramdeviceTime to read the models due in one poll cycle; cycles with nothing due are not recorded.
sunspec_device_upgaugedevice0 from the moment the device's poller starts; 1 from the first successful read of a session until the session ends, then 0 again.
sunspec_device_last_success_timestamp_secondsgaugedeviceUnix time of the last successful read.
sunspec_poller_restarts_totalcounterdeviceDevice sessions that ended with an error and were scheduled for retry.
sunspec_poller_stalls_totalcounterdeviceSessions ended by stall detection (no successful read within poller.stall_timeout_ms).
sunspec_devices_knowngauge—Devices being supervised.
sunspec_samples_dropped_totalcounterreason = channel_fullSamples dropped before reaching the buffer.
sunspec_buffer_writes_totalcounter—Samples written to SQLite.
sunspec_buffer_dropped_totalcounterreason = max_rows, max_age, corrupt, write_errorRows or samples dropped without being published.
sunspec_buffer_rowsgauge—Rows waiting in the buffer. Updated every 10 s.
sunspec_buffer_oldest_age_secondsgauge—Age of the oldest buffered row; 0 when empty. Updated every 10 s.
sunspec_uplink_records_totalcounter—Records acknowledged by Kafka.
sunspec_uplink_errors_totalcounter—Delivery attempts Kafka did not acknowledge. The records stay buffered and are retried, so one record can count more than once.
sunspec_uplink_publish_secondshistogram—Time to publish one batch and receive its acknowledgements.

Notes:

  • The device label is host/unit, for example 192.168.1.20/1.
  • sunspec_device_up is published with the value 0 as soon as a device's poller starts, so a device that has never answered has a series with the value 0. Most other series appear when they are first recorded; in particular sunspec_device_last_success_timestamp_seconds appears only after a device's first successful read.
  • sunspec_poll_errors_total counts a failed connect at the start of a session, Modbus errors while discovering the model chain, and failed model reads while polling. Exceptions returned while probing the candidate base addresses are not counted, because the collector expects them and moves on to the next base. A device with no SunSpec map at any base therefore shows only sunspec_poller_restarts_total and a device session ended log line.
  • kind values: exception (the device returned a Modbus exception), timeout (a read or a connect timed out), transport (I/O or protocol error on the connection, or the device closed it), connect (the connection was refused or failed, or the host could not be resolved), address (the read would run past register 65535).
  • sunspec_uplink_errors_total counts per attempt: a record that fails three times counts three times. During a Kafka outage it grows by up to buffer.batch_size per attempt.
  • Without kafka.brokers, records handed to the log-only sink count in sunspec_uplink_records_total.
  • sunspec_uplink_publish_seconds is recorded only for batches in which at least one record was acknowledged.
  • reason="write_error" counts samples lost because an SQLite write failed (for example, a full disk). reason="corrupt" counts rows that could not be decoded or encoded and were deleted.

Kafka record contract

Schema version 2. This format is not compatible with earlier versions of the collector (see Earlier format).

Record

  • Topic: kafka.topic (default sunspec.telemetry).

  • One record per sample, that is per model read from one device.

  • Key: device_key, host/unit_id, for example 192.168.1.20/1. With librdkafka's default partitioner (CRC32 of the key) all records of a device go to the same partition.

  • Value: Avro single-object encoding:

    C3 01 | 8-byte CRC-64-AVRO (Rabin) fingerprint of the schema, little-endian | Avro binary datum
    
  • Headers:

    • content-type: application/avro; encoding=single-object
    • schema-fingerprint: <16 hex characters>: the 8 fingerprint bytes in the order they appear in the value.

Records are published in buffer order, but a record that failed delivery is retried in a later batch, after records that were acknowledged. Order consumers by collected_at_ms when order matters.

Schema

com.rusty.sunspec.v2.SunspecSample, in crates/avro-kafka/schemas/sunspec_sample.avsc; collector-app --print-schema prints it.

FieldAvro typeMeaning
device_keystringhost/unit_id; also the record key
ipstringDevice host (IP address or hostname as configured)
unit_idintModbus unit ID
model_idintSunSpec model ID, e.g. 103
model_namestringSunSpec model name, e.g. inverter_three_phase, or model_<id>
startintRegister address of the model header
collected_at_mslong (timestamp-millis)When the registers were read
pointsmap of ["null", "double", "long", "string"]Decoded points by SunSpec name
labelsmap of string, default {}Enum values and set bitfield bits by name (schema 2.1)
registersarray of intRaw registers, including the two header registers

Point semantics, as described in SunSpec decoding:

  • names are the SunSpec point names; repeating groups are group[i].Point (e.g. module[0].DCA) and fixed sub-groups group.Point;
  • scaled values are double; enums, bitfields and unscaled integers are long; strings are string, trimmed;
  • "not implemented" values, and points whose scale factor is not implemented, are null;
  • the model header (ID, L), scale-factor points and padding are omitted;
  • models without a bundled definition have an empty points map; only registers is set.

points and labels are Avro maps, so their order is not preserved. Point units, labels and descriptions are in the model catalog, by model ID and point name.

Schema evolution

Changes that old readers can resolve, such as a new field with a default, keep the com.rusty.sunspec.v2 namespace and get a minor schema version in the CHANGELOG (2.1 added labels). They still change the schema's fingerprint, so a consumer that resolves the writer schema by fingerprint needs the new schema (collector-app --print-schema) before it reads new records. A change that old readers cannot resolve would get a new namespace (v3) and a breaking CHANGELOG entry.

A model 103 record, shown as JSON for readability (unions unwrapped, lists shortened):

{
  "device_key": "192.168.1.20/1",
  "ip": "192.168.1.20",
  "unit_id": 1,
  "model_id": 103,
  "model_name": "inverter_three_phase",
  "start": 40070,
  "collected_at_ms": 1760000000123,
  "points": { "A": 12.34, "PhVphA": 230.1, "W": 4321.0, "Hz": 50.0, "St": 4, "VA": null, ... },
  "labels": { "St": "MPPT", "Evt1": "", ... },
  "registers": [103, 50, 1234, ...]
}

Reading records

  • Rust: avro_kafka::decode(bytes) checks the header and fingerprint and returns a Sample (points sorted by name).
  • Other languages: check that the value starts with C3 01 and that the next 8 bytes match the fingerprint of the schema you hold, then decode the rest as an Avro binary datum with that schema. Most Avro libraries implement single-object decoding directly (for example BinaryMessageDecoder in Java).

Producer settings

acks=all, enable.idempotence=true, compression.type=zstd, linger.ms=50 (fixed) and message.timeout.ms=5000 by default. Every setting except linger.ms can be changed in the [kafka] section. An idempotent producer requires acks=all, so configuration validation rejects kafka.acks 0 or 1 while kafka.enable_idempotence is true (the default).

Connection security is librdkafka's: security.protocol (plaintext, ssl, sasl_plaintext, sasl_ssl), the ssl.* CA, client certificate and key for (mutual) TLS, and SASL PLAIN or SCRAM-SHA-256/512 credentials. Passwords are held in a Secret type whose debug output is redacted and can be read from files at startup. Validation rejects settings that do not match the protocol, so a half-configured TLS or SASL setup fails instead of falling back to plaintext.

Earlier format

Earlier versions sent one Avro Object Container File per Kafka message, mixing samples from several devices, with the schema embedded in every message, no key, raw registers only, and model names such as three_phase_inverter. Consumers of that format must be updated.

Supported SunSpec models

The collector bundles 112 official SunSpec model definitions (3217 points) and decodes every one of them. Each page below lists a group of models with their points: the name records use, the type, units, label and description from the definitions, and the names of enum values and bitfield bits. In a point name, [*] stands for a repeating group's index (module[*].DCA covers module[0].DCA, module[1].DCA and so on).

The same data is available from a running collector at /models and from collector-app --print-models.

Common and network

ModelName in recordsLabelPoints
1commonCommon6
2aggregatorBasic Aggregator10
3model_3Secure Dataset Read Request58
4model_4Secure Dataset Read Response60
5model_5Secure Write Request88
6model_6Secure Write Sequential Request89
7model_7Secure Write Response Model (DRAFT 1)9
8model_8Get Device Security Certificate3
9model_9Set Operator Security Certificate92
10model_10Communication Interface Header3
11model_11Ethernet Link Layer7
12model_12IPv415
13model_13IPv615
14model_14Proxy Server8
15model_15Interface Counters Model12
16model_16Simple IP Network10
17model_17Serial Interface8
18model_18Cellular Link5
19model_19PPP Link9

Inverters and inverter controls

ModelName in recordsLabelPoints
101inverter_single_phaseInverter (Single Phase)31
102inverter_split_phaseInverter (Split-Phase)31
103inverter_three_phaseInverter (Three Phase)31
111inverter_single_phase_floatInverter (Single Phase) FLOAT31
112inverter_split_phase_floatInverter (Split Phase) FLOAT31
113inverter_three_phase_floatInverter (Three Phase) FLOAT31
120nameplateNameplate16
121settingsBasic Settings20
122statusMeasurements_Status17
123controlsImmediate Controls21
124storage_basicStorage16
125pricingPricing6
126volt_varStatic Volt-VAR54
127freq_watt_paramFreq-Watt Param6
128reactive_currentDynamic Reactive Current11
129lvrtLVRTD50
130hvrtHVRTD50
131watt_pfWatt-PF53
132volt_wattVolt-Watt54
133scheduleBasic Scheduling34
134freq_wattFreq-Watt Crv58
135lfrtLFRT50
136hfrtHFRT50
137lvrtcLVRTC50
138hvrtcHVRTC50
139lvrtxLVRTX51
140hvrtxHVRTX51
141lfrtcLFRTC50
142hfrtcHFRTC50
143lfrtxLFRTX51
144hfrtxHFRTX51
145ext_settingsExtended Settings7
160mpptMultiple MPPT Inverter Extension Model13

Meters

ModelName in recordsLabelPoints
201ac_meter_an_or_abMeter (Single Phase) single phase (AN or AB) meter62
202ac_meter_abnsplit single phase (ABN) meter62
203ac_meter_abcnwye-connect three phase (abcn) meter62
204ac_meter_abcdelta-connect three phase (abc) meter62
211ac_meter_an_or_ab_floatsingle phase (AN or AB) meter62
212ac_meter_abn_floatsplit single phase (ABN) meter62
213ac_meter_abcn_floatwye-connect three phase (abcn) meter62
214ac_meter_abc_floatdelta-connect three phase (abc) meter62
220ac_meter_secureSecure AC Meter Selected Readings22

Environmental sensors

ModelName in recordsLabelPoints
302irradianceIrradiance Model5
303bom_tempBack of Module Temperature Model1
304inclinometerInclinometer Model3
305locationGPS6
306ref_pointReference Point Model4
307base_metBase Met11
308mini_metMini Met Model4

String combiners

ModelName in recordsLabelPoints
401string_combiner_currentString Combiner (Current)13
402string_combiner_advancedString Combiner (Advanced)21
403string_combiner_current_inputString Combiner (Current)13
404string_combiner_advanced_inputsString Combiner (Advanced)21

Solar modules

ModelName in recordsLabelPoints
501solar_module_floatSolar Module17
502solar_moduleSolar Module17

Trackers

ModelName in recordsLabelPoints
601tracker_controllerTracker Controller DRAFT 219

DER (IEEE 1547-2018)

ModelName in recordsLabelPoints
701DERMeasureACDER AC Measurement60
702DERCapacityDER Capacity42
703DEREnterServiceEnter Service9
704DERCtlACDER AC Controls45
705DERVoltVarDER Volt-Var19
706DERVoltWattDER Volt-Watt14
707DERTripLVDER Trip LV15
708DERTripHVDER Trip HV15
709DERTripLFDER Trip LF15
710DERTripHFDER Trip HF15
711DERFreqDroopDER Frequency Droop14
712DERWattVarDER Watt-Var14
713DERStorageCapacityDER Storage Capacity5
714DERMeasureDCDER DC Measurement17
715DERCtlDERCtl5

Storage

ModelName in recordsLabelPoints
801storageEnergy Storage Base Model (DEPRECATED)1
802batteryBattery Base Model44
803lithium_ion_bankLithium-Ion Battery Bank Model44
804lithium_ion_stringLithium-Ion String Model41
805lithium-ion-moduleLithium-Ion Module Model23
806flow_batteryFlow Battery Model2
807flow_battery_stringFlow Battery String Model44
808flow_battery_moduleFlow Battery Module Model2
809flow_battery_stackFlow Battery Stack Model2

Test and vendor models

ModelName in recordsLabelPoints
63001model_63001SunSpec Test Model 159
63002model_63002SunSpec Test Model 22
64001model_64001Veris Status and Configuration35
64020model_64020Mersen GreenString30
64101model_64101Eltek Inverter Extension7
64111model_64111Basic Charge Controller18
64112model_64112OutBack FM Charge Controller58
64410DCSimInterfaceDC Simulator Control Interface32
64411ACSimInterfaceAC Simulator Control Interface51
64412DERExploitationDER Cyber Exploitation43
64413PVSimCurvesPV Curves5
64414DERSimControlsDER Simulation Controls9
64415CSIPControlCSIP Client Control5

Common and network

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
1common6
2aggregator10
3model_358
4model_460
5model_588
6model_689
7model_79
8model_83
9model_992
10model_103
11model_117
12model_1215
13model_1315
14model_148
15model_1512
16model_1610
17model_178
18model_185
19model_199

Model 1: Common

Name in records: common.

All SunSpec compliant devices must include this as the first model

PointTypeUnitsLabelDescription
MnstringManufacturerWell known value registered with SunSpec for compliance
MdstringModelManufacturer specific value (32 chars)
OptstringOptionsManufacturer specific value (16 chars)
VrstringVersionManufacturer specific value (16 chars)
SNstringSerial NumberManufacturer specific value (32 chars)
DAuint16Device AddressModbus device address

Model 2: Basic Aggregator

Name in records: aggregator.

Aggregates a collection of models for a given model id

PointTypeUnitsLabelDescription
AIDuint16AIDAggregated model id
Nuint16NNumber of aggregated models
UNuint16UNUpdate Number. Incrementing number each time the mapping is changed. If the number is not changed from the last reading the direct access to a specific offset will result in reading the same logical model as before. Otherwise the entire model must be read to refresh the changes
Stenum16StatusEnumerated status code
StVndenum16Vendor StatusVendor specific status code
Evtbitfield32Event CodeBitmask event code
EvtVndbitfield32Vendor Event CodeVendor specific event code
Ctlenum16ControlControl register for all aggregated devices
CtlVndenum32Vendor ControlVendor control register for all aggregated devices
CtlVlenum32Control ValueNumerical value used as a parameter to the control

Value names:

  • St (enum16): 1 OFF, 2 ON, 3 FULL, 4 FAULT
  • Evt (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 2 RESERVED_2, bit 3 DC_DISCONNECT, bit 4 RESERVED_4, bit 5 RESERVED_5, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMPERATURE, bit 8 RESERVED_8, bit 9 RESERVED_9, bit 10 RESERVED_10, bit 11 RESERVED_11, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMPERATURE, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 16 THEFT_DETECTION, bit 17 OUTPUT_OVER_CURRENT, bit 18 OUTPUT_OVER_VOLTAGE, bit 19 OUTPUT_UNDER_VOLTAGE, bit 20 TEST_FAILED
  • Ctl (enum16): 0 NONE, 1 AUTOMATIC, 2 FORCE_OFF, 3 TEST, 4 THROTTLE

Model 3: Secure Dataset Read Request

Name in records: model_3.

Request a digital signature over a specified set of data registers

PointTypeUnitsLabelDescription
Xuint16XNumber of registers being requested
Off1uint16Offset1Offset of value to read
Off2uint16
Off3uint16
Off4uint16
Off5uint16
Off6uint16
Off7uint16
Off8uint16
Off9uint16
Off10uint16
Off11uint16
Off12uint16
Off13uint16
Off14uint16
Off15uint16
Off16uint16
Off17uint16
Off18uint16
Off19uint16
Off20uint16
Off21uint16
Off22uint16
Off23uint16
Off24uint16
Off25uint16
Off26uint16
Off27uint16
Off28uint16
Off29uint16
Off30uint16
Off31uint16
Off32uint16
Off33uint16
Off34uint16
Off35uint16
Off36uint16
Off37uint16
Off38uint16
Off39uint16
Off40uint16
Off41uint16
Off42uint16
Off43uint16
Off44uint16
Off45uint16
Off46uint16
Off47uint16
Off48uint16
Off49uint16
Off50uint16
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of request
Roleuint16RoleDigital Signature ID
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16DSDigital Signature

Value names:

  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 4: Secure Dataset Read Response

Name in records: model_4.

Compute a digital signature over a specified set of data registers

PointTypeUnitsLabelDescription
RqSequint16Request SequenceSequence number from the request
Stsenum16StatusStatus of last read operation
Xuint16XNumber of values from the request
Val1uint16Value1Copy of value from register Off1.
Val2uint16
Val3uint16
Val4uint16
Val5uint16
Val6uint16
Val7uint16
Val8uint16
Val9uint16
Val10uint16
Val11uint16
Val12uint16
Val13uint16
Val14uint16
Val15uint16
Val16uint16
Val17uint16
Val18uint16
Val19uint16
Val20uint16
Val21uint16
Val22uint16
Val23uint16
Val24uint16
Val25uint16
Val26uint16
Val27uint16
Val28uint16
Val29uint16
Val30uint16
Val31uint16
Val32uint16
Val33uint16
Val34uint16
Val35uint16
Val36uint16
Val37uint16
Val38uint16
Val39uint16
Val40uint16
Val41uint16
Val42uint16
Val43uint16
Val44uint16
Val45uint16
Val46uint16
Val47uint16
Val48uint16
Val49uint16
Val50uint16
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of response
Almenum16AlarmBitmask alarm code
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16DSDigital Signature

Value names:

  • Sts (enum16): 0 SUCCESS, 1 DS, 2 ACL, 3 OFF
  • Alm (enum16): 0 NONE, 1 ALM
  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 5: Secure Write Request

Name in records: model_5.

Include a digital signature along with the control data

PointTypeUnitsLabelDescription
Xuint16XNumber of (offset, value) pairs being written
Off1uint16Offset1Offset of control register to write value to
Val1uint16Value1Value to write to control register at offset
Off2uint16
Val2uint16
Off3uint16
Val3uint16
Off4uint16
Val4uint16
Off5uint16
Val5uint16
Off6uint16
Val6uint16
Off7uint16
Val7uint16
Off8uint16
Val8uint16
Off9uint16
Val9uint16
Off10uint16
Val10uint16
Off11uint16
Val11uint16
Off12uint16
Val12uint16
Off13uint16
Val13uint16
Off14uint16
Val14uint16
Off15uint16
Val15uint16
Off16uint16
Val16uint16
Off17uint16
Val17uint16
Off18uint16
Val18uint16
Off19uint16
Val19uint16
Off20uint16
Val20uint16
Off21uint16
Val21uint16
Off22uint16
Val22uint16
Off23uint16
Val23uint16
Off24uint16
Val24uint16
Off25uint16
Val25uint16
Off26uint16
Val26uint16
Off27uint16
Val27uint16
Off28uint16
Val28uint16
Off29uint16
Val29uint16
Off30uint16
Val30uint16
Off31uint16
Val31uint16
Off32uint16
Val32uint16
Off33uint16
Val33uint16
Off34uint16
Val34uint16
Off35uint16
Val35uint16
Off36uint16
Val36uint16
Off37uint16
Val37uint16
Off38uint16
Val38uint16
Off39uint16
Val39uint16
Off40uint16
Val40uint16
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of request
Roleuint16RoleSigning key used 0-5
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16DSDigital Signature

Value names:

  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 6: Secure Write Sequential Request

Name in records: model_6.

Include a digital signature along with the control data

PointTypeUnitsLabelDescription
Xuint16XNumber of (offset, value) pairs being written
Offuint16OffsetStarting offset for write operation
Val1uint16Value1Value to write to control register at offset
Val2uint16
Val3uint16
Val4uint16
Val5uint16
Val6uint16
Val7uint16
Val8uint16
Val9uint16
Val10uint16
Val11uint16
Val12uint16
Val13uint16
Val14uint16
Val15uint16
Val16uint16
Val17uint16
Val18uint16
Val19uint16
Val20uint16
Val21uint16
Val22uint16
Val23uint16
Val24uint16
Val25uint16
Val26uint16
Val27uint16
Val28uint16
Val29uint16
Val30uint16
Val31uint16
Val32uint16
Val33uint16
Val34uint16
Val35uint16
Val36uint16
Val37uint16
Val38uint16
Val39uint16
Val40uint16
Val41uint16
Val42uint16
Val43uint16
Val44uint16
Val45uint16
Val46uint16
Val47uint16
Val48uint16
Val49uint16
Val50uint16
Val51uint16
Val52uint16
Val53uint16
Val54uint16
Val55uint16
Val56uint16
Val57uint16
Val58uint16
Val59uint16
Val60uint16
Val61uint16
Val62uint16
Val63uint16
Val64uint16
Val65uint16
Val66uint16
Val67uint16
Val68uint16
Val69uint16
Val70uint16
Val71uint16
Val72uint16
Val73uint16
Val74uint16
Val75uint16
Val76uint16
Val77uint16
Val78uint16
Val79uint16
Val80uint16
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of request
Roleuint16RoleSigning key used 0-5
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16DSDigital Signature

Value names:

  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 7: Secure Write Response Model (DRAFT 1)

Name in records: model_7.

Include a digital signature over the response

PointTypeUnitsLabelDescription
RqSequint16Request SequenceSequence number from the request
Stsenum16StatusStatus of last write operation
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of response
Almenum16AlarmBitmask alarm code
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16DSDigital Signature

Value names:

  • Sts (enum16): 0 SUCCESS, 1 DS, 2 ACL, 3 OFF, 4 VAL
  • Alm (enum16): 0 NONE, 1 ALM
  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 8: Get Device Security Certificate

Name in records: model_8.

Security model for PKI

PointTypeUnitsLabelDescription
Fmtenum16FormatX.509 format of the certificate. DER or PEM.
Nuint16NNumber of registers to follow for the certificate
repeating[*].Certuint16CertX.509 Certificate of the device

Value names:

  • Fmt (enum16): 0 NONE, 1 X509_PEM, 2 X509_DER

Model 9: Set Operator Security Certificate

Name in records: model_9.

Security model for PKI

PointTypeUnitsLabelDescription
CertUIDuint16Cert_UIDUser ID for this certificate
CertRoleuint16Cert_RoleRole for this certificate
Fmtenum16FormatFormat of this certificate
Typenum16TypeType of this certificate
TotLnuint16Total LengthTotal Length of the Certificate
FrgLnuint16Fragment lengthLength of this fragment
Frg1uint16Frag1First word of this fragment
Frg2uint16
Frg3uint16
Frg4uint16
Frg5uint16
Frg6uint16
Frg7uint16
Frg8uint16
Frg9uint16
Frg10uint16
Frg11uint16
Frg12uint16
Frg13uint16
Frg14uint16
Frg15uint16
Frg16uint16
Frg17uint16
Frg18uint16
Frg19uint16
Frg20uint16
Frg21uint16
Frg22uint16
Frg23uint16
Frg24uint16
Frg25uint16
Frg26uint16
Frg27uint16
Frg28uint16
Frg29uint16
Frg30uint16
Frg31uint16
Frg32uint16
Frg33uint16
Frg34uint16
Frg35uint16
Frg36uint16
Frg37uint16
Frg38uint16
Frg39uint16
Frg40uint16
Frg41uint16
Frg42uint16
Frg43uint16
Frg44uint16
Frg45uint16
Frg46uint16
Frg47uint16
Frg48uint16
Frg49uint16
Frg50uint16
Frg51uint16
Frg52uint16
Frg53uint16
Frg54uint16
Frg55uint16
Frg56uint16
Frg57uint16
Frg58uint16
Frg59uint16
Frg60uint16
Frg61uint16
Frg62uint16
Frg63uint16
Frg64uint16
Frg65uint16
Frg66uint16
Frg67uint16
Frg68uint16
Frg69uint16
Frg70uint16
Frg71uint16
Frg72uint16
Frg73uint16
Frg74uint16
Frg75uint16
Frg78uint16
Frg79uint16
Frg80uint16Frag80Last word of this fragment
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of request
UIDuint16UIDUser ID for the request signature
Roleuint16RoleSigning key used 0-5
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers to follow for the certificate
repeating[*].Certuint16

Value names:

  • Fmt (enum16): 0 NONE, 1 X509_PEM, 2 X509_DER
  • Typ (enum16): 0 DEV_KEY_PAIR, 1 DEV_SHARED_KEY, 2 OPERATOR_PUB, 3 OPERATOR_SHARED, 4 CA_PUB
  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Model 10: Communication Interface Header

Name in records: model_10.

To be included first for a complete interface description

PointTypeUnitsLabelDescription
Stenum16Interface StatusOverall interface status
Ctluint16Interface ControlOverall interface control (TBD)
Typenum16Physical Access TypeType of physical media

Value names:

  • St (enum16): 0 DOWN, 1 UP, 2 FAULT
  • Typ (enum16): 0 UNKNOWN, 1 INTERNAL, 2 TWISTED_PAIR, 3 FIBER, 4 WIRELESS

Name in records: model_11.

Include to support a wired ethernet port

PointTypeUnitsLabelDescription
Spduint16MbpsEthernet Link SpeedInterface speed in Mb/s
CfgStbitfield16Interface Status FlagsInterface flags.
Stenum16Link StateState information for this interface
MACeui48MACIEEE MAC address of this interface
NamstringNameInterface name (8 chars)
Ctlbitfield16ControlControl flags
FrcSpduint16MbpsForced SpeedForced interface speed in Mb/s when AUTO is disabled

Value names:

  • CfgSt (bitfield16): bit 0 LINK, bit 1 FULL_DUPLEX, bit 2 AUTO_NEG1, bit 3 AUTO_NEG2, bit 4 AUTO_NEG3, bit 5 RESET_REQUIRED, bit 6 HW_FAULT
  • St (enum16): 0 UNKNOWN, 1 ENABLED, 2 DISABLED, 3 TESTING
  • Ctl (bitfield16): bit 0 AUTO, bit 1 FULL_DUPLEX

Model 12: IPv4

Name in records: model_12.

Include to support an IPv4 protocol stack on this interface

PointTypeUnitsLabelDescription
NamstringNameInterface name
CfgStenum16Config StatusConfiguration status
ChgStbitfield16Change StatusA configuration change is pending
Capbitfield16Config CapabilityIdentify capable sources of configuration
Cfgenum16IPv4 ConfigConfiguration method used.
Ctlenum16ControlConfigure use of services
AddrstringIPIPv4 numeric address as a dotted string xxx.xxx.xxx.xxx
MskstringNetmaskIPv4 numeric netmask as a dotted string xxx.xxx.xxx.xxx
GwstringGatewayIPv4 numeric gateway address as a dotted string xxx.xxx.xxx.xxx
DNS1stringDNS1IPv4 numeric DNS address as a dotted string xxx.xxx.xxx.xxx
DNS2stringDNS2IPv4 numeric DNS address as a dotted string xxx.xxx.xxx.xxx
NTP1stringNTP1IPv4 numeric NTP address as a dotted string xxx.xxx.xxx.xxx
NTP2stringNTP2IPv4 numeric NTP address as a dotted string xxx.xxx.xxx.xxx
DomNamstringDomainDomain name (24 chars max)
HostNamstringHost NameHost name (24 chars max)

Value names:

  • CfgSt (enum16): 0 NOT_CONFIGURED, 1 VALID_SETTING, 2 VALID_HW
  • ChgSt (bitfield16): bit 0 PENDING
  • Cap (bitfield16): bit 0 DHCP, bit 1 BOOTP, bit 2 ZEROCONF, bit 3 DNS, bit 4 CFG_SETTABLE, bit 5 HW_CONFIG, bit 6 NTP_CLIENT, bit 7 RESET_REQUIRED
  • Cfg (enum16): 0 STATIC, 1 DHCP, 2 BOOTP, 3 ZEROCONF
  • Ctl (enum16): 0 ENABLE_DNS, 1 ENABLE_NTP

Model 13: IPv6

Name in records: model_13.

Include to support an IPv6 protocol stack on this interface

PointTypeUnitsLabelDescription
NamstringNameInterface name
CfgStenum16Config StatusConfiguration status
ChgStbitfield16Change StatusA configuration change is pending
Capbitfield16Config CapabilityIdentify capable sources of configuration
Cfgenum16IPv6 ConfigConfiguration method used.
Ctlenum16ControlConfigure use of services
AddrstringIPIPv6 numeric address as a dotted string xxxx.xxxx.xxxx.xxxx
CIDRstringCIDRClassless Inter-Domain Routing Number
GwstringGatewayIPv6 numeric address as a dotted string xxxx.xxxx.xxxx.xxxx
DNS1stringDNS1IPv6 numeric DNS address as a dotted string xxxx.xxxx.xxxx.xxxx
DNS2stringDNS2IPv6 numeric DNS address as a dotted string xxxx.xxxx.xxxx.xxxx
NTP1stringNTP1IPv6 numeric NTP address as a name or dotted string xxxx.xxxx.xxxx.xxxx
NTP2stringNTP2IPv6 numeric NTP address as a name or dotted string xxxx.xxxx.xxxx.xxxx
DomNamstringDomainDomain name (24 chars max)
HostNamstringHost NameHost name (24 chars max)

Value names:

  • CfgSt (enum16): 0 NOT_CONFIGURED, 1 VALID_SETTING, 2 VALID_HW
  • ChgSt (bitfield16): bit 0 PENDING
  • Cap (bitfield16): bit 0 DHCP, bit 1 BOOTP, bit 2 ZEROCONF, bit 3 DNS, bit 4 CFG_SETTABLE, bit 5 HW_CONFIG, bit 6 NTP_CLIENT, bit 7 RESET_REQUIRED
  • Cfg (enum16): 0 STATIC, 1 DHCP, 2 BOOTP, 3 ZEROCONF
  • Ctl (enum16): 0 ENABLE_DNS, 1 ENABLE_NTP

Model 14: Proxy Server

Name in records: model_14.

Include this block to allow for a proxy server

PointTypeUnitsLabelDescription
NamstringnameInterface name (8 chars)
Capbitfield16CapabilitiesProxy configuration capabilities
Cfgenum16ConfigSet proxy address type
Typbitfield16TypeEnumerate value. Proxy server type
AddrstringAddressIPv4 or IPv6 proxy hostname or dotted address (40 chars)
Portuint16PortProxy port number
UserstringUsernameProxy user name
PwstringPasswordProxy password

Value names:

  • Cap (bitfield16): bit 0 NO_PROXY, bit 1 IPV4_PROXY, bit 2 IPV6_PROXY

Model 15: Interface Counters Model

Name in records: model_15.

Interface counters

PointTypeUnitsLabelDescription
Clruint16ClearWrite a "1" to clear all counters
InCntacc32Input CountNumber of bytes received
InUcCntacc32Input Unicast CountNumber of Unicast packets received
InNUcCntacc32Input Non-Unicast CountNumber of non-Unicast packets received
InDscCntacc32Input Discarded CountNumber of inbound packets received on the interface but discarded
InErrCntacc32Input Error CountNumber of inbound packets that contain errors (excluding discards)
InUnkCntacc32Input Unknown CountNumber of inbound packets with unknown protocol
OutCntacc32Output CountTotal number of bytes transmitted on this interface
OutUcCntacc32Output Unicast CountNumber of Unicast packets transmitted
OutNUcCntacc32Output Non-Unicast CountNumber of Non-Unicast packets transmitted
OutDscCntacc32Output Discarded CountNumber of Discarded output packets
OutErrCntacc32Output Error CountNumber of outbound error packets

Model 16: Simple IP Network

Name in records: model_16.

Include this model for a simple IPv4 network stack

PointTypeUnitsLabelDescription
NamstringNameInterface name. (8 chars)
Cfgenum16ConfigForce IPv4 configuration method
Ctlbitfield16ControlConfigure use of services
AddrstringAddressIP address
MskstringNetmaskNetmask
GwstringGatewayGateway IP address
DNS1stringDNS132 bit IP address of DNS server
DNS2stringDNS232 bit IP address of DNS server
MACeui48MACIEEE MAC address of this interface
LnkCtlbitfield16Link ControlLink control flags

Value names:

  • Cfg (enum16): 0 STATIC, 1 DHCP
  • Ctl (bitfield16): bit 0 ENABLE_DNS, bit 1 ENABLE_NTP
  • LnkCtl (bitfield16): bit 0 AUTONEGOTIATE, bit 1 FULL_DUPLEX, bit 2 FORCE_10MB, bit 3 FORCE_100MB, bit 4 FORCE_1GB

Model 17: Serial Interface

Name in records: model_17.

Include this model for serial interface configuration support

PointTypeUnitsLabelDescription
NamstringNameInterface name (8 chars)
Rteuint32bpsRateInterface baud rate in bits per second
Bitsuint16BitsNumber of data bits per character
Ptyenum16ParityParity setting
Dupenum16DuplexDuplex mode
Flwenum16Flow ControlFlow Control Method
Typenum16Interface TypeInterface type
Pcolenum16ProtocolSerial protocol selection

Value names:

  • Pty (enum16): 0 NONE, 1 ODD, 2 EVEN
  • Dup (enum16): 0 FULL, 1 HALF
  • Flw (enum16): 0 NONE, 1 HW, 2 XONXOFF
  • Typ (enum16): 0 UNKNOWN, 1 RS232, 2 RS485
  • Pcol (enum16): 0 UNKNOWN, 1 MODBUS, 2 VENDOR

Name in records: model_18.

Include this model to support a cellular interface link

PointTypeUnitsLabelDescription
NamstringNameInterface name
IMEIuint32IMEIInternational Mobile Equipment Identifier for the interface
APNstringAPNAccess Point Name for the interface
NumstringNumberPhone number for the interface
PinstringPINPersonal Identification Number for the interface

Name in records: model_19.

Include this model to configure a Point-to-Point Protocol link

PointTypeUnitsLabelDescription
NamstringNameInterface name
Rteuint32bpsRateInterface baud rate in bits per second
Bitsuint16BitsNumber of data bits per character
Ptyenum16ParityParity setting
Dupenum16DuplexDuplex mode
Flwenum16Flow ControlFlow Control Method
Authenum16AuthenticationAuthentication method
UsrNamstringUsernameUsername for authentication
PwstringPasswordPassword for authentication

Value names:

  • Pty (enum16): 0 NONE, 1 ODD, 2 EVEN
  • Dup (enum16): 0 FULL, 1 HALF
  • Flw (enum16): 0 NONE, 1 HW, 2 XONXOFF
  • Auth (enum16): 0 NONE, 1 PAP, 2 CHAP

Inverters and inverter controls

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
101inverter_single_phase31
102inverter_split_phase31
103inverter_three_phase31
111inverter_single_phase_float31
112inverter_split_phase_float31
113inverter_three_phase_float31
120nameplate16
121settings20
122status17
123controls21
124storage_basic16
125pricing6
126volt_var54
127freq_watt_param6
128reactive_current11
129lvrt50
130hvrt50
131watt_pf53
132volt_watt54
133schedule34
134freq_watt58
135lfrt50
136hfrt50
137lvrtc50
138hvrtc50
139lvrtx51
140hvrtx51
141lfrtc50
142hfrtc50
143lfrtx51
144hfrtx51
145ext_settings7
160mppt13

Model 101: Inverter (Single Phase)

Name in records: inverter_single_phase.

Include this model for single phase inverter monitoring

PointTypeUnitsLabelDescription
Auint16AAmpsAC Current
AphAuint16AAmps PhaseAPhase A Current
AphBuint16AAmps PhaseBPhase B Current
AphCuint16AAmps PhaseCPhase C Current
PPVphABuint16VPhase Voltage ABPhase Voltage AB
PPVphBCuint16VPhase Voltage BCPhase Voltage BC
PPVphCAuint16VPhase Voltage CAPhase Voltage CA
PhVphAuint16VPhase Voltage ANPhase Voltage AN
PhVphBuint16VPhase Voltage BNPhase Voltage BN
PhVphCuint16VPhase Voltage CNPhase Voltage CN
Wint16WWattsAC Power
Hzuint16HzHzLine Frequency
VAint16VAVAAC Apparent Power
VArint16varVArAC Reactive Power
PFint16PctPFAC Power Factor
WHacc32WhWattHoursAC Energy
DCAuint16ADC AmpsDC Current
DCVuint16VDC VoltageDC Voltage
DCWint16WDC WattsDC Power
TmpCabint16CCabinet TemperatureCabinet Temperature
TmpSnkint16CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsint16CTransformer TemperatureTransformer Temperature
TmpOtint16COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 102: Inverter (Split-Phase)

Name in records: inverter_split_phase.

Include this model for split phase inverter monitoring

PointTypeUnitsLabelDescription
Auint16AAmpsAC Current
AphAuint16AAmps PhaseAPhase A Current
AphBuint16AAmps PhaseBPhase B Current
AphCuint16AAmps PhaseCPhase C Current
PPVphABuint16VPhase Voltage ABPhase Voltage AB
PPVphBCuint16VPhase Voltage BCPhase Voltage BC
PPVphCAuint16VPhase Voltage CAPhase Voltage CA
PhVphAuint16VPhase Voltage ANPhase Voltage AN
PhVphBuint16VPhase Voltage BNPhase Voltage BN
PhVphCuint16VPhase Voltage CNPhase Voltage CN
Wint16WWattsAC Power
Hzuint16HzHzLine Frequency
VAint16VAVAAC Apparent Power
VArint16varVArAC Reactive Power
PFint16PctPFAC Power Factor
WHacc32WhWattHoursAC Energy
DCAuint16ADC AmpsDC Current
DCVuint16VDC VoltageDC Voltage
DCWint16WDC WattsDC Power
TmpCabint16CCabinet TemperatureCabinet Temperature
TmpSnkint16CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsint16CTransformer TemperatureTransformer Temperature
TmpOtint16COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 103: Inverter (Three Phase)

Name in records: inverter_three_phase.

Include this model for three phase inverter monitoring

PointTypeUnitsLabelDescription
Auint16AAmpsAC Current
AphAuint16AAmps PhaseAPhase A Current
AphBuint16AAmps PhaseBPhase B Current
AphCuint16AAmps PhaseCPhase C Current
PPVphABuint16VPhase Voltage ABPhase Voltage AB
PPVphBCuint16VPhase Voltage BCPhase Voltage BC
PPVphCAuint16VPhase Voltage CAPhase Voltage CA
PhVphAuint16VPhase Voltage ANPhase Voltage AN
PhVphBuint16VPhase Voltage BNPhase Voltage BN
PhVphCuint16VPhase Voltage CNPhase Voltage CN
Wint16WWattsAC Power
Hzuint16HzHzLine Frequency
VAint16VAVAAC Apparent Power
VArint16varVArAC Reactive Power
PFint16PctPFAC Power Factor
WHacc32WhWattHoursAC Energy
DCAuint16ADC AmpsDC Current
DCVuint16VDC VoltageDC Voltage
DCWint16WDC WattsDC Power
TmpCabint16CCabinet TemperatureCabinet Temperature
TmpSnkint16CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsint16CTransformer TemperatureTransformer Temperature
TmpOtint16COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 111: Inverter (Single Phase) FLOAT

Name in records: inverter_single_phase_float.

Include this model for single phase inverter monitoring using float values

PointTypeUnitsLabelDescription
Afloat32AAmpsAC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
Wfloat32WWattsAC Power
Hzfloat32HzHzLine Frequency
VAfloat32VAVAAC Apparent Power
VArfloat32varVArAC Reactive Power
PFfloat32PctPFAC Power Factor
WHfloat32WhWattHoursAC Energy
DCAfloat32ADC AmpsDC Current
DCVfloat32VDC VoltageDC Voltage
DCWfloat32WDC WattsDC Power
TmpCabfloat32CCabinet TemperatureCabinet Temperature
TmpSnkfloat32CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsfloat32CTransformer TemperatureTransformer Temperature
TmpOtfloat32COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 ggOFF, 2 ggSLEEPING, 3 ggSTARTING, 4 ggMPPT, 5 ggTHROTTLED, 6 ggSHUTTING_DOWN, 7 ggFAULT, 8 ggSTANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 112: Inverter (Split Phase) FLOAT

Name in records: inverter_split_phase_float.

Include this model for split phase inverter monitoring using float values

PointTypeUnitsLabelDescription
Afloat32AAmpsAC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
Wfloat32WWattsAC Power
Hzfloat32HzHzLine Frequency
VAfloat32VAVAAC Apparent Power
VArfloat32varVArAC Reactive Power
PFfloat32PctPFAC Power Factor
WHfloat32WhWattHoursAC Energy
DCAfloat32ADC AmpsDC Current
DCVfloat32VDC VoltageDC Voltage
DCWfloat32WDC WattsDC Power
TmpCabfloat32CCabinet TemperatureCabinet Temperature
TmpSnkfloat32CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsfloat32CTransformer TemperatureTransformer Temperature
TmpOtfloat32COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 113: Inverter (Three Phase) FLOAT

Name in records: inverter_three_phase_float.

Include this model for three phase inverter monitoring using float values

PointTypeUnitsLabelDescription
Afloat32AAmpsAC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
Wfloat32WWattsAC Power
Hzfloat32HzHzLine Frequency
VAfloat32VAVAAC Apparent Power
VArfloat32varVArAC Reactive Power
PFfloat32PctPFAC Power Factor
WHfloat32WhWattHoursAC Energy
DCAfloat32ADC AmpsDC Current
DCVfloat32VDC VoltageDC Voltage
DCWfloat32WDC WattsDC Power
TmpCabfloat32CCabinet TemperatureCabinet Temperature
TmpSnkfloat32CHeat Sink TemperatureHeat Sink Temperature
TmpTrnsfloat32CTransformer TemperatureTransformer Temperature
TmpOtfloat32COther TemperatureOther Temperature
Stenum16Operating StateOperating state
StVndenum16Vendor Operating StateVendor specific operating state code
Evt1bitfield32Event1Event fields
Evt2bitfield32Event Bitfield 2Reserved for future use
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events
EvtVnd3bitfield32Vendor Event Bitfield 3Vendor defined events
EvtVnd4bitfield32Vendor Event Bitfield 4Vendor defined events

Value names:

  • St (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY
  • Evt1 (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE

Model 120: Nameplate

Name in records: nameplate.

Inverter Controls Nameplate Ratings

PointTypeUnitsLabelDescription
DERTypenum16DERTypType of DER device. Default value is 4 to indicate PV device.
WRtguint16WWRtgContinuous power output capability of the inverter.
VARtguint16VAVARtgContinuous Volt-Ampere capability of the inverter.
VArRtgQ1int16varVArRtgQ1Continuous VAR capability of the inverter in quadrant 1.
VArRtgQ2int16varVArRtgQ2Continuous VAR capability of the inverter in quadrant 2.
VArRtgQ3int16varVArRtgQ3Continuous VAR capability of the inverter in quadrant 3.
VArRtgQ4int16varVArRtgQ4Continuous VAR capability of the inverter in quadrant 4.
ARtguint16AARtgMaximum RMS AC current level capability of the inverter.
PFRtgQ1int16cos()PFRtgQ1Minimum power factor capability of the inverter in quadrant 1.
PFRtgQ2int16cos()PFRtgQ2Minimum power factor capability of the inverter in quadrant 2.
PFRtgQ3int16cos()PFRtgQ3Minimum power factor capability of the inverter in quadrant 3.
PFRtgQ4int16cos()PFRtgQ4Minimum power factor capability of the inverter in quadrant 4.
WHRtguint16WhWHRtgNominal energy rating of storage device.
AhrRtguint16AHAhrRtgThe usable capacity of the battery. Maximum charge minus minimum charge from a technology capability perspective (Amp-hour capacity rating).
MaxChaRteuint16WMaxChaRteMaximum rate of energy transfer into the storage device.
MaxDisChaRteuint16WMaxDisChaRteMaximum rate of energy transfer out of the storage device.

Value names:

  • DERTyp (enum16): 4 PV, 82 PV_STOR

Model 121: Basic Settings

Name in records: settings.

Inverter Controls Basic Settings

PointTypeUnitsLabelDescription
WMaxuint16WWMaxSetting for maximum power output. Default to WRtg.
VRefuint16VVRefVoltage at the PCC.
VRefOfsint16VVRefOfsOffset from PCC to inverter.
VMaxuint16VVMaxSetpoint for maximum voltage.
VMinuint16VVMinSetpoint for minimum voltage.
VAMaxuint16VAVAMaxSetpoint for maximum apparent power. Default to VARtg.
VArMaxQ1int16varVArMaxQ1Setting for maximum reactive power in quadrant 1. Default to VArRtgQ1.
VArMaxQ2int16varVArMaxQ2Setting for maximum reactive power in quadrant 2. Default to VArRtgQ2.
VArMaxQ3int16varVArMaxQ3Setting for maximum reactive power in quadrant 3. Default to VArRtgQ3.
VArMaxQ4int16varVArMaxQ4Setting for maximum reactive power in quadrant 4. Default to VArRtgQ4.
WGrauint16% WMax/secWGraDefault ramp rate of change of active power due to command or internal action.
PFMinQ1int16cos()PFMinQ1Setpoint for minimum power factor value in quadrant 1. Default to PFRtgQ1.
PFMinQ2int16cos()PFMinQ2Setpoint for minimum power factor value in quadrant 2. Default to PFRtgQ2.
PFMinQ3int16cos()PFMinQ3Setpoint for minimum power factor value in quadrant 3. Default to PFRtgQ3.
PFMinQ4int16cos()PFMinQ4Setpoint for minimum power factor value in quadrant 4. Default to PFRtgQ4.
VArActenum16VArActVAR action on change between charging and discharging: 1=switch 2=maintain VAR characterization.
ClcTotVAenum16ClcTotVACalculation method for total apparent power. 1=vector 2=arithmetic.
MaxRmpRteuint16% WGraMaxRmpRteSetpoint for maximum ramp rate as percentage of nominal maximum ramp rate. This setting will limit the rate that watts delivery to the grid can increase or decrease in response to intermittent PV generation.
ECPNomHzuint16HzECPNomHzSetpoint for nominal frequency at the ECP.
ConnPhenum16ConnPhIdentity of connected phase for single phase inverters. A=1 B=2 C=3.

Value names:

  • VArAct (enum16): 1 SWITCH, 2 MAINTAIN
  • ClcTotVA (enum16): 1 VECTOR, 2 ARITHMETIC
  • ConnPh (enum16): 1 A, 2 B, 3 C

Model 122: Measurements_Status

Name in records: status.

Inverter Controls Extended Measurements and Status

PointTypeUnitsLabelDescription
PVConnbitfield16PVConnPV inverter present/available status.
StorConnbitfield16StorConnStorage inverter present/available status.
ECPConnbitfield16ECPConnECP connection status: disconnected=0 connected=1.
ActWhacc64WhActWhAC lifetime active (real) energy output.
ActVAhacc64VAhActVAhAC lifetime apparent energy output.
ActVArhQ1acc64varhActVArhQ1AC lifetime reactive energy output in quadrant 1.
ActVArhQ2acc64varhActVArhQ2AC lifetime reactive energy output in quadrant 2.
ActVArhQ3acc64varhActVArhQ3AC lifetime negative energy output in quadrant 3.
ActVArhQ4acc64varhActVArhQ4AC lifetime reactive energy output in quadrant 4.
VArAvalint16varVArAvalAmount of VARs available without impacting watts output.
WAvaluint16WWAvalAmount of Watts available.
StSetLimMskbitfield32StSetLimMskSetpoint limit(s) reached.
StActCtlbitfield32StActCtlWhich inverter controls are currently active.
TmSrcstringTmSrcSource of time synchronization.
Tmsuint32SecsTmsSeconds since 01-01-2000 00:00 UTC
RtStbitfield16RtStActive ride-through status.
Risuint16ohmsRisIsolation resistance.

Value names:

  • PVConn (bitfield16): bit 0 CONNECTED, bit 1 AVAILABLE, bit 2 OPERATING, bit 3 TEST
  • StorConn (bitfield16): bit 0 CONNECTED, bit 1 AVAILABLE, bit 2 OPERATING, bit 3 TEST
  • ECPConn (bitfield16): bit 0 DISCONNECTED, bit 1 CONNECTED
  • StSetLimMsk (bitfield32): bit 0 WMax, bit 1 VAMax, bit 2 VArAval, bit 3 VArMaxQ1, bit 4 VArMaxQ2, bit 5 VArMaxQ3, bit 6 VArMaxQ4, bit 7 PFMinQ1, bit 8 PFMinQ2, bit 9 PFMinQ3, bit 10 PFMinQ4
  • StActCtl (bitfield32): bit 0 FixedW, bit 1 FixedVAR, bit 2 FixedPF, bit 3 Volt-VAr, bit 4 Freq-Watt-Param, bit 5 Freq-Watt-Curve, bit 6 Dyn-Reactive-Current, bit 7 LVRT, bit 8 HVRT, bit 9 Watt-PF, bit 10 Volt-Watt, bit 12 Scheduled, bit 13 LFRT, bit 14 HFRT
  • RtSt (bitfield16): bit 0 LVRT_ACTIVE, bit 1 HVRT_ACTIVE, bit 2 LFRT_ACTIVE, bit 3 HFRT_ACTIVE

Model 123: Immediate Controls

Name in records: controls.

Immediate Inverter Controls

PointTypeUnitsLabelDescription
Conn_WinTmsuint16SecsConn_WinTmsTime window for connect/disconnect.
Conn_RvrtTmsuint16SecsConn_RvrtTmsTimeout period for connect/disconnect.
Connenum16ConnConnection control.
WMaxLimPctuint16% WMaxWMaxLimPctSet power output to specified level.
WMaxLimPct_WinTmsuint16SecsWMaxLimPct_WinTmsTime window for power limit change.
WMaxLimPct_RvrtTmsuint16SecsWMaxLimPct_RvrtTmsTimeout period for power limit.
WMaxLimPct_RmpTmsuint16SecsWMaxLimPct_RmpTmsRamp time for moving from current setpoint to new setpoint.
WMaxLim_Enaenum16WMaxLim_EnaThrottle enable/disable control.
OutPFSetint16cos()OutPFSetSet power factor to specific value - cosine of angle.
OutPFSet_WinTmsuint16SecsOutPFSet_WinTmsTime window for power factor change.
OutPFSet_RvrtTmsuint16SecsOutPFSet_RvrtTmsTimeout period for power factor.
OutPFSet_RmpTmsuint16SecsOutPFSet_RmpTmsRamp time for moving from current setpoint to new setpoint.
OutPFSet_Enaenum16OutPFSet_EnaFixed power factor enable/disable control.
VArWMaxPctint16% WMaxVArWMaxPctReactive power in percent of WMax.
VArMaxPctint16% VArMaxVArMaxPctReactive power in percent of VArMax.
VArAvalPctint16% VArAvalVArAvalPctReactive power in percent of VArAval.
VArPct_WinTmsuint16SecsVArPct_WinTmsTime window for VAR limit change.
VArPct_RvrtTmsuint16SecsVArPct_RvrtTmsTimeout period for VAR limit.
VArPct_RmpTmsuint16SecsVArPct_RmpTmsRamp time for moving from current setpoint to new setpoint.
VArPct_Modenum16VArPct_ModVAR percent limit mode.
VArPct_Enaenum16VArPct_EnaPercent limit VAr enable/disable control.

Value names:

  • Conn (enum16): 0 DISCONNECT, 1 CONNECT
  • WMaxLim_Ena (enum16): 0 DISABLED, 1 ENABLED
  • OutPFSet_Ena (enum16): 0 DISABLED, 1 ENABLED
  • VArPct_Mod (enum16): 0 NONE, 1 WMax, 2 VArMax, 3 VArAval
  • VArPct_Ena (enum16): 0 DISABLED, 1 ENABLED

Model 124: Storage

Name in records: storage_basic.

Basic Storage Controls

PointTypeUnitsLabelDescription
WChaMaxuint16WWChaMaxSetpoint for maximum charge.
WChaGrauint16% WChaMax/secWChaGraSetpoint for maximum charging rate. Default is MaxChaRte.
WDisChaGrauint16% WChaMax/secWDisChaGraSetpoint for maximum discharge rate. Default is MaxDisChaRte.
StorCtl_Modbitfield16StorCtl_ModActivate hold/discharge/charge storage control mode.
VAChaMaxuint16VAVAChaMaxSetpoint for maximum charging VA.
MinRsvPctuint16% WChaMaxMinRsvPctSetpoint for minimum reserve for storage as a percentage of the nominal maximum storage.
ChaStateuint16% AhrRtgChaStateCurrently available energy as a percent of the capacity rating.
StorAvaluint16AHStorAvalState of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg).
InBatVuint16VInBatVInternal battery voltage.
ChaStenum16ChaStCharge status of storage device.
OutWRteint16% WDisChaMaxOutWRtePercent of max discharge rate.
InWRteint16% WChaMaxInWRtePercent of max charging rate.
InOutWRte_WinTmsuint16SecsInOutWRte_WinTmsTime window for charge/discharge rate change.
InOutWRte_RvrtTmsuint16SecsInOutWRte_RvrtTmsTimeout period for charge/discharge rate.
InOutWRte_RmpTmsuint16SecsInOutWRte_RmpTmsRamp time for moving from current setpoint to new setpoint.
ChaGriSetenum16

Value names:

  • StorCtl_Mod (bitfield16): bit 0 CHARGE, bit 1 DiSCHARGE
  • ChaSt (enum16): 1 OFF, 2 EMPTY, 3 DISCHARGING, 4 CHARGING, 5 FULL, 6 HOLDING, 7 TESTING
  • ChaGriSet (enum16): 0 PV, 1 GRID

Model 125: Pricing

Name in records: pricing.

Pricing Signal

PointTypeUnitsLabelDescription
ModEnabitfield16ModEnaIs price-based charge/discharge mode active?
SigTypeenum16SigTypeMeaning of the pricing signal. When a Price schedule is used, type must match the schedule range variable description.
Sigint16SigUtility/ESP specific pricing signal. Content depends on pricing signal type. When H/M/L type is specified. Low=0; Med=1; High=2.
WinTmsuint16SecsWinTmsTime window for charge/discharge pricing change.
RvtTmsuint16SecsRvtTmsTimeout period for charge/discharge pricing change.
RmpTmsuint16SecsRmpTmsRamp time for moving from current charge or discharge level to new level.

Value names:

  • ModEna (bitfield16): bit 0 ENABLE
  • SigType (enum16): 0 UNKNOWN, 1 ABSOLUTE, 2 RELATIVE, 3 MULTIPLIER, 4 LEVEL

Model 126: Static Volt-VAR

Name in records: volt_var.

Static Volt-VAR Arrays

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaIs Volt-VAR control active.
WinTmsuint16SecsWinTmsTime window for volt-VAR change.
RvrtTmsuint16SecsRvrtTmsTimeout period for volt-VAR curve selection.
RmpTmsuint16SecsRmpTmsThe time of the PT1 in seconds (time to accomplish a change of 95%).
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].DeptRefenum16DeptRefMeaning of dependent variable: 1=%WMax 2=%VArMax 3=%VArAval.
curve[*].V1uint16% VRefV1Point 1 Volts.
curve[*].VAr1int16VAr1Point 1 VARs.
curve[*].V2uint16% VRefV2Point 2 Volts.
curve[*].VAr2int16VAr2Point 2 VARs.
curve[*].V3uint16% VRefV3Point 3 Volts.
curve[*].VAr3int16VAr3Point 3 VARs.
curve[*].V4uint16% VRefV4Point 4 Volts.
curve[*].VAr4int16VAr4Point 4 VARs.
curve[*].V5uint16% VRefV5Point 5 Volts.
curve[*].VAr5int16VAr5Point 5 VARs.
curve[*].V6uint16% VRefV6Point 6 Volts.
curve[*].VAr6int16VAr6Point 6 VARs.
curve[*].V7uint16% VRefV7Point 7 Volts.
curve[*].VAr7int16VAr7Point 7 VARs.
curve[*].V8uint16% VRefV8Point 8 Volts.
curve[*].VAr8int16VAr8Point 8 VARs.
curve[*].V9uint16% VRefV9Point 9 Volts.
curve[*].VAr9int16VAr9Point 9 VARs.
curve[*].V10uint16% VRefV10Point 10 Volts.
curve[*].VAr10int16VAr10Point 10 VARs.
curve[*].V11uint16% VRefV11Point 11 Volts.
curve[*].VAr11int16VAr11Point 11 VARs.
curve[*].V12uint16% VRefV12Point 12 Volts.
curve[*].VAr12int16VAr12Point 12 VARs.
curve[*].V13uint16% VRefV13Point 13 Volts.
curve[*].VAr13int16VAr13Point 13 VARs.
curve[*].V14uint16% VRefV14Point 14 Volts.
curve[*].VAr14int16VAr14Point 14 VARs.
curve[*].V15uint16% VRefV15Point 15 Volts.
curve[*].VAr15int16VAr15Point 15 VARs.
curve[*].V16uint16% VRefV16Point 16 Volts.
curve[*].VAr16int16VAr16Point 16 VARs.
curve[*].V17uint16% VRefV17Point 17 Volts.
curve[*].VAr17int16VAr17Point 17 VARs.
curve[*].V18uint16% VRefV18Point 18 Volts.
curve[*].VAr18int16VAr18Point 18 VARs.
curve[*].V19uint16% VRefV19Point 19 Volts.
curve[*].VAr19int16VAr19Point 19 VARs.
curve[*].V20uint16% VRefV20Point 20 Volts.
curve[*].VAr20int16VAr20Point 20 VARs.
curve[*].CrvNamstringCrvNamOptional description for curve. (Max 16 chars)
curve[*].RmpTmsuint16SecsRmpTmsThe time of the PT1 in seconds (time to accomplish a change of 95%).
curve[*].RmpDecTmmuint16% ref_value/minRmpDecTmmThe maximum rate at which the VAR value may be reduced in response to changes in the voltage value. %refVal is %WMax %VArMax or %VArAval depending on value of DeptRef.
curve[*].RmpIncTmmuint16% ref_value/minRmpIncTmmThe maximum rate at which the VAR value may be increased in response to changes in the voltage value. %refVal is %WMax %VArMax or %VArAval depending on value of DeptRef.
curve[*].ReadOnlyenum16ReadOnlyBoolean flag indicates if curve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].DeptRef (enum16): 1 WMax, 2 VArMax, 3 VArAval
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 127: Freq-Watt Param

Name in records: freq_watt_param.

Parameterized Frequency-Watt

PointTypeUnitsLabelDescription
WGrauint16% PM/HzWGraThe slope of the reduction in the maximum allowed watts output as a function of frequency.
HzStrint16HzHzStrThe frequency deviation from nominal frequency (ECPNomHz) at which a snapshot of the instantaneous power output is taken to act as the CAPPED power level (PM) and above which reduction in power output occurs.
HzStopint16HzHzStopThe frequency deviation from nominal frequency (ECPNomHz) at which curtailed power output may return to normal and the cap on the power level value is removed.
HysEnabitfield16HysEnaEnable hysteresis
ModEnabitfield16ModEnaIs Parameterized Frequency-Watt control active.
HzStopWGrauint16% WMax/minHzStopWGraThe maximum time-based rate of change at which power output returns to normal after having been capped by an over frequency event.

Value names:

  • HysEna (bitfield16): bit 0 ENABLED
  • ModEna (bitfield16): bit 0 ENABLED

Model 128: Dynamic Reactive Current

Name in records: reactive_current.

Dynamic Reactive Current

PointTypeUnitsLabelDescription
ArGraModenum16ArGraModIndicates if gradients trend toward zero at the edges of the deadband or trend toward zero at the center of the deadband.
ArGraSaguint16%ARtg/%dVArGraSagThe gradient used to increase capacitive dynamic current. A value of 0 indicates no additional reactive current support.
ArGraSwelluint16%ARtg/%dVArGraSwellThe gradient used to increase inductive dynamic current. A value of 0 indicates no additional reactive current support.
ModEnabitfield16ModEnaActivate dynamic reactive current model
FilTmsuint16SecsFilTmsThe time window used to calculate the moving average voltage.
DbVMinuint16% VRefDbVMinThe lower delta voltage limit for which negative voltage deviations less than this value no dynamic vars are produced.
DbVMaxuint16% VRefDbVMaxThe upper delta voltage limit for which positive voltage deviations less than this value no dynamic current produced.
BlkZnVuint16% VRefBlkZnVBlock zone voltage which defines a lower voltage boundary below which no dynamic current is produced.
HysBlkZnVuint16% VRefHysBlkZnVHysteresis voltage used with BlkZnV.
BlkZnTmmsuint16mSecsBlkZnTmmsBlock zone time the time before which reactive current support remains active regardless of how low the voltage drops.
HoldTmmsuint16mSecsHoldTmmsHold time during which reactive current support continues after the average voltage has entered the dead zone.

Value names:

  • ArGraMod (enum16): 0 EDGE, 1 CENTER
  • ModEna (bitfield16): bit 0 ENABLED

Model 129: LVRTD

Name in records: lvrt.

LVRT Must Disconnect

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must disconnect duration.
curve[*].V1uint16% VRefV1Point 1 must disconnect voltage.
curve[*].Tms2uint16SecsTms2Point 2 must disconnect duration.
curve[*].V2uint16% VRefV2Point 2 must disconnect voltage.
curve[*].Tms3uint16SecsTms3Point 3 must disconnect duration.
curve[*].V3uint16% VRefV3Point 3 must disconnect voltage.
curve[*].Tms4uint16SecsTms4Point 4 must disconnect duration.
curve[*].V4uint16% VRefV4Point 4 must disconnect voltage.
curve[*].Tms5uint16SecsTms5Point 5 must disconnect duration.
curve[*].V5uint16% VRefV5Point 5 must disconnect voltage.
curve[*].Tms6uint16SecsTms6Point 6 must disconnect duration.
curve[*].V6uint16% VRefV6Point 6 must disconnect voltage.
curve[*].Tms7uint16SecsTms7Point 7 must disconnect duration.
curve[*].V7uint16% VRefV7Point 7 must disconnect voltage.
curve[*].Tms8uint16SecsTms8Point 8 must disconnect duration.
curve[*].V8uint16% VRefV8Point 8 must disconnect voltage.
curve[*].Tms9uint16SecsTms9Point 9 must disconnect duration.
curve[*].V9uint16% VRefV9Point 9 must disconnect voltage.
curve[*].Tms10uint16SecsTms10Point 10 must disconnect duration.
curve[*].V10uint16% VRefV10Point 10 must disconnect voltage.
curve[*].Tms11uint16SecsTms11Point 11 must disconnect duration.
curve[*].V11uint16% VRefV11Point 11 must disconnect voltage.
curve[*].Tms12uint16SecsTms12Point 12 must disconnect duration.
curve[*].V12uint16% VRefV12Point 12 must disconnect voltage.
curve[*].Tms13uint16SecsTms13Point 13 must disconnect duration.
curve[*].V13uint16% VRefV13Point 13 must disconnect voltage.
curve[*].Tms14uint16SecsTms14Point 14 must disconnect duration.
curve[*].V14uint16% VRefV14Point 14 must disconnect voltage.
curve[*].Tms15uint16SecsTms15Point 15 must disconnect duration.
curve[*].V15uint16% VRefV15Point 15 must disconnect voltage.
curve[*].Tms16uint16SecsTms16Point 16 must disconnect duration.
curve[*].V16uint16% VRefV16Point 16 must disconnect voltage.
curve[*].Tms17uint16SecsTms17Point 17 must disconnect duration.
curve[*].V17uint16% VRefV17Point 17 must disconnect voltage.
curve[*].Tms18uint16SecsTms18Point 18 must disconnect duration.
curve[*].V18uint16% VRefV18Point 18 must disconnect voltage.
curve[*].Tms19uint16SecsTms19Point 19 must disconnect duration.
curve[*].V19uint16% VRefV19Point 19 must disconnect voltage.
curve[*].Tms20uint16SecsTms20Point 20 must disconnect duration.
curve[*].V20uint16% VRefV20Point 20 must disconnect voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 130: HVRTD

Name in records: hvrt.

HVRT Must Disconnect

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaHVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for HVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for HVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must disconnect duration.
curve[*].V1uint16% VRefV1Point 1 must disconnect voltage.
curve[*].Tms2uint16SecsTms2Point 2 must disconnect duration.
curve[*].V2uint16% VRefV2Point 2 must disconnect voltage.
curve[*].Tms3uint16SecsTms3Point 3 must disconnect duration.
curve[*].V3uint16% VRefV3Point 3 must disconnect voltage.
curve[*].Tms4uint16SecsTms4Point 4 must disconnect duration.
curve[*].V4uint16% VRefV4Point 4 must disconnect voltage.
curve[*].Tms5uint16SecsTms5Point 5 must disconnect duration.
curve[*].V5uint16% VRefV5Point 5 must disconnect voltage.
curve[*].Tms6uint16SecsTms6Point 6 must disconnect duration.
curve[*].V6uint16% VRefV6Point 6 must disconnect voltage.
curve[*].Tms7uint16SecsTms7Point 7 must disconnect duration.
curve[*].V7uint16% VRefV7Point 7 must disconnect voltage.
curve[*].Tms8uint16SecsTms8Point 8 must disconnect duration.
curve[*].V8uint16% VRefV8Point 8 must disconnect voltage.
curve[*].Tms9uint16SecsTms9Point 9 must disconnect duration.
curve[*].V9uint16% VRefV9Point 9 must disconnect voltage.
curve[*].Tms10uint16SecsTms10Point 10 must disconnect duration.
curve[*].V10uint16% VRefV10Point 10 must disconnect voltage.
curve[*].Tms11uint16SecsTms11Point 11 must disconnect duration.
curve[*].V11uint16% VRefV11Point 11 must disconnect voltage.
curve[*].Tms12uint16SecsTms12Point 12 must disconnect duration.
curve[*].V12uint16% VRefV12Point 12 must disconnect voltage.
curve[*].Tms13uint16SecsTms13Point 13 must disconnect duration.
curve[*].V13uint16% VRefV13Point 13 must disconnect voltage.
curve[*].Tms14uint16SecsTms14Point 14 must disconnect duration.
curve[*].V14uint16% VRefV14Point 14 must disconnect voltage.
curve[*].Tms15uint16SecsTms15Point 15 must disconnect duration.
curve[*].V15uint16% VRefV15Point 15 must disconnect voltage.
curve[*].Tms16uint16SecsTms16Point 16 must disconnect duration.
curve[*].V16uint16% VRefV16Point 16 must disconnect voltage.
curve[*].Tms17uint16SecsTms17Point 17 must disconnect duration.
curve[*].V17uint16% VRefV17Point 17 must disconnect voltage.
curve[*].Tms18uint16SecsTms18Point 18 must disconnect duration.
curve[*].V18uint16% VRefV18Point 18 must disconnect voltage.
curve[*].Tms19uint16SecsTms19Point 19 must disconnect duration.
curve[*].V19uint16% VRefV19Point 19 must disconnect voltage.
curve[*].Tms20uint16SecsTms20Point 20 must disconnect duration.
curve[*].V20uint16% VRefV20Point 20 must disconnect voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 131: Watt-PF

Name in records: watt_pf.

Watt-Power Factor

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaIs watt-PF mode active.
WinTmsuint16SecsWinTmsTime window for watt-PF change.
RvrtTmsuint16SecsRvrtTmsTimeout period for watt-PF curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtMax number of points in array.
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].W1int16% WMaxW1Point 1 Watts.
curve[*].PF1int16cos()PF1Point 1 PF in EEI notation.
curve[*].W2int16% WMaxW2Point 2 Watts.
curve[*].PF2int16cos()PF2Point 2 PF in EEI notation.
curve[*].W3int16% WMaxW3Point 3 Watts.
curve[*].PF3int16cos()PF3Point 3 PF in EEI notation.
curve[*].W4int16% WMaxW4Point 4 Watts.
curve[*].PF4int16cos()PF4Point 4 PF in EEI notation.
curve[*].W5int16% WMaxW5Point 5 Watts.
curve[*].PF5int16cos()PF5Point 5 PF in EEI notation.
curve[*].W6int16% WMaxW6Point 6 Watts.
curve[*].PF6int16cos()PF6Point 6 PF in EEI notation.
curve[*].W7int16% WMaxW7Point 7 Watts.
curve[*].PF7int16cos()PF7Point 7 PF in EEI notation.
curve[*].W8int16% WMaxW8Point 8 Watts.
curve[*].PF8int16cos()PF8Point 8 PF in EEI notation.
curve[*].W9int16% WMaxW9Point 9 Watts.
curve[*].PF9int16cos()PF9Point 9 PF in EEI notation.
curve[*].W10int16% WMaxW10Point 10 Watts.
curve[*].PF10int16cos()PF10Point 10 PF in EEI notation.
curve[*].W11int16% WMaxW11Point 11 Watts.
curve[*].PF11int16cos()PF11Point 11 PF in EEI notation.
curve[*].W12int16% WMaxW12Point 12 Watts.
curve[*].PF12int16cos()PF12Point 12 PF in EEI notation.
curve[*].W13int16% WMaxW13Point 13 Watts.
curve[*].PF13int16cos()PF13Point 13 PF in EEI notation.
curve[*].W14int16% WMaxW14Point 14 Watts.
curve[*].PF14int16cos()PF14Point 14 PF in EEI notation.
curve[*].W15int16% WMaxW15Point 15 Watts.
curve[*].PF15int16cos()PF15Point 15 PF in EEI notation.
curve[*].W16int16% WMaxW16Point 16 Watts.
curve[*].PF16int16cos()PF16Point 16 PF in EEI notation.
curve[*].W17int16% WMaxW17Point 17 Watts.
curve[*].PF17int16cos()PF17Point 17 PF in EEI notation.
curve[*].W18int16% WMaxW18Point 18 Watts.
curve[*].PF18int16cos()PF18Point 18 PF in EEI notation.
curve[*].W19int16% WMaxW19Point 19 Watts.
curve[*].PF19int16cos()PF19Point 19 PF in EEI notation.
curve[*].W20int16% WMaxW20Point 20 Watts.
curve[*].PF20int16cos()PF20Point 20 PF in EEI notation.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].RmpPT1Tmsuint16SecsRmpPT1TmsThe time of the PT1 in seconds (time to accomplish a change of 95%).
curve[*].RmpDecTmmuint16% PF/minRmpDecTmmThe maximum rate at which the power factor may be reduced in response to changes in the power value.
curve[*].RmpIncTmmuint16% PF/minRmpIncTmmThe maximum rate at which the power factor may be increased in response to changes in the power value.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 132: Volt-Watt

Name in records: volt_watt.

Volt-Watt

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaIs Volt-Watt control active.
WinTmsuint16SecsWinTmsTime window for volt-watt change.
RvrtTmsuint16SecsRvrtTmsTimeout period for volt-watt curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend min. 4).
NPtuint16NPtNumber of points in array (maximum 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].DeptRefenum16DeptRefDefines the meaning of the Watts DeptRef. 1=% WMax 2=% WAvail
curve[*].V1uint16% VRefV1Point 1 Volts.
curve[*].W1int16% VRefW1Point 1 Watts.
curve[*].V2uint16% VRefV2Point 2 Volts.
curve[*].W2int16% VRefW2Point 2 Watts.
curve[*].V3uint16% VRefV3Point 3 Volts.
curve[*].W3int16% VRefW3Point 3 Watts.
curve[*].V4uint16% VRefV4Point 4 Volts.
curve[*].W4int16% VRefW4Point 4 Watts.
curve[*].V5uint16% VRefV5Point 5 Volts.
curve[*].W5int16% VRefW5Point 5 Watts.
curve[*].V6uint16% VRefV6Point 6 Volts.
curve[*].W6int16% VRefW6Point 6 Watts.
curve[*].V7uint16% VRefV7Point 7 Volts.
curve[*].W7int16% VRefW7Point 7 Watts.
curve[*].V8uint16% VRefV8Point 8 Volts.
curve[*].W8int16% VRefW8Point 8 Watts.
curve[*].V9uint16% VRefV9Point 9 Volts.
curve[*].W9int16% VRefW9Point 9 Watts.
curve[*].V10uint16% VRefV10Point 10 Volts.
curve[*].W10int16% VRefW10Point 10 Watts.
curve[*].V11uint16% VRefV11Point 11 Volts.
curve[*].W11int16% VRefW11Point 11 Watts.
curve[*].V12uint16% VRefV12Point 12 Volts.
curve[*].W12int16% VRefW12Point 12 Watts.
curve[*].V13uint16% VRefV13Point 13 Volts.
curve[*].W13int16% VRefW13Point 13 Watts.
curve[*].V14uint16% VRefV14Point 14 Volts.
curve[*].W14int16% VRefW14Point 14 Watts.
curve[*].V15uint16% VRefV15Point 15 Volts.
curve[*].W15int16% VRefW15Point 15 Watts.
curve[*].V16uint16% VRefV16Point 16 Volts.
curve[*].W16int16% VRefW16Point 16 Watts.
curve[*].V17uint16% VRefV17Point 17 Volts.
curve[*].W17int16% VRefW17Point 17 Watts.
curve[*].V18uint16% VRefV18Point 18 Volts.
curve[*].W18int16% VRefW18Point 18 Watts.
curve[*].V19uint16% VRefV19Point 19 Volts.
curve[*].W19int16% VRefW19Point 19 Watts.
curve[*].V20uint16% VRefV20Point 20 Volts.
curve[*].W20int16% VRefW20Point 20 Watts.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].RmpPt1Tmsuint16SecsRmpPt1TmsThe time of the PT1 in seconds (time to accomplish a change of 95%).
curve[*].RmpDecTmmuint16% WMax/minRmpDecTmmThe maximum rate at which the watt value may be reduced in response to changes in the voltage value.
curve[*].RmpIncTmmuint16% WMax/minRmpIncTmmThe maximum rate at which the watt value may be increased in response to changes in the voltage value.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].DeptRef (enum16): 1 %WMax, 2 %WAval
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 133: Basic Scheduling

Name in records: schedule.

Basic Scheduling

PointTypeUnitsLabelDescription
ActSchdbitfield32ActSchdBitfield of active schedules
ModEnabitfield16ModEnaIs basic scheduling active.
NSchduint16NSchdNumber of schedules supported (recommend min. 4, max 32)
NPtsuint16NPtsNumber of schedule entries supported (maximum of 10).
repeating[*].ActPtsuint16ActPtsNumber of active entries in schedule.
repeating[*].StrTmsuint32SecsStrTmsSchedule start in seconds since 2000 JAN 01 00:00:00 UTC.
repeating[*].RepPeruint16RepPerThe repetition count for time-based schedules (0=repeat forever)
repeating[*].IntvTypenum16SchdTypThe repetition frequency for time-based schedules: no repeat=0
repeating[*].XTypenum16XTypThe meaning of the X-values in the array.
repeating[*].YTypenum16YTypThe meaning of the Y-values in the array.
repeating[*].X1int32X1Entry 1 range.
repeating[*].Y1int32Y1Entry 1 target.
repeating[*].X2int32X2Entry 2 range.
repeating[*].Y2int32Y2Entry 2 target.
repeating[*].X3int32X3Entry 3 range.
repeating[*].Y3int32Y3Entry 3 target.
repeating[*].X4int32X4Entry 4 range.
repeating[*].Y4int32Y4Entry 4 target.
repeating[*].X5int32X5Entry 15range.
repeating[*].Y5int32Y5Entry 5 target.
repeating[*].X6int32X6Entry 6 range.
repeating[*].Y6int32Y6Entry 6 target.
repeating[*].X7int32X7Entry 7 range.
repeating[*].Y7int32Y7Entry 7 target.
repeating[*].X8int32X8Entry 8 range.
repeating[*].Y8int32Y8Entry 8 target.
repeating[*].X9int32X9Entry 9 range.
repeating[*].Y9int32Y9Entry 9 target.
repeating[*].X10int32X10Entry 10 range.
repeating[*].Y10int32Y10Entry 10 target.
repeating[*].NamstringNamOptional description for schedule.
repeating[*].WinTmsuint16SecsWinTmsTime window for schedule entry change.
repeating[*].RmpTmsuint16SecsRmpTmsRamp time for moving from current target to new target.
repeating[*].ActIndxuint16ActIndxIndex of active entry in the active schedule.

Value names:

  • ActSchd (bitfield32): bit 0 SCHED1, bit 1 SCHED2, bit 2 SCHED3, bit 3 SCHED4, bit 4 SCHED5, bit 5 SCHED6, bit 6 SCHED7, bit 7 SCHED8, bit 8 SCHED9, bit 9 SCHED10, bit 10 SCHED12, bit 11 SCHED13, bit 12 SCHED14, bit 13 SCHED15, bit 14 SCHED16, bit 15 SCHED17, bit 16 SCHED18, bit 17 SCHED19, bit 18 SCHED20, bit 19 SCHED21, bit 21 SCHED22, bit 22 SCHED23, bit 23 SCHED24, bit 24 SCHED25, bit 25 SCHED26, bit 26 SCHED27, bit 27 SCHED28, bit 28 SCHED29, bit 29 SCHED30, bit 30 SCHED31, bit 31 SCHED32
  • ModEna (bitfield16): bit 0 ENABLED
  • repeating[*].IntvTyp (enum16): 0 ONETIME, 1 DAILY, 2 WEEKLY, 3 MONTHLY, 4 WEEKDAY, 5 HOLIDAY, 6 WEEKEND, 7 YEARLY
  • repeating[*].XTyp (enum16): 0 UNSET, 1 TIME, 2 TEMP, 3 PRICE, 99 OTHER
  • repeating[*].YTyp (enum16): 0 UNSET, 1 WMax, 2 RSRVD2, 3 PF, 4 RSRVD4, 5 WATT_PRICE, 6 VAR_PRICE, 7 RSRVD7, 8 VOLT_VAR_ARRAY, 9 WChaGra, 10 WDisChaGra, 11 VArAval, 12 Schedule, 99 OTHER

Model 134: Freq-Watt Crv

Name in records: freq_watt.

Curve-Based Frequency-Watt

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaIs curve-based Frequency-Watt control active.
WinTmsuint16SecsWinTmsTime window for freq-watt change.
RvrtTmsuint16SecsRvrtTmsTimeout period for freq-watt curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend min. 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Hz1uint16HzHz1Point 1 Hertz.
curve[*].W1int16% WRefW1Point 1 Watts.
curve[*].Hz2uint16HzHz2Point 2 Hertz.
curve[*].W2int16% WRefW2Point 2 Watts.
curve[*].Hz3uint16HzHz3Point 3 Hertz.
curve[*].W3int16% WRefW3Point 3 Watts.
curve[*].Hz4uint16HzHz4Point 4 Hertz.
curve[*].W4int16% WRefW4Point 4 Watts.
curve[*].Hz5uint16HzHz5Point 5 Hertz.
curve[*].W5int16% WRefW5Point 5 Watts.
curve[*].Hz6uint16HzHz6Point 6 Hertz.
curve[*].W6int16% WRefW6Point 6 Watts.
curve[*].Hz7uint16HzHz7Point 7 Hertz.
curve[*].W7int16% WRefW7Point 7 Watts.
curve[*].Hz8uint16HzHz8Point 8 Hertz.
curve[*].W8int16% WRefW8Point 8 Watts.
curve[*].Hz9uint16HzHz9Point 9 Hertz.
curve[*].W9int16% WRefW9Point 9 Watts.
curve[*].Hz10uint16HzHz10Point 10 Hertz.
curve[*].W10int16% WRefW10Point 10 Watts.
curve[*].Hz11uint16HzHz11Point 11 Hertz.
curve[*].W11int16% WRefW11Point 11 Watts.
curve[*].Hz12uint16HzHz12Point 12 Hertz.
curve[*].W12int16% WRefW12Point 12 Watts.
curve[*].Hz13uint16HzHz13Point 13 Hertz.
curve[*].W13int16% WRefW13Point 13 Watts.
curve[*].Hz14uint16HzHz14Point 14 Hertz.
curve[*].W14int16% WRefW14Point 14 Watts.
curve[*].Hz15uint16HzHz15Point 15 Hertz.
curve[*].W15int16% WRefW15Point 15 Watts.
curve[*].Hz16uint16HzHz16Point 16 Hertz.
curve[*].W16int16% WRefW16Point 16 Watts.
curve[*].Hz17uint16HzHz17Point 17 Hertz.
curve[*].W17int16% WRefW17Point 17 Watts.
curve[*].Hz18uint16HzHz18Point 18 Hertz.
curve[*].W18int16% WRefW18Point 18 Watts.
curve[*].Hz19uint16HzHz19Point 19 Hertz.
curve[*].W19int16% WRefW19Point 19 Watts.
curve[*].Hz20uint16HzHz20Point 20 Hertz.
curve[*].W20int16% WRefW20Point 20 Watts.
curve[*].CrvNamstringCrvNamOptional description for curve. (Max 16 chars)
curve[*].RmpPT1Tmsuint16SecsRmpPT1TmsThe time of the PT1 in seconds (time to accomplish a change of 95%).
curve[*].RmpDecTmmuint16% WMax/minRmpDecTmmThe maximum rate at which the power value may be reduced in response to changes in the frequency value.
curve[*].RmpIncTmmuint16% WMax/minRmpIncTmmThe maximum rate at which the power value may be increased in response to changes in the frequency value.
curve[*].RmpRsUpuint16% WMax/minRmpRsUpThe maximum rate at which the power may be increased after releasing the frozen value of snap shot function.
curve[*].SnptWbitfield16SnptW1=enable snapshot/capture mode
curve[*].WRefuint16WWRefReference active power (default = WMax).
curve[*].WRefStrHzuint16HzWRefStrHzFrequency deviation from nominal frequency at the time of the snapshot to start constraining power output.
curve[*].WRefStopHzuint16HzWRefStopHzFrequency deviation from nominal frequency at which to release the power output.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 135: LFRT

Name in records: lfrt.

Low Frequency Ride-through

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLHzRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must disconnect duration.
curve[*].Hz1uint16HzHz1Point 1 must disconnect frequency.
curve[*].Tms2uint16SecsTms2Point 2 must disconnect duration.
curve[*].Hz2uint16HzHz2Point 2 must disconnect frequency.
curve[*].Tms3uint16SecsTms3Point 3 must disconnect duration.
curve[*].Hz3uint16HzHz3Point 3 must disconnect frequency.
curve[*].Tms4uint16SecsTms4Point 4 must disconnect duration.
curve[*].Hz4uint16HzHz4Point 4 must disconnect frequency.
curve[*].Tms5uint16SecsTms5Point 5 must disconnect duration.
curve[*].Hz5uint16HzHz5Point 5 must disconnect frequency.
curve[*].Tms6uint16SecsTms6Point 6 must disconnect duration.
curve[*].Hz6uint16HzHz6Point 6 must disconnect frequency.
curve[*].Tms7uint16SecsTms7Point 7 must disconnect duration.
curve[*].Hz7uint16HzHz7Point 7 must disconnect frequency.
curve[*].Tms8uint16SecsTms8Point 8 must disconnect duration.
curve[*].Hz8uint16HzHz8Point 8 must disconnect frequency.
curve[*].Tms9uint16SecsTms9Point 9 must disconnect duration.
curve[*].Hz9uint16HzHz9Point 9 must disconnect frequency.
curve[*].Tms10uint16SecsTms10Point 10 must disconnect duration.
curve[*].Hz10uint16HzHz10Point 10 must disconnect frequency.
curve[*].Tms11uint16SecsTms11Point 11 must disconnect duration.
curve[*].Hz11uint16HzHz11Point 11 must disconnect frequency.
curve[*].Tms12uint16SecsTms12Point 12 must disconnect duration.
curve[*].Hz12uint16HzHz12Point 12 must disconnect frequency.
curve[*].Tms13uint16SecsTms13Point 13 must disconnect duration.
curve[*].Hz13uint16HzHz13Point 13 must disconnect frequency.
curve[*].Tms14uint16SecsTms14Point 14 must disconnect duration.
curve[*].Hz14uint16HzHz14Point 14 must disconnect frequency.
curve[*].Tms15uint16SecsTms15Point 15 must disconnect duration.
curve[*].Hz15uint16HzHz15Point 15 must disconnect frequency.
curve[*].Tms16uint16SecsTms16Point 16 must disconnect duration.
curve[*].Hz16uint16HzHz16Point 16 must disconnect frequency.
curve[*].Tms17uint16SecsTms17Point 17 must disconnect duration.
curve[*].Hz17uint16HzHz17Point 17 must disconnect frequency.
curve[*].Tms18uint16SecsTms18Point 18 must disconnect duration.
curve[*].Hz18uint16HzHz18Point 18 must disconnect frequency.
curve[*].Tms19uint16SecsTms19Point 19 must disconnect duration.
curve[*].Hz19uint16HzHz19Point 19 must disconnect frequency.
curve[*].Tms20uint16SecsTms20Point 20 must disconnect duration.
curve[*].Hz20uint16HzHz20Point 20 must disconnect frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 136: HFRT

Name in records: hfrt.

High Frequency Ride-through

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaHFRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for HFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for HFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must disconnect duration.
curve[*].Hz1uint16HzHz1Point 1 must disconnect frequency.
curve[*].Tms2uint16SecsTms2Point 2 must disconnect duration.
curve[*].Hz2uint16HzHz2Point 2 must disconnect frequency.
curve[*].Tms3uint16SecsTms3Point 3 must disconnect duration.
curve[*].Hz3uint16HzHz3Point 3 must disconnect frequency.
curve[*].Tms4uint16SecsTms4Point 4 must disconnect duration.
curve[*].Hz4uint16HzHz4Point 4 must disconnect frequency.
curve[*].Tms5uint16SecsTms5Point 5 must disconnect duration.
curve[*].Hz5uint16HzHz5Point 5 must disconnect frequency.
curve[*].Tms6uint16SecsTms6Point 6 must disconnect duration.
curve[*].Hz6uint16HzHz6Point 6 must disconnect frequency.
curve[*].Tms7uint16SecsTms7Point 7 must disconnect duration.
curve[*].Hz7uint16HzHz7Point 7 must disconnect frequency.
curve[*].Tms8uint16SecsTms8Point 8 must disconnect duration.
curve[*].Hz8uint16HzHz8Point 8 must disconnect frequency.
curve[*].Tms9uint16SecsTms9Point 9 must disconnect duration.
curve[*].Hz9uint16HzHz9Point 9 must disconnect frequency.
curve[*].Tms10uint16SecsTms10Point 10 must disconnect duration.
curve[*].Hz10uint16HzHz10Point 10 must disconnect frequency.
curve[*].Tms11uint16SecsTms11Point 11 must disconnect duration.
curve[*].Hz11uint16HzHz11Point 11 must disconnect frequency.
curve[*].Tms12uint16SecsTms12Point 12 must disconnect duration.
curve[*].Hz12uint16HzHz12Point 12 must disconnect frequency.
curve[*].Tms13uint16SecsTms13Point 13 must disconnect duration.
curve[*].Hz13uint16HzHz13Point 13 must disconnect frequency.
curve[*].Tms14uint16SecsTms14Point 14 must disconnect duration.
curve[*].Hz14uint16HzHz14Point 14 must disconnect frequency.
curve[*].Tms15uint16SecsTms15Point 15 must disconnect duration.
curve[*].Hz15uint16HzHz15Point 15 must disconnect frequency.
curve[*].Tms16uint16SecsTms16Point 16 must disconnect duration.
curve[*].Hz16uint16HzHz16Point 16 must disconnect frequency.
curve[*].Tms17uint16SecsTms17Point 17 must disconnect duration.
curve[*].Hz17uint16HzHz17Point 17 must disconnect frequency.
curve[*].Tms18uint16SecsTms18Point 18 must disconnect duration.
curve[*].Hz18uint16HzHz18Point 18 must disconnect frequency.
curve[*].Tms19uint16SecsTms19Point 19 must disconnect duration.
curve[*].Hz19uint16HzHz19Point 19 must disconnect frequency.
curve[*].Tms20uint16SecsTms20Point 20 must disconnect duration.
curve[*].Hz20uint16HzHz20Point 20 must disconnect frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 137: LVRTC

Name in records: lvrtc.

LVRT must remain connected

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must remain connected duration.
curve[*].V1uint16% VRefV1Point 1 must remain connected voltage.
curve[*].Tms2uint16SecsTms2Point 2 must remain connected duration.
curve[*].V2uint16% VRefV2Point 2 must remain connected voltage.
curve[*].Tms3uint16SecsTms3Point 3 must remain connected duration.
curve[*].V3uint16% VRefV3Point 3 must remain connected voltage.
curve[*].Tms4uint16SecsTms4Point 4 must remain connected duration.
curve[*].V4uint16% VRefV4Point 4 must remain connected voltage.
curve[*].Tms5uint16SecsTms5Point 5 must remain connected duration.
curve[*].V5uint16% VRefV5Point 5 must remain connected voltage.
curve[*].Tms6uint16SecsTms6Point 6 must remain connected duration.
curve[*].V6uint16% VRefV6Point 6 must remain connected voltage.
curve[*].Tms7uint16SecsTms7Point 7 must remain connected duration.
curve[*].V7uint16% VRefV7Point 7 must remain connected voltage.
curve[*].Tms8uint16SecsTms8Point 8 must remain connected duration.
curve[*].V8uint16% VRefV8Point 8 must remain connected voltage.
curve[*].Tms9uint16SecsTms9Point 9 must remain connected duration.
curve[*].V9uint16% VRefV9Point 9 must remain connected voltage.
curve[*].Tms10uint16SecsTms10Point 10 must remain connected duration.
curve[*].V10uint16% VRefV10Point 10 must remain connected voltage.
curve[*].Tms11uint16SecsTms11Point 11 must remain connected duration.
curve[*].V11uint16% VRefV11Point 11 must remain connected voltage.
curve[*].Tms12uint16SecsTms12Point 12 must remain connected duration.
curve[*].V12uint16% VRefV12Point 12 must remain connected voltage.
curve[*].Tms13uint16SecsTms13Point 13 must remain connected duration.
curve[*].V13uint16% VRefV13Point 13 must remain connected voltage.
curve[*].Tms14uint16SecsTms14Point 14 must remain connected duration.
curve[*].V14uint16% VRefV14Point 14 must remain connected voltage.
curve[*].Tms15uint16SecsTms15Point 15 must remain connected duration.
curve[*].V15uint16% VRefV15Point 15 must remain connected voltage.
curve[*].Tms16uint16SecsTms16Point 16 must remain connected duration.
curve[*].V16uint16% VRefV16Point 16 must remain connected voltage.
curve[*].Tms17uint16SecsTms17Point 17 must remain connected duration.
curve[*].V17uint16% VRefV17Point 17 must remain connected voltage.
curve[*].Tms18uint16SecsTms18Point 18 must remain connected duration.
curve[*].V18uint16% VRefV18Point 18 must remain connected voltage.
curve[*].Tms19uint16SecsTms19Point 19 must remain connected duration.
curve[*].V19uint16% VRefV19Point 19 must remain connected voltage.
curve[*].Tms20uint16SecsTms20Point 20 must remain connected duration.
curve[*].V20uint16% VRefV20Point 20 must remain connected voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 138: HVRTC

Name in records: hvrtc.

HVRT must remain connected

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaHVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for HVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for HVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must remain connected duration.
curve[*].V1uint16% VRefV1Point 1 must remain connected voltage.
curve[*].Tms2uint16SecsTms2Point 2 must remain connected duration.
curve[*].V2uint16% VRefV2Point 2 must remain connected voltage.
curve[*].Tms3uint16SecsTms3Point 3 must remain connected duration.
curve[*].V3uint16% VRefV3Point 3 must remain connected voltage.
curve[*].Tms4uint16SecsTms4Point 4 must remain connected duration.
curve[*].V4uint16% VRefV4Point 4 must remain connected voltage.
curve[*].Tms5uint16SecsTms5Point 5 must remain connected duration.
curve[*].V5uint16% VRefV5Point 5 must remain connected voltage.
curve[*].Tms6uint16SecsTms6Point 6 must remain connected duration.
curve[*].V6uint16% VRefV6Point 6 must remain connected voltage.
curve[*].Tms7uint16SecsTms7Point 7 must remain connected duration.
curve[*].V7uint16% VRefV7Point 7 must remain connected voltage.
curve[*].Tms8uint16SecsTms8Point 8 must remain connected duration.
curve[*].V8uint16% VRefV8Point 8 must remain connected voltage.
curve[*].Tms9uint16SecsTms9Point 9 must remain connected duration.
curve[*].V9uint16% VRefV9Point 9 must remain connected voltage.
curve[*].Tms10uint16SecsTms10Point 10 must remain connected duration.
curve[*].V10uint16% VRefV10Point 10 must remain connected voltage.
curve[*].Tms11uint16SecsTms11Point 11 must remain connected duration.
curve[*].V11uint16% VRefV11Point 11 must remain connected voltage.
curve[*].Tms12uint16SecsTms12Point 12 must remain connected duration.
curve[*].V12uint16% VRefV12Point 12 must remain connected voltage.
curve[*].Tms13uint16SecsTms13Point 13 must remain connected duration.
curve[*].V13uint16% VRefV13Point 13 must remain connected voltage.
curve[*].Tms14uint16SecsTms14Point 14 must remain connected duration.
curve[*].V14uint16% VRefV14Point 14 must remain connected voltage.
curve[*].Tms15uint16SecsTms15Point 15 must remain connected duration.
curve[*].V15uint16% VRefV15Point 15 must remain connected voltage.
curve[*].Tms16uint16SecsTms16Point 16 must remain connected duration.
curve[*].V16uint16% VRefV16Point 16 must remain connected voltage.
curve[*].Tms17uint16SecsTms17Point 17 must remain connected duration.
curve[*].V17uint16% VRefV17Point 17 must remain connected voltage.
curve[*].Tms18uint16SecsTms18Point 18 must remain connected duration.
curve[*].V18uint16% VRefV18Point 18 must remain connected voltage.
curve[*].Tms19uint16SecsTms19Point 19 must remain connected duration.
curve[*].V19uint16% VRefV19Point 19 must remain connected voltage.
curve[*].Tms20uint16SecsTms20Point 20 must remain connected duration.
curve[*].V20uint16% VRefV20Point 20 must remain connected voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 139: LVRTX

Name in records: lvrtx.

LVRT extended curve

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
CrvTypeenum16
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 duration.
curve[*].V1uint16% VRefV1Point 1 voltage.
curve[*].Tms2uint16SecsTms2Point 2 duration.
curve[*].V2uint16% VRefV2Point 2 voltage.
curve[*].Tms3uint16SecsTms3Point 3 duration.
curve[*].V3uint16% VRefV3Point 3 voltage.
curve[*].Tms4uint16SecsTms4Point 4 duration.
curve[*].V4uint16% VRefV4Point 4 voltage.
curve[*].Tms5uint16SecsTms5Point 5 duration.
curve[*].V5uint16% VRefV5Point 5 voltage.
curve[*].Tms6uint16SecsTms6Point 6 duration.
curve[*].V6uint16% VRefV6Point 6 voltage.
curve[*].Tms7uint16SecsTms7Point 7 duration.
curve[*].V7uint16% VRefV7Point 7 voltage.
curve[*].Tms8uint16SecsTms8Point 8 duration.
curve[*].V8uint16% VRefV8Point 8 voltage.
curve[*].Tms9uint16SecsTms9Point 9 duration.
curve[*].V9uint16% VRefV9Point 9 voltage.
curve[*].Tms10uint16SecsTms10Point 10 duration.
curve[*].V10uint16% VRefV10Point 10 voltage.
curve[*].Tms11uint16SecsTms11Point 11 duration.
curve[*].V11uint16% VRefV11Point 11 voltage.
curve[*].Tms12uint16SecsTms12Point 12 duration.
curve[*].V12uint16% VRefV12Point 12 voltage.
curve[*].Tms13uint16SecsTms13Point 13 duration.
curve[*].V13uint16% VRefV13Point 13 voltage.
curve[*].Tms14uint16SecsTms14Point 14 duration.
curve[*].V14uint16% VRefV14Point 14 voltage.
curve[*].Tms15uint16SecsTms15Point 15 duration.
curve[*].V15uint16% VRefV15Point 15 voltage.
curve[*].Tms16uint16SecsTms16Point 16 duration.
curve[*].V16uint16% VRefV16Point 16 voltage.
curve[*].Tms17uint16SecsTms17Point 17 duration.
curve[*].V17uint16% VRefV17Point 17 voltage.
curve[*].Tms18uint16SecsTms18Point 18 duration.
curve[*].V18uint16% VRefV18Point 18 voltage.
curve[*].Tms19uint16SecsTms19Point 19 duration.
curve[*].V19uint16% VRefV19Point 19 voltage.
curve[*].Tms20uint16SecsTms20Point 20 duration.
curve[*].V20uint16% VRefV20Point 20 voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • CrvType (enum16): 1 CEASE_TO_ENERGIZE
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 140: HVRTX

Name in records: hvrtx.

HVRT extended curve

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLVRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LVRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LVRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
CrvTypeenum16
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 duration.
curve[*].V1uint16% VRefV1Point 1 voltage.
curve[*].Tms2uint16SecsTms2Point 2 duration.
curve[*].V2uint16% VRefV2Point 2 voltage.
curve[*].Tms3uint16SecsTms3Point 3 duration.
curve[*].V3uint16% VRefV3Point 3 voltage.
curve[*].Tms4uint16SecsTms4Point 4 duration.
curve[*].V4uint16% VRefV4Point 4 voltage.
curve[*].Tms5uint16SecsTms5Point 5 duration.
curve[*].V5uint16% VRefV5Point 5 voltage.
curve[*].Tms6uint16SecsTms6Point 6 duration.
curve[*].V6uint16% VRefV6Point 6 voltage.
curve[*].Tms7uint16SecsTms7Point 7 duration.
curve[*].V7uint16% VRefV7Point 7 voltage.
curve[*].Tms8uint16SecsTms8Point 8 duration.
curve[*].V8uint16% VRefV8Point 8 voltage.
curve[*].Tms9uint16SecsTms9Point 9 duration.
curve[*].V9uint16% VRefV9Point 9 voltage.
curve[*].Tms10uint16SecsTms10Point 10 duration.
curve[*].V10uint16% VRefV10Point 10 voltage.
curve[*].Tms11uint16SecsTms11Point 11 duration.
curve[*].V11uint16% VRefV11Point 11 voltage.
curve[*].Tms12uint16SecsTms12Point 12 duration.
curve[*].V12uint16% VRefV12Point 12 voltage.
curve[*].Tms13uint16SecsTms13Point 13 duration.
curve[*].V13uint16% VRefV13Point 13 voltage.
curve[*].Tms14uint16SecsTms14Point 14 duration.
curve[*].V14uint16% VRefV14Point 14 voltage.
curve[*].Tms15uint16SecsTms15Point 15 duration.
curve[*].V15uint16% VRefV15Point 15 voltage.
curve[*].Tms16uint16SecsTms16Point 16 duration.
curve[*].V16uint16% VRefV16Point 16 voltage.
curve[*].Tms17uint16SecsTms17Point 17 duration.
curve[*].V17uint16% VRefV17Point 17 voltage.
curve[*].Tms18uint16SecsTms18Point 18 duration.
curve[*].V18uint16% VRefV18Point 18 voltage.
curve[*].Tms19uint16SecsTms19Point 19 duration.
curve[*].V19uint16% VRefV19Point 19 voltage.
curve[*].Tms20uint16SecsTms20Point 20 duration.
curve[*].V20uint16% VRefV20Point 20 voltage.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • CrvType (enum16): 1 CEASE_TO_ENERGIZE
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 141: LFRTC

Name in records: lfrtc.

LFRT must remain connected

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLHzRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must remain connected duration.
curve[*].Hz1uint16HzHz1Point 1 must remain connected frequency.
curve[*].Tms2uint16SecsTms2Point 2 must remain connected duration.
curve[*].Hz2uint16HzHz2Point 2 must remain connected frequency.
curve[*].Tms3uint16SecsTms3Point 3 must remain connected duration.
curve[*].Hz3uint16HzHz3Point 3 must remain connected frequency.
curve[*].Tms4uint16SecsTms4Point 4 must remain connected duration.
curve[*].Hz4uint16HzHz4Point 4 must remain connected frequency.
curve[*].Tms5uint16SecsTms5Point 5 must remain connected duration.
curve[*].Hz5uint16HzHz5Point 5 must remain connected frequency.
curve[*].Tms6uint16SecsTms6Point 6 must remain connected duration.
curve[*].Hz6uint16HzHz6Point 6 must remain connected frequency.
curve[*].Tms7uint16SecsTms7Point 7 must remain connected duration.
curve[*].Hz7uint16HzHz7Point 7 must remain connected frequency.
curve[*].Tms8uint16SecsTms8Point 8 must remain connected duration.
curve[*].Hz8uint16HzHz8Point 8 must remain connected frequency.
curve[*].Tms9uint16SecsTms9Point 9 must remain connected duration.
curve[*].Hz9uint16HzHz9Point 9 must remain connected frequency.
curve[*].Tms10uint16SecsTms10Point 10 must remain connected duration.
curve[*].Hz10uint16HzHz10Point 10 must remain connected frequency.
curve[*].Tms11uint16SecsTms11Point 11 must remain connected duration.
curve[*].Hz11uint16HzHz11Point 11 must remain connected frequency.
curve[*].Tms12uint16SecsTms12Point 12 must remain connected duration.
curve[*].Hz12uint16HzHz12Point 12 must remain connected frequency.
curve[*].Tms13uint16SecsTms13Point 13 must remain connected duration.
curve[*].Hz13uint16HzHz13Point 13 must remain connected frequency.
curve[*].Tms14uint16SecsTms14Point 14 must remain connected duration.
curve[*].Hz14uint16HzHz14Point 14 must remain connected frequency.
curve[*].Tms15uint16SecsTms15Point 15 must remain connected duration.
curve[*].Hz15uint16HzHz15Point 15 must remain connected frequency.
curve[*].Tms16uint16SecsTms16Point 16 must remain connected duration.
curve[*].Hz16uint16HzHz16Point 16 must remain connected frequency.
curve[*].Tms17uint16SecsTms17Point 17 must remain connected duration.
curve[*].Hz17uint16HzHz17Point 17 must remain connected frequency.
curve[*].Tms18uint16SecsTms18Point 18 must remain connected duration.
curve[*].Hz18uint16HzHz18Point 18 must remain connected frequency.
curve[*].Tms19uint16SecsTms19Point 19 must remain connected duration.
curve[*].Hz19uint16HzHz19Point 19 must remain connected frequency.
curve[*].Tms20uint16SecsTms20Point 20 must remain connected duration.
curve[*].Hz20uint16HzHz20Point 20 must remain connected frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 142: HFRTC

Name in records: hfrtc.

HFRT must remain connected

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLHzRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 must remain connected duration.
curve[*].Hz1uint16HzHz1Point 1 must remain connected frequency.
curve[*].Tms2uint16SecsTms2Point 2 must remain connected duration.
curve[*].Hz2uint16HzHz2Point 2 must remain connected frequency.
curve[*].Tms3uint16SecsTms3Point 3 must remain connected duration.
curve[*].Hz3uint16HzHz3Point 3 must remain connected frequency.
curve[*].Tms4uint16SecsTms4Point 4 must remain connected duration.
curve[*].Hz4uint16HzHz4Point 4 must remain connected frequency.
curve[*].Tms5uint16SecsTms5Point 5 must remain connected duration.
curve[*].Hz5uint16HzHz5Point 5 must remain connected frequency.
curve[*].Tms6uint16SecsTms6Point 6 must remain connected duration.
curve[*].Hz6uint16HzHz6Point 6 must remain connected frequency.
curve[*].Tms7uint16SecsTms7Point 7 must remain connected duration.
curve[*].Hz7uint16HzHz7Point 7 must remain connected frequency.
curve[*].Tms8uint16SecsTms8Point 8 must remain connected duration.
curve[*].Hz8uint16HzHz8Point 8 must remain connected frequency.
curve[*].Tms9uint16SecsTms9Point 9 must remain connected duration.
curve[*].Hz9uint16HzHz9Point 9 must remain connected frequency.
curve[*].Tms10uint16SecsTms10Point 10 must remain connected duration.
curve[*].Hz10uint16HzHz10Point 10 must remain connected frequency.
curve[*].Tms11uint16SecsTms11Point 11 must remain connected duration.
curve[*].Hz11uint16HzHz11Point 11 must remain connected frequency.
curve[*].Tms12uint16SecsTms12Point 12 must remain connected duration.
curve[*].Hz12uint16HzHz12Point 12 must remain connected frequency.
curve[*].Tms13uint16SecsTms13Point 13 must remain connected duration.
curve[*].Hz13uint16HzHz13Point 13 must remain connected frequency.
curve[*].Tms14uint16SecsTms14Point 14 must remain connected duration.
curve[*].Hz14uint16HzHz14Point 14 must remain connected frequency.
curve[*].Tms15uint16SecsTms15Point 15 must remain connected duration.
curve[*].Hz15uint16HzHz15Point 15 must remain connected frequency.
curve[*].Tms16uint16SecsTms16Point 16 must remain connected duration.
curve[*].Hz16uint16HzHz16Point 16 must remain connected frequency.
curve[*].Tms17uint16SecsTms17Point 17 must remain connected duration.
curve[*].Hz17uint16HzHz17Point 17 must remain connected frequency.
curve[*].Tms18uint16SecsTms18Point 18 must remain connected duration.
curve[*].Hz18uint16HzHz18Point 18 must remain connected frequency.
curve[*].Tms19uint16SecsTms19Point 19 must remain connected duration.
curve[*].Hz19uint16HzHz19Point 19 must remain connected frequency.
curve[*].Tms20uint16SecsTms20Point 20 must remain connected duration.
curve[*].Hz20uint16HzHz20Point 20 must remain connected frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 143: LFRTX

Name in records: lfrtx.

LFRT extended curve

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLHzRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
CrvTypeenum16
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 duration.
curve[*].Hz1uint16HzHz1Point 1 frequency.
curve[*].Tms2uint16SecsTms2Point 2 duration.
curve[*].Hz2uint16HzHz2Point 2 frequency.
curve[*].Tms3uint16SecsTms3Point 3 duration.
curve[*].Hz3uint16HzHz3Point 3 frequency.
curve[*].Tms4uint16SecsTms4Point 4 duration.
curve[*].Hz4uint16HzHz4Point 4 frequency.
curve[*].Tms5uint16SecsTms5Point 5 duration.
curve[*].Hz5uint16HzHz5Point 5 frequency.
curve[*].Tms6uint16SecsTms6Point 6 duration.
curve[*].Hz6uint16HzHz6Point 6 frequency.
curve[*].Tms7uint16SecsTms7Point 7 duration.
curve[*].Hz7uint16HzHz7Point 7 frequency.
curve[*].Tms8uint16SecsTms8Point 8 duration.
curve[*].Hz8uint16HzHz8Point 8 frequency.
curve[*].Tms9uint16SecsTms9Point 9 duration.
curve[*].Hz9uint16HzHz9Point 9 frequency.
curve[*].Tms10uint16SecsTms10Point 10 duration.
curve[*].Hz10uint16HzHz10Point 10 frequency.
curve[*].Tms11uint16SecsTms11Point 11 duration.
curve[*].Hz11uint16HzHz11Point 11 frequency.
curve[*].Tms12uint16SecsTms12Point 12 duration.
curve[*].Hz12uint16HzHz12Point 12 frequency.
curve[*].Tms13uint16SecsTms13Point 13 duration.
curve[*].Hz13uint16HzHz13Point 13 frequency.
curve[*].Tms14uint16SecsTms14Point 14 duration.
curve[*].Hz14uint16HzHz14Point 14 frequency.
curve[*].Tms15uint16SecsTms15Point 15 duration.
curve[*].Hz15uint16HzHz15Point 15 frequency.
curve[*].Tms16uint16SecsTms16Point 16 duration.
curve[*].Hz16uint16HzHz16Point 16 frequency.
curve[*].Tms17uint16SecsTms17Point 17 duration.
curve[*].Hz17uint16HzHz17Point 17 frequency.
curve[*].Tms18uint16SecsTms18Point 18 duration.
curve[*].Hz18uint16HzHz18Point 18 frequency.
curve[*].Tms19uint16SecsTms19Point 19 duration.
curve[*].Hz19uint16HzHz19Point 19 frequency.
curve[*].Tms20uint16SecsTms20Point 20 duration.
curve[*].Hz20uint16HzHz20Point 20 frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • CrvType (enum16): 1 CEASE_TO_ENERGIZE
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 144: HFRTX

Name in records: hfrtx.

HFRT extended curve

PointTypeUnitsLabelDescription
ActCrvuint16ActCrvIndex of active curve. 0=no active curve.
ModEnabitfield16ModEnaLHzRT control mode. Enable active curve.
WinTmsuint16SecsWinTmsTime window for LFRT change.
RvrtTmsuint16SecsRvrtTmsTimeout period for LFRT curve selection.
RmpTmsuint16SecsRmpTmsRamp time for moving from current mode to new mode.
NCrvuint16NCrvNumber of curves supported (recommend 4).
NPtuint16NPtNumber of curve points supported (maximum of 20).
CrvTypeenum16
curve[*].ActPtuint16ActPtNumber of active points in array.
curve[*].Tms1uint16SecsTms1Point 1 duration.
curve[*].Hz1uint16HzHz1Point 1 frequency.
curve[*].Tms2uint16SecsTms2Point 2 duration.
curve[*].Hz2uint16HzHz2Point 2 frequency.
curve[*].Tms3uint16SecsTms3Point 3 duration.
curve[*].Hz3uint16HzHz3Point 3 frequency.
curve[*].Tms4uint16SecsTms4Point 4 duration.
curve[*].Hz4uint16HzHz4Point 4 frequency.
curve[*].Tms5uint16SecsTms5Point 5 duration.
curve[*].Hz5uint16HzHz5Point 5 frequency.
curve[*].Tms6uint16SecsTms6Point 6 duration.
curve[*].Hz6uint16HzHz6Point 6 frequency.
curve[*].Tms7uint16SecsTms7Point 7 duration.
curve[*].Hz7uint16HzHz7Point 7 frequency.
curve[*].Tms8uint16SecsTms8Point 8 duration.
curve[*].Hz8uint16HzHz8Point 8 frequency.
curve[*].Tms9uint16SecsTms9Point 9 duration.
curve[*].Hz9uint16HzHz9Point 9 frequency.
curve[*].Tms10uint16SecsTms10Point 10 duration.
curve[*].Hz10uint16HzHz10Point 10 frequency.
curve[*].Tms11uint16SecsTms11Point 11 duration.
curve[*].Hz11uint16HzHz11Point 11 frequency.
curve[*].Tms12uint16SecsTms12Point 12 duration.
curve[*].Hz12uint16HzHz12Point 12 frequency.
curve[*].Tms13uint16SecsTms13Point 13 duration.
curve[*].Hz13uint16HzHz13Point 13 frequency.
curve[*].Tms14uint16SecsTms14Point 14 duration.
curve[*].Hz14uint16HzHz14Point 14 frequency.
curve[*].Tms15uint16SecsTms15Point 15 duration.
curve[*].Hz15uint16HzHz15Point 15 frequency.
curve[*].Tms16uint16SecsTms16Point 16 duration.
curve[*].Hz16uint16HzHz16Point 16 frequency.
curve[*].Tms17uint16SecsTms17Point 17 duration.
curve[*].Hz17uint16HzHz17Point 17 frequency.
curve[*].Tms18uint16SecsTms18Point 18 duration.
curve[*].Hz18uint16HzHz18Point 18 frequency.
curve[*].Tms19uint16SecsTms19Point 19 duration.
curve[*].Hz19uint16HzHz19Point 19 frequency.
curve[*].Tms20uint16SecsTms20Point 20 duration.
curve[*].Hz20uint16HzHz20Point 20 frequency.
curve[*].CrvNamstringCrvNamOptional description for curve.
curve[*].ReadOnlyenum16ReadOnlyCurve is read-only or can be modified.

Value names:

  • ModEna (bitfield16): bit 0 ENABLED
  • CrvType (enum16): 1 CEASE_TO_ENERGIZE
  • curve[*].ReadOnly (enum16): 0 READWRITE, 1 READONLY

Model 145: Extended Settings

Name in records: ext_settings.

Inverter controls extended settings

PointTypeUnitsLabelDescription
NomRmpUpRteuint16PctRamp Up RateRamp up rate as a percentage of max current.
NomRmpDnRteuint16PctNomRmpDnRteRamp down rate as a percentage of max current.
EmgRmpUpRteuint16PctEmergency Ramp Up RateEmergency ramp up rate as a percentage of max current.
EmgRmpDnRteuint16PctEmergency Ramp Down RateEmergency ramp down rate as a percentage of max current.
ConnRmpUpRteuint16PctConnect Ramp Up RateConnect ramp up rate as a percentage of max current.
ConnRmpDnRteuint16PctConnect Ramp Down RateConnect ramp down rate as a percentage of max current.
AGrauint16PctDefault Ramp RateRamp rate specified in percent of max current.

Model 160: Multiple MPPT Inverter Extension Model

Name in records: mppt.

PointTypeUnitsLabelDescription
Evtbitfield32Global Events
NcountNumber of Modules
TmsPeruint16Timestamp Period
module[*].IDuint16Input ID
module[*].IDStrstringInput ID String
module[*].DCAuint16ADC Current
module[*].DCVuint16VDC Voltage
module[*].DCWuint16WDC Power
module[*].DCWHacc32WhLifetime Energy
module[*].Tmsuint32SecsTimestamp
module[*].Tmpint16CTemperature
module[*].DCStenum16Operating State
module[*].DCEvtbitfield32Module Events

Value names:

  • Evt (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 2 RESERVED_2, bit 3 DC_DISCONNECT, bit 4 RESERVED_4, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 RESERVED_8, bit 9 RESERVED_9, bit 10 RESERVED_10, bit 11 RESERVED_11, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 16 RESERVED_16, bit 17 RESERVED_17, bit 18 RESERVED_18, bit 19 RESERVED_19, bit 20 TEST_FAILED, bit 21 INPUT_UNDER_VOLTAGE, bit 22 INPUT_OVER_CURRENT
  • module[*].DCSt (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY, 9 TEST, 10 RESERVED_10
  • module[*].DCEvt (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 2 RESERVED_2, bit 3 DC_DISCONNECT, bit 4 RESERVED_4, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 RESERVED_8, bit 9 RESERVED_9, bit 10 RESERVED_10, bit 11 RESERVED_11, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 16 RESERVED_16, bit 17 RESERVED_17, bit 18 RESERVED_18, bit 19 RESERVED_19, bit 20 TEST_FAILED, bit 21 INPUT_UNDER_VOLTAGE, bit 22 INPUT_OVER_CURRENT

Meters

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
201ac_meter_an_or_ab62
202ac_meter_abn62
203ac_meter_abcn62
204ac_meter_abc62
211ac_meter_an_or_ab_float62
212ac_meter_abn_float62
213ac_meter_abcn_float62
214ac_meter_abc_float62
220ac_meter_secure22

Model 201: Meter (Single Phase) single phase (AN or AB) meter

Name in records: ac_meter_an_or_ab.

Include this model for single phase (AN or AB) metering

PointTypeUnitsLabelDescription
Aint16AAmpsTotal AC Current
AphAint16AAmps PhaseAPhase A Current
AphBint16AAmps PhaseBPhase B Current
AphCint16AAmps PhaseCPhase C Current
PhVint16VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAint16VPhase Voltage ANPhase Voltage AN
PhVphBint16VPhase Voltage BNPhase Voltage BN
PhVphCint16VPhase Voltage CNPhase Voltage CN
PPVint16VVoltage LLLine to Line AC Voltage (average of active phases)
PPVphABint16VPhase Voltage ABPhase Voltage AB
PPVphBCint16VPhase Voltage BCPhase Voltage BC
PPVphCAint16VPhase Voltage CAPhase Voltage CA
Hzint16HzHzFrequency
Wint16WWattsTotal Real Power
WphAint16WWatts phase A
WphBint16WWatts phase B
WphCint16WWatts phase C
VAint16VAVAAC Apparent Power
VAphAint16VAVA phase A
VAphBint16VAVA phase B
VAphCint16VAVA phase C
VARint16varVARReactive Power
VARphAint16varVAR phase A
VARphBint16varVAR phase B
VARphCint16varVAR phase C
PFint16PctPFPower Factor
PFphAint16PctPF phase A
PFphBint16PctPF phase B
PFphCint16PctPF phase C
TotWhExpacc32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAacc32WhTotal Watt-hours Exported phase A
TotWhExpPhBacc32WhTotal Watt-hours Exported phase B
TotWhExpPhCacc32WhTotal Watt-hours Exported phase C
TotWhImpacc32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAacc32WhTotal Watt-hours Imported phase A
TotWhImpPhBacc32WhTotal Watt-hours Imported phase B
TotWhImpPhCacc32WhTotal Watt-hours Imported phase C
TotVAhExpacc32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAacc32VAhTotal VA-hours Exported phase A
TotVAhExpPhBacc32VAhTotal VA-hours Exported phase B
TotVAhExpPhCacc32VAhTotal VA-hours Exported phase C
TotVAhImpacc32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAacc32VAhTotal VA-hours Imported phase A
TotVAhImpPhBacc32VAhTotal VA-hours Imported phase B
TotVAhImpPhCacc32VAhTotal VA-hours Imported phase C
TotVArhImpQ1acc32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1PhAacc32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1PhBacc32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1PhCacc32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2acc32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2PhAacc32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2PhBacc32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2PhCacc32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3acc32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3PhAacc32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3PhBacc32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3PhCacc32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4acc32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4PhAacc32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4PhBacc32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4PhCacc32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 Power_Failure, bit 3 Under_Voltage, bit 4 Low_PF, bit 5 Over_Current, bit 6 Over_Voltage, bit 7 Missing_Sensor, bit 16 OEM01, bit 17 OEM02, bit 18 OEM03, bit 19 OEM04, bit 20 OEM05, bit 21 OEM06, bit 22 OEM07, bit 23 OEM08, bit 24 OEM09, bit 25 OEM10, bit 26 OEM11, bit 27 OEM12, bit 28 OEM13, bit 29 OEM14, bit 30 OEM15

Model 202: split single phase (ABN) meter

Name in records: ac_meter_abn.

PointTypeUnitsLabelDescription
Aint16AAmpsTotal AC Current
AphAint16AAmps PhaseAPhase A Current
AphBint16AAmps PhaseBPhase B Current
AphCint16AAmps PhaseCPhase C Current
PhVint16VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAint16VPhase Voltage ANPhase Voltage AN
PhVphBint16VPhase Voltage BNPhase Voltage BN
PhVphCint16VPhase Voltage CNPhase Voltage CN
PPVint16VVoltage LLLine to Line AC Voltage (average of active phases)
PhVphABint16VPhase Voltage ABPhase Voltage AB
PhVphBCint16VPhase Voltage BCPhase Voltage BC
PhVphCAint16VPhase Voltage CAPhase Voltage CA
Hzint16HzHzFrequency
Wint16WWattsTotal Real Power
WphAint16WWatts phase A
WphBint16WWatts phase B
WphCint16WWatts phase C
VAint16VAVAAC Apparent Power
VAphAint16VAVA phase A
VAphBint16VAVA phase B
VAphCint16VAVA phase C
VARint16varVARReactive Power
VARphAint16varVAR phase A
VARphBint16varVAR phase B
VARphCint16varVAR phase C
PFint16PctPFPower Factor
PFphAint16PctPF phase A
PFphBint16PctPF phase B
PFphCint16PctPF phase C
TotWhExpacc32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAacc32WhTotal Watt-hours Exported phase A
TotWhExpPhBacc32WhTotal Watt-hours Exported phase B
TotWhExpPhCacc32WhTotal Watt-hours Exported phase C
TotWhImpacc32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAacc32WhTotal Watt-hours Imported phase A
TotWhImpPhBacc32WhTotal Watt-hours Imported phase B
TotWhImpPhCacc32WhTotal Watt-hours Imported phase C
TotVAhExpacc32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAacc32VAhTotal VA-hours Exported phase A
TotVAhExpPhBacc32VAhTotal VA-hours Exported phase B
TotVAhExpPhCacc32VAhTotal VA-hours Exported phase C
TotVAhImpacc32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAacc32VAhTotal VA-hours Imported phase A
TotVAhImpPhBacc32VAhTotal VA-hours Imported phase B
TotVAhImpPhCacc32VAhTotal VA-hours Imported phase C
TotVArhImpQ1acc32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1PhAacc32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1PhBacc32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1PhCacc32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2acc32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2PhAacc32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2PhBacc32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2PhCacc32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3acc32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3PhAacc32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3PhBacc32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3PhCacc32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4acc32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4PhAacc32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4PhBacc32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4PhCacc32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 203: wye-connect three phase (abcn) meter

Name in records: ac_meter_abcn.

PointTypeUnitsLabelDescription
Aint16AAmpsTotal AC Current
AphAint16AAmps PhaseAPhase A Current
AphBint16AAmps PhaseBPhase B Current
AphCint16AAmps PhaseCPhase C Current
PhVint16VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAint16VPhase Voltage ANPhase Voltage AN
PhVphBint16VPhase Voltage BNPhase Voltage BN
PhVphCint16VPhase Voltage CNPhase Voltage CN
PPVint16VVoltage LLLine to Line AC Voltage (average of active phases)
PhVphABint16VPhase Voltage ABPhase Voltage AB
PhVphBCint16VPhase Voltage BCPhase Voltage BC
PhVphCAint16VPhase Voltage CAPhase Voltage CA
Hzint16HzHzFrequency
Wint16WWattsTotal Real Power
WphAint16WWatts phase A
WphBint16WWatts phase B
WphCint16WWatts phase C
VAint16VAVAAC Apparent Power
VAphAint16VAVA phase A
VAphBint16VAVA phase B
VAphCint16VAVA phase C
VARint16varVARReactive Power
VARphAint16varVAR phase A
VARphBint16varVAR phase B
VARphCint16varVAR phase C
PFint16PctPFPower Factor
PFphAint16PctPF phase A
PFphBint16PctPF phase B
PFphCint16PctPF phase C
TotWhExpacc32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAacc32WhTotal Watt-hours Exported phase A
TotWhExpPhBacc32WhTotal Watt-hours Exported phase B
TotWhExpPhCacc32WhTotal Watt-hours Exported phase C
TotWhImpacc32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAacc32WhTotal Watt-hours Imported phase A
TotWhImpPhBacc32WhTotal Watt-hours Imported phase B
TotWhImpPhCacc32WhTotal Watt-hours Imported phase C
TotVAhExpacc32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAacc32VAhTotal VA-hours Exported phase A
TotVAhExpPhBacc32VAhTotal VA-hours Exported phase B
TotVAhExpPhCacc32VAhTotal VA-hours Exported phase C
TotVAhImpacc32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAacc32VAhTotal VA-hours Imported phase A
TotVAhImpPhBacc32VAhTotal VA-hours Imported phase B
TotVAhImpPhCacc32VAhTotal VA-hours Imported phase C
TotVArhImpQ1acc32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1PhAacc32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1PhBacc32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1PhCacc32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2acc32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2PhAacc32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2PhBacc32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2PhCacc32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3acc32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3PhAacc32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3PhBacc32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3PhCacc32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4acc32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4PhAacc32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4PhBacc32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4PhCacc32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 204: delta-connect three phase (abc) meter

Name in records: ac_meter_abc.

PointTypeUnitsLabelDescription
Aint16AAmpsTotal AC Current
AphAint16AAmps PhaseAPhase A Current
AphBint16AAmps PhaseBPhase B Current
AphCint16AAmps PhaseCPhase C Current
PhVint16VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAint16VPhase Voltage ANPhase Voltage AN
PhVphBint16VPhase Voltage BNPhase Voltage BN
PhVphCint16VPhase Voltage CNPhase Voltage CN
PPVint16VVoltage LLLine to Line AC Voltage (average of active phases)
PhVphABint16VPhase Voltage ABPhase Voltage AB
PhVphBCint16VPhase Voltage BCPhase Voltage BC
PhVphCAint16VPhase Voltage CAPhase Voltage CA
Hzint16HzHzFrequency
Wint16WWattsTotal Real Power
WphAint16WWatts phase A
WphBint16WWatts phase B
WphCint16WWatts phase C
VAint16VAVAAC Apparent Power
VAphAint16VAVA phase A
VAphBint16VAVA phase B
VAphCint16VAVA phase C
VARint16varVARReactive Power
VARphAint16varVAR phase A
VARphBint16varVAR phase B
VARphCint16varVAR phase C
PFint16PctPFPower Factor
PFphAint16PctPF phase A
PFphBint16PctPF phase B
PFphCint16PctPF phase C
TotWhExpacc32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAacc32WhTotal Watt-hours Exported phase A
TotWhExpPhBacc32WhTotal Watt-hours Exported phase B
TotWhExpPhCacc32WhTotal Watt-hours Exported phase C
TotWhImpacc32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAacc32WhTotal Watt-hours Imported phase A
TotWhImpPhBacc32WhTotal Watt-hours Imported phase B
TotWhImpPhCacc32WhTotal Watt-hours Imported phase C
TotVAhExpacc32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAacc32VAhTotal VA-hours Exported phase A
TotVAhExpPhBacc32VAhTotal VA-hours Exported phase B
TotVAhExpPhCacc32VAhTotal VA-hours Exported phase C
TotVAhImpacc32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAacc32VAhTotal VA-hours Imported phase A
TotVAhImpPhBacc32VAhTotal VA-hours Imported phase B
TotVAhImpPhCacc32VAhTotal VA-hours Imported phase C
TotVArhImpQ1acc32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1PhAacc32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1PhBacc32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1PhCacc32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2acc32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2PhAacc32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2PhBacc32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2PhCacc32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3acc32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3PhAacc32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3PhBacc32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3PhCacc32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4acc32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4PhAacc32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4PhBacc32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4PhCacc32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 211: single phase (AN or AB) meter

Name in records: ac_meter_an_or_ab_float.

PointTypeUnitsLabelDescription
Afloat32AAmpsTotal AC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PhVfloat32VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
PPVfloat32VVoltage LLLine to Line AC Voltage (average of active phases)
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
Hzfloat32HzHzFrequency
Wfloat32WWattsTotal Real Power
WphAfloat32WWatts phase A
WphBfloat32WWatts phase B
WphCfloat32WWatts phase C
VAfloat32VAVAAC Apparent Power
VAphAfloat32VAVA phase A
VAphBfloat32VAVA phase B
VAphCfloat32VAVA phase C
VARfloat32varVARReactive Power
VARphAfloat32varVAR phase A
VARphBfloat32varVAR phase B
VARphCfloat32varVAR phase C
PFfloat32PFPFPower Factor
PFphAfloat32PFPF phase A
PFphBfloat32PFPF phase B
PFphCfloat32PFPF phase C
TotWhExpfloat32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAfloat32WhTotal Watt-hours Exported phase A
TotWhExpPhBfloat32WhTotal Watt-hours Exported phase B
TotWhExpPhCfloat32WhTotal Watt-hours Exported phase C
TotWhImpfloat32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAfloat32WhTotal Watt-hours Imported phase A
TotWhImpPhBfloat32WhTotal Watt-hours Imported phase B
TotWhImpPhCfloat32WhTotal Watt-hours Imported phase C
TotVAhExpfloat32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAfloat32VAhTotal VA-hours Exported phase A
TotVAhExpPhBfloat32VAhTotal VA-hours Exported phase B
TotVAhExpPhCfloat32VAhTotal VA-hours Exported phase C
TotVAhImpfloat32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAfloat32VAhTotal VA-hours Imported phase A
TotVAhImpPhBfloat32VAhTotal VA-hours Imported phase B
TotVAhImpPhCfloat32VAhTotal VA-hours Imported phase C
TotVArhImpQ1float32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1phAfloat32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1phBfloat32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1phCfloat32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2float32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2phAfloat32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2phBfloat32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2phCfloat32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3float32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3phAfloat32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3phBfloat32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3phCfloat32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4float32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4phAfloat32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4phBfloat32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4phCfloat32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 212: split single phase (ABN) meter

Name in records: ac_meter_abn_float.

PointTypeUnitsLabelDescription
Afloat32AAmpsTotal AC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PhVfloat32VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
PPVfloat32VVoltage LLLine to Line AC Voltage (average of active phases)
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
Hzfloat32HzHzFrequency
Wfloat32WWattsTotal Real Power
WphAfloat32WWatts phase A
WphBfloat32WWatts phase B
WphCfloat32WWatts phase C
VAfloat32VAVAAC Apparent Power
VAphAfloat32VAVA phase A
VAphBfloat32VAVA phase B
VAphCfloat32VAVA phase C
VARfloat32varVARReactive Power
VARphAfloat32varVAR phase A
VARphBfloat32varVAR phase B
VARphCfloat32varVAR phase C
PFfloat32PFPFPower Factor
PFphAfloat32PFPF phase A
PFphBfloat32PFPF phase B
PFphCfloat32PFPF phase C
TotWhExpfloat32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAfloat32WhTotal Watt-hours Exported phase A
TotWhExpPhBfloat32WhTotal Watt-hours Exported phase B
TotWhExpPhCfloat32WhTotal Watt-hours Exported phase C
TotWhImpfloat32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAfloat32WhTotal Watt-hours Imported phase A
TotWhImpPhBfloat32WhTotal Watt-hours Imported phase B
TotWhImpPhCfloat32WhTotal Watt-hours Imported phase C
TotVAhExpfloat32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAfloat32VAhTotal VA-hours Exported phase A
TotVAhExpPhBfloat32VAhTotal VA-hours Exported phase B
TotVAhExpPhCfloat32VAhTotal VA-hours Exported phase C
TotVAhImpfloat32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAfloat32VAhTotal VA-hours Imported phase A
TotVAhImpPhBfloat32VAhTotal VA-hours Imported phase B
TotVAhImpPhCfloat32VAhTotal VA-hours Imported phase C
TotVArhImpQ1float32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1phAfloat32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1phBfloat32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1phCfloat32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2float32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2phAfloat32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2phBfloat32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2phCfloat32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3float32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3phAfloat32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3phBfloat32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3phCfloat32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4float32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4phAfloat32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4phBfloat32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4phCfloat32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 213: wye-connect three phase (abcn) meter

Name in records: ac_meter_abcn_float.

PointTypeUnitsLabelDescription
Afloat32AAmpsTotal AC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PhVfloat32VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
PPVfloat32VVoltage LLLine to Line AC Voltage (average of active phases)
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
Hzfloat32HzHzFrequency
Wfloat32WWattsTotal Real Power
WphAfloat32WWatts phase A
WphBfloat32WWatts phase B
WphCfloat32WWatts phase C
VAfloat32VAVAAC Apparent Power
VAphAfloat32VAVA phase A
VAphBfloat32VAVA phase B
VAphCfloat32VAVA phase C
VARfloat32varVARReactive Power
VARphAfloat32varVAR phase A
VARphBfloat32varVAR phase B
VARphCfloat32varVAR phase C
PFfloat32PFPFPower Factor
PFphAfloat32PFPF phase A
PFphBfloat32PFPF phase B
PFphCfloat32PFPF phase C
TotWhExpfloat32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAfloat32WhTotal Watt-hours Exported phase A
TotWhExpPhBfloat32WhTotal Watt-hours Exported phase B
TotWhExpPhCfloat32WhTotal Watt-hours Exported phase C
TotWhImpfloat32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAfloat32WhTotal Watt-hours Imported phase A
TotWhImpPhBfloat32WhTotal Watt-hours Imported phase B
TotWhImpPhCfloat32WhTotal Watt-hours Imported phase C
TotVAhExpfloat32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAfloat32VAhTotal VA-hours Exported phase A
TotVAhExpPhBfloat32VAhTotal VA-hours Exported phase B
TotVAhExpPhCfloat32VAhTotal VA-hours Exported phase C
TotVAhImpfloat32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAfloat32VAhTotal VA-hours Imported phase A
TotVAhImpPhBfloat32VAhTotal VA-hours Imported phase B
TotVAhImpPhCfloat32VAhTotal VA-hours Imported phase C
TotVArhImpQ1float32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1phAfloat32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1phBfloat32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1phCfloat32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2float32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2phAfloat32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2phBfloat32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2phCfloat32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3float32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3phAfloat32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3phBfloat32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3phCfloat32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4float32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4phAfloat32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4phBfloat32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4phCfloat32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 214: delta-connect three phase (abc) meter

Name in records: ac_meter_abc_float.

PointTypeUnitsLabelDescription
Afloat32AAmpsTotal AC Current
AphAfloat32AAmps PhaseAPhase A Current
AphBfloat32AAmps PhaseBPhase B Current
AphCfloat32AAmps PhaseCPhase C Current
PhVfloat32VVoltage LNLine to Neutral AC Voltage (average of active phases)
PhVphAfloat32VPhase Voltage ANPhase Voltage AN
PhVphBfloat32VPhase Voltage BNPhase Voltage BN
PhVphCfloat32VPhase Voltage CNPhase Voltage CN
PPVfloat32VVoltage LLLine to Line AC Voltage (average of active phases)
PPVphABfloat32VPhase Voltage ABPhase Voltage AB
PPVphBCfloat32VPhase Voltage BCPhase Voltage BC
PPVphCAfloat32VPhase Voltage CAPhase Voltage CA
Hzfloat32HzHzFrequency
Wfloat32WWattsTotal Real Power
WphAfloat32WWatts phase A
WphBfloat32WWatts phase B
WphCfloat32WWatts phase C
VAfloat32VAVAAC Apparent Power
VAphAfloat32VAVA phase A
VAphBfloat32VAVA phase B
VAphCfloat32VAVA phase C
VARfloat32varVARReactive Power
VARphAfloat32varVAR phase A
VARphBfloat32varVAR phase B
VARphCfloat32varVAR phase C
PFfloat32PFPFPower Factor
PFphAfloat32PFPF phase A
PFphBfloat32PFPF phase B
PFphCfloat32PFPF phase C
TotWhExpfloat32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhExpPhAfloat32WhTotal Watt-hours Exported phase A
TotWhExpPhBfloat32WhTotal Watt-hours Exported phase B
TotWhExpPhCfloat32WhTotal Watt-hours Exported phase C
TotWhImpfloat32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotWhImpPhAfloat32WhTotal Watt-hours Imported phase A
TotWhImpPhBfloat32WhTotal Watt-hours Imported phase B
TotWhImpPhCfloat32WhTotal Watt-hours Imported phase C
TotVAhExpfloat32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhExpPhAfloat32VAhTotal VA-hours Exported phase A
TotVAhExpPhBfloat32VAhTotal VA-hours Exported phase B
TotVAhExpPhCfloat32VAhTotal VA-hours Exported phase C
TotVAhImpfloat32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVAhImpPhAfloat32VAhTotal VA-hours Imported phase A
TotVAhImpPhBfloat32VAhTotal VA-hours Imported phase B
TotVAhImpPhCfloat32VAhTotal VA-hours Imported phase C
TotVArhImpQ1float32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ1phAfloat32varhTotal VAr-hours Imported Q1 phase A
TotVArhImpQ1phBfloat32varhTotal VAr-hours Imported Q1 phase B
TotVArhImpQ1phCfloat32varhTotal VAr-hours Imported Q1 phase C
TotVArhImpQ2float32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhImpQ2phAfloat32varhTotal VAr-hours Imported Q2 phase A
TotVArhImpQ2phBfloat32varhTotal VAr-hours Imported Q2 phase B
TotVArhImpQ2phCfloat32varhTotal VAr-hours Imported Q2 phase C
TotVArhExpQ3float32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ3phAfloat32varhTotal VAr-hours Exported Q3 phase A
TotVArhExpQ3phBfloat32varhTotal VAr-hours Exported Q3 phase B
TotVArhExpQ3phCfloat32varhTotal VAr-hours Exported Q3 phase C
TotVArhExpQ4float32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
TotVArhExpQ4phAfloat32varhTotal VAr-hours Exported Q4 Imported phase A
TotVArhExpQ4phBfloat32varhTotal VAr-hours Exported Q4 Imported phase B
TotVArhExpQ4phCfloat32varhTotal VAr-hours Exported Q4 Imported phase C
Evtbitfield32EventsMeter Event Flags

Value names:

  • Evt (bitfield32): bit 2 M_EVENT_Power_Failure, bit 3 M_EVENT_Under_Voltage, bit 4 M_EVENT_Low_PF, bit 5 M_EVENT_Over_Current, bit 6 M_EVENT_Over_Voltage, bit 7 M_EVENT_Missing_Sensor, bit 8 M_EVENT_Reserved1, bit 9 M_EVENT_Reserved2, bit 10 M_EVENT_Reserved3, bit 11 M_EVENT_Reserved4, bit 12 M_EVENT_Reserved5, bit 13 M_EVENT_Reserved6, bit 14 M_EVENT_Reserved7, bit 15 M_EVENT_Reserved8, bit 16 M_EVENT_OEM01, bit 17 M_EVENT_OEM02, bit 18 M_EVENT_OEM03, bit 19 M_EVENT_OEM04, bit 20 M_EVENT_OEM05, bit 21 M_EVENT_OEM06, bit 22 M_EVENT_OEM07, bit 23 M_EVENT_OEM08, bit 24 M_EVENT_OEM09, bit 25 M_EVENT_OEM10, bit 26 M_EVENT_OEM11, bit 27 M_EVENT_OEM12, bit 28 M_EVENT_OEM13, bit 29 M_EVENT_OEM14, bit 30 M_EVENT_OEM15

Model 220: Secure AC Meter Selected Readings

Name in records: ac_meter_secure.

Include this model for secure metering

PointTypeUnitsLabelDescription
Aint16AAmpsTotal AC Current
PhVint16VVoltageAverage phase or line voltage
Hzint16HzHzFrequency
Wint16WWattsTotal Real Power
VAint16VAVAAC Apparent Power
VARint16varVARReactive Power
PFint16PctPFPower Factor
TotWhExpacc32WhTotal Watt-hours ExportedTotal Real Energy Exported
TotWhImpacc32WhTotal Watt-hours ImportedTotal Real Energy Imported
TotVAhExpacc32VAhTotal VA-hours ExportedTotal Apparent Energy Exported
TotVAhImpacc32VAhTotal VA-hours ImportedTotal Apparent Energy Imported
TotVArhImpQ1acc32varhTotal VAR-hours Imported Q1Total Reactive Energy Imported Quadrant 1
TotVArhImpQ2acc32varhTotal VAr-hours Imported Q2Total Reactive Power Imported Quadrant 2
TotVArhExpQ3acc32varhTotal VAr-hours Exported Q3Total Reactive Power Exported Quadrant 3
TotVArhExpQ4acc32varhTotal VAr-hours Exported Q4Total Reactive Power Exported Quadrant 4
Evtbitfield32EventsMeter Event Flags
Tsuint32TimestampTimestamp value is the number of seconds since January 1, 2000
Msuint16MillisecondsMillisecond counter 0-999
Sequint16SequenceSequence number of request
Algenum16AlgorithmAlgorithm used to compute the digital signature
Nuint16NNumber of registers comprising the digital signature.
repeating[*].DSuint16

Value names:

  • Evt (bitfield32): bit 2 Power_Failure, bit 3 Under_Voltage, bit 4 Low_PF, bit 5 Over_Current, bit 6 Over_Voltage, bit 7 Missing_Sensor, bit 16 OEM01, bit 17 OEM02, bit 18 OEM03, bit 19 OEM04, bit 20 OEM05, bit 21 OEM06, bit 22 OEM07, bit 23 OEM08, bit 24 OEM09, bit 25 OEM10, bit 26 OEM11, bit 27 OEM12, bit 28 OEM13, bit 29 OEM14, bit 30 OEM15
  • Alg (enum16): 0 NONE, 1 AES-GMAC-64, 2 ECC-256

Environmental sensors

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
302irradiance5
303bom_temp1
304inclinometer3
305location6
306ref_point4
307base_met11
308mini_met4

Model 302: Irradiance Model

Name in records: irradiance.

Include to support various irradiance measurements

PointTypeUnitsLabelDescription
repeating[*].GHIuint16W/m2GHIGlobal Horizontal Irradiance
repeating[*].POAIuint16W/m2POAIPlane-of-Array Irradiance
repeating[*].DFIuint16W/m2DFIDiffuse Irradiance
repeating[*].DNIuint16W/m2DNIDirect Normal Irradiance
repeating[*].OTIuint16W/m2OTIOther Irradiance

Model 303: Back of Module Temperature Model

Name in records: bom_temp.

Include to support variable number of back of module temperature measurements

PointTypeUnitsLabelDescription
temp[*].TmpBOMint16CTempBack of module temperature measurement

Model 304: Inclinometer Model

Name in records: inclinometer.

Include to support orientation measurements

PointTypeUnitsLabelDescription
incl[*].Inclxint32DegreesXX-Axis inclination
incl[*].Inclyint32DegreesYY-Axis inclination
incl[*].Inclzint32DegreesZZ-Axis inclination

Model 305: GPS

Name in records: location.

Include to support location measurements

PointTypeUnitsLabelDescription
Tmstringhhmmss.sssZTmUTC 24 hour time stamp to millisecond hhmmss.sssZ format
DatestringYYYYMMDDDateUTC Date string YYYYMMDD format
LocstringtextLocationLocation string (40 chars max)
Latint32DegreesLatLatitude with seven degrees of precision
Longint32DegreesLongLongitude with seven degrees of precision
Altint32metersAltitudeAltitude measurement in meters

Model 306: Reference Point Model

Name in records: ref_point.

Include to support a standard reference point

PointTypeUnitsLabelDescription
GHIuint16W/m2GHIGlobal Horizontal Irradiance
Auint16W/m2AmpsCurrent measurement at reference point
Vuint16W/m2VoltageVoltage measurement at reference point
Tmpuint16W/m2TemperatureTemperature measurement at reference point

Model 307: Base Met

Name in records: base_met.

Base Meteorological Model

PointTypeUnitsLabelDescription
TmpAmbint16CAmbient Temperature
RHint16PctRelative Humidity
Presint16HPaBarometric Pressure
WndSpdint16mpsWind Speed
WndDirint16degWind Direction
Rainint16mmRainfall
Snwint16mmSnow Depth
PPTint16Precipitation TypePrecipitation Type (WMO 4680 SYNOP code reference)
ElecFldint16VmElectric Field
SurWetint16kOSurface Wetness
SoilWetint16PctSoil Wetness

Model 308: Mini Met Model

Name in records: mini_met.

Include to support a few basic measurements

PointTypeUnitsLabelDescription
GHIuint16W/m2GHIGlobal Horizontal Irradiance
TmpBOMint16CTempBack of module temperature measurement
TmpAmbint16CAmbient Temperature
WndSpduint16m/sWind Speed

String combiners

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
401string_combiner_current13
402string_combiner_advanced21
403string_combiner_current_input13
404string_combiner_advanced_inputs21

Model 401: String Combiner (Current)

Name in records: string_combiner_current.

A basic string combiner

PointTypeUnitsLabelDescription
DCAMaxuint16ARatingMaximum DC Current Rating
NcountNNumber of Inputs
Evtbitfield32EventEvents
EvtVndbitfield32Vendor EventVendor defined events
DCAint16AAmpsTotal measured current
DCAhruint32AhAmp-hoursTotal metered Amp-hours
DCVuint16VVoltageOutput Voltage
Tmpint16CTempInternal operating temperature
string[*].InIDuint16IDUniquely identifies this input set
string[*].InEvtbitfield32Input EventString Input Event Flags
string[*].InEvtVndbitfield32Input Event VendorString Input Vendor Event Flags
string[*].InDCAint16AAmpsString Input Current
string[*].InDCAhruint32AhAmp-hoursString Input Amp-Hours

Value names:

  • Evt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED
  • string[*].InEvt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED

Model 402: String Combiner (Advanced)

Name in records: string_combiner_advanced.

An advanced string combiner

PointTypeUnitsLabelDescription
DCAMaxuint16ARatingMaximum DC Current Rating
NcountNNumber of Inputs
Evtbitfield32EventEvents
EvtVndbitfield32Vendor EventVendor defined events
DCAint16AAmpsTotal measured current
DCAhruint32AhAmp-hoursTotal metered Amp-hours
DCVuint16VVoltageOutput Voltage
Tmpint16CTempInternal operating temperature
DCWint16WWattsOutput power
DCPRuint16PctPRDC Performance ratio value
DCWhuint32WhWatt-hoursOutput energy
string[*].InIDuint16IDUniquely identifies this input set
string[*].InEvtbitfield32Input EventString Input Event Flags
string[*].EvtVndbitfield32Vendor EventVendor defined events
string[*].InDCAint16AAmpsString Input Current
string[*].InDCAhruint32AhAmp-hoursString Input Amp-Hours
string[*].InDCVuint16VVoltageString Input Voltage
string[*].InDCWint16WWattsString Input Power
string[*].InDCWhuint32WhWatt-hoursString Input Energy
string[*].InDCPRuint16PctPRString Performance Ratio
string[*].InNuint16NNumber of modules in this input string

Value names:

  • Evt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED
  • string[*].InEvt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED

Model 403: String Combiner (Current)

Name in records: string_combiner_current_input.

A basic string combiner model

PointTypeUnitsLabelDescription
DCAMaxuint16ARatingMaximum DC Current Rating
NcountNNumber of Inputs
Evtbitfield32EventEvents
EvtVndbitfield32Vendor EventVendor defined events
DCAint16AAmpsTotal measured current
DCAhracc32AhAmp-hoursTotal metered Amp-hours
DCVint16VVoltageOutput Voltage
Tmpint16CTempInternal operating temperature
string[*].InIDuint16IDUniquely identifies this input set
string[*].InEvtbitfield32Input EventString Input Event Flags
string[*].InEvtVndbitfield32Input Event VendorString Input Vendor Event Flags
string[*].InDCAint16AAmpsString Input Current
string[*].InDCAhracc32AhAmp-hoursString Input Amp-Hours

Value names:

  • Evt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED
  • string[*].InEvt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED

Model 404: String Combiner (Advanced)

Name in records: string_combiner_advanced_inputs.

An advanced string combiner including voltage and energy measurements

PointTypeUnitsLabelDescription
DCAMaxuint16ARatingMaximum DC Current Rating
NcountNNumber of Inputs
Evtbitfield32EventEvents
EvtVndbitfield32Vendor EventVendor defined events
DCAint16AAmpsTotal measured current
DCAhracc32AhAmp-hoursTotal metered Amp-hours
DCVint16VVoltageOutput Voltage
Tmpint16CTempInternal operating temperature
DCWint16WWattsOutput power
DCPRint16PctPRDC Performance ratio value
DCWhacc32WhWatt-hoursOutput energy
string[*].InIDuint16IDUniquely identifies this input set
string[*].InEvtbitfield32Input EventString Input Event Flags
string[*].InEvtVndbitfield32Input Event VendorString Input Vendor Event Flags
string[*].InDCAint16AAmpsString Input Current
string[*].InDCAhracc32AhAmp-hoursString Input Amp-Hours
string[*].InDCVint16VVoltageString Input Voltage
string[*].InDCWint16WWattsString Input Power
string[*].InDCWhacc32WhWatt-hoursString Input Energy
string[*].InDCPRuint16PctPRString Performance Ratio
string[*].InNuint16NNumber of modules in this input string

Value names:

  • Evt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED
  • string[*].InEvt (bitfield32): bit 0 LOW_VOLTAGE, bit 1 LOW_POWER, bit 2 LOW_EFFICIENCY, bit 3 CURRENT, bit 4 VOLTAGE, bit 5 POWER, bit 6 PR, bit 7 DISCONNECTED, bit 8 FUSE_FAULT, bit 9 COMBINER_FUSE_FAULT, bit 10 COMBINER_CABINET_OPEN, bit 11 TEMP, bit 12 GROUNDFAULT, bit 13 REVERSED_POLARITY, bit 14 INCOMPATIBLE, bit 15 COMM_ERROR, bit 16 INTERNAL_ERROR, bit 17 THEFT, bit 18 ARC_DETECTED

Solar modules

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
501solar_module_float17
502solar_module17

Model 501: Solar Module

Name in records: solar_module_float.

A solar module model supporting DC-DC converter

PointTypeUnitsLabelDescription
Statenum16StatusModule Status Code
StatVendenum16Vendor StatusModule Vendor Status Code
Evtbitfield32EventsModule Event Flags
EvtVendbitfield32Vendor Module Event FlagsVendor specific flags
Ctlenum16ControlModule Control
CtlVendenum32Vendor ControlVendor Module Control
CtlValint32Control ValueModule Control Value
Tmsuint32SecsTimestampTime in seconds since 2000 epoch
OutAfloat32AOutput CurrentOutput Current
OutVfloat32VOutput VoltageOutput Voltage
OutWhfloat32WhOutput EnergyOutput Energy
OutWfloat32WOutput PowerOutput Power
Tmpfloat32CTempModule Temperature
InAfloat32AInput CurrentInput Current
InVfloat32VInput VoltageInput Voltage
InWhfloat32WhInput EnergyInput Energy
InWfloat32WInput PowerInput Power

Value names:

  • Stat (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY, 9 TEST, 10 OTHER
  • Evt (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 2 RESERVED_2, bit 3 DC_DISCONNECT, bit 4 RESERVED_4, bit 5 RESERVED_5, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMPERATURE, bit 8 RESERVED_8, bit 9 RESERVED_9, bit 10 RESERVED_10, bit 11 RESERVED_11, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMPERATURE, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 16 THEFT_DETECTION, bit 17 OUTPUT_OVER_CURRENT, bit 18 OUTPUT_OVER_VOLTAGE, bit 19 OUTPUT_UNDER_VOLTAGE, bit 20 TEST_FAILED

Model 502: Solar Module

Name in records: solar_module.

A solar module model supporting DC-DC converter

PointTypeUnitsLabelDescription
Statenum16StatusModule Status Code
StatVendenum16Vendor StatusModule Vendor Status Code
Evtbitfield32EventsModule Event Flags
EvtVendbitfield32Vendor Module Event FlagsVendor specific flags
Ctlenum16ControlModule Control
CtlVendenum32Vendor ControlVendor Module Control
CtlValint32Control ValueModule Control Value
Tmsuint32SecsTimestampTime in seconds since 2000 epoch
OutAint16AOutput CurrentOutput Current
OutVint16VOutput VoltageOutput Voltage
OutWhacc32WhOutput EnergyOutput Energy
OutPwint16WOutput PowerOutput Power
Tmpint16CTempModule Temperature
InAint16AInput CurrentInput Current
InVint16VInput VoltageInput Voltage
InWhacc32WhInput EnergyInput Energy
InWint16WInput PowerInput Power

Value names:

  • Stat (enum16): 1 OFF, 2 SLEEPING, 3 STARTING, 4 MPPT, 5 THROTTLED, 6 SHUTTING_DOWN, 7 FAULT, 8 STANDBY, 9 TEST, 10 OTHER
  • Evt (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 2 RESERVED_2, bit 3 DC_DISCONNECT, bit 4 RESERVED_4, bit 5 RESERVED_5, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMPERATURE, bit 8 RESERVED_8, bit 9 RESERVED_9, bit 10 RESERVED_10, bit 11 RESERVED_11, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMPERATURE, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 16 THEFT_DETECTION, bit 17 OUTPUT_OVER_CURRENT, bit 18 OUTPUT_OVER_VOLTAGE, bit 19 OUTPUT_UNDER_VOLTAGE, bit 20 TEST_FAILED

Trackers

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
601tracker_controller19

Model 601: Tracker Controller DRAFT 2

Name in records: tracker_controller.

Monitors and controls multiple trackers

PointTypeUnitsLabelDescription
NamstringControllerDescriptive name for this control unit
Typenum16TypeType of tracker
DtLocstringYYYYMMDDDateLocal date in YYYYMMDD format
TmLocstringhhmmssTime24 hour local time stamp to second
Dayuint16DayNumber of the day in the year (1-366)
GlblElCtlint32DegreesManual ElevationGlobal manual override target position of elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type
GlblAzCtlint32DegreesManual AzimuthGlobal manual override target position of azimuth in degrees from true north towards east. Unimplemented for single axis azimuth tracker type
GlblCtlenum16Global ModeGlobal Control register operates on all trackers. Normal operation is automatic. Operator can override the position by setting the ElCtl, AzCtl and enabling Manual operation. Entering calibration mode will revert to automatic operation after calibration is complete.
GlblAlmbitfield16Global AlarmGlobal tracker alarm conditions
Nuint16TrackersNumber of trackers being controlled. Size of repeating block.
tracker[*].IdstringTrackerDescriptive name for this tracker unit
tracker[*].ElTrgtint32DegreesTarget ElevationAuto target elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type
tracker[*].AzTrgtint32DegreesTarget AzimuthAuto target azimuth in degrees from true north towards east. Unimplemented for single axis horizontal tracker type
tracker[*].ElPosint32DegreesElevationActual elevation position in degrees from horizontal. Unimplemented for single axis azimuth tracker type
tracker[*].AzPosint32DegreesAzimuthActual azimuth position in degrees from true north towards east. Unimplemented for single axis horizontal tracker type
tracker[*].ElCtlint32DegreesManual ElevationManual override target position of elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type
tracker[*].AzCtlint32DegreesManual AzimuthManual override target position of azimuth in degrees from true north towards east. Unimplemented for single axis azimuth tracker type
tracker[*].Ctlenum16ModeControl register. Normal operation is automatic. Operator can override the position by setting the ElCtl, AzCtl and enabling Manual operation. Entering calibration mode will revert to automatic operation after calibration is complete.
tracker[*].Almbitfield16AlarmTracker alarm conditions

Value names:

  • Typ (enum16): 0 Unknown, 1 Fixed, 2 Horizontal, 3 Tilted, 4 Azimuth, 5 Dual, 99 Other
  • GlblCtl (enum16): 0 Automatic, 1 Manual, 2 Calibrate
  • GlblAlm (bitfield16): bit 0 SetPoint, bit 1 ObsEl, bit 2 ObsAz
  • tracker[*].Ctl (enum16): 0 Automatic, 1 Manual, 2 Calibrate
  • tracker[*].Alm (bitfield16): bit 0 SetPoint, bit 1 ObsEl, bit 2 ObsAz

DER (IEEE 1547-2018)

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
701DERMeasureAC60
702DERCapacity42
703DEREnterService9
704DERCtlAC45
705DERVoltVar19
706DERVoltWatt14
707DERTripLV15
708DERTripHV15
709DERTripLF15
710DERTripHF15
711DERFreqDroop14
712DERWattVar14
713DERStorageCapacity5
714DERMeasureDC17
715DERCtl5

Model 701: DER AC Measurement

Name in records: DERMeasureAC.

DER AC measurement model.

PointTypeUnitsLabelDescription
ACTypeenum16AC Wiring TypeAC wiring type.
Stenum16Operating StateOperating state of the DER.
InvStenum16Inverter StateInverter state.
ConnStenum16Grid Connection StateGrid connection state of the DER.
Alrmbitfield32Alarm BitfieldActive alarms for the DER.
DERModebitfield32DER Operational CharacteristicsCurrent operational characteristics of the DER.
Wint16WActive PowerTotal active power. Active power is positive for DER generation and negative for absorption.
VAint16VAApparent PowerTotal apparent power.
Varint16VarReactive PowerTotal reactive power.
PFint16Power FactorPower factor. The sign of power factor should be the sign of active power.
Aint16ATotal AC CurrentTotal AC current.
LLVuint16VVoltage LLLine to line AC voltage as an average of active phases.
LNVuint16VVoltage LNLine to neutral AC voltage as an average of active phases.
Hzuint32HzFrequencyAC frequency.
TotWhInjuint64WhTotal Energy InjectedTotal active energy injected (Quadrants 1 & 4).
TotWhAbsuint64WhTotal Energy AbsorbedTotal active energy absorbed (Quadrants 2 & 3).
TotVarhInjuint64VarhTotal Reactive Energy InjTotal reactive energy injected (Quadrants 1 & 2).
TotVarhAbsuint64VarhTotal Reactive Energy AbsTotal reactive energy absorbed (Quadrants 3 & 4).
TmpAmbint16CAmbient TemperatureAmbient temperature.
TmpCabint16CCabinet TemperatureCabinet temperature.
TmpSnkint16CHeat Sink TemperatureHeat sink temperature.
TmpTrnsint16CTransformer TemperatureTransformer temperature.
TmpSwint16CIGBT/MOSFET TemperatureIGBT/MOSFET temperature.
TmpOtint16COther TemperatureOther temperature.
WL1int16WWatts L1Active power L1.
VAL1int16VAVA L1Apparent power L1.
VarL1int16VarVar L1Reactive power L1.
PFL1int16PF L1Power factor phase L1.
AL1int16AAmps L1Current phase L1.
VL1L2uint16VPhase Voltage L1-L2Phase voltage L1-L2.
VL1uint16VPhase Voltage L1-NPhase voltage L1-N.
TotWhInjL1uint64WhTotal Watt-Hours Inj L1Total active energy injected L1.
TotWhAbsL1uint64WhTotal Watt-Hours Abs L1Total active energy absorbed L1.
TotVarhInjL1uint64VarhTotal Var-Hours Inj L1Total reactive energy injected L1.
TotVarhAbsL1uint64VarhTotal Var-Hours Abs L1Total reactive energy absorbed L1.
WL2int16WWatts L2Active power L2.
VAL2int16VAVA L2Apparent power L2.
VarL2int16VarVar L2Reactive power L2.
PFL2int16PF L2Power factor L2.
AL2int16AAmps L2Current L2.
VL2L3uint16VPhase Voltage L2-L3Phase voltage L2-L3.
VL2uint16VPhase Voltage L2-NPhase voltage L2-N.
TotWhInjL2uint64WhTotal Watt-Hours Inj L2Total active energy injected L2.
TotWhAbsL2uint64WhTotal Watt-Hours Abs L2Total active energy absorbed L2.
TotVarhInjL2uint64VarhTotal Var-Hours Inj L2Total reactive energy injected L2.
TotVarhAbsL2uint64VarhTotal Var-Hours Abs L2Total reactive energy absorbed L2.
WL3int16WWatts L3Active power L3.
VAL3int16VAVA L3Apparent power L3.
VarL3int16VarVar L3Reactive power L3.
PFL3int16PF L3Power factor L3.
AL3int16AAmps L3Current L3.
VL3L1uint16VPhase Voltage L3-L1Phase voltage L3-L1.
VL3uint16VPhase Voltage L3-NPhase voltage L3-N.
TotWhInjL3uint64WhTotal Watt-Hours Inj L3Total active energy injected L3.
TotWhAbsL3uint64WhTotal Watt-Hours Abs L3Total active energy absorbed L3.
TotVarhInjL3uint64VarhTotal Var-Hours Inj L3Total reactive energy injected L3.
TotVarhAbsL3uint64VarhTotal Var-Hours Abs L3Total reactive energy absorbed L3.
ThrotPctuint16PctThrottling In PctThrottling in pct of maximum active power.
ThrotSrcbitfield32Throttle Source InformationActive throttling source.
MnAlrmInfostringManufacturer Alarm InfoManufacturer alarm information. Valid if MANUFACTURER_ALRM indication is active.

Value names:

  • ACType (enum16): 0 SINGLE_PHASE, 1 SPLIT_PHASE, 2 THREE_PHASE
  • St (enum16): 0 OFF, 1 ON
  • InvSt (enum16): 0 OFF, 1 SLEEPING, 2 STARTING, 3 RUNNING, 4 THROTTLED, 5 SHUTTING_DOWN, 6 FAULT, 7 STANDBY
  • ConnSt (enum16): 0 DISCONNECTED, 1 CONNECTED
  • Alrm (bitfield32): bit 0 GROUND_FAULT, bit 1 DC_OVER_VOLT, bit 2 AC_DISCONNECT, bit 3 DC_DISCONNECT, bit 4 GRID_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 8 OVER_FREQUENCY, bit 9 UNDER_FREQUENCY, bit 10 AC_OVER_VOLT, bit 11 AC_UNDER_VOLT, bit 12 BLOWN_STRING_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 HW_TEST_FAILURE, bit 16 MANUFACTURER_ALRM
  • DERMode (bitfield32): bit 0 GRID_FOLLOWING, bit 1 GRID_FORMING, bit 2 PV_CLIPPED
  • ThrotSrc (bitfield32): bit 0 MAX_W, bit 1 FIXED_W, bit 2 FIXED_VAR, bit 3 FIXED_PF, bit 4 VOLT_VAR, bit 5 FREQ_WATT, bit 6 DYN_REACT_CURR, bit 7 LVRT, bit 8 HVRT, bit 9 WATT_VAR, bit 10 VOLT_WATT, bit 11 SCHEDULED, bit 12 LFRT, bit 13 HFRT, bit 14 DERATED

Model 702: DER Capacity

Name in records: DERCapacity.

DER capacity model.

PointTypeUnitsLabelDescription
WMaxRtguint16WActive Power Max RatingMaximum active power rating at unity power factor in watts.
WOvrExtRtguint16WActive Power (Over-Excited) RatingActive power rating at specified over-excited power factor in watts.
WOvrExtRtgPFuint16Specified Over-Excited PFSpecified over-excited power factor.
WUndExtRtguint16WActive Power (Under-Excited) RatingActive power rating at specified under-excited power factor in watts.
WUndExtRtgPFuint16Specified Under-Excited PFSpecified under-excited power factor.
VAMaxRtguint16VAApparent Power Max RatingMaximum apparent power rating in voltamperes.
VarMaxInjRtguint16VarReactive Power Injected RatingMaximum injected reactive power rating in vars.
VarMaxAbsRtguint16VarReactive Power Absorbed RatingMaximum absorbed reactive power rating in vars.
WChaRteMaxRtguint16WCharge Rate Max RatingMaximum active power charge rate in watts.
WDisChaRteMaxRtguint16WDischarge Rate Max RatingMaximum active power discharge rate in watts.
VAChaRteMaxRtguint16VACharge Rate Max VA RatingMaximum apparent power charge rate in voltamperes.
VADisChaRteMaxRtguint16VADischarge Rate Max VA RatingMaximum apparent power discharge rate in voltamperes.
VNomRtguint16VAC Voltage Nominal RatingAC voltage nominal rating.
VMaxRtguint16VAC Voltage Max RatingAC voltage maximum rating.
VMinRtguint16VAC Voltage Min RatingAC voltage minimum rating.
AMaxRtguint16AAC Current Max RatingAC current maximum rating in amps.
PFOvrExtRtguint16PF Over-Excited Rating (Unused)Unused. Please use WOvrExtRtgPF.
PFUndExtRtguint16PF Under-Excited Rating (Unused)Unused. Please use WUndExtRtgPF.
ReactSusceptRtguint16SReactive SusceptanceReactive susceptance that remains connected to the Area EPS in the cease to energize and trip state.
NorOpCatRtgenum16Normal Operating CategoryNormal operating performance category as specified in IEEE 1547-2018.
AbnOpCatRtgenum16Abnormal Operating CategoryAbnormal operating performance category as specified in IEEE 1547-2018.
CtrlModesbitfield32Supported Control ModesSupported control mode functions.
IntIslandCatRtgbitfield16Intentional Island CategoriesIntentional island categories.
WMaxuint16WActive Power Max SettingMaximum active power setting used to adjust maximum active power setting.
WMaxOvrExtuint16WActive Power (Over-Excited) SettingActive power setting at specified over-excited power factor in watts.
WOvrExtPFuint16Specified Over-Excited PFSpecified over-excited power factor.
WMaxUndExtuint16WActive Power (Under-Excited) SettingActive power setting at specified under-excited power factor in watts.
WUndExtPFuint16Specified Under-Excited PFSpecified under-excited power factor.
VAMaxuint16VAApparent Power Max SettingMaximum apparent power setting used to adjust maximum apparent power rating.
VarMaxInjuint16VarReactive Power Injected SettingMaximum injected reactive power setting used to adjust maximum injected reactive power rating.
VarMaxAbsuint16VarReactive Power Absorbed SettingMaximum absorbed reactive power setting used to adjust maximum absorbed reactive power rating.
WChaRteMaxuint16WCharge Rate Max SettingMaximum active power charge rate setting used to adjust maximum active power charge rate rating.
WDisChaRteMaxuint16WDischarge Rate Max SettingMaximum active power discharge rate setting used to adjust maximum active power discharge rate rating.
VAChaRteMaxuint16VACharge Rate Max VA SettingMaximum apparent power charge rate setting used to adjust maximum apparent power charge rate rating.
VADisChaRteMaxuint16VADischarge Rate Max VA SettingMaximum apparent power discharge rate setting used to adjust maximum apparent power discharge rate rating.
VNomuint16VNominal AC Voltage SettingNominal AC voltage setting.
VMaxuint16VAC Voltage Max SettingAC voltage maximum setting used to adjust AC voltage maximum rating.
VMinuint16VAC Voltage Min SettingAC voltage minimum setting used to adjust AC voltage minimum rating.
AMaxuint16AAC Current Max SettingMaximum AC current setting used to adjust maximum AC current rating.
PFOvrExtuint16PF Over-Excited Setting (Unused)Unused. Please use WOvrExtPF.
PFUndExtuint16PF Under-Excited Setting (Unused)Unused. Please use WUndExtPF.
IntIslandCatbitfield16Intentional Island CategoriesIntentional island categories.

Value names:

  • NorOpCatRtg (enum16): 0 CAT_A, 1 CAT_B
  • AbnOpCatRtg (enum16): 0 CAT_1, 1 CAT_2, 2 CAT_3
  • CtrlModes (bitfield32): bit 0 MAX_W, bit 1 FIXED_W, bit 2 FIXED_VAR, bit 3 FIXED_PF, bit 4 VOLT_VAR, bit 5 FREQ_WATT, bit 6 DYN_REACT_CURR, bit 7 LV_TRIP, bit 8 HV_TRIP, bit 9 WATT_VAR, bit 10 VOLT_WATT, bit 11 SCHEDULED, bit 12 LF_TRIP, bit 13 HF_TRIP
  • IntIslandCatRtg (bitfield16): bit 0 UNCATEGORIZED, bit 1 INT_ISL_CAPABLE, bit 2 BLACK_START_CAPABLE, bit 3 ISOCH_CAPABLE
  • IntIslandCat (bitfield16): bit 0 UNCATEGORIZED, bit 1 INT_ISL_CAPABLE, bit 2 BLACK_START_CAPABLE, bit 3 ISOCH_CAPABLE

Model 703: Enter Service

Name in records: DEREnterService.

Enter service model.

PointTypeUnitsLabelDescription
ESenum16Permit Enter ServicePermit enter service.
ESVHiuint16PctEnter Service Voltage HighEnter service voltage high threshold as percent of normal voltage.
ESVLouint16PctEnter Service Voltage LowEnter service voltage low threshold as percent of normal voltage.
ESHzHiuint32HzEnter Service Frequency HighEnter service frequency high threshold.
ESHzLouint32HzEnter Service Frequency LowEnter service frequency low threshold.
ESDlyTmsuint32SecsEnter Service Delay TimeEnter service delay time in seconds.
ESRndTmsuint32SecsEnter Service Random DelayEnter service random delay in seconds.
ESRmpTmsuint32SecsEnter Service Ramp TimeEnter service ramp time in seconds.
ESDlyRemTmsuint32SecsEnter Service Delay RemainingEnter service delay time remaining in seconds.

Value names:

  • ES (enum16): 0 DISABLED, 1 ENABLED

Model 704: DER AC Controls

Name in records: DERCtlAC.

DER AC controls model.

PointTypeUnitsLabelDescription
PFWInjEnaenum16Power Factor Enable (W Inj) EnablePower factor enable when injecting active power.
PFWInjEnaRvrtenum16Power Factor Reversion Enable (W Inj)Power factor reversion timer when injecting active power enable.
PFWInjRvrtTmsuint32SecsPF Reversion Time (W Inj)Power factor reversion timer when injecting active power.
PFWInjRvrtRemuint32SecsPF Reversion Time Rem (W Inj)Power factor reversion time remaining when injecting active power.
PFWAbsEnaenum16Power Factor Enable (W Abs) EnablePower factor enable when absorbing active power.
PFWAbsEnaRvrtenum16Power Factor Reversion Enable (W Abs)Power factor reversion timer when absorbing active power enable.
PFWAbsRvrtTmsuint32SecsPF Reversion Time (W Abs)Power factor reversion timer when absorbing active power.
PFWAbsRvrtRemuint32SecsPF Reversion Time Rem (W Abs)Power factor reversion time remaining when absorbing active power.
WMaxLimPctEnaenum16Limit Max Power Pct EnableLimit maximum active power percent enable.
WMaxLimPctuint16PctLimit Max Power Pct SetpointLimit maximum active power percent value.
WMaxLimPctRvrtuint16PctReversion Limit Max Power PctReversion limit maximum active power percent value.
WMaxLimPctEnaRvrtenum16Reversion Limit Max Power Pct EnableReversion limit maximum active power percent value enable.
WMaxLimPctRvrtTmsuint32SecsLimit Max Power Pct Reversion TimeLimit maximum active power percent reversion time.
WMaxLimPctRvrtRemuint32SecsLimit Max Power Pct Rev Time RemLimit maximum active power percent reversion time remaining.
WSetEnaenum16Set Active Power EnableSet active power enable.
WSetModenum16Set Active Power ModeSet active power mode.
WSetint32WActive Power Setpoint (W)Active power setting value in watts.
WSetRvrtint32WReversion Active Power (W)Reversion active power setting value in watts.
WSetPctint16PctActive Power Setpoint (Pct)Active power setting value as percent.
WSetPctRvrtint16PctReversion Active Power (Pct)Reversion active power setting value as percent.
WSetEnaRvrtenum16Reversion Active Power EnableReversion active power function enable.
WSetRvrtTmsuint32SecsActive Power Reversion TimeSet active power reversion time.
WSetRvrtRemuint32SecsActive Power Rev Time RemSet active power reversion time remaining.
VarSetEnaenum16Set Reactive Power EnableSet reactive power enable.
VarSetModenum16Set Reactive Power ModeSet reactive power mode.
VarSetPrienum16Reactive Power PriorityReactive power priority.
VarSetint32VarReactive Power Setpoint (Vars)Reactive power setting value in vars.
VarSetRvrtint32VarReversion Reactive Power (Vars)Reversion reactive power setting value in vars.
VarSetPctint16PctReactive Power Setpoint (Pct)Reactive power setting value as percent.
VarSetPctRvrtint16PctReversion Reactive Power (Pct)Reversion reactive power setting value as percent.
VarSetEnaRvrtenum16Reversion Reactive Power EnableReversion reactive power function enable.
VarSetRvrtTmsuint32SecsReactive Power Reversion TimeSet reactive power reversion time.
VarSetRvrtRemuint32SecsReactive Power Rev Time RemSet reactive power reversion time remaining.
WRmpuint16%Max/SecNormal Ramp RateRamp rate for increases in active power during normal generation.
WRmpRefenum16Normal Ramp Rate ReferenceRamp rate reference unit for increases in active power or current during normal generation.
VarRmpuint16%Max/SecReactive Power Ramp RateRamp rate based on max reactive power per second.
AntiIslEnaenum16Anti-Islanding EnableAnti-islanding enable.
PFWInj.PFuint16Power Factor (W Inj)Power factor setpoint when injecting active power.
PFWInj.Extenum16Power Factor Excitation (W Inj)Power factor excitation setpoint when injecting active power.
PFWInjRvrt.PFuint16Reversion Power Factor (W Inj)Reversion power factor setpoint when injecting active power.
PFWInjRvrt.Extenum16Reversion PF Excitation (W Inj)Reversion power factor excitation setpoint when injecting active power.
PFWAbs.PFuint16Power Factor (W Abs)Power factor setpoint when absorbing active power.
PFWAbs.Extenum16Power Factor Excitation (W Abs)Power factor excitation setpoint when absorbing active power.
PFWAbsRvrt.PFuint16Reversion Power Factor (W Abs)Reversion power factor setpoint when absorbing active power.
PFWAbsRvrt.Extenum16Reversion PF Excitation (W Abs)Reversion power factor excitation setpoint when absorbing active power.

Value names:

  • PFWInjEna (enum16): 0 DISABLED, 1 ENABLED
  • PFWInjEnaRvrt (enum16): 0 DISABLED, 1 ENABLED
  • PFWAbsEna (enum16): 0 DISABLED, 1 ENABLED
  • PFWAbsEnaRvrt (enum16): 0 DISABLED, 1 ENABLED
  • WMaxLimPctEna (enum16): 0 DISABLED, 1 ENABLED
  • WMaxLimPctEnaRvrt (enum16): 0 DISABLED, 1 ENABLED
  • WSetEna (enum16): 0 DISABLED, 1 ENABLED
  • WSetMod (enum16): 0 W_MAX_PCT, 1 WATTS
  • WSetEnaRvrt (enum16): 0 DISABLED, 1 ENABLED
  • VarSetEna (enum16): 0 DISABLED, 1 ENABLED
  • VarSetMod (enum16): 0 W_MAX_PCT, 1 VAR_MAX_PCT, 2 VAR_AVAIL_PCT, 3 VA_MAX_PCT, 4 VARS
  • VarSetPri (enum16): 0 ACTIVE, 1 REACTIVE, 2 VENDOR
  • VarSetEnaRvrt (enum16): 0 DISABLED, 1 ENABLED
  • WRmpRef (enum16): 0 A_MAX, 1 W_MAX
  • AntiIslEna (enum16): 0 DISABLED, 1 ENABLED
  • PFWInj.Ext (enum16): 0 OVER_EXCITED, 1 UNDER_EXCITED
  • PFWInjRvrt.Ext (enum16): 0 OVER_EXCITED, 1 UNDER_EXCITED
  • PFWAbs.Ext (enum16): 0 OVER_EXCITED, 1 UNDER_EXCITED
  • PFWAbsRvrt.Ext (enum16): 0 OVER_EXCITED, 1 UNDER_EXCITED

Model 705: DER Volt-Var

Name in records: DERVoltVar.

DER Volt-Var model.

PointTypeUnitsLabelDescription
Enaenum16DER Volt-Var Module EnableVolt-Var control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvuint16Stored Curve CountNumber of stored curves supported.
RvrtTmsuint32SecsReversion TimeoutReversion time in seconds. 0 = No reversion time.
RvrtRemuint32SecsReversion Time RemainingReversion time remaining in seconds.
RvrtCrvuint16Reversion CurveDefault curve after reversion timeout.
Crv[*].ActPtuint16Active PointsNumber of active points.
Crv[*].DeptRefenum16Dependent ReferenceCurve dependent reference.
Crv[*].Prienum16Power PriorityPower priority.
Crv[*].VRefuint16VNomPctVref AdjustmentVref adjustment as percentage of nominal voltage.
Crv[*].VRefAutouint16VNomPctCurrent Autonomous VrefAutonomous vref value as a percentage of nominal voltage.
Crv[*].VRefAutoEnaenum16Autonomous Vref EnableEnable autonomous vref.
Crv[*].VRefAutoTmsuint16SecsAuto Vref Time ConstantAutonomous vref time constant.
Crv[*].RspTmsuint32SecsOpen Loop Response TimeOpen loop response time.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].Pt[*].Varint16DeptRefReactive Power PointCurve reactive power point as set in DeptRef point.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].DeptRef (enum16): 0 W_MAX_PCT, 1 VAR_MAX_PCT, 2 VAR_AVAL_PCT, 3 VA_MAX_PCT
  • Crv[*].Pri (enum16): 0 ACTIVE, 1 REACTIVE, 2 VENDOR
  • Crv[*].VRefAutoEna (enum16): 0 DISABLED, 1 ENABLED
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 706: DER Volt-Watt

Name in records: DERVoltWatt.

DER Volt-Watt model.

PointTypeUnitsLabelDescription
Enaenum16DER Volt-Watt Module EnableVolt-Watt control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvuint16Stored Curve CountNumber of stored curves supported.
RvrtTmsuint32SecsReversion TimeoutReversion time in seconds. 0 = No reversion time.
RvrtRemuint32SecsReversion Time RemainingReversion time remaining in seconds.
RvrtCrvuint16Reversion CurveDefault curve after reversion timeout.
Crv[*].ActPtuint16Active PointsNumber of active points.
Crv[*].DeptRefenum16Dependent ReferenceCurve dependent reference.
Crv[*].RspTmsuint32SecsOpen Loop Response TimeOpen loop response time.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].Pt[*].Wint16DeptRefDependent ReferenceActive power in percent of rated active power.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].DeptRef (enum16): 0 W_MAX_PCT, 1 W_AVAL_PCT
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 707: DER Trip LV

Name in records: DERTripLV.

DER low voltage trip model.

PointTypeUnitsLabelDescription
Enaenum16DER Trip LV Module EnableDER low voltage trip control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvSetuint16Stored Curve CountNumber of stored curves supported.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].MustTrip.ActPtuint16Number Of Active PointsNumber of active points in must trip curve.
Crv[*].MustTrip.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MustTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MayTrip.ActPtuint16Number Of Active PointsNumber of active points in may trip curve.
Crv[*].MayTrip.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MayTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MomCess.ActPtuint16Number Of Active PointsNumber of active points in the momentary cessation curve.
Crv[*].MomCess.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MomCess.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 708: DER Trip HV

Name in records: DERTripHV.

DER high voltage trip model.

PointTypeUnitsLabelDescription
Enaenum16DER Trip HV Module EnableDER high voltage trip control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvSetuint16Stored Curve CountNumber of stored curves supported.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].MustTrip.ActPtuint16Number Of Active PointsNumber of active points in must trip curve.
Crv[*].MustTrip.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MustTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MayTrip.ActPtuint16Number Of Active PointsNumber of active points in may trip curve.
Crv[*].MayTrip.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MayTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MomCess.ActPtuint16Number Of Active PointsNumber of active points in the momentary cessation curve.
Crv[*].MomCess.Pt[*].Vuint16VNomPctVoltage PointCurve voltage point as percentage.
Crv[*].MomCess.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 709: DER Trip LF

Name in records: DERTripLF.

DER low frequency trip model.

PointTypeUnitsLabelDescription
Enaenum16DER Trip LF Module EnableDER low frequency trip control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvSetuint16Stored Curve CountNumber of stored curves supported.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].MustTrip.ActPtuint16Number Of Active PointsNumber of active points in must trip curve.
Crv[*].MustTrip.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MustTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MayTrip.ActPtuint16Number Of Active PointsNumber of active points in may trip curve.
Crv[*].MayTrip.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MayTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MomCess.ActPtuint16Number Of Active PointsNumber of active points in the momentary cessation curve.
Crv[*].MomCess.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MomCess.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 710: DER Trip HF

Name in records: DERTripHF.

DER high frequency trip model.

PointTypeUnitsLabelDescription
Enaenum16DER Trip HF Module EnableDER high frequency trip control enable.
AdptCrvRequint16Adopt Curve RequestIndex of curve points to adopt. First curve index is 1.
AdptCrvRsltenum16Adopt Curve ResultResult of last adopt curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvSetuint16Stored Curve CountNumber of stored curves supported.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].MustTrip.ActPtuint16Number Of Active PointsNumber of active points in must trip curve.
Crv[*].MustTrip.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MustTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MayTrip.ActPtuint16Number Of Active PointsNumber of active points in may trip curve.
Crv[*].MayTrip.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MayTrip.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.
Crv[*].MomCess.ActPtuint16Number Of Active PointsNumber of active points in the momentary cessation curve.
Crv[*].MomCess.Pt[*].Hzuint32HzFrequency PointCurve frequency point.
Crv[*].MomCess.Pt[*].Tmsuint32SecsTime PointCurve time point in seconds.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 711: DER Frequency Droop

Name in records: DERFreqDroop.

DER Frequency Droop model.

PointTypeUnitsLabelDescription
Enaenum16DER Frequency Droop Module EnableDER Frequency-Watt (Frequency-Droop) control enable.
AdptCtlRequint16Set Active Control RequestSet active control. 0 = No active control.
AdptCtlRsltenum16Set Active Control ResultResult of last set active control operation.
NCtluint16Stored Control CountNumber of stored controls supported.
RvrtTmsuint32SecsReversion TimeoutReversion time in seconds. 0 = No reversion time.
RvrtRemuint32SecsReversion Time LeftReversion time remaining in seconds.
RvrtCtluint16Reversion ControlDefault control after reversion timeout.
Ctl[*].DbOfuint32HzOver-Frequency DeadbandThe deadband value for over-frequency conditions in Hz.
Ctl[*].DbUfuint32HzUnder-Frequency DeadbandThe deadband value for under-frequency conditions in Hz.
Ctl[*].KOfuint16Over-Frequency Change RatioFrequency droop per-unit frequency change for over-frequency conditions corresponding to 1 per-unit power output change.
Ctl[*].KUfuint16Under-Frequency Change RatioFrequency droop per-unit frequency change for under-frequency conditions corresponding to 1 per-unit power output change.
Ctl[*].RspTmsuint32SecsOpen-Loop Response TimeThe open-loop response time in seconds.
Ctl[*].PMinint16PctMinimum Active PowerThe minimum active power output due to DER prime mover constraints, in percent of the DER active power rating. The valid range is -100 to 100. This setting applies only to the frequency droop control.
Ctl[*].ReadOnlyenum16Control AccessControl read-write access.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCtlRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Ctl[*].ReadOnly (enum16): 0 RW, 1 R

Model 712: DER Watt-Var

Name in records: DERWattVar.

DER Watt-Var model.

PointTypeUnitsLabelDescription
Enaenum16DER Watt-Var Module EnableDER Watt-Var control enable.
AdptCrvRequint16Set Active Curve RequestSet active curve. 0 = No active curve.
AdptCrvRsltenum16Set Active Curve ResultResult of last set active curve operation.
NPtuint16Number Of PointsNumber of curve points supported.
NCrvuint16Stored Curve CountNumber of stored curves supported.
RvrtTmsuint32SecsReversion TimeoutReversion time in seconds. 0 = No reversion time.
RvrtRemuint32SecsReversion Time LeftReversion time remaining in seconds.
RvrtCrvuint16Reversion CurveDefault curve after reversion timeout.
Crv[*].ActPtuint16Active PointsNumber of active points.
Crv[*].DeptRefenum16Dependent ReferenceCurve dependent reference.
Crv[*].Prienum16Power PriorityPower priority.
Crv[*].ReadOnlyenum16Curve AccessCurve read-write access.
Crv[*].Pt[*].Wint16WMaxPctActive Power PointCurve active power point as percentage.
Crv[*].Pt[*].Varint16VarPctReactive Power PointCurve reactive power point as set in DeptRef point.

Value names:

  • Ena (enum16): 0 DISABLED, 1 ENABLED
  • AdptCrvRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Crv[*].DeptRef (enum16): 0 W_MAX_PCT, 1 VAR_MAX_PCT, 2 VAR_AVAL_PCT, 3 VA_MAX_PCT
  • Crv[*].Pri (enum16): 0 ACTIVE, 1 REACTIVE
  • Crv[*].ReadOnly (enum16): 0 RW, 1 R

Model 713: DER Storage Capacity

Name in records: DERStorageCapacity.

DER storage capacity.

PointTypeUnitsLabelDescription
WHRtguint16WHEnergy RatingEnergy rating of the DER storage.
WHAvailuint16WHEnergy AvailableEnergy available of the DER storage (WHAvail = WHRtg * SoC * SoH)
SoCuint16PctState of ChargeState of charge of the DER storage.
SoHuint16PctState of HealthState of health of the DER storage.
Staenum16StatusStorage status.

Value names:

  • Sta (enum16): 0 OK, 1 WARNING, 2 ERROR

Model 714: DER DC Measurement

Name in records: DERMeasureDC.

DER DC measurement.

PointTypeUnitsLabelDescription
PrtAlrmsbitfield32Port AlarmsBitfield of ports with active alarms. Bit is 1 if port has an active alarm. Bit 0 is first port.
NPrtuint16Number Of PortsNumber of DC ports.
DCAint16ADC CurrentTotal DC current for all ports.
DCWint16WDC PowerTotal DC power for all ports.
DCWhInjuint64WhDC Energy InjectedTotal cumulative DC energy injected for all ports.
DCWhAbsuint64WhDC Energy AbsorbedTotal cumulative DC energy absorbed for all ports.
Prt[*].PrtTypenum16Port TypePort type.
Prt[*].IDuint16Port IDPort ID.
Prt[*].IDStrstringPort ID StringPort ID string.
Prt[*].DCAint16ADC CurrentDC current for the port.
Prt[*].DCVuint16VDC VoltageDC voltage for the port.
Prt[*].DCWint16WDC PowerDC power for the port.
Prt[*].DCWhInjuint64WhDC Energy InjectedTotal cumulative DC energy injected for the port.
Prt[*].DCWhAbsuint64WhDC Energy AbsorbedTotal cumulative DC energy absorbed for the port.
Prt[*].Tmpint16CDC Port TemperatureDC port temperature.
Prt[*].DCStaenum16DC Port StatusDC port status.
Prt[*].DCAlrmbitfield32DC Port AlarmDC port alarm.

Value names:

  • Prt[*].PrtTyp (enum16): 0 PV, 1 ESS, 2 EV, 3 INJ, 4 ABS, 5 BIDIR, 6 DC_DC
  • Prt[*].DCSta (enum16): 0 OFF, 1 ON, 2 WARNING, 3 ERROR
  • Prt[*].DCAlrm (bitfield32): bit 0 GROUND_FAULT, bit 1 INPUT_OVER_VOLTAGE, bit 3 DC_DISCONNECT, bit 5 CABINET_OPEN, bit 6 MANUAL_SHUTDOWN, bit 7 OVER_TEMP, bit 12 BLOWN_FUSE, bit 13 UNDER_TEMP, bit 14 MEMORY_LOSS, bit 15 ARC_DETECTION, bit 19 RESERVED, bit 20 TEST_FAILED, bit 21 INPUT_UNDER_VOLTAGE, bit 22 INPUT_OVER_CURRENT

Model 715: DERCtl

Name in records: DERCtl.

DER Control

PointTypeUnitsLabelDescription
LocRemCtlenum16Control ModeDER control mode. Enumeration.
DERHbuint32DER HeartbeatValue is incremented every second by the DER with periodic resets to zero.
ControllerHbuint32Controller HeartbeatValue is incremented every second by the controller with periodic resets to zero.
AlarmResetuint16Alarm ResetUsed to reset any latched alarms. 1 = Reset.
OpCtlenum16Set OperationCommands to PCS.

Value names:

  • LocRemCtl (enum16): 0 REMOTE, 1 LOCAL
  • OpCtl (enum16): 0 STOP, 1 START, 2 ENTER_STANDBY, 3 EXIT_STANDBY

Storage

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
801storage1
802battery44
803lithium_ion_bank44
804lithium_ion_string41
805lithium-ion-module23
806flow_battery2
807flow_battery_string44
808flow_battery_module2
809flow_battery_stack2

Model 801: Energy Storage Base Model (DEPRECATED)

Name in records: storage.

This model has been deprecated.

PointTypeUnitsLabelDescription
DEPRECATEDenum16Deprecated ModelThis model has been deprecated.

Model 802: Battery Base Model

Name in records: battery.

PointTypeUnitsLabelDescription
AHRtguint16AhNameplate Charge CapacityNameplate charge capacity in amp-hours.
WHRtguint16WhNameplate Energy CapacityNameplate energy capacity in DC watt-hours.
WChaRteMaxuint16WNameplate Max Charge RateMaximum rate of energy transfer into the storage device in DC watts.
WDisChaRteMaxuint16WNameplate Max Discharge RateMaximum rate of energy transfer out of the storage device in DC watts.
DisChaRteuint16%WHRtgSelf Discharge RateSelf discharge rate. Percentage of capacity (WHRtg) discharged per day.
SoCMaxuint16%WHRtgNameplate Max SoCManufacturer maximum state of charge, expressed as a percentage.
SoCMinuint16%WHRtgNameplate Min SoCManufacturer minimum state of charge, expressed as a percentage.
SocRsvMaxuint16%WHRtgMax Reserve PercentSetpoint for maximum reserve for storage as a percentage of the nominal maximum storage.
SoCRsvMinuint16%WHRtgMin Reserve PercentSetpoint for minimum reserve for storage as a percentage of the nominal maximum storage.
SoCuint16%WHRtgState of ChargeState of charge, expressed as a percentage.
DoDuint16%Depth of DischargeDepth of discharge, expressed as a percentage.
SoHuint16%State of HealthPercentage of battery life remaining.
NCycuint32Cycle CountNumber of cycles executed in the battery.
ChaStenum16Charge StatusCharge status of storage device. Enumeration.
LocRemCtlenum16Control ModeBattery control mode. Enumeration.
Hbuint16Battery HeartbeatValue is incremented every second with periodic resets to zero.
CtrlHbuint16Controller HeartbeatValue is incremented every second with periodic resets to zero.
AlmRstuint16Alarm ResetUsed to reset any latched alarms. 1 = Reset.
Typenum16Battery TypeType of battery. Enumeration.
Stateenum16State of the Battery BankState of the battery bank. Enumeration.
StateVndenum16Vendor Battery Bank StateVendor specific battery bank state. Enumeration.
WarrDtuint32Warranty DateDate the device warranty expires.
Evt1bitfield32Battery Event 1 BitfieldAlarms and warnings.
Evt2bitfield32Battery Event 2 BitfieldAlarms and warnings.
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events.
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events.
Vuint16VExternal Battery VoltageDC Bus Voltage.
VMaxuint16VMax Battery VoltageInstantaneous maximum battery voltage.
VMinuint16VMin Battery VoltageInstantaneous minimum battery voltage.
CellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the bank.
CellVMaxStruint16Max Cell Voltage StringString containing the cell with maximum voltage.
CellVMaxModuint16Max Cell Voltage ModuleModule containing the cell with maximum voltage.
CellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the bank.
CellVMinStruint16Min Cell Voltage StringString containing the cell with minimum voltage.
CellVMinModuint16Min Cell Voltage ModuleModule containing the cell with minimum voltage.
CellVAvguint16VAverage Cell VoltageAverage cell voltage for all cells in the bank.
Aint16ATotal DC CurrentTotal DC current flowing to/from the battery bank.
AChaMaxuint16AMax Charge CurrentInstantaneous maximum DC charge current.
ADisChaMaxuint16AMax Discharge CurrentInstantaneous maximum DC discharge current.
Wint16WTotal PowerTotal power flowing to/from the battery bank.
ReqInvStateenum16Inverter State RequestRequest from battery to start or stop the inverter. Enumeration.
ReqWint16WBattery Power RequestAC Power requested by battery.
SetOpenum16Set OperationInstruct the battery bank to perform an operation such as connecting. Enumeration.
SetInvStateenum16Set Inverter StateSet the current state of the inverter.

Value names:

  • ChaSt (enum16): 1 OFF, 2 EMPTY, 3 DISCHARGING, 4 CHARGING, 5 FULL, 6 HOLDING, 7 TESTING
  • LocRemCtl (enum16): 0 REMOTE, 1 LOCAL
  • Typ (enum16): 0 NOT APPLICABLE_UNKNOWN, 1 LEAD_ACID, 2 NICKEL_METAL_HYDRATE, 3 NICKEL_CADMIUM, 4 LITHIUM_ION, 5 CARBON_ZINC, 6 ZINC_CHLORIDE, 7 ALKALINE, 8 RECHARGEABLE_ALKALINE, 9 SODIUM_SULFUR, 10 FLOW, 99 OTHER
  • State (enum16): 1 DISCONNECTED, 2 INITIALIZING, 3 CONNECTED, 4 STANDBY, 5 SOC PROTECTION, 6 SUSPENDING, 99 FAULT
  • Evt1 (bitfield32): bit 0 COMMUNICATION_ERROR, bit 1 OVER_TEMP_ALARM, bit 2 OVER_TEMP_WARNING, bit 3 UNDER_TEMP_ALARM, bit 4 UNDER_TEMP_WARNING, bit 5 OVER_CHARGE_CURRENT_ALARM, bit 6 OVER_CHARGE_CURRENT_WARNING, bit 7 OVER_DISCHARGE_CURRENT_ALARM, bit 8 OVER_DISCHARGE_CURRENT_WARNING, bit 9 OVER_VOLT_ALARM, bit 10 OVER_VOLT_WARNING, bit 11 UNDER_VOLT_ALARM, bit 12 UNDER_VOLT_WARNING, bit 13 UNDER_SOC_MIN_ALARM, bit 14 UNDER_SOC_MIN_WARNING, bit 15 OVER_SOC_MAX_ALARM, bit 16 OVER_SOC_MAX_WARNING, bit 17 VOLTAGE_IMBALANCE_WARNING, bit 18 TEMPERATURE_IMBALANCE_ALARM, bit 19 TEMPERATURE_IMBALANCE_WARNING, bit 20 CONTACTOR_ERROR, bit 21 FAN_ERROR, bit 22 GROUND_FAULT, bit 23 OPEN_DOOR_ERROR, bit 24 CURRENT_IMBALANCE_WARNING, bit 25 OTHER_ALARM, bit 26 OTHER_WARNING, bit 27 RESERVED_1, bit 28 CONFIGURATION_ALARM, bit 29 CONFIGURATION_WARNING
  • ReqInvState (enum16): 0 NO REQUEST, 1 START, 2 STOP
  • SetOp (enum16): 1 CONNECT, 2 DISCONNECT
  • SetInvState (enum16): 1 INVERTER_STOPPED, 2 INVERTER_STANDBY, 3 INVERTER_STARTED

Model 803: Lithium-Ion Battery Bank Model

Name in records: lithium_ion_bank.

PointTypeUnitsLabelDescription
NStruint16String CountNumber of strings in the bank.
NStrConuint16Connected String CountNumber of strings with contactor closed.
ModTmpMaxint16CMax Module TemperatureMaximum temperature for all modules in the bank.
ModTmpMaxStruint16Max Module Temperature StringString containing the module with maximum temperature.
ModTmpMaxModuint16Max Module Temperature ModuleModule with maximum temperature.
ModTmpMinint16CMin Module TemperatureMinimum temperature for all modules in the bank.
ModTmpMinStruint16Min Module Temperature StringString containing the module with minimum temperature.
ModTmpMinModuint16Min Module Temperature ModuleModule with minimum temperature.
ModTmpAvgint16CAverage Module TemperatureAverage temperature for all modules in the bank.
StrVMaxuint16VMax String VoltageMaximum string voltage for all strings in the bank.
StrVMaxStruint16Max String Voltage StringString with maximum voltage.
StrVMinuint16VMin String VoltageMinimum string voltage for all strings in the bank.
StrVMinStruint16Min String Voltage StringString with minimum voltage.
StrVAvguint16VAverage String VoltageAverage string voltage for all strings in the bank.
StrAMaxint16AMax String CurrentMaximum current of any string in the bank.
StrAMaxStruint16Max String Current StringString with the maximum current.
StrAMinint16AMin String CurrentMinimum current of any string in the bank.
StrAMinStruint16Min String Current StringString with the minimum current.
StrAAvgint16AAverage String CurrentAverage string current for all strings in the bank.
NCellBaluint16Battery Cell Balancing CountTotal number of cells that are currently being balanced.
string[*].StrNModuint16Module CountCount of modules in the string.
string[*].StrStbitfield32String StatusCurrent status of the string.
string[*].StrConFailenum16Connection Failure Reason
string[*].StrSoCuint16%String State of ChargeBattery string state of charge, expressed as a percentage.
string[*].StrSoHuint16%String State of HealthBattery string state of health, expressed as a percentage.
string[*].StrAint16AString CurrentString current measurement.
string[*].StrCellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the string.
string[*].StrCellVMaxModuint16Max Cell Voltage ModuleModule containing the maximum cell voltage.
string[*].StrCellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the string.
string[*].StrCellVMinModuint16Min Cell Voltage ModuleModule containing the minimum cell voltage.
string[*].StrCellVAvguint16VAverage Cell VoltageAverage voltage for all cells in the string.
string[*].StrModTmpMaxint16CMax Module TemperatureMaximum temperature for all modules in the bank.
string[*].StrModTmpMaxModuint16Max Module Temperature ModuleModule with the maximum temperature.
string[*].StrModTmpMinint16CMin Module TemperatureMinimum temperature for all modules in the bank.
string[*].StrModTmpMinModuint16Min Module Temperature ModuleModule with the minimum temperature.
string[*].StrModTmpAvgint16CAverage Module TemperatureAverage temperature for all modules in the bank.
string[*].StrDisRsnenum16Disabled ReasonReason why the string is currently disabled.
string[*].StrConStbitfield32Contactor StatusStatus of the contactor(s) for the string.
string[*].StrEvt1bitfield32String Event 1Alarms, warnings and status values.
string[*].StrEvt2bitfield32String Event 2Alarms, warnings and status values.
string[*].StrEvtVnd1bitfield32Vendor String Event Bitfield 1Vendor defined events.
string[*].StrEvtVnd2bitfield32Vendor String Event Bitfield 2Vendor defined events.
string[*].StrSetEnaenum16Enable/Disable StringEnables and disables the string.
string[*].StrSetConenum16Connect/Disconnect StringConnects and disconnects the string.

Value names:

  • string[*].StrSt (bitfield32): bit 0 STRING_ENABLED, bit 1 CONTACTOR_STATUS
  • string[*].StrConFail (enum16): 0 NO_FAILURE, 1 BUTTON_PUSHED, 2 STR_GROUND_FAULT, 3 OUTSIDE_VOLTAGE_RANGE, 4 STRING_NOT_ENABLED, 5 FUSE_OPEN, 6 CONTACTOR_FAILURE, 7 PRECHARGE_FAILURE, 8 STRING_FAULT
  • string[*].StrDisRsn (enum16): 0 NONE, 1 FAULT, 2 MAINTENANCE, 3 EXTERNAL, 4 OTHER
  • string[*].StrConSt (bitfield32): bit 0 CONTACTOR_0, bit 1 CONTACTOR_1, bit 2 CONTACTOR_2, bit 3 CONTACTOR_3, bit 4 CONTACTOR_4, bit 5 CONTACTOR_5, bit 6 CONTACTOR_6, bit 7 CONTACTOR_7, bit 8 CONTACTOR_8, bit 9 CONTACTOR_9, bit 10 CONTACTOR_10, bit 11 CONTACTOR_11, bit 12 CONTACTOR_12, bit 13 CONTACTOR_13, bit 14 CONTACTOR_14, bit 15 CONTACTOR_15, bit 16 CONTACTOR_16, bit 17 CONTACTOR_17, bit 18 CONTACTOR_18, bit 19 CONTACTOR_19, bit 20 CONTACTOR_20, bit 21 CONTACTOR_21, bit 22 CONTACTOR_22, bit 23 CONTACTOR_23, bit 24 CONTACTOR_24, bit 25 CONTACTOR_25, bit 26 CONTACTOR_26, bit 27 CONTACTOR_27, bit 28 CONTACTOR_28, bit 29 CONTACTOR_29, bit 30 CONTACTOR_30
  • string[*].StrEvt1 (bitfield32): bit 0 COMMUNICATION_ERROR, bit 1 OVER_TEMP_ALARM, bit 2 OVER_TEMP_WARNING, bit 3 UNDER_TEMP_ALARM, bit 4 UNDER_TEMP_WARNING, bit 5 OVER_CHARGE_CURRENT_ALARM, bit 6 OVER_CHARGE_CURRENT_WARNING, bit 7 OVER_DISCHARGE_CURRENT_ALARM, bit 8 OVER_DISCHARGE_CURRENT_WARNING, bit 9 OVER_VOLT_ALARM, bit 10 OVER_VOLT_WARNING, bit 11 UNDER_VOLT_ALARM, bit 12 UNDER_VOLT_WARNING, bit 13 UNDER_SOC_MIN_ALARM, bit 14 UNDER_SOC_MIN_WARNING, bit 15 OVER_SOC_MAX_ALARM, bit 16 OVER_SOC_MAX_WARNING, bit 17 VOLTAGE_IMBALANCE_WARNING, bit 18 TEMPERATURE_IMBALANCE_ALARM, bit 19 TEMPERATURE_IMBALANCE_WARNING, bit 20 CONTACTOR_ERROR, bit 21 FAN_ERROR, bit 22 GROUND_FAULT, bit 23 OPEN_DOOR_ERROR, bit 24 RESERVED_1, bit 25 OTHER_ALARM, bit 26 OTHER_WARNING, bit 27 RESERVED_2, bit 28 CONFIGURATION_ALARM, bit 29 CONFIGURATION_WARNING
  • string[*].StrSetEna (enum16): 1 ENABLE_STRING, 2 DISABLE_STRING
  • string[*].StrSetCon (enum16): 1 CONNECT_STRING, 2 DISCONNECT_STRING

Model 804: Lithium-Ion String Model

Name in records: lithium_ion_string.

PointTypeUnitsLabelDescription
Idxuint16String IndexIndex of the string within the bank.
NModuint16Module CountCount of modules in the string.
Stbitfield32String StatusCurrent status of the string.
ConFailenum16Connection Failure Reason
NCellBaluint16String Cell Balancing CountNumber of cells currently being balanced in the string.
SoCuint16%String State of ChargeBattery string state of charge, expressed as a percentage.
DoDuint16%String Depth of DischargeDepth of discharge for the string, expressed as a percentage.
NCycuint32String Cycle CountNumber of discharge cycles executed upon the string.
SoHuint16%String State of HealthBattery string state of health, expressed as a percentage.
Aint16AString CurrentString current measurement.
Vuint16VString VoltageString voltage measurement.
CellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the string.
CellVMaxModuint16Max Cell Voltage ModuleModule containing the cell with maximum cell voltage.
CellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the string.
CellVMinModuint16Min Cell Voltage ModuleModule containing the cell with minimum cell voltage.
CellVAvguint16VAverage Cell VoltageAverage voltage for all cells in the string.
ModTmpMaxint16CMax Module TemperatureMaximum temperature for all modules in the string.
ModTmpMaxModuint16Max Module Temperature ModuleModule with the maximum temperature.
ModTmpMinint16CMin Module TemperatureMinimum temperature for all modules in the string.
ModTmpMinModuint16Min Module Temperature ModuleModule with the minimum temperature.
ModTmpAvgint16CAverage Module TemperatureAverage temperature for all modules in the string.
ConStbitfield32Contactor StatusStatus of the contactor(s) for the string.
Evt1bitfield32String Event 1Alarms, warnings and status values.
Evt2bitfield32String Event 2Alarms, warnings and status values.
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events.
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events.
SetEnaenum16Enable/Disable StringEnables and disables the string. Should reset to 0 upon completion.
SetConenum16Connect/Disconnect StringConnects and disconnects the string.
lithium_ion_string_module[*].ModNCelluint16Module Cell CountCount of all cells in the module.
lithium_ion_string_module[*].ModSoCuint16%Module SoCModule state of charge, expressed as a percentage.
lithium_ion_string_module[*].ModSoHuint16%Module SoHModule state of health, expressed as a percentage.
lithium_ion_string_module[*].ModCellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the module.
lithium_ion_string_module[*].ModCellVMaxCelluint16Max Cell Voltage CellCell with maximum voltage.
lithium_ion_string_module[*].ModCellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the module.
lithium_ion_string_module[*].ModCellVMinCelluint16Min Cell Voltage CellCell with minimum voltage.
lithium_ion_string_module[*].ModCellVAvguint16VAverage Cell VoltageAverage voltage for all cells in the module.
lithium_ion_string_module[*].ModCellTmpMaxint16CMax Cell TemperatureMaximum temperature for all cells in the module.
lithium_ion_string_module[*].ModCellTmpMaxCelluint16Max Cell Temperature CellCell with maximum temperature.
lithium_ion_string_module[*].ModCellTmpMinint16CMin Cell TemperatureMinimum temperature for all cells in the module.
lithium_ion_string_module[*].ModCellTmpMinCelluint16Min Cell Temperature CellCell with minimum temperature.
lithium_ion_string_module[*].ModCellTmpAvgint16CAverage Cell TemperatureAverage temperature for all cells in the module.

Value names:

  • St (bitfield32): bit 0 STRING_ENABLED, bit 1 CONTACTOR_STATUS
  • ConFail (enum16): 0 NO_FAILURE, 1 BUTTON_PUSHED, 2 STR_GROUND_FAULT, 3 OUTSIDE_VOLTAGE_RANGE, 4 STRING_NOT_ENABLED, 5 FUSE_OPEN, 6 CONTACTOR_FAILURE, 7 PRECHARGE_FAILURE, 8 STRING_FAULT
  • ConSt (bitfield32): bit 0 CONTACTOR_0, bit 1 CONTACTOR_1, bit 2 CONTACTOR_2, bit 3 CONTACTOR_3, bit 4 CONTACTOR_4, bit 5 CONTACTOR_5, bit 6 CONTACTOR_6, bit 7 CONTACTOR_7, bit 8 CONTACTOR_8, bit 9 CONTACTOR_9, bit 10 CONTACTOR_10, bit 11 CONTACTOR_11, bit 12 CONTACTOR_12, bit 13 CONTACTOR_13, bit 14 CONTACTOR_14, bit 15 CONTACTOR_15, bit 16 CONTACTOR_16, bit 17 CONTACTOR_17, bit 18 CONTACTOR_18, bit 19 CONTACTOR_19, bit 20 CONTACTOR_20, bit 21 CONTACTOR_21, bit 22 CONTACTOR_22, bit 23 CONTACTOR_23, bit 24 CONTACTOR_24, bit 25 CONTACTOR_25, bit 26 CONTACTOR_26, bit 27 CONTACTOR_27, bit 28 CONTACTOR_28, bit 29 CONTACTOR_29, bit 30 CONTACTOR_30
  • Evt1 (bitfield32): bit 0 COMMUNICATION_ERROR, bit 1 OVER_TEMP_ALARM, bit 2 OVER_TEMP_WARNING, bit 3 UNDER_TEMP_ALARM, bit 4 UNDER_TEMP_WARNING, bit 5 OVER_CHARGE_CURRENT_ALARM, bit 6 OVER_CHARGE_CURRENT_WARNING, bit 7 OVER_DISCHARGE_CURRENT_ALARM, bit 8 OVER_DISCHARGE_CURRENT_WARNING, bit 9 OVER_VOLT_ALARM, bit 10 OVER_VOLT_WARNING, bit 11 UNDER_VOLT_ALARM, bit 12 UNDER_VOLT_WARNING, bit 13 UNDER_SOC_MIN_ALARM, bit 14 UNDER_SOC_MIN_WARNING, bit 15 OVER_SOC_MAX_ALARM, bit 16 OVER_SOC_MAX_WARNING, bit 17 VOLTAGE_IMBALANCE_WARNING, bit 18 TEMPERATURE_IMBALANCE_ALARM, bit 19 TEMPERATURE_IMBALANCE_WARNING, bit 20 CONTACTOR_ERROR, bit 21 FAN_ERROR, bit 22 GROUND_FAULT, bit 23 OPEN_DOOR_ERROR, bit 24 RESERVED_1, bit 25 OTHER_ALARM, bit 26 OTHER_WARNING, bit 27 RESERVED_2, bit 28 CONFIGURATION_ALARM, bit 29 CONFIGURATION_WARNING
  • SetCon (enum16): 1 CONNECT_STRING, 2 DISCONNECT_STRING

Model 805: Lithium-Ion Module Model

Name in records: lithium-ion-module.

PointTypeUnitsLabelDescription
StrIdxuint16String IndexIndex of the string containing the module.
ModIdxuint16Module IndexIndex of the module within the string.
NCelluint16Module Cell CountCount of all cells in the module.
SoCuint16%Module SoCModule state of charge, expressed as a percentage.
DoDuint16%Depth of DischargeDepth of discharge for the module.
SoHuint16%Module SoHModule state of health, expressed as a percentage.
NCycuint32Cycle CountCount of cycles executed.
Vuint16VModule VoltageVoltage of the module.
CellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the module.
CellVMaxCelluint16Max Cell Voltage CellCell with the maximum voltage.
CellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the module.
CellVMinCelluint16Min Cell Voltage CellCell with the minimum voltage.
CellVAvguint16VAverage Cell VoltageAverage voltage for all cells in the module.
CellTmpMaxint16CMax Cell TemperatureMaximum temperature for all cells in the module.
CellTmpMaxCelluint16Max Cell Temperature CellCell with the maximum cell temperature.
CellTmpMinint16CMin Cell TemperatureMinimum temperature for all cells in the module.
CellTmpMinCelluint16Min Cell Temperature CellCell with the minimum cell temperature.
CellTmpAvgint16CAverage Cell TemperatureAverage temperature for all cells in the module.
NCellBaluint16Balanced Cell CountNumber of cells currently being balanced in the module.
SNstringSerial NumberSerial number for the module.
lithium-ion-module-cell[*].CellVuint16VCell VoltageCell terminal voltage.
lithium-ion-module-cell[*].CellTmpint16CCell TemperatureCell temperature.
lithium-ion-module-cell[*].CellStbitfield32Cell StatusStatus of the cell.

Value names:

  • lithium-ion-module-cell[*].CellSt (bitfield32): bit 0 CELL_IS_BALANCING

Model 806: Flow Battery Model

Name in records: flow_battery.

PointTypeUnitsLabelDescription
BatTBDuint16Battery Points To Be Determined
battery_string[*].BatStTBDuint16Battery String Points To Be Determined

Model 807: Flow Battery String Model

Name in records: flow_battery_string.

PointTypeUnitsLabelDescription
Idxuint16String IndexIndex of the string within the bank.
NModuint16Module CountNumber of modules in this string.
NModConuint16Connected Module CountNumber of electrically connected modules in this string.
ModVMaxuint16VMax Module VoltageMaximum voltage for all modules in the string.
ModVMaxModuint16Max Module Voltage ModuleModule with the maximum voltage.
ModVMinuint16VMin Module VoltageMinimum voltage for all modules in the string.
ModVMinModuint16Min Module Voltage ModuleModule with the minimum voltage.
ModVAvguint16VAverage Module VoltageAverage voltage for all modules in the string.
CellVMaxuint16VMax Cell VoltageMaximum voltage for all cells in the string.
CellVMaxModuint16Max Cell Voltage ModuleModule containing the cell with the maximum voltage.
CellVMaxStkuint16Max Cell Voltage StackStack containing the cell with the maximum voltage.
CellVMinuint16VMin Cell VoltageMinimum voltage for all cells in the string.
CellVMinModuint16Min Cell Voltage ModuleModule containing the cell with the minimum voltage.
CellVMinStkuint16Min Cell Voltage StackStack containing the cell with the minimum voltage.
CellVAvguint16VAverage Cell VoltageAverage voltage for all cells in the string.
TmpMaxint16CMax TemperatureMaximum electrolyte temperature for all modules in the string.
TmpMaxModuint16Max Temperature ModuleModule with the maximum temperature.
TmpMinint16CMin TemperatureMinimum electrolyte temperature for all modules in the string.
TmpMinModuint16Min Temperature ModuleModule with the minimum temperature.
TmpAvgint16CAverage TemperatureAverage electrolyte temperature for all modules in the string.
Evt1bitfield32String Event 1Alarms, warnings and status values.
Evt2bitfield32String Event 2Alarms, warnings and status values.
EvtVnd1bitfield32Vendor Event Bitfield 1Vendor defined events.
EvtVnd2bitfield32Vendor Event Bitfield 2Vendor defined events.
module[*].ModIdxuint16Module IndexIndex of the module within the string.
module[*].ModNStkuint16Stack CountNumber of stacks in this module.
module[*].ModStbitfield32Module StatusCurrent status of the module.
module[*].ModSoCuint16%Module State of ChargeState of charge for this module.
module[*].ModOCVuint16VOpen Circuit VoltageOpen circuit voltage for this module.
module[*].ModVuint16VExternal VoltageExternal voltage fo this module.
module[*].ModCellVMaxuint16VMaximum Cell VoltageMaximum voltage for all cells in this module.
module[*].ModCellVMaxCelluint16Max Cell Voltage CellCell with the maximum cell voltage.
module[*].ModCellVMinuint16VMinimum Cell VoltageMinimum voltage for all cells in this module.
module[*].ModCellVMinCelluint16Min Cell Voltage CellCell with the minimum cell voltage.
module[*].ModCellVAvguint16VAverage Cell VoltageAverage voltage for all cells in this module.
module[*].ModAnoTmpuint16CAnolyte Temperature
module[*].ModCatTmpuint16CCatholyte Temperature
module[*].ModConStbitfield32Contactor Status
module[*].ModEvt1bitfield32Module Event 1Alarms, warnings and status values.
module[*].ModEvt2bitfield32Module Event 2Alarms, warnings and status values.
module[*].ModConFailenum16Connection Failure Reason
module[*].ModSetEnaenum16Enable/Disable ModuleEnables and disables the module.
module[*].ModSetConenum16Connect/Disconnect ModuleConnects and disconnects the module.
module[*].ModDisRsnenum16Disabled ReasonReason why the module is currently disabled.

Value names:

  • Evt1 (bitfield32): bit 0 COMMUNICATION_ERROR, bit 1 OVER_TEMP_ALARM, bit 2 OVER_TEMP_WARNING, bit 3 UNDER_TEMP_ALARM, bit 4 UNDER_TEMP_WARNING, bit 5 OVER_CHARGE_CURRENT_ALARM, bit 6 OVER_CHARGE_CURRENT_WARNING, bit 7 OVER_DISCHARGE_CURRENT_ALARM, bit 8 OVER_DISCHARGE_CURRENT_WARNING, bit 9 OVER_VOLT_ALARM, bit 10 OVER_VOLT_WARNING, bit 11 UNDER_VOLT_ALARM, bit 12 UNDER_VOLT_WARNING, bit 13 UNDER_SOC_MIN_ALARM, bit 14 UNDER_SOC_MIN_WARNING, bit 15 OVER_SOC_MAX_ALARM, bit 16 OVER_SOC_MAX_WARNING, bit 17 VOLTAGE_IMBALANCE_WARNING, bit 18 RESERVED_1, bit 19 RESERVED_2, bit 20 CONTACTOR_ERROR, bit 21 FAN_ERROR, bit 22 GROUND_FAULT, bit 23 OPEN_DOOR_ERROR, bit 24 RESERVED_3, bit 25 OTHER_ALARM, bit 26 OTHER_WARNING, bit 27 FIRE_ALARM, bit 28 CONFIGURATION_ALARM, bit 29 CONFIGURATION_WARNING
  • Evt2 (bitfield32): bit 0 LEAK_ALARM, bit 1 PUMP_ALARM, bit 2 HIGH_PRESSURE_ALARM, bit 3 HIGH_PRESSURE_WARNING, bit 4 LOW_FLOW_ALARM, bit 5 LOW_FLOW_WARNING
  • module[*].ModSt (bitfield32): bit 0 MODULE_ENABLED, bit 1 CONTACTOR_STATUS
  • module[*].ModConSt (bitfield32): bit 0 CONTACTOR_0, bit 1 CONTACTOR_1, bit 2 CONTACTOR_2, bit 3 CONTACTOR_3, bit 4 CONTACTOR_4, bit 5 CONTACTOR_5, bit 6 CONTACTOR_6, bit 7 CONTACTOR_7, bit 8 CONTACTOR_8, bit 9 CONTACTOR_9, bit 10 CONTACTOR_10, bit 11 CONTACTOR_11, bit 12 CONTACTOR_12, bit 13 CONTACTOR_13, bit 14 CONTACTOR_14, bit 15 CONTACTOR_15, bit 16 CONTACTOR_16, bit 17 CONTACTOR_17, bit 18 CONTACTOR_18, bit 19 CONTACTOR_19, bit 20 CONTACTOR_20, bit 21 CONTACTOR_21, bit 22 CONTACTOR_22, bit 23 CONTACTOR_23, bit 24 CONTACTOR_24, bit 25 CONTACTOR_25, bit 26 CONTACTOR_26, bit 27 CONTACTOR_27, bit 28 CONTACTOR_28, bit 29 CONTACTOR_29, bit 30 CONTACTOR_30
  • module[*].ModEvt1 (bitfield32): bit 0 COMMUNICATION_ERROR, bit 1 OVER_TEMP_ALARM, bit 2 OVER_TEMP_WARNING, bit 3 UNDER_TEMP_ALARM, bit 4 UNDER_TEMP_WARNING, bit 5 OVER_CHARGE_CURRENT_ALARM, bit 6 OVER_CHARGE_CURRENT_WARNING, bit 7 OVER_DISCHARGE_CURRENT_ALARM, bit 8 OVER_DISCHARGE_CURRENT_WARNING, bit 9 OVER_VOLT_ALARM, bit 10 OVER_VOLT_WARNING, bit 11 UNDER_VOLT_ALARM, bit 12 UNDER_VOLT_WARNING, bit 13 UNDER_SOC_MIN_ALARM, bit 14 UNDER_SOC_MIN_WARNING, bit 15 OVER_SOC_MAX_ALARM, bit 16 OVER_SOC_MAX_WARNING, bit 17 VOLTAGE_IMBALANCE_WARNING, bit 18 RESERVED_1, bit 19 RESERVED_2, bit 20 CONTACTOR_ERROR, bit 21 FAN_ERROR, bit 22 GROUND_FAULT, bit 23 OPEN_DOOR_ERROR, bit 24 RESERVED_3, bit 25 RESERVED_4, bit 26 RESERVED_5, bit 27 FIRE_ALARM, bit 28 MODULE_CONFIGURATION_ALARM, bit 29 MODULE_CONFIGURATION_WARNING
  • module[*].ModEvt2 (bitfield32): bit 0 LEAK_ALARM, bit 1 PUMP_ALARM, bit 2 HIGH_PRESSURE_ALARM, bit 3 HIGH_PRESSURE_WARNING, bit 4 LOW_FLOW_ALARM, bit 5 LOW_FLOW_WARNING
  • module[*].ModConFail (enum16): 0 NO_FAILURE, 1 BUTTON_PUSHED, 2 MODULE_GROUND_FAULT, 3 OUTSIDE_VOLTAGE_RANGE, 4 MODULE_NOT_ENABLED, 5 FUSE_OPEN, 6 CONTACTOR_FAILURE, 7 PRECHARGE_FAILURE, 8 MODULE_FAULT
  • module[*].ModSetEna (enum16): 1 ENABLE_MODULE, 2 DISABLE_MODULE
  • module[*].ModSetCon (enum16): 1 CONNECT_MODULE, 2 DISCONNECT_MODULE
  • module[*].ModDisRsn (enum16): 0 NONE, 1 FAULT, 2 MAINTENANCE, 3 EXTERNAL, 4 OTHER

Model 808: Flow Battery Module Model

Name in records: flow_battery_module.

PointTypeUnitsLabelDescription
ModuleTBDuint16Module Points To Be Determined
stack[*].StackTBDuint16Stack Points To Be Determined

Model 809: Flow Battery Stack Model

Name in records: flow_battery_stack.

PointTypeUnitsLabelDescription
StackTBDuint16Stack Points To Be Determined
cell[*].CellTBDuint16Cell Points To Be Determined

Test and vendor models

Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.

ModelName in recordsPoints
63001model_6300159
63002model_630022
64001model_6400135
64020model_6402030
64101model_641017
64111model_6411118
64112model_6411258
64410DCSimInterface32
64411ACSimInterface51
64412DERExploitation43
64413PVSimCurves5
64414DERSimControls9
64415CSIPControl5

Model 63001: SunSpec Test Model 1

Name in records: model_63001.

PointTypeUnitsLabelDescription
int16_1int16
int16_2int16
int16_3int16
int16_4int16
int16_5int16
int16_uint16
uint16_1uint16
uint16_2uint16
uint16_3uint16
uint16_4uint16
uint16_5uint16
uint16_uuint16
acc16acc16
acc16_uacc16
enum16enum16
enum16_uenum16
bitfield16bitfield16
bitfield16_ubitfield16
int32_1int32
int32_2int32
int32_3int32
int32_4int32
int32_5int32
int32_uint32
uint32_1uint32
uint32_2uint32
uint32_3uint32
uint32_4uint32
uint32_5uint32
uint32_uuint32
acc32acc32
acc32_uacc32
enum32enum32
enum32_uenum32
bitfield32bitfield32
bitfield32_ubitfield32
ipaddripaddr
ipaddr_uipaddr
int64int64
int64_uint64
acc64acc64
acc64_uacc64
ipv6addripv6addr
ipv6addr_uipv6addr
float32float32
float32_ufloat32
stringstring
string_ustring
repeating[*].int16_11int16
repeating[*].int16_12int16
repeating[*].int16_uint16
repeating[*].uint16_11uint16
repeating[*].uint16_12uint16
repeating[*].uint16_13uint16
repeating[*].uint16_uuint16
repeating[*].int32int32
repeating[*].int32_uint32
repeating[*].uint32uint32
repeating[*].uint32_uuint32

Model 63002: SunSpec Test Model 2

Name in records: model_63002.

PointTypeUnitsLabelDescription
repeating[*].int16_1int16
repeating[*].int16_2int16

Model 64001: Veris Status and Configuration

Name in records: model_64001.

PointTypeUnitsLabelDescription
Cmdenum16Command Code
HWRevuint16Hardware Revision
RSFWRevuint16RS FW Revision
OSFWRevuint16OS FW Revision
ProdRevstringProduct Revision
Bootsuint16Boot Count
Switchbitfield16DIP Switches
Sensorsuint16Num Detected Sensors
Talkinguint16Num Communicating Sensors
Statusbitfield16System Status
Configbitfield16System Configuration
LEDblinkuint16PctLED Blink Threshold
LEDonuint16PctLED On Threshold
Reserveduint16
LocstringLocation String
S1IDenum16Sensor 1 Unit ID
S1Addruint16Sensor 1 Address
S1OSVeruint16Sensor 1 OS Version
S1VerstringSensor 1 Product Version
S1SerialstringSensor 1 Serial Num
S2IDenum16Sensor 2 Unit ID
S2Addruint16Sensor 2 Address
S2OSVeruint16Sensor 2 OS Version
S2VerstringSensor 2 Product Version
S2SerialstringSensor 2 Serial Num
S3IDenum16Sensor 3 Unit ID
S3Addruint16Sensor 3 Address
S3OSVeruint16Sensor 3 OS Version
S3VerstringSensor 3 Product Version
S3SerialstringSensor 3 Serial Num
S4IDenum16Sensor 4 Unit ID
S4Addruint16Sensor 4 Address
S4OSVeruint16Sensor 4 OS Version
S4VerstringSensor 4 Product Version
S4SerialstringSensor 4 Serial Num

Model 64020: Mersen GreenString

Name in records: model_64020.

PointTypeUnitsLabelDescription
Aux0Tmpint16CAux 0 temperature
Aux1Tmpint16CAux 1 temperature
Aux2Tmpint16CAux 2 temperature
Aux3Tmpint16CAux 3 temperature
Aux4Tmpint16CAux 4 temperature
ProbeTmpint16CProbe Temperature
MainTmpint16CMain Temperature
Sensor1Voltageint16VSensor1 Voltagescale of 0-10V
Sensor2Voltageint16VSensor2 Voltagescale of 0-10V
Sensor3Voltageint16VSensor3 Voltagescale of 0-10V
Sensor4Voltageint16VSensor4 Voltagescale of 0-10V
Sensor5Voltageint16VSensor5 Voltagescale of 0-10V
Sensor6Voltageint16VSensor6 Voltagescale of 0-10V
Sensor7Voltageint16VSensor7 Voltagescale of 0-10V
Sensor1Currentint16ASensor1 Currentscale of 4-20mA
Sensor2Currentint16ASensor2 Currentin 4-20mA or 4-20mA
Sensor3Currentint16ASensor3 Currentin 4-20mA or 4-20mA
Sensor4Currentint16ASensor4 Currentin 4-20mA or 4-20mA
Sensor5Currentint16ASensor5 Currentin 4-20mA or 4-20mA
Sensor6Currentint16ASensor6 Currentin 4-20mA or 4-20mA
Sensor7Currentint16ASensor7 Currentin 4-20mA or 4-20mA
Sensor8uint16HzSensor8 frequencyfrequency in Hz
Relay1uint16Relay 1 state
Relay2uint16Relay 2 state
Relay3uint16Relay 3 state
ResetAccumulatorsuint16Reset the accumulatorsalways 0 in reading, used the code 0xC0DA during the writing for resetting them
Resetuint16Reset the systemalways 0 in reading, used the code 0xC0DA during the writing for resetting the system
repeating[*].SerialNumberstringSerial numberstrings of 16 characters
repeating[*].FirmwarestringFirmware versionstring of 11 characters
repeating[*].Hardwareuint16Hardware version

Model 64101: Eltek Inverter Extension

Name in records: model_64101.

PointTypeUnitsLabelDescription
Eltek_Country_Codeuint16
Eltek_Feeding_Phaseuint16
Eltek_APD_Methoduint16
Eltek_APD_Power_Refuint16
Eltek_RPS_Methoduint16
Eltek_RPS_Q_Refuint16
Eltek_RPS_CosPhi_Refint16

Model 64111: Basic Charge Controller

Name in records: model_64111.

PointTypeUnitsLabelDescription
Portuint16Port Number
BattVuint16VBattery Voltage
ArrayVuint16VArray Voltage
OutputAuint16AOutput Current
InputAuint16AArray Current
ChargerStenum16Operating State
OutputWuint16WOutput Wattage
TodayMinBatVuint16VToday's Minimum Battery Voltage
TodayMaxBatVuint16VToday's Maximum Battery Voltage
VOCVuint16VVOC
TodayMaxVOCuint16VToday's Maximum VOC
TodaykWhOutputuint16kWhToday's kWh
TodayAHOutputuint16AHToday's AH
LifeTimeKWHOutuint16kWhLifetime kWh
LifeTimeAHOutuint16kAHLifetime kAH
LifeTimeMaxOutuint16WLifetime Maximum Output Wattage
LifeTimeMaxBattuint16VLifetime Maximum Battery Voltage
LifeTimeMaxVOCuint16VLifetime Maximum VOC Voltage

Value names:

  • ChargerSt (enum16): 0 Off, 1 Float, 2 Bulk, 3 Absorb, 4 EQ

Model 64112: OutBack FM Charge Controller

Name in records: model_64112.

PointTypeUnitsLabelDescription
Portuint16Port Number
CC_Config_faultbitfield16Faults
CC_Config_absorb_Vuint16VAbsorb
CC_Config_absorb_Hruint16TmhAbsorb Time
CC_Config_absorb_End_Auint16AAbsorb End
CC_Config_rebulk_Vuint16VRebulk
CC_Config_float_Vuint16VFloat
CC_Config_max_Chg_Auint16AMaximum Charge
CC_Config_equalize_Vuint16VEqualize
CC_Config_equalize_Hruint16TmhEqualize Time
CC_Config_auto_equalizeuint16TmdAuto Equalize Interval
CC_Config_MPPT_modeenum16MPPT mode
CC_Config_sweep_widthenum16Sweep Width
CC_Config_sweep_maxenum16Sweep Maximum
CC_Config_U_Pick_Duty_cycuint16PctU-Pick PWM Duty Cycle
CC_Config_grid_tieenum16Grid Tie Mode
CC_Config_temp_compenum16Temp Comp Mode
CC_Config_temp_comp_llimtuint16VTemp Comp Lower Limit
CC_Config_temp_comp_hlimtuint16VTemp Comp Upper Limit
CC_Config_auto_restartenum16Auto Restart Mode
CC_Config_wakeup_VOCuint16VWakeup VOC Change
CC_Config_snooze_mode_Auint16ASnooze Mode
CC_Config_wakeup_intervaluint16TmsWakeup Interval
CC_Config_AUX_modeenum16AUX Output Mode
CC_Config_AUX_controlenum16AUX Output Control
CC_Config_AUX_stateenum16AUX Output State
CC_Config_AUX_polarityenum16AUX Output Polarity
CC_Config_AUX_L_Batt_discuint16VAUX Low Battery Disconnect
CC_Config_AUX_L_Batt_rconuint16VAUX Low Battery Reconnect
CC_Config_AUX_L_Batt_dlyuint16TmsAUX Low Battery Disconnect Delay
CC_Config_AUX_Vent_fan_Vuint16VAUX Vent Fan
CC_Config_AUX_PV_triggerVuint16VAUX PV Trigger
CC_Config_AUX_PV_trg_h_tmuint16TmsAUX PV Trigger Hold Time
CC_Config_AUX_Nlite_ThrsVuint16VAUX Night Light Threshold
CC_Config_AUX_Nlite_On_tmuint16TmhAUX Night Light On Time
CC_Config_AUX_Nlite_On_histuint16TmsAUX Night Light On Hysteresis
CC_Config_AUX_Nlite_Off_histuint16TmsAUX Night Light Off Hysteresis
CC_Config_AUX_Error_batt_Vuint16VAUX Error Output Low Battery
CC_Config_AUX_Divert_h_timeuint16TmsAUX Divert Hold Time
CC_Config_AUX_Divert_dly_timeuint16TmsAUX Divert Delay Time
CC_Config_AUX_Divert_Rel_Vuint16VAUX Divert Relative
CC_Config_AUX_Divert_Hyst_Vuint16VAUX Divert Hysteresis
CC_Config_MajorFWRevuint16FM CC Major Firmware Number
CC_Config_MidFWRevuint16FM CC Mid Firmware Number
CC_Config_MinorFWRevuint16FM CC Minor Firmware Number
CC_Config_DataLog_Day_offsetuint16TmdSet Data Log Day Offset
CC_Config_DataLog_Cur_Day_offuint16TmdCurrent Data Log Day Offset
CC_Config_DataLog_Daily_AHuint16AhData Log Daily (Ah)
CC_Config_DataLog_Daily_KWHuint16kWhData Log Daily (kWh)
CC_Config_DataLog_Max_Out_Auint16AData Log Daily Maximum Output (A)
CC_Config_DataLog_Max_Out_Wuint16WData Log Daily Maximum Output (W)
CC_Config_DataLog_Absorb_Tuint16TmsData Log Daily Absorb Time
CC_Config_DataLog_Float_Tuint16TmsData Log Daily Float Time
CC_Config_DataLog_Min_Batt_Vuint16VData Log Daily Minimum Battery
CC_Config_DataLog_Max_Batt_Vuint16VData Log Daily Maximum Battery
CC_Config_DataLog_Max_Input_Vuint16VData Log Daily Maximum Input
CC_Config_DataLog_Clearuint16Data Log Clear
CC_Config_DataLog_Clr_Compuint16Data Log Clear Complement

Value names:

  • CC_Config_MPPT_mode (enum16): 0 Auto, 1 U_Pick, 2 Wind
  • CC_Config_sweep_width (enum16): 0 Half, 1 Full
  • CC_Config_sweep_max (enum16): 0 Eighty_Percent, 1 Eighty_Five_Percent, 2 Ninty_Percent, 3 Ninty_Nine_Percent
  • CC_Config_grid_tie (enum16): 0 Disabled, 1 Enabled
  • CC_Config_temp_comp (enum16): 0 Wide, 1 Limited
  • CC_Config_auto_restart (enum16): 0 Off, 1 Every_90_Minutes, 2 Every_90_Minutes_if_Absorb_or_Float
  • CC_Config_AUX_mode (enum16): 0 Float, 1 Diversion_Relay, 2 Diversion_Solid_St, 3 Low_Batt_Disconnect, 4 Remote, 5 Vent_Fan, 6 PV_Trigger, 7 Error_Output, 8 Night_Light
  • CC_Config_AUX_control (enum16): 0 Off, 1 Auto, 2 On
  • CC_Config_AUX_state (enum16): 0 Disabled, 1 Enabled
  • CC_Config_AUX_polarity (enum16): 0 Low, 1 High

Model 64410: DC Simulator Control Interface

Name in records: DCSimInterface.

A generic DC simulator/power supply control interface for DER electrical testing.

PointTypeUnitsLabelDescription
VMaxLimuint16VMaximum VoltageUpper Voltage Protection Limit
PMaxLimuint16WMaximum PowerUpper Power Protection Limit
IMaxLimuint16AMaximum CurrentUpper Current Protection Limit
Modeenum16CV or CC ModeConstant Voltage (CV) or Constant Current (CC) Mode
Enaenum16Power On/OffPower On/Off
Resetenum16Reset DeviceReset Device
VSetuint16VVoltage SetpointVoltage Setpoint
PSetuint16WPower SetpointPower Setpoint
ISetuint16ACurrent SetpointCurrent Setpoint
EN50530enum16EN50530 ModeEN50530 Mode - Enable or disable EN50530 profile mode
Vmppuint16VEN50530 MPP VoltageEN50530 MPP Voltage
Pmppuint16WEN50530 MPP PowerEN50530 MPP Power
GSetuint16W/m^2Irradiance SetpointIrradiance Setpoint
VSlewRateuint16V/sVoltage Slew RateVoltage Slew Rate
PSlewRateuint16W/sPower Slew RatePower Slew Rate
ISlewRateuint16A/sCurrent Slew RateCurrent Slew Rate
EnaProfenum16Enable ProfileStart/Stop the Profile
AdptProfRequint16Profile Adoption RequestIndex of profile points to adopt. First curve index is 1.
AdptProfRsltenum16Adopt Profile ResultResult of last adopt profile operation.
Vint32Measured VoltageMeasured Voltage
Pint32Measured PowerMeasured Power
Iint32Measured CurrentMeasured Current
ErrorsstringErrorsError States
NPtuint16Number Of PointsNumber of profile points supported.
NProfuint16Stored Profile CountNumber of stored profiles supported.
Prof[*].ActPtuint16Active PointsNumber of active points.
Prof[*].DeptRefbitfield32Dependent ReferencesProfile references.
Prof[*].Pt[*].Tmsuint16SecProfile TimeProfile time.
Prof[*].Pt[*].Vuint16VVoltage PointProfile voltage point in Volts.
Prof[*].Pt[*].Puint16WPower PointProfile power point in Watts.
Prof[*].Pt[*].Iuint16ACurrent PointProfile current point in Amps.
Prof[*].Pt[*].Guint16PctIrradiance PointProfile irradiance point as percentage.

Value names:

  • Mode (enum16): 0 CV, 1 CC
  • Ena (enum16): 1 ON, 0 OFF
  • Reset (enum16): 1 RESET, 0 DO_NOT_RESET
  • EN50530 (enum16): 1 EN50530, 0 DO_NOT_EN50530
  • EnaProf (enum16): 1 START, 0 STOP
  • AdptProfRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED
  • Prof[*].DeptRef (bitfield32): bit 0 VOLTAGE, bit 1 POWER, bit 2 CURRENT, bit 3 IRRADIANCE

Model 64411: AC Simulator Control Interface

Name in records: ACSimInterface.

A generic AC simulator/power supply control interface for DER electrical testing.

PointTypeUnitsLabelDescription
Phasesuint16Active PhasesSet the number of active phases for the power supply
PhaseAngleuint16Phase AnglePhase angle (deg) between phases. 0 for signle phase, 120 for two phase, 120 for three phase.
VNomuint16VNominal VoltageNominal L-N Voltage
VMaxuint16VMaximum VoltageMaximum Voltage Protection Level
IMaxuint16AMaximum CurrentMaximum Current Protection Level
Frequint16HzFrequencyFrequency Setpoint
Outputenum16Output StateAC Output State
Relayenum16Relay StateAC Relay State
Regenenum16Regeneration StateRegeneration State
VSetuint16VVoltage SetpointVoltage Setpoint (all phases)
VSetAuint16VVoltage Setpoint Phase AVoltage Setpoint Phase A
VSetBuint16VVoltage Setpoint Phase BVoltage Setpoint Phase B
VSetCuint16VVoltage Setpoint Phase CVoltage Setpoint Phase C
FreqSlewuint16Hz/sFrequency Slew RateFrequency Slew Rate
VSlewuint16V/sVoltage Slew RateVoltage Slew Rate
VAint32VMeasured Voltage Phase AMeasured Voltage Phase A
VBint32VMeasured Voltage Phase BMeasured Voltage Phase B
VCint32VMeasured Voltage Phase CMeasured Voltage Phase C
Hzint32HzMeasured FrequencyMeasured Frequency
IAint32AMeasured Current Phase AMeasured Current Phase A
IBint32AMeasured Current Phase BMeasured Current Phase B
ICint32AMeasured Current Phase CMeasured Current Phase C
VHarAstringVVoltage Harmonics Phase AVoltage Harmonics Pct, Phase A (comma seperated string for harmonics 1-50)
VHarBstringVVoltage Harmonics Phase BVoltage Harmonics Pct, Phase B (comma seperated string for harmonics 1-50)
VHarCstringVVoltage Harmonics Phase CVoltage Harmonics Pct, Phase C (comma seperated string for harmonics 1-50)
IHarAstringACurrent Harmonics Phase ACurrent Harmonics Pct, Phase A (comma seperated string for harmonics 1-50)
IHarBstringACurrent Harmonics Phase BCurrent Harmonics Pct, Phase B (comma seperated string for harmonics 1-50)
IHarCstringACurrent Harmonics Phase CCurrent Harmonics Pct, Phase C (comma seperated string for harmonics 1-50)
IIntHarAstringACurrent Interharmonics Phase ACurrent Interharmonics Pct, Phase A (comma seperated string for interharmonics 1-50)
IIntHarBstringACurrent Interharmonics Phase BCurrent Interharmonics Pct, Phase B (comma seperated string for interharmonics 1-50)
IIntHarCstringACurrent Interharmonics Phase CCurrent Interharmonics Pct, Phase C (comma seperated string for interharmonics 1-50)
VThdAuint16%Voltage THD Phase AVoltage THD Phase A
VThdBuint16%Voltage THD Phase BVoltage THD Phase B
VThdCuint16%Voltage THD Phase CVoltage THD Phase C
IThdAuint16%Current THD Phase ACurrent THD Phase A
IThdBuint16%Current THD Phase BCurrent THD Phase B
IThdCuint16%Current THD Phase CCurrent THD Phase C
EnaProfenum16Enable ProfileStart/Stop the AC Profile
ProfRsltenum16Profile ResultResult of last profile operation.
NProfuint16Stored Profile CountNumber of stored profiles supported.
NPtuint16Max Profile Point CountMax profile points in the profiles.
Prof[*].NamestringProfile NameProfile name.
Prof[*].ActPtuint16Active PointsNumber of active points.
Prof[*].Pt[*].Tmsuint16SecProfile TimeProfile time.
Prof[*].Pt[*].VAuint16VVoltage PointProfile voltage phase A point in Volts.
Prof[*].Pt[*].VBuint16VVoltage Point Phase BProfile voltage phase B point in Volts.
Prof[*].Pt[*].VCuint16VVoltage Point Phase CProfile voltage phase C point in Volts.
Prof[*].Pt[*].Hzuint16HzFrequency PointProfile frequency point in Hz.
Prof[*].Pt[*].PhaseAngleAuint16DegreesPhase Angle AProfile phase A angle in degrees.
Prof[*].Pt[*].PhaseAngleBuint16DegreesPhase Angle BProfile phase B angle in degrees.
Prof[*].Pt[*].PhaseAngleCuint16DegreesPhase Angle CProfile phase C angle in degrees.

Value names:

  • Output (enum16): 0 OFF, 1 ON
  • Relay (enum16): 0 OPEN, 1 CLOSED
  • Regen (enum16): 0 OFF, 1 ON
  • EnaProf (enum16): 0 STOP, 1 START, 2 TRIGGER
  • ProfRslt (enum16): 0 IN_PROGRESS, 1 COMPLETED, 2 FAILED

Model 64412: DER Cyber Exploitation

Name in records: DERExploitation.

Operations that represent a hacked DER device

PointTypeUnitsLabelDescription
DAManipulationenum16DA ManipulationModify the device ID of the DER
FalsifyDeviceIdentityenum16Falsify Device IdentityChange the DER manufacturer and model
MeasPAlwaysNameplateenum16Meas P Always NameplateSet the DER meas to always be at nameplate power
MeasQAlwaysMinimumenum16Meas Q Always MinimumSet the DER to always be at minimum reactive power
MeasQAlwaysMaximumenum16Meas Q Always MaximumSet the DER to always be at maximum reactive power
MeasQAlwaysZeroenum16Meas Q Always ZeroSet the DER to always be at zero reactive power
MeasZeroPenum16Meas Zero PSet the DER to always be at zero P, Q, and S
MeasInvertQenum16Meas Invert QSet the DER to reverse the Q measurement data
MeasLowVenum16Meas Low VSet the DER to always measure low voltage
MeasHighVenum16Meas High VSet the DER to always measure high voltage
MeasLowL1Venum16Meas Low L1 VSet the DER to always measure low line 1 voltage
MeasHighL1Venum16Meas High L1 VSet the DER to always measure high line 1 voltage
MeasLowFenum16Meas Low FSet the DER to always measure low frequency
MeasHighFenum16Meas High FSet the DER to always measure high frequency
MeasLowAmpsenum16Meas Low AmpsSet the DER to always measure low current
MeasHighAmpsenum16Meas High AmpsSet the DER to always measure high current
MeasHighSenum16Meas High SSet the DER to always measure high apparent power
MeasLowSenum16Meas Low SSet the DER to always measure low apparent power
MeasHighQenum16Meas High QSet the DER to always measure high reactive power
MeasLowQenum16Meas Low QSet the DER to always measure low reactive power
MeasLowPFenum16Meas Low PFSet the DER to always measure low power factor
MeasLowReversedPFenum16Meas Low Reversed PFSet the DER to always measure low reversed power factor
NameplateHighPenum16Nameplate High PSet the DER nameplate power to be high
NameplateLowPenum16Nameplate Low PSet the DER nameplate power to be low
NameplateHighSenum16Nameplate High SSet the DER nameplate apparent power to be high
NameplateLowSenum16Nameplate Low SSet the DER nameplate apparent power to be low
NameplateHighQenum16Nameplate High QSet the DER nameplate reactive power to be high
NameplateLowQenum16Nameplate Low QSet the DER nameplate reactive power to be low
NameplateHighNomVenum16Nameplate High Nom VSet the DER nameplate voltage to be high
NameplateLowNomVenum16Nameplate Low Nom VSet the DER nameplate voltage to be low
NameplateLowAmpsenum16Nameplate Low AmpsSet the DER nameplate current to be low
NameplateLowVarmaxinjenum16Nameplate Low VarmaxinjSet the DER nameplate VarMaxInj to be low
NameplateLowVarmaxabsenum16Nameplate Low VarmaxabsSet the DER nameplate VarMaxAbs to be low
NameplateLowPFenum16Nameplate Low PFSet the DER nameplate power factor to be low
SettingsHighNomVenum16Settings High Nom VSet the DER settings voltage to be high
SettingsLowAmpsenum16Settings Low AmpsSet the DER settings current to be low
SettingsHighPenum16Settings High PSet the DER settings power to be high
SettingsLowPenum16Settings Low PSet the DER settings power to be low
SettingsHighVAMaxenum16Settings High VAMaxSet the DER settings VAMax to be high
SettingsHighVarmaxinjenum16Settings High VarmaxinjSet the DER settings VarMaxInj to be high
SettingsHighVarmaxabsenum16Settings High VarmaxabsSet the DER settings VarMaxAbs to be high
ChangeCommonModelIDenum16Change Common Model IDChange the common model ID
ChangeCommonModelLengthenum16Change Common Model LengthChange the common model length

Value names:

  • DAManipulation (enum16): 0 OFF, 1 ON
  • FalsifyDeviceIdentity (enum16): 0 OFF, 1 ON
  • MeasPAlwaysNameplate (enum16): 0 OFF, 1 ON
  • MeasQAlwaysMinimum (enum16): 0 OFF, 1 ON
  • MeasQAlwaysMaximum (enum16): 0 OFF, 1 ON
  • MeasQAlwaysZero (enum16): 0 OFF, 1 ON
  • MeasZeroP (enum16): 0 OFF, 1 ON
  • MeasInvertQ (enum16): 0 OFF, 1 ON
  • MeasLowV (enum16): 0 OFF, 1 ON
  • MeasHighV (enum16): 0 OFF, 1 ON
  • MeasLowL1V (enum16): 0 OFF, 1 ON
  • MeasHighL1V (enum16): 0 OFF, 1 ON
  • MeasLowF (enum16): 0 OFF, 1 ON
  • MeasHighF (enum16): 0 OFF, 1 ON
  • MeasLowAmps (enum16): 0 OFF, 1 ON
  • MeasHighAmps (enum16): 0 OFF, 1 ON
  • MeasHighS (enum16): 0 OFF, 1 ON
  • MeasLowS (enum16): 0 OFF, 1 ON
  • MeasHighQ (enum16): 0 OFF, 1 ON
  • MeasLowQ (enum16): 0 OFF, 1 ON
  • MeasLowPF (enum16): 0 OFF, 1 ON
  • MeasLowReversedPF (enum16): 0 OFF, 1 ON
  • NameplateHighP (enum16): 0 OFF, 1 ON
  • NameplateLowP (enum16): 0 OFF, 1 ON
  • NameplateHighS (enum16): 0 OFF, 1 ON
  • NameplateLowS (enum16): 0 OFF, 1 ON
  • NameplateHighQ (enum16): 0 OFF, 1 ON
  • NameplateLowQ (enum16): 0 OFF, 1 ON
  • NameplateHighNomV (enum16): 0 OFF, 1 ON
  • NameplateLowNomV (enum16): 0 OFF, 1 ON
  • NameplateLowAmps (enum16): 0 OFF, 1 ON
  • NameplateLowVarmaxinj (enum16): 0 OFF, 1 ON
  • NameplateLowVarmaxabs (enum16): 0 OFF, 1 ON
  • NameplateLowPF (enum16): 0 OFF, 1 ON
  • SettingsHighNomV (enum16): 0 OFF, 1 ON
  • SettingsLowAmps (enum16): 0 OFF, 1 ON
  • SettingsHighP (enum16): 0 OFF, 1 ON
  • SettingsLowP (enum16): 0 OFF, 1 ON
  • SettingsHighVAMax (enum16): 0 OFF, 1 ON
  • SettingsHighVarmaxinj (enum16): 0 OFF, 1 ON
  • SettingsHighVarmaxabs (enum16): 0 OFF, 1 ON
  • ChangeCommonModelID (enum16): 0 OFF, 1 ON
  • ChangeCommonModelLength (enum16): 0 OFF, 1 ON

Model 64413: PV Curves

Name in records: PVSimCurves.

Current-Voltage and Power-Voltage Profiles for PV Simulation.

PointTypeUnitsLabelDescription
IVLencountIV lengthNumber of points in the IV curve.
Irruint16W/m^2POA IrradiancePlane of Array Irradiance
IV[*].Pfloat32WPowerPower
IV[*].Ifloat32ACurrentCurrent
IV[*].Vfloat32VVoltageVoltage

Model 64414: DER Simulation Controls

Name in records: DERSimControls.

Configuration parameters for the DER device simulator.

PointTypeUnitsLabelDescription
TimestringTime offsetTime offset for simulation formatted 'HH:MM:SS'
Temperaturefloat32Ambient temperature (degrees Celsius)
GridModelSourcestringThe data source for the grid model. 'csv' or 'const'
IrradianceModelSourcestringThe data source for the irradiance model. 'csv' or 'const'
Irradiancefloat32The irradiance on the DER device (W/m^2) for the 'const' irradiance model
GridVoltageAfloat32Phase A RMS Voltage (pu) for the 'const' grid model
GridVoltageBfloat32Phase B RMS Voltage (pu) for the 'const' grid model
GridVoltageCfloat32Phase C RMS Voltage (pu) for the 'const' grid model
GridFrequencyfloat32Grid frequency (Hz) for the 'const' grid model

Model 64415: CSIP Client Control

Name in records: CSIPControl.

CSIP Client Control for Alarms and Error tests

PointTypeUnitsLabelDescription
LogEventEnaenum16LogEvent Mode EnableEnable or disable the LogEvent mode
HTTPMsgenum16HTTP Message Mode EnableEnable or disable the HTTP Message mode
COMM004Certenum16COMM-004 CertificateSelect COMM-004 certificate type
SubscribedResourcestringSubscribed Resource URLThe URL of the resource to subscribe to
SubscriptionEnaenum16Subscribtion EnableEnable or disable the Subscription mode

Value names:

  • LogEventEna (enum16): 0 DISABLED, 1 ENABLED
  • HTTPMsg (enum16): 0 DISABLED, 1 ENABLED
  • COMM004Cert (enum16): 0 Default Certificate, 1 COMM_004A, 2 COMM_004B, 3 COMM_004C, 4 COMM_004D, 5 COMM_004E, 6 COMM_004F, 7 COMM_004G
  • SubscriptionEna (enum16): 0 DISABLED, 1 ENABLED

Architecture Guide

This document describes how Rusty SunSpec Collector is built: the crates and what each one owns, the life of a device, how registers become Kafka records, what is durable and what is not, and why the main design choices were made.

For building and testing see the Developer Guide and Building & Packaging. Every setting mentioned here is listed in Configuration; deployment is covered in Deployment, metrics in Metrics and alerts in Monitoring and alerts.


Overview

The collector is a single Rust binary (collector-app) that runs at the edge, next to the devices. It:

  1. finds SunSpec devices (inverters, meters, batteries) on Modbus TCP, either from a static list or by scanning a subnet;
  2. walks each device's SunSpec model chain and then reads every model on a fixed schedule;
  3. decodes each model's registers into named points with the official SunSpec model definitions (scale factors applied, "not implemented" values mapped to null);
  4. buffers the samples in a local SQLite database;
  5. publishes each sample to Kafka as one Avro single-object record keyed by device.

Delivery is at-least-once: a buffered sample is deleted only after Kafka has acknowledged it.

  Modbus TCP                    one supervised task per device (poller-actor)
 +------------+  read models  +-------------------+
 | 10.0.0.5/1 | <-----------> | device supervisor |---+
 +------------+               +-------------------+   |
 +------------+               +-------------------+   |   Sample
 | 10.0.0.5/2 | <-----------> | device supervisor |---+-------------+
 +------------+               +-------------------+   bounded mpsc  |
                                                      (256)         v
                                                              +-----------+
                                    batches of up to 256 rows |  ingest   |
                                    in one transaction        +-----------+
                                                                    |
                                                                    v
                     every 10 s: drop rows past        +-------------------------+
                     max_age / beyond max_rows ------> |  SQLite buffer (WAL)    |
                     (retention)                       |  table telemetry_queue  |
                                                       +-------------------------+
                                                                    | oldest rows first,
                                                                    | buffer.batch_size per batch
                                                                    v
 +-------------+   one record per sample, key host/unit       +-----------+
 |    Kafka    | <------------------------------------------- |  uplink   |
 +-------------+   delete only the rows Kafka acknowledged    +-----------+

 HTTP on metrics.port: /metrics (Prometheus), /healthz and /devices; systemd watchdog on Linux.

Workspace and crate responsibilities

The workspace has nine crates under crates/. Only collector-app and sunspec-sim build binaries; everything else is a library.

CrateResponsibility
typesShared data types, depends only on serde: DeviceIdentity { ip, unit_id } with key() = "host/unit", Sample (one model read from one device: device, model ID and name, start address, raw registers, read time, decoded points) and PointValue (Null, Int(i64), Float(f64), Text(String)).
discoveryIPv4 CIDR subnet scan: a TCP connect probe on the Modbus port for every host, with bounded concurrency and a per-host timeout. Each responding host yields one device per configured unit ID.
modbus-clientModbus TCP client on tokio-modbus 0.17: splits reads at 125 registers, retries with exponential backoff, reconnects after connection errors, resolves hostnames.
sunspec-parserThe official SunSpec JSON model definitions, embedded at compile time, and decode_model(), which turns a model's registers into named points. Also model_name(), SUNSPEC_ID and SUNSPEC_END_ID.
poller-actorThe per-device lifecycle: run_device() supervises sessions (connect, discover the model chain, poll) with backoff and stall detection, and records per-device metrics. discover_models() walks the chain.
bufferBufferStore, a SQLite queue (sqlx 0.8): batched enqueue, dequeue of the oldest rows, delete by ID, row-count and age retention.
avro-kafkaThe record schema (schemas/sunspec_sample.avsc), single-object encode/decode, and Publisher, which sends to Kafka through rdkafka 0.39, only logs (no brokers configured), or keeps records in memory (tests).
collector-appThe binary and a library (collector_app::run, run_with_publisher) with the orchestration modules: config, devices, ingest, uplink, retention, health, metrics_server, systemd.
sunspec-simA SunSpec Modbus TCP simulator (library and sunspec-sim binary) with fault injection, used by the tests and the Docker Compose stack.

Dependency direction

Dependencies point from the application towards the shared types; no library depends on collector-app, and types depends on nothing in the workspace.

collector-app
  |-- poller-actor --+-- modbus-client
  |                  +-- sunspec-parser -- types
  |                  +-- types
  |-- discovery ------- types
  |-- avro-kafka ------ types
  |-- buffer            (stores opaque bytes; no workspace dependencies)
  |-- modbus-client, sunspec-parser, types

sunspec-sim -- sunspec-parser
  (a dev-dependency of modbus-client, poller-actor and collector-app only)

Startup

main() in crates/collector-app/src/main.rs:

  1. If --print-schema or --print-models is given, prints the Avro schema or the model catalog and exits.
  2. Initialises logging (RUST_LOG, SUNSPEC_LOG_FORMAT).
  3. Installs the SIGINT/SIGTERM handler before loading the configuration, so a stop during startup still takes the graceful path.
  4. Loads the configuration (defaults, then the config file, then SUNSPEC_* variables) and validates it. Any error ends the process with exit status 78 (EX_CONFIG), which the systemd unit does not restart on (RestartPreventExitStatus=78); other startup errors exit with 1.
  5. Installs the Prometheus recorder and calls collector_app::run.

collector_app::run builds the publisher (Kafka when kafka.brokers is set, otherwise a sink that only logs) and calls run_with_publisher, which:

  1. creates the shutdown watch channel and the shared Health state;
  2. binds metrics.port and starts the HTTP server for /metrics, /healthz and /devices; if the port cannot be bound, startup fails with an error containing bind metrics port <port>;
  3. opens the SQLite buffer (failure ends the process);
  4. creates the bounded sample channel (runtime.channel_capacity, default 256);
  5. starts the ingest, uplink, retention and device tasks;
  6. tells systemd it is ready (READY=1, Linux only) and starts the watchdog task;
  7. waits for the shutdown signal (see Shutdown).

Device lifecycle

The device set

devices::run owns the set of devices and starts one supervised task per device.

  • Static mode. When discovery.static_devices (or SUNSPEC_STATIC_DEVICES) is non-empty, those devices are started once and no subnet scan runs. Hosts may be IP addresses or hostnames.
  • Subnet mode. Otherwise discovery.subnet is scanned. Every host that accepts a TCP connection on the Modbus port yields one device per entry in discovery.unit_ids. The scan is repeated every discovery.rescan_interval_s (default 600 s) and pollers are started for devices not seen before. Devices are never removed: a device that disappears keeps being retried by its supervisor.
  • With no devices at all the collector logs a warning and keeps running (and, in subnet mode, keeps rescanning).

A device is identified by host and unit ID (DeviceIdentity), so every unit behind a Modbus gateway gets its own poller, metrics series and Kafka key (192.168.1.20/1, 192.168.1.20/2). sunspec_devices_known reports how many devices are supervised.

Supervisor

poller_actor::run_device runs a device until shutdown. It repeatedly runs a session; when a session ends for any reason other than shutdown, it marks the device down, counts a restart (sunspec_poller_restarts_total, plus sunspec_poller_stalls_total for stalls), logs the reason and waits before the next session:

delay = runtime.respawn_delay_ms x 2^(n - 1), capped at 60 s, plus 0-10 % jitter

n is the number of consecutive failed sessions. It is reset when a session read at least one model successfully, so a device that worked and then failed is retried after the base delay. The jitter is derived from a hash of the device identity, so devices that fail together (for example behind one gateway) do not retry in lockstep. The supervisor only returns on shutdown; a device that is offline at startup is picked up when it comes online.

Session

A session is: connect, discover the model chain, then poll until something fails.

  1. Connect. A ModbusClient is created for the device with poller.request_timeout_ms as both the connect timeout and the per-request timeout.
  2. Discover. The base addresses are tried in order: sunspec.base_address (default 40000) first, then the other standard bases 40000, 0 and 50000. At each base two registers are read and compared with the SunS marker (0x5375 0x6e53). A mismatch or a Modbus exception moves on to the next base; a timeout or transport error ends the session. At the first base with the marker, the model chain is walked one header at a time: read the two-register header (model ID, body length) at the current address, record the model, advance by length + 2, and stop at model ID 0xFFFF. A chain of more than 256 models, an address overflow or a chain with no models ends the session as malformed. Models are rediscovered on every new session, so firmware updates that change the chain are picked up after a reconnect.
  3. Poll. Polling runs on a fixed schedule with period poller.poll_interval_ms (missed ticks are skipped, not bunched). The first tick is offset by up to poller.jitter_ms, derived from the device identity, so devices sharing a network do not all poll at the same instant. In each cycle every polled model that is due is read in full, header included, and decoded (see SunSpec decoding); each successful read produces one Sample. The device's ModelSchedule (from poller.include_models, exclude_models and model_intervals_s, plus static-device overrides) decides which models are polled and which are due: a model with an interval is due again on the first tick at least that long after its last successful read, measured from the scheduled tick so intervals stay aligned with the cycle. A cycle with nothing due does not contact the device and counts as progress for stall detection.

A session ends with one of these errors (PollerError):

ErrorCause
ModbusConnect failure, or a timeout/transport error while discovering the model chain.
NoSunSpecMapNo SunS marker at any candidate base address.
MalformedChainAddress or length overflow, more than 256 models, or no models before the end marker.
NothingToPollThe include and exclude lists leave none of the discovered models; retried with backoff, rediscovering the chain each time.
TooManyFailedCycles10 consecutive poll cycles in which at least one model read failed.
StalledNo successful read for the stall timeout (see below).
ChannelClosedThe sample channel closed; only happens during shutdown.

Stall detection

Every read has a timeout, but a device can still make no progress for a long time: with several models, retries and backoff, ten failed cycles can take minutes. A watchdog inside each session checks the device's last successful read every quarter of the stall timeout. If no read has succeeded for poller.stall_timeout_ms (default: the larger of 10 poll intervals and 30 s), the session is aborted with Stalled and the supervisor starts a new one after the usual backoff.

Full channel

A sample is handed to the ingest task through the bounded channel with a timeout of one poll interval. If the channel is still full after that, the sample is dropped, a warning is logged and sunspec_samples_dropped_total{reason="channel_full"} is incremented. Polling never blocks for longer than that, so a slow disk delays samples rather than the polling schedule.


Modbus client

modbus_client::ModbusClient wraps a tokio-modbus TCP context behind a mutex, so each device session issues one request at a time.

  • Read splitting. read_range(unit, start, count) splits a read into requests of at most modbus.max_batch_size registers. The value defaults to 125, the protocol limit for function 0x03, and configuration validation rejects values outside 1-125. A model longer than the batch size is read in several requests. modbus.inter_read_delay_ms optionally pauses between the requests of one split read, for devices that cannot handle back-to-back requests.
  • Retries. Each request is retried up to modbus.retry_count times (default 2) after the first attempt, waiting modbus.retry_backoff_ms x 2^attempt (default 100 ms, doubling) up to modbus.retry_max_backoff_ms (default 2000 ms). Modbus exceptions are not retried, except 0x06 (Server Device Busy); register address overflow is not retried.
  • Reconnects. A failed hostname lookup, a failed connect, a transport error or a timeout drops the connection; the next attempt opens a new one. After a timeout the old connection could still deliver the late response and have it matched to the next request, so it is not reused.
  • Exceptions keep the connection. A Modbus exception is a valid response from the device, so the connection stays open.
  • Hostnames. The host is resolved on every connect and each resolved address is tried in turn (for example localhost may resolve to ::1 before 127.0.0.1).

Errors are counted in sunspec_poll_errors_total{device, kind} with kind one of exception, timeout, transport, connect (connect or resolve failure) and address. Counted are failed model reads in the polling loop, a failed connect at the start of a session (connect, or timeout when the connect timed out), and Modbus errors while discovering the model chain. A Modbus exception while probing a base address for the SunS marker is not counted, because it only means the map is not at that base. A session that ends without a Modbus error (NoSunSpecMap, MalformedChain, Stalled) shows up only in sunspec_poller_restarts_total.


SunSpec decoding

Bundled definitions

crates/sunspec-parser/models/ holds every official SunSpec JSON model definition from github.com/sunspec/models (commit 90b4a331, Apache-2.0, unmodified): 112 models. build.rs embeds every model_<id>.json in that directory with include_str!, and src/registry.rs parses them once on first use. The families:

ModelsDescription
1Common (manufacturer, model, serial number)
2-19Aggregator, secure dataset and certificate models, network and communication interfaces
101-103, 111-113Inverters, single/split/three phase (integer + scale factor, and float)
120-145Nameplate, settings, status, controls, storage, and the IEEE 1547-2003 era control curves
160Multiple MPPT (repeating modules)
201-204, 211-214, 220AC meters (integer + scale factor, float, secure readings)
302-308, 401-404, 501-502, 601Environmental sensors, string combiners, solar modules, tracker controller
701-715IEEE 1547-2018 DER: measurements, capacity, controls, curves, trip settings, storage capacity, DC measurement
801-809Storage: battery base, lithium-ion and flow battery banks, strings and modules
63001-64415SunSpec test models and the vendor models kept in the official repository

The full list with labels is in crates/sunspec-parser/models/README.md. A test decodes a zeroed register map of every bundled model and checks that each top-level point appears once and that point names are unique.

model_name(id) returns the definition's name (for example inverter_three_phase for 103), or model_<id> for a model without a definition. Models without a definition are still polled and published, with raw registers and an empty points map.

Decoding

decode_model(model_id, registers) takes a model's registers starting at the header and returns the points in definition order.

  • Groups. A definition is a tree of groups. Each group's points are sliced first and their values computed afterwards, because a scale factor may appear after the points that use it. Nested groups follow their parent's points:

    • count 1: a fixed sub-group, named group.Point;
    • count n > 1: n repetitions, named group[i].Point;
    • count 0: repeated to fill the rest of the model;
    • count naming a point: repeated as many times as that point's value. The point may belong to an enclosing group, and 0xFFFF (not implemented) means no repetitions.

    For example, model 160 publishes module[0].DCA, module[1].DCA and so on. The IEEE 1547 curve models nest repeating groups sized by top-level points: model 705 (volt-var) has NCrv curves of NPt points each, published as Crv[1].Pt[2].V, and the trip models (707-710) add a fixed level in between, e.g. Crv[0].MayTrip.Pt[1].V.

  • Omitted. The top-level ID and L header points, sunssf (scale factor) points and pad points are not published.

  • Truncated input. If the registers end early, the points that are present are decoded and the rest are omitted.

Point types and "not implemented" values

SunSpec marks unimplemented points with a sentinel value. Sentinels become Null.

Point typePublished asNot implemented
int16long (double if scaled)0x8000
uint16, enum16, bitfield16long (double if scaled)0xFFFF
count, acc16long (double if scaled)0
int32long (double if scaled)0x80000000
uint32, enum32, bitfield32long (double if scaled)0xFFFFFFFF
acc32, acc64, ipaddrlong (double if scaled)0
int64long (double if scaled)0x8000000000000000
uint64long (double if scaled)0xFFFFFFFFFFFFFFFF
float32, float64doubleNaN
stringstring, trimmed of NULs and whitespaceempty after trimming
eui48string of hex digitsall registers 0xFFFF
ipv6addrstring of hex digitsall registers 0

Enums and bitfields are published as integers in points, and by name in labels (decode_model_labeled, in the same pass): an enum's symbol name for its value, or a bitfield's set bits by name, comma-separated, with bit<n> for a bit the definition does not name and an empty string when no bit is set. A value with no symbol, and a "not implemented" value, get no label. An integer that does not fit an i64 (a large uint64 or acc64) is published as a double.

Model catalog

sunspec_parser::catalog() and model_info(id) describe every bundled model: its name, label and description, and each point records can carry, with its type, units, label, description and symbols. Point names match records, with [*] for a repeating group's index (module[*].DCA), and the header, scale factors and padding are left out as in records. collector-app --print-models prints the whole catalog, and the metrics port serves /models (an index) and /models/{id}. A test checks that every point name a record can carry, for every model, has a catalog entry.

Scale factors

A point with a scale factor is published as value x 10^sf, always as a double (also when the exponent is 0). For negative exponents the result is rounded to that many decimal places, so 2301 with V_SF = -1 is 230.1 rather than 230.10000000000002.

  • A scale factor is either a literal exponent in the definition or the name of a sunssf point.
  • Names are resolved from the innermost group outwards, so points in a repeating group can use a scale factor defined at the top level of the model (model 160's module[i].DCA uses the top-level DCA_SF).
  • If the scale factor point itself is not implemented (0x8000), the point is Null: the value cannot be interpreted.

Data path and durability

Ingest

ingest::run receives samples from the channel. When a sample arrives it waits up to 100 ms for more (at most 256 in total), serialises them to JSON and writes them to the buffer in one transaction with BufferStore::enqueue_batch. One transaction per batch means one fsync per batch rather than per sample, which matters on flash storage. Written rows are counted in sunspec_buffer_writes_total; if the write fails, the batch is dropped and counted in sunspec_buffer_dropped_total{reason="write_error"}.

Ingest runs until every sender has been dropped, so at shutdown it writes everything still queued before it returns.

Buffer

The buffer is one SQLite file (buffer.path) with one table:

CREATE TABLE telemetry_queue (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    topic      TEXT NOT NULL,
    payload    BLOB NOT NULL,     -- the Sample as JSON
    created_at INTEGER NOT NULL   -- enqueue time, Unix ms
);
CREATE INDEX idx_created_at ON telemetry_queue(created_at);

The database is created if missing and opened in WAL mode with synchronous=NORMAL, applied to every pooled connection (pool of up to 5). Rows are dequeued in id order, oldest first. The payload is JSON so the buffer can be inspected with the sqlite3 CLI; rows written before decoded points existed (no points field) still deserialize. JSON has no infinity, so an infinite float point is stored, and later published, as null.

uplink::run drains the buffer to the publisher:

  1. Dequeue the oldest buffer.batch_size rows (default 100).
  2. Parse each row. Rows that are not valid JSON are deleted immediately and counted in sunspec_buffer_dropped_total{reason="corrupt"}, so a bad row cannot block the queue.
  3. Publish the samples, one Kafka record per sample. All records of the batch are handed to the producer before waiting for acknowledgements, so librdkafka can batch and compress them.
  4. Delete only the rows Kafka acknowledged and count them in sunspec_uplink_records_total. Rows that could never be encoded are deleted and counted as corrupt. Rows that were not acknowledged stay in the buffer, are counted in sunspec_uplink_errors_total (once per failed attempt), and are retried first in the next batch because they are the oldest.

Scheduling:

  • While batches come back full, the next batch is drained immediately (back to back), so a backlog drains at the speed of Kafka rather than one batch per interval.
  • When the backlog is empty, the uplink waits buffer.drain_interval_ms (default 500 ms).
  • When the dequeue fails or no record of a batch is acknowledged, it backs off 1 s x 2^(n-1), up to 30 s (never less than the drain interval).
  • When every record of the batch timed out and a broker connection has failed since the last delivery, the brokers are taken to be unreachable: the backoff asks them for the topic's metadata every 2 s (1 s timeout) and ends as soon as they answer, so a backlog starts draining within seconds of an outage ending. Rejected records keep the full backoff.
  • The first batch delivered after failures logs records delivered again with the number of failed attempts.

sunspec_uplink_publish_seconds records the time to publish a batch and receive its acknowledgements.

Without kafka.brokers the publisher only encodes each record and logs it at debug level (target avro_kafka); the rows are then deleted as if delivered. This mode is for local development.

Retention

retention::run runs every 10 s. It deletes rows enqueued more than buffer.max_age_s ago (default 604800 s, 7 days) and then the oldest rows beyond buffer.max_rows (default 1,000,000), counting them in sunspec_buffer_dropped_total{reason="max_age"} and {reason="max_rows"}. It also updates the sunspec_buffer_rows and sunspec_buffer_oldest_age_seconds gauges. While the uplink keeps up, the buffer stays small and retention deletes nothing; its job is to bound the buffer during long outages so it cannot fill the disk.

Delivery semantics

Delivery from the buffer to Kafka is at-least-once:

  • A row is deleted only after Kafka acknowledged its record. If the process dies between the acknowledgement and the delete, or the delete fails, the record is sent again after the restart. The producer's idempotence avoids duplicates from its own retries within one run, but not across a restart.
  • Consumers that need exactly-once processing should deduplicate on (device_key, model_id, collected_at_ms).

Samples can be lost before or after they reach the buffer, and each case is counted:

WhereCounter
Channel full for a whole poll intervalsunspec_samples_dropped_total{reason="channel_full"}
SQLite write failedsunspec_buffer_dropped_total{reason="write_error"}
Row older than buffer.max_age_ssunspec_buffer_dropped_total{reason="max_age"}
More than buffer.max_rows rowssunspec_buffer_dropped_total{reason="max_rows"}
Row cannot be parsed or encodedsunspec_buffer_dropped_total{reason="corrupt"}

Samples still in the channel or in an ingest batch that has not been committed are lost if the process is killed; a graceful stop writes them first. With WAL and synchronous=NORMAL, committed rows survive a process crash, but SQLite may roll back the most recent transactions after a power loss or operating-system crash.


Kafka record contract

The record, its schema, how the schema evolves, how to read records and the producer settings are described in Record format.

Health and observability

Heartbeats and /healthz

The ingest and uplink tasks record a heartbeat on every loop iteration: ingest at least every 5 s while idle, the uplink at least every 30 s (its longest backoff) plus up to 1 s for a broker probe and the time one drain takes.

GET /healthz on metrics.port returns 200 while both heartbeats are younger than 120 s, and 503 otherwise, with a JSON body:

{"healthy":true,"ingest_age_s":0.7,"uplink_age_s":0.3,"devices_up":2,"devices_total":2}

Ages are null before the first heartbeat. devices_up counts devices whose current session has read successfully. Device outages do not make the collector unhealthy: restarting the collector would not bring a device back, and they are visible in the per-device metrics. The Docker HEALTHCHECK uses this endpoint.

/devices

Each device's DeviceHealth also holds a DeviceSnapshot: the base address and model chain from the latest discovery, the polled models, and the latest Sample of each polled model (replaced on every successful read, and pruned when a rediscovery drops a model). GET /devices and GET /devices/{host}/{unit} serialise these from the health registry; points keep definition order (types::ordered_points). Memory is bounded by one sample per device and model.

systemd watchdog

On Linux, when systemd sets WATCHDOG_USEC (the unit uses Type=notify and WatchdogSec=30), a task pings the watchdog every half watchdog interval, but only while /healthz would report healthy. If ingest or the uplink stops checking in, the pings stop and systemd restarts the service. On other platforms the watchdog and readiness notification are no-ops.

Metrics

The HTTP server (axum) listens on 0.0.0.0:metrics.port (default 9090; 0 picks a free port). The port is bound before the buffer is opened and the tasks start; if it is taken, the collector exits with an error rather than running without /metrics and /healthz.

Naming conventions: every metric has the sunspec_ prefix, counters end in _total, durations are in seconds and end in _seconds, and every metric has HELP text. *_seconds metrics are histograms with buckets from 1 ms to 10 s. Per-device metrics carry a device label in the host/unit form (192.168.1.20/1); there are no other high-cardinality labels.

AreaMetrics
Devicessunspec_poll_success_total, sunspec_poll_errors_total{kind}, sunspec_poll_duration_seconds, sunspec_device_up, sunspec_device_last_success_timestamp_seconds, sunspec_poller_restarts_total, sunspec_poller_stalls_total, sunspec_devices_known
Buffersunspec_samples_dropped_total{reason}, sunspec_buffer_writes_total, sunspec_buffer_dropped_total{reason}, sunspec_buffer_rows, sunspec_buffer_oldest_age_seconds
Uplinksunspec_uplink_records_total, sunspec_uplink_errors_total, sunspec_uplink_publish_seconds

sunspec_device_up is set to 0 when a device's poller starts, to 1 on a successful read and back to 0 when a session ends, so a device that has never answered has a series with value 0. sunspec_uplink_errors_total counts delivery attempts: a record that stays buffered and fails again is counted again. The buffer gauges are updated every 10 s by the retention task. The full table is in Metrics and suggested alerts are in Monitoring and alerts.

Logs

Logging uses tracing with structured fields (device, model_id, error, ...). RUST_LOG sets the filter (default info) and SUNSPEC_LOG_FORMAT=json writes one JSON object per line. Steady-state operation is quiet at info: per-cycle and per-batch messages are at debug; session failures, dropped samples and undelivered records are warnings. Expected misses are at debug too: a base address discovery tries without finding a map, and each failed Modbus attempt before a retry (the poller reports the read that finally failed). The Kafka producer has its own client context: librdkafka reports every reconnect attempt to a broker that is down (about once a second, twice each), so a failed broker connection is logged at warn at most once per 5 minutes and the repeats at debug. The uplink's records not delivered warning (once per backoff) and records delivered again (info) mark an outage's start and end.


Shutdown

SIGINT and SIGTERM (sent by systemd and Docker on stop) both start a graceful shutdown:

  1. The shutdown flag is set on the watch channel. The HTTP server, the retention task and the watchdog stop.
  2. Device pollers stop. Each supervisor sees the flag and ends its session, cancelling any request in flight. The device task waits up to 10 s for all pollers, then aborts the rest. The pollers own the only senders of the sample channel, so the channel closes.
  3. Ingest writes everything still queued to the buffer and returns.
  4. The uplink stops, then keeps draining batch after batch for up to buffer.shutdown_drain_timeout_ms (default 5000; 0 skips the final drain). It is stopped through its own watch channel, only after ingest has finished.
  5. The process exits with code 0. Rows that were not published stay in the buffer and are sent after the next start.

The systemd unit allows 30 s for this (TimeoutStopSec=30).


Concurrency model

The binary runs on Tokio's multi-threaded runtime. Every long-lived component is a Tokio task:

TaskCountLifetime
Signal handler1Until SIGINT or SIGTERM
Device set (devices::run)1Until shutdown; runs subnet scans
Device supervisor (run_device)1 per device, in a JoinSetUntil shutdown
Ingest1Until the sample channel closes
Uplink1Until its stop signal, plus the final drain
Retention1Until shutdown
HTTP server (/metrics, /healthz, /devices)1Until shutdown
systemd watchdog0 or 1 (Linux, when enabled)Until shutdown

Coordination:

  • Shutdown: a tokio::sync::watch::channel(bool) shared by all tasks; the uplink has a second watch channel so it stops after ingest.
  • Samples: one bounded tokio::sync::mpsc channel (runtime.channel_capacity) from all pollers to the single ingest task. It is the only queue in memory; the SQLite buffer absorbs anything longer.
  • Shared state: device and pipeline health are atomics behind Arc (DeviceHealth, Health), read by /healthz and the watchdog without locks on the polling path.
  • Modbus: each session owns its client; requests to one device are serialised by the client's mutex, and different devices are polled concurrently. Devices with framing = "rtu" on the same host and port (units behind one serial converter) instead share one client (DeviceConfig::shared_client, created by collector-app's device supervisor), so their requests run one at a time over one connection: RTU frames carry no transaction ID.
  • SQLite: ingest, uplink and retention share one sqlx pool; sqlx runs each SQLite connection on its own thread, so database I/O does not block the runtime's worker threads.
  • Kafka: librdkafka runs its own threads; the uplink awaits delivery reports.
  • Discovery: a subnet scan probes hosts concurrently, bounded by a semaphore of discovery.max_concurrency permits.

Configuration

Values are resolved in this order, later sources winning:

  1. compiled defaults;
  2. the config file given with --config PATH or SUNSPEC_CONFIG (.json is parsed as JSON, anything else as TOML);
  3. SUNSPEC_* environment variables.

Loading is strict: unknown keys in the file, unparsable environment values (the error names the variable), invalid static device entries and unsupported kafka.compression / kafka.acks values fail at startup. validate() also rejects zero intervals, ports and sizes, a modbus.max_batch_size outside 1-125, and kafka.acks 0 or 1 while idempotence is enabled. The full reference is in Configuration.


Error handling

Each library crate has its own error type built with thiserror: ClientError (modbus-client), DiscoveryError (discovery), PollerError (poller-actor), BufferError (buffer) and PublishError (avro-kafka). sunspec-parser has no error type: decoding is total and returns None only for models without a definition. collector-app uses anyhow with .context() for startup errors; once running, runtime failures are logged, counted and recovered rather than propagated.

ComponentFailureRecovery
Modbus requestTimeout, transport or connect error, or the device closed the connectionRetry with backoff on a new connection
Modbus requestExceptionNot retried (except Server Device Busy); connection kept
Device sessionConnect, discovery, 10 failed cycles, stallSupervisor restarts it with exponential backoff
Sample channelFull for a poll intervalSample dropped and counted
Buffer writeSQLite errorBatch dropped and counted (write_error)
Buffer rowNot parsable or not encodableRow deleted and counted (corrupt)
Kafka publishNot acknowledgedRow kept and retried first; uplink backs off when a whole batch fails, ending early once unreachable brokers answer again
Subnet scanHost unreachableHost skipped; found on a later rescan
Ingest or uplinkStuck/healthz returns 503; the systemd watchdog restarts the service

Design decisions

One task per device

Each device runs in its own supervised task. A device that times out does not delay the others, each device has its own backoff and health, and the Tokio runtime spreads the work over its worker threads. The supervisor never gives up, because at the edge a device that is offline now (at night, during maintenance) is expected to come back.

Walk the model chain instead of reading a fixed window

Earlier versions read a fixed 200-register window at the base address and parsed the model list from it, which silently dropped every model past the window (status, controls, MPPT). The chain is now walked one header at a time: each read is two registers, never runs past the end of the map, and works for chains of any length.

Decode at the edge

The collector already needs the model definitions to know which registers form a model. Scale factors are stored in other registers of the same model, so a value and its scale factor have to come from the same read to be consistent; decoding at read time guarantees that. Consumers then receive named, scaled values and need no SunSpec knowledge. The raw registers are still included, so data can be re-decoded later or checked against the device. The costs are larger records and that new model definitions need a collector update; models without a definition still pass through as raw registers.

Avro single-object encoding instead of container files or a Schema Registry

Earlier versions sent an Avro Object Container File per message. A container embeds the full schema in every message and, because one message mixed devices, there was no meaningful key. Single-object encoding adds a 10-byte header to each record and allows one record per sample, keyed by device, so Kafka can partition and compact per device and consumers can process records one at a time.

A Schema Registry (Confluent wire format) was not used because it adds a service the edge device must reach and register with before it can publish, and its schema IDs only mean something with that registry. Single-object encoding is defined by the Avro specification itself: the fingerprint is computed from the schema, so any consumer that has the schema file can check that a record was written with it. The trade-off is that there is no central compatibility checking; a new schema version gets a new fingerprint (and namespace), and consumers must be given the new schema.

SQLite as the buffer

The buffer has to survive restarts, crashes and long uplink outages without another service on the edge device. SQLite is a single file, needs no daemon, is crash-safe, and in WAL mode lets the uplink read while ingest writes. Writes are batched into transactions to limit fsyncs and flash wear. Retention bounds its size. One collector process should use a given buffer file.

JSON in the buffer, Avro on the wire

Samples are stored as JSON and encoded to Avro when published. The buffer stays readable with the sqlite3 CLI, and the stored format does not have to change when the wire format does: rows buffered by the previous version, which had no decoded points, are still published after an upgrade, in the new format with an empty points map.

At-least-once delivery

Deleting a row only after Kafka acknowledged it means a crash can produce a duplicate but never silently loses a buffered sample. Exactly-once would need the Kafka acknowledgement and the SQLite delete to commit atomically, which two independent systems cannot do without transactions on both sides and a consumer that reads only committed data. Duplicates are cheap to remove downstream with the (device_key, model_id, collected_at_ms) key.

Health means the pipeline, not the devices

/healthz and the watchdog only consider ingest and the uplink. A restart fixes a stuck pipeline but not an offline inverter, so device outages are reported through metrics instead of causing restart loops.


Technology choices

ComponentChoice
Async runtimeTokio (multi-threaded)
Modbus TCPtokio-modbus 0.17
SunSpec modelsOfficial SunSpec JSON definitions, parsed with serde_json
BufferSQLite through sqlx 0.8 (bundled SQLite)
SerializationJSON in the buffer, Avro single-object (apache-avro 0.21) on the wire
Kafkardkafka 0.39 (librdkafka 2.12, built from source with SSL and zstd)
HTTPaxum 0.8
Metricsmetrics 0.22 with metrics-exporter-prometheus 0.13
Loggingtracing with tracing-subscriber (text or JSON)
Errorsthiserror in libraries, anyhow in the application

Platform support

PlatformStatusNotes
Linux x86_64Supportedsystemd readiness and watchdog; release archives (glibc 2.18+) and Docker images
Linux aarch64SupportedCross-compiled with cross and docker/Dockerfile.cross; release archives (glibc 2.18+) and Docker images (see Building & Packaging)
macOSDevelopmentsystemd readiness and watchdog are no-ops
WindowsNot testedOnly SIGINT (Ctrl-C) triggers a graceful stop

Limitations and future work

  • Kafka authentication is limited to SASL PLAIN and SCRAM (no Kerberos or OAuth); certificates and passwords are read at startup, so rotating them needs a restart.
  • No Schema Registry integration; records use fingerprint-based single-object encoding.
  • Point units and descriptions are not in each record; they are in the model catalog.
  • No configuration reload (SIGHUP).
  • Delivery is at-least-once; duplicates are possible after a crash.
  • Subnet scanning is IPv4 only, and devices found by a scan are never removed from the set.
  • Running two collectors against the same devices duplicates polling and records; there is no coordination between instances.
  • A slow uplink does not slow polling down; the buffer grows until retention drops the oldest rows.

Developer Guide

Everything needed to build, run, test and change Rusty SunSpec Collector.

For release builds, Docker images and cross-compiling see Building & Packaging. For configuration options see Configuration, for the system design see Architecture, and for deployment see Deployment.


Prerequisites

Required

ToolVersionPurpose
Rust1.88+ (MSRV, rust-version in the workspace Cargo.toml)Compiler and cargo
cmake3.xBuilds librdkafka from source (rdkafka's cmake-build feature)
C/C++ toolchainanyCompiles librdkafka and the bundled SQLite and zstd
pkg-configanyLocates OpenSSL on Linux
OpenSSL headers3.xTLS support in librdkafka (libssl-dev)
libcurl headersanyNeeded to compile librdkafka 2.12, although curl support is disabled (libcurl4-openssl-dev); see Building & Packaging

SQLite (through sqlx) and zstd are bundled and need no system packages.

Optional

ToolPurpose
Docker with Compose v2Docker images and the local stack (Kafka, simulator, collector, Prometheus)
crossaarch64 cross-compilation
cargo-auditThe security audit CI runs (cargo install cargo-audit --locked)
mdBook 0.4.40Building this documentation
sqlite3 CLIInspecting the buffer

Debian / Ubuntu

sudo apt-get update
sudo apt-get install -y build-essential cmake pkg-config libssl-dev libcurl4-openssl-dev

macOS

xcode-select --install          # C/C++ toolchain and SDK (includes the curl headers)
brew install cmake openssl@3

The OpenSSL build scripts find Homebrew's OpenSSL on their own; the resulting debug and release binaries link it dynamically from Homebrew.


Repository layout

rusty-sunspec-collector/
  Cargo.toml              Workspace root (members, MSRV 1.88, shared dependencies)
  Cargo.lock              Committed; CI and Docker builds use --locked
  CHANGELOG.md            Keep a Changelog format
  CODE_OF_CONDUCT.md      Contributor Covenant 2.1
  SECURITY.md             How to report vulnerabilities privately
  Dockerfile              Multi-target build: builder, runtime, simulator, collector
  docker-compose.yml      Kafka + simulator + collector + Prometheus
  Cross.toml              Maps the release targets to the custom cross images
  rustfmt.toml            Formatting rules (max width 100)
  .github/
    workflows/ci.yml          fmt, clippy, build, test, cargo audit, Docker E2E
    workflows/release.yml     Release archives and images (dry run unless a v* tag)
    workflows/deploy-docs.yml mdBook -> GitHub Pages
    workflows/scorecard.yml   OpenSSF Scorecard (README badge, Security tab)
    workflows/codeql.yml      CodeQL static analysis of the Rust code and the workflows
    workflows/fuzz.yml        ClusterFuzzLite: runs the fuzz targets
    dependabot.yml            Weekly Cargo, GitHub Actions and Docker image updates
    ISSUE_TEMPLATE/           Issue forms: bug report, feature request, device report
    pull_request_template.md  PR checklist
  crates/
    types/                Shared DTOs (serde only)
    discovery/            CIDR subnet scan
    modbus-client/        Modbus TCP client
      tests/sim_tests.rs
    sunspec-parser/       SunSpec model definitions and decoder
      models/             Official SunSpec JSON definitions (Apache-2.0)
      build.rs            Embeds every models/model_<id>.json
      src/registry.rs     Model registry and the definition types
      src/decode.rs       decode_model()
      tests/decode_tests.rs
    poller-actor/         Per-device supervisor, model-chain walk, polling
      tests/device_tests.rs
    buffer/               SQLite queue
      tests/buffer_tests.rs
    avro-kafka/           Record schema, single-object codec, Kafka publisher
      schemas/sunspec_sample.avsc
      tests/              codec, producer config and (ignored) Kafka integration tests
    collector-app/        The collector binary and its library
      src/main.rs         CLI, logging, signals
      src/app.rs          run(), run_with_publisher(), shutdown order
      src/config.rs       Config loading and validation
      src/devices.rs      Device set and per-device tasks
      src/ingest.rs       Channel -> SQLite batches
      src/uplink.rs       SQLite -> Kafka
      src/retention.rs    Row and age limits, buffer gauges
      src/health.rs       Heartbeats and /healthz report
      src/metrics_server.rs  Prometheus recorder, /metrics, /healthz
      src/systemd.rs      READY and watchdog notifications (Linux)
      tests/              End-to-end, CLI, config, metrics and signal tests
    sunspec-sim/          SunSpec Modbus TCP simulator (library + binary)
  fuzz/                   cargo-fuzz targets (own workspace, nightly); seeds.py writes seed corpora
  .clusterfuzzlite/       Build image and script for running the fuzz targets in CI
  docker/
    Dockerfile.cross      Cross-compilation image for the release targets
    prometheus.yml        Scrape config for the compose stack
  scripts/                Release packaging, install.sh, CHANGELOG section extraction
  fixtures/devices/       collector-app --probe dumps replayed by the fixture test
  docs/                   This documentation (mdBook), example config, systemd unit

Building

cargo build --workspace                  # debug build of every crate
cargo build --release -p collector-app   # optimised collector binary

Binaries land in target/debug/ or target/release/ (collector-app, sunspec-sim). The first build compiles librdkafka from source and takes noticeably longer than later ones. See Building & Packaging for release builds, Docker images and cross-compiling.


Running locally with the simulator

No hardware or Kafka is needed. Start two simulated inverters behind one address:

# Terminal 1
cargo run -p sunspec-sim -- --bind 127.0.0.1:1502 --units 1,2

Then point the collector at them:

# Terminal 2
SUNSPEC_STATIC_DEVICES=127.0.0.1:1,127.0.0.1:2 SUNSPEC_PORT=1502 cargo run -p collector-app

Without SUNSPEC_KAFKA_BROKERS the collector encodes each record and only logs it, at debug level, then removes it from the buffer. To see one log line per record:

SUNSPEC_STATIC_DEVICES=127.0.0.1:1,127.0.0.1:2 SUNSPEC_PORT=1502 \
  RUST_LOG=info,avro_kafka=debug cargo run -p collector-app

RUST_LOG=info,collector_app=debug,poller_actor=debug shows the collector's own detail, such as every poll cycle and uplink batch. A bare RUST_LOG=debug also turns on the libraries' debug output (Avro schema parsing, every SQL statement), which buries those lines. The buffer is sunspec-buffer.sqlite in the current directory. Check the collector from a third terminal:

curl localhost:9090/healthz
curl -s localhost:9090/metrics | grep '^sunspec_'

With Kafka

Start only the broker from the compose stack and publish to its host listener:

docker compose up -d kafka
SUNSPEC_STATIC_DEVICES=127.0.0.1:1,127.0.0.1:2 SUNSPEC_PORT=1502 \
  SUNSPEC_KAFKA_BROKERS=localhost:29092 cargo run -p collector-app

# Record keys (values are binary Avro)
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 --topic sunspec.telemetry --from-beginning \
  --property print.key=true --property print.value=false

docker compose up --build runs the whole stack instead, with the collector and the simulator in containers; see Building & Packaging.

Other ways to run

cargo run -p collector-app -- --config docs/config.example.toml   # edit devices and brokers first
cargo run -p collector-app -- --print-schema                      # print the Avro schema and exit

The simulator

crates/sunspec-sim is a SunSpec Modbus TCP device simulator, used by the test suite, the compose stack and local development.

What it serves

  • Read holding registers (function 0x03) for one or more unit IDs on one listener. Each unit has its own register map: the SunS marker at the base address, each model's header (ID, length) and a zero-filled body, then the end marker 0xFFFF 0x0000.
  • Reads of more than 125 registers (or 0) are rejected with exception 0x03, like a real device.
  • Other function codes get exception 0x01; addresses outside the map get exception 0x02 (or zeros, see Unmapped); unknown unit IDs get exception 0x0B (gateway target device failed to respond).

Binary

cargo run -p sunspec-sim -- [--bind ADDR:PORT] [--units 1,2] [--models 1,103,160:48] [--base 40000]
cargo run -p sunspec-sim -- [--bind ADDR:PORT] --fixture fixtures/devices/<device>.json
FlagDefaultMeaning
--bind ADDR:PORT0.0.0.0:1502Listen address
--units LIST1Unit IDs to serve, each with the same model chain
--models LIST1,103,120,160:48Model chain. A bare ID uses the body length of the bundled definition; id:len sets the body length explicitly, which models with repeating groups need (160:48 is model 160 with two MPPT modules).
--base ADDR40000Base address of the SunS marker
--rtuoffSpeak Modbus RTU frames (unit, PDU, CRC) over TCP, like a transparent serial converter: absent units stay silent and frames with a bad CRC are ignored. Only function 0x03 is understood.
--fixture FILEnoneReplay a collector-app --probe dump: its unit ID, base address, model chain and registers. Cannot be combined with --units, --models or --base; the demo does not run.

Invalid arguments print the usage and exit with code 2. The binary also runs a live demo for every unit: it writes nameplate strings into model 1 (Mn, Md, SN, DA), sets the scale factors of model 103, and once per second updates W, A, PhVphA, Hz, St and WH, so dashboards show changing values. Points of models that are not in the chain are skipped.

Library

Tests start a simulator on an ephemeral port and drive it through SimHandle. Add it as a dev-dependency (sunspec-sim = { path = "../sunspec-sim" }).

ItemPurpose
sunspec_sim::start(config, bind)Starts a simulator (async, needs a Tokio runtime); returns io::Result<SimHandle>. Bind 127.0.0.1:0 for an ephemeral port.
sunspec_sim::start_rtu_over_tcp(config, bind)The same, speaking RTU frames over TCP like a serial converter
SimConfig { units, unmapped }, SimConfig::single(unit)Units to serve and how unmapped addresses are answered
UnitSpec::inverter(unit_id)Models 1 and 103 (body lengths 66 and 50) at base 40000
UnitSpec::with_models(unit_id, models), UnitSpec { unit_id, base, models }Custom chains
ModelSpec { id, len }One model; len excludes the 2 header registers
parse_models("1,103,160:48"), default_body_len(id)Build chains the way the binary does
Unmapped::Exception / Unmapped::ZerosException 0x02 or zeros outside the map
read_dump(path), UnitSpec::from_dump(&dump), handle.load_dump(unit_id, &dump)Replay a collector-app --probe dump (types::DeviceDump); from_dump rejects a chain whose model addresses do not line up
handle.reads_at(unit_id, addr)Read requests that started at an address (includes discovery's header read), for polling-schedule tests
run_demo(handle, unit_ids)The binary's live demo

SimHandle methods (cloning a handle shares the same simulator):

MethodEffect
local_addr(), port()Where the simulator listens
model_start(unit, model_id)Address of a model's header, if the unit has the model
set_register(unit, addr, value), set_registers(unit, addr, &values)Write raw registers
set_point(unit, model_id, "W", &values)Write a top-level point by name (raw big-endian words); returns false if the unit, model or point does not exist
set_text(unit, model_id, "Mn", "text")Write a string point, NUL-padded to its size
set_stalled(true)Read requests but never answer (timeouts, stall detection)
drop_connections()Close every open connection; new connections are still accepted
go_offline() / go_online()Refuse connections (and drop open ones) / listen again on the same port
connections_accepted(), requests_served()Counters for assertions

Example:

#![allow(unused)]
fn main() {
use sunspec_sim::{SimConfig, UnitSpec};

#[tokio::test]
async fn reads_scaled_power() {
    let sim = sunspec_sim::start(
        SimConfig::single(UnitSpec::inverter(1)),
        "127.0.0.1:0".parse().unwrap(),
    )
    .await
    .expect("start simulator");
    sim.set_point(1, 103, "W", &[4321]);
    sim.set_point(1, 103, "W_SF", &[(-1i16) as u16]); // W = 432.1
    sim.set_text(1, 1, "Mn", "Example");

    // Point a ModbusClient or a CollectorConfig at 127.0.0.1:sim.port() ...

    sim.set_stalled(true); // requests now time out
    sim.go_offline(); // connections are refused
    sim.go_online(); // and accepted again
}
}

Tests

cargo test --workspace                                # everything except the Kafka tests
cargo test -p sunspec-parser                          # one crate
cargo test -p collector-app --test app_tests          # one test file
cargo test -p poller-actor stalled_session_is_restarted  # one test

The suite has about 60 tests and needs no hardware, network access or Kafka: device behaviour is tested against the simulator on ephemeral ports, and the collector end to end with an in-memory publisher. Tests use short real intervals (100 ms polls), so they take seconds once built.

Integration test files

FileCovers
crates/sunspec-parser/tests/decode_tests.rsEnum and bitfield labels (named, unnamed bits, unknown and unimplemented values, repeating groups); the catalog covers every point name records carry, for every model, with units, labels and symbols; every file in models/ is bundled; every model decodes a zeroed map with each top-level point once and unique point names, and short or long maps never panic; float meters 211–214; model names; scaling (model 103); "not implemented" values become null; header, scale factors and padding are omitted; model 1 strings; model 160 repeating modules using top-level scale factors; float models and NaN; truncated registers
crates/modbus-client/tests/sim_tests.rsReads over 125 registers are split; an oversized max_batch_size passed to the client library is clamped to 125; an exception keeps the connection; reconnect after a dropped connection, which reads as Disconnected rather than a leftover OS error; a timeout drops the connection and recovers; offline device fails to connect, then recovers; hostnames are resolved
crates/modbus-client/tests/rtu_tests.rsRTU over TCP against the simulator's converter mode: split reads and two units over one connection, exceptions, an absent unit timing out and the client reconnecting, concurrent readers sharing one client
crates/poller-actor/tests/device_tests.rsDiscovery of a 290-register, 7-model chain; map found at another standard base; device without a SunSpec map (the error gives its last exception); device offline at startup is polled once it appears; a stalled session is restarted
crates/buffer/tests/buffer_tests.rsEnqueue, dequeue and delete; deleting nothing is a no-op; batch enqueue keeps order; trimming keeps the newest rows; age-based retention
crates/avro-kafka/tests/codec_tests.rsSingle-object round trip (points and labels); labels was added with a default; C3 01 and fingerprint header; a foreign fingerprint is rejected; one record per sample keyed by device
crates/avro-kafka/tests/kafka_config_tests.rsThe producer accepts every supported compression codec (zstd is compiled in), the default config and each security protocol (TLS with the tests/fixtures/test-ca.pem CA, SASL PLAIN and SCRAM); an unreadable CA is rejected; secrets are redacted in debug output; an unreachable broker is reported and fails the probe the uplink uses to end its backoff early. No broker needed
crates/avro-kafka/tests/kafka_integration_tests.rsIgnored by default; see below
crates/collector-app/tests/app_tests.rsThe whole collector against the simulator: every unit behind a gateway is polled; every model of a long chain is polled; subnet mode keeps running and finds a device on rescan; a 5,000-row backlog drains within 2 s; published records are decoded and keyed by device; a metrics port that is already taken fails startup (a_taken_metrics_port_fails_startup)
crates/collector-app/tests/config_validation_tests.rsTOML and JSON fixtures (tests/fixtures/); invalid values fail validation; unknown keys are rejected; unparsable environment values name the variable; static device parsing (unit range, hostnames, default unit); Kafka compression and acks values; acks below all only with idempotence off (acks_below_all_require_idempotence_off); modbus.max_batch_size at most 125 (modbus_batch_size_is_limited_to_the_protocol_maximum); [metrics] and [runtime] sections; the shipped docs/config.example.toml and docs/sunspec-collector.env load and validate (shipped_example_config_file_is_valid, shipped_example_env_file_is_valid; the env file is read the way systemd and Docker read it, without trailing comments); Kafka security: SASL requires mechanism, username and password, ssl_*/sasl_* settings must match the protocol, certificate files must exist, passwords load from *_password_file and never appear in debug output
crates/collector-app/tests/polling_profile_tests.rsPolling profiles from TOML, JSON and environment variables; per-device merge rules; SUNSPEC_STATIC_DEVICES dropping file overrides; every validation error; every TOML and JSON example in the Polling profiles and Complete examples sections of docs/configuration.md; an end-to-end run where the simulator's read counts show slow models are read less often
crates/collector-app/tests/devices_endpoint_tests.rs/devices against a collector polling the simulator: devices sorted, a down unit listed without readings, scaled values in definition order, a changed register visible within a few poll intervals, /devices/{host}/{unit} and its 404
crates/collector-app/tests/rtu_tests.rsFraming from the file, the environment and device entries; units on one RTU converter must share timeout and batch size; two units behind one simulated converter are polled end to end over a single connection; --probe through a converter
crates/collector-app/tests/cli_tests.rs--version, --help, --print-schema; --probe against the simulator prints only JSON on stdout with the serial number blanked; a bad --probe target fails; a bad setting exits with 78 (EX_CONFIG) and other startup errors with 1
crates/collector-app/tests/fixture_replay_tests.rsEvery dump in fixtures/devices/ is served by the simulator, probed again, and must give the same model chain, registers and decoded points; a dump with a broken chain is rejected
crates/collector-app/tests/metrics_tests.rs/healthz and /metrics of a collector polling unit 1 and a unit 7 that does not exist: devices_up 1 of devices_total 2, HELP text, per-device series, sunspec_device_up 1 for unit 1 and 0 for the unreachable unit 7, histogram buckets, no batch_size label. It installs the global metrics recorder, so it is its own test binary.
crates/collector-app/tests/signal_tests.rsUnix only: spawns the built collector-app binary, sends SIGTERM and expects a graceful stop with exit code 0

Unit tests inside the crates

ModuleCovers
poller-actor/src/lib.rsBase address order; respawn backoff doubles and caps at 60 s; default stall timeout
collector-app/src/health.rsHealthy only while both heartbeats are fresh; device counts do not affect health
collector-app/src/ingest.rsEverything queued before the channel closes is persisted
collector-app/src/uplink.rsNo pause while a backlog remains; failure backoff up to 30 s
collector-app/src/retention.rsRow and age limits

Kafka integration tests

crates/avro-kafka/tests/kafka_integration_tests.rs has two #[ignore]d tests that need a broker:

  • publishes_keyed_records_to_kafka publishes two records to SUNSPEC_KAFKA_BROKERS.
  • published_records_decode_to_samples consumes the topic and checks that the records published by a running collector decode, carry the content-type header, are keyed by their device, and include a decoded model 103 power value. It expects records from devices on SUNSPEC_E2E_DEVICE_HOST, by default simulator, the compose stack's simulator (keys simulator/1, simulator/2). For a collector polling a simulator on the host, set SUNSPEC_E2E_DEVICE_HOST=127.0.0.1.

Run them against the compose stack, once the collector is healthy and publishing:

docker compose up -d --build
curl -fsS localhost:9090/healthz                       # wait until this returns 200
SUNSPEC_KAFKA_BROKERS=localhost:29092 \
  cargo test -p avro-kafka --test kafka_integration_tests -- --ignored --nocapture
docker compose down -v

The CI Docker E2E job does the same: it builds the simulator and collector images, starts Kafka, the simulator and the collector with docker compose, waits for /healthz to return 200 and for sunspec_uplink_records_total to be non-zero, checks with the console consumer that records are keyed simulator/1 and simulator/2, runs the ignored tests above with SUNSPEC_KAFKA_BROKERS=localhost:29092, and finally stops the collector with SIGTERM and checks that it logged collector stopped and exited with code 0.

Writing tests

  • Use the simulator rather than mocks, and bind it to 127.0.0.1:0.
  • A test that runs the collector sets metrics_port = 0 so it gets a free port: a taken port fails startup, and tests run in parallel.
  • Put SQLite buffers in the system temp directory with a unique name and remove the file and its -wal and -shm files afterwards.
  • A test that installs the global metrics recorder must live in its own test file (the recorder can be installed once per process).
  • Tests that set environment variables hold the file's ENV_LOCK mutex.
  • Tests that need external services are #[ignore = "reason"], with the reason saying what they need.

CI

.github/workflows/ci.yml runs on every push to main and on every pull request, with RUSTFLAGS=-Dwarnings:

JobWhat it runsOn a pull request, runs when it changes
ChangesClassifies the PR's changed filesAlways
Check & Lintcargo fmt --all -- --check, cargo clippy --all-targets --workspace, cargo build --workspaceCode (see below)
Testcargo test --workspaceCode
Security Auditcargo audit (prebuilt cargo-audit)Cargo.lock, a Cargo.toml or ci.yml
Docker E2EThe compose stack end to end (see Kafka integration tests)Code

"Code" is anything compiled, tested or built into an image: crates/, the Cargo files, rustfmt.toml, Cross.toml, the Dockerfiles and docker/, docker-compose.yml, fixtures/, ci.yml, and the three docs files the tests read (docs/configuration.md, docs/config.example.toml, docs/sunspec-collector.env). A PR that changes only other files (README, other docs, images, issue templates, the release or docs workflows) skips the jobs it does not need. The four jobs stay required for merging: a job skipped by its if: reports as skipped, which GitHub counts as passing. If you add a file that tests or the image build read, add it to the Changes job's list. Pushes to main always run every job.

A new push to a pull request cancels that PR's run in progress. audit.yml runs cargo audit on main every day as well, because advisories are published for dependencies that have not changed.

Other workflows: audit.yml is the daily audit above, release.yml builds and tests the release archives and images, and publishes them on a v* tag (see Building & Packaging), deploy-docs.yml builds this book with mdbook-linkcheck on pull requests that change the docs and publishes it to GitHub Pages on pushes to main that touch docs/, README.md or CHANGELOG.md (mdBook 0.4.40), and scorecard.yml runs OpenSSF Scorecard weekly and on pushes to main, publishing the score behind the README badge and the findings to the repository's Security tab. codeql.yml runs CodeQL on the Rust code and on the workflows (pull requests that change code or workflows, every push to main, weekly), and fuzz.yml runs the fuzz targets (see Fuzzing). Neither is required for merging; CodeQL's check on a pull request lists the alerts it adds, and the Security tab has all of them.

Workflow hygiene, which Scorecard checks:

  • Every action is pinned to a full commit SHA, with the release it is in as a comment (uses: actions/checkout@<sha> # v6.1.0); dtolnay/rust-toolchain is pinned to a commit on its master branch and given toolchain: stable. Base images in the Dockerfiles are pinned by digest, with the tag kept for readers, and the release workflow passes each target's pinned cross-rs image to docker/Dockerfile.cross. Dependabot proposes new SHAs and digests weekly.
  • Each workflow's top-level permissions is contents: read; a job that needs more (publishing images, releases or Pages) asks for it in its own permissions.

The CI jobs use the latest stable Rust; the Docker images are built with Rust 1.88, so the Docker E2E job is the one that exercises the MSRV. Every crate inherits rust-version from the workspace, and the workspace uses resolver 3, so cargo update and Dependabot prefer dependency versions that support 1.88 (a crate whose newer releases need a newer Rust stays at its last compatible version). To check a lockfile change locally: rustup toolchain install 1.88 and cargo +1.88 check --locked --workspace --all-targets.

Before pushing

cargo fmt --all -- --check
cargo clippy --all-targets --workspace -- -D warnings
RUSTFLAGS=-Dwarnings cargo build --workspace
RUSTFLAGS=-Dwarnings cargo test --workspace
cargo audit

Changing RUSTFLAGS rebuilds every dependency, librdkafka included. Setting CARGO_TARGET_DIR=target/ci for the last two commands keeps that build separate from your normal one.


Fuzzing

See Fuzzing.

Adding a SunSpec model definition

Every model in the pinned commit of github.com/sunspec/models is already bundled. To add a model published later, or to move to a newer commit:

  1. Copy json/model_<id>.json into crates/sunspec-parser/models/, unmodified, and update the commit recorded in crates/sunspec-parser/models/README.md.
  2. Nothing else needs registering: build.rs embeds every model_<id>.json in that directory. every_model_file_is_bundled and every_bundled_model_decodes_its_fixed_points (crates/sunspec-parser/tests/decode_tests.rs) check that the file parses, decodes, and has unique point names.
  3. If the model has repeating groups, count points or float points, add a value test for it. Point types the decoder does not know are published as null.
  4. Update the model tables in crates/sunspec-parser/models/README.md and Architecture, regenerate the docs' model pages (UPDATE_MODEL_DOCS=1 cargo test -p sunspec-parser --test models_docs; a new range of model IDs also needs a line in CATEGORIES there and in docs/SUMMARY.md), and add a CHANGELOG entry.

The simulator can then serve the model: --models 1,103,<id>, or <id>:<len> when the model has repeating groups (the bundled length only counts fixed groups).


Adding a crate

cargo new --lib crates/my-new-crate

Make sure it is listed in [workspace] members in the root Cargo.toml, inherit edition, rust-version, license and repository from [workspace.package] (edition.workspace = true, ...), use workspace dependencies where they exist, define a thiserror error type, and make sure cargo clippy and cargo fmt pass.


Coding conventions

Crates and errors

  • Library crates return Result<T, CrateError> with an error enum built with thiserror; collector-app uses anyhow::Result with .context().
  • types stays lightweight: serde is its only dependency.
  • No unwrap() on runtime paths. expect() is used only for invariants such as the bundled schema and model JSON or a poisoned lock.
  • After startup, failures are logged, counted and recovered rather than ending the process.
  • No #![allow(dead_code)]; fix warnings instead (CI builds with -Dwarnings).

Logging

  • Use tracing macros with structured fields, not formatted strings: warn!(device = %device, model_id = model.id, error = %err, "modbus read failed").
  • Messages are short, lower case, without a trailing period.
  • debug! for anything per read, per cycle or per batch; info! for lifecycle events (startup, models discovered, shutdown); warn! for recoverable failures (a session ended, a sample dropped, records not delivered). Steady-state operation should be quiet at info.

Metrics

  • Names start with sunspec_; counters end in _total; durations are histograms in seconds ending in _seconds.
  • Register HELP text for every new metric in describe_metrics() (crates/collector-app/src/metrics_server.rs) and add it to the metrics table in Metrics.
  • Per-device metrics use the device label with DeviceIdentity::key() (host/unit). Do not add labels with unbounded values (batch sizes, row IDs, error messages).

Formatting

rustfmt.toml: edition 2021, max_width = 100, reorder_imports and reorder_modules. Run cargo fmt --all before committing.

Record schema

Any change to crates/avro-kafka/schemas/sunspec_sample.avsc changes the schema fingerprint, so consumers holding the old schema can no longer decode new records. Treat it as a breaking change: bump the namespace (com.rusty.sunspec.v2 to v3), update the codec and its tests, and describe the change in the CHANGELOG.


Changelog and releases

  • CHANGELOG.md follows Keep a Changelog. Every user-visible change gets an entry under ## [Unreleased] in the same pull request, in Added, Changed, Fixed, Removed or Security.
  • Changes that need action from users (record format, metric names, config keys and validation) start with Breaking:.
  • All crates share one version, version under [workspace.package] in Cargo.toml. Releases are published by .github/workflows/release.yml when a v* tag is pushed; the steps are in Cutting a release.
  • Dependabot opens weekly pull requests for Cargo dependencies (deps prefix; patch updates grouped into one PR, minor and major updates one PR each, since a 0.x minor bump is breaking), GitHub Actions (ci prefix; minor and patch releases grouped) and the base image digests of the Dockerfiles and .clusterfuzzlite/Dockerfile (docker prefix, grouped). The Rust builder image stays on the MSRV, so Dependabot updates only its digest.

Documentation

The documentation is an mdBook in docs/ (docs/SUMMARY.md lists the pages). It is organized by what readers come to do: getting-started/, guides/, reference/, contributing/ and project/, plus the larger pages at the top of docs/ (configuration, architecture, this guide, building and packaging, buffer maintenance). Links between pages are relative ([Architecture](ARCHITECTURE.md) from the top, [Metrics](../reference/metrics.md) from a folder); the changelog page includes CHANGELOG.md from the repository root.

docs/reference/models/ is generated from the bundled SunSpec definitions, one page per group of model IDs plus an index. Do not edit it by hand: after changing the bundled models, run UPDATE_MODEL_DOCS=1 cargo test -p sunspec-parser --test models_docs. Without the variable the same test fails when the pages are stale or missing from docs/SUMMARY.md.

cargo install mdbook --version 0.4.40 --locked
cargo install mdbook-linkcheck --version 0.7.7 --locked   # optional: checks links and files
mdbook serve docs --open    # local preview with reload
mdbook build docs           # static site in docs/book (docs/book/html with mdbook-linkcheck)

The Deploy Docs workflow runs the same build, with mdbook-linkcheck, on pull requests that change the docs, and deploys the site from main. A page that moves gets a redirect in docs/book.toml ([output.html.redirect]).


Debugging

Verbose logging

RUST_LOG=info,collector_app=debug,poller_actor=debug cargo run -p collector-app
RUST_LOG=info,poller_actor=debug,modbus_client=debug cargo run -p collector-app   # every read
RUST_LOG=debug cargo run -p collector-app             # everything, libraries included
SUNSPEC_LOG_FORMAT=json cargo run -p collector-app    # one JSON object per line

Common problems

ProblemFix
cmake not found while building rdkafka-sysInstall cmake (apt install cmake, brew install cmake)
curl/curl.h: No such file or directoryInstall the curl headers (libcurl4-openssl-dev); see Building & Packaging
openssl-sys cannot find OpenSSLInstall libssl-dev and pkg-config (Linux) or brew install openssl@3 (macOS)
Startup fails with load config failed or config validation failedConfiguration is strict; the error names the key or variable
Startup fails with bind metrics port 9090The port is taken (often by another collector); set SUNSPEC_METRICS_PORT to a free port (0 picks one)
failed to install metrics recorderThe recorder can be installed once per process; keep such tests in their own test file
SQLite database is lockedUse one collector per buffer file
Modbus timeoutsCheck nc -zv <ip> 502; raise SUNSPEC_REQUEST_TIMEOUT_MS (SUNSPEC_MODBUS_TIMEOUT_MS is deprecated and ignored)
no SunSpec register map at base addressesThe device does not expose SunS at the configured base or at 40000, 0 or 50000; check the unit ID and SUNSPEC_BASE_ADDRESS

Building and Packaging

How to build the collector natively, as Docker images and as portable release binaries for x86_64 and 64-bit ARM, how releases are published, and how to run the resulting binary. For the development workflow (tests, CI, the simulator) see the Developer Guide; for deploying with systemd see Deployment.


What gets built

The workspace produces two binaries:

  • collector-app, the collector (crate crates/collector-app);
  • sunspec-sim, the SunSpec Modbus TCP simulator (crate crates/sunspec-sim), for local development and tests.
BuildCommandOutput
Debugcargo build --workspacetarget/debug/collector-app, target/debug/sunspec-sim
Releasecargo build --release --locked -p collector-app -p sunspec-simtarget/release/collector-app, target/release/sunspec-sim
Portable release (x86_64 or aarch64)cross build --release --locked --target <target> -p collector-app -p sunspec-sim --features collector-app/vendored-openssltarget/<target>/release/collector-app, target/<target>/release/sunspec-sim
Collector imagedocker build -t sunspec-collector ./app/collector-app in the image
Simulator imagedocker build --target simulator -t sunspec-simulator ./app/sunspec-sim in the image

--locked builds with the committed Cargo.lock, as CI and the Dockerfile do. The workspace uses Cargo's default release profile.


Toolchain and MSRV

The minimum supported Rust version is 1.88, set as rust-version in the workspace Cargo.toml. The Docker builder stage uses the same version (rust:1.88-slim-bookworm), so the Docker build checks that the MSRV still works; the other CI jobs use the latest stable Rust. When the MSRV changes, change both places.


Prerequisites

Debian / Ubuntu

sudo apt-get update
sudo apt-get install -y build-essential cmake pkg-config libssl-dev libcurl4-openssl-dev

These are the packages CI installs (build-essential is preinstalled on the CI runners).

macOS

xcode-select --install       # compiler, linker and SDK (the SDK provides the curl headers)
brew install cmake openssl@3

openssl-sys finds Homebrew's OpenSSL without further configuration. Native macOS binaries link libssl and libcrypto from Homebrew dynamically, so openssl@3 must stay installed on the machine that runs them.


Why cmake, OpenSSL and curl

The Kafka client is rdkafka 0.39 with the features cmake-build, ssl, tokio and zstd:

  • cmake and a C/C++ toolchain. cmake-build compiles librdkafka 2.12 (bundled in rdkafka-sys 4.10.0+2.12.1) from source with CMake. No system librdkafka is used, so the version is the same on every platform.
  • OpenSSL. ssl builds librdkafka with TLS support against the system OpenSSL, which needs the headers at build time (libssl-dev) and the library at run time (libssl3), unless the vendored-openssl feature is used (see below).
  • curl headers. librdkafka is built with curl disabled and the binary does not link libcurl. However, librdkafka 2.12's rdkafka_conf.c includes <curl/curl.h> under #ifdef WITH_OAUTHBEARER_OIDC, and the generated config.h always defines that macro (as 0 or 1), so the header has to be present for the build to compile.
  • zstd and SQLite need nothing from the system: zstd-sys compiles its bundled zstd, and sqlx's sqlite feature compiles a bundled SQLite.

The first build compiles librdkafka and therefore takes noticeably longer than later builds.

Statically linked OpenSSL

collector-app has an opt-in feature vendored-openssl (forwarded to avro-kafka and rdkafka's ssl-vendored). With it, OpenSSL is compiled from source and linked statically, so the binary does not depend on the OpenSSL of the machine it runs on. Building OpenSSL needs perl and make.

cargo build --release --locked -p collector-app --features vendored-openssl

The release builds use it; native and Docker builds do not.


Native builds

cargo build --workspace                                       # debug, all crates
cargo build --release --locked -p collector-app               # release collector
cargo build --release --locked -p collector-app -p sunspec-sim

A native Linux release build links OpenSSL 3 and glibc dynamically; check the full list for your system with ldd target/release/collector-app. To install it, copy the binary, for example to /usr/local/bin/collector-app for the systemd unit described in Deployment.


Docker images

The Dockerfile has these stages:

StageBaseContents
builderrust:1.88-slim-bookwormInstalls cmake, build-essential, libssl-dev, libcurl4-openssl-dev, pkg-config and python3, then runs cargo build --release --locked -p collector-app -p sunspec-sim and copies both binaries to /out
prebuiltscratchOnly with --build-arg BINARIES=prebuilt: copies dist/bin/<arch>/collector-app and sunspec-sim to /out instead of compiling (see Release images)
runtimedebian:bookworm-slimInstalls ca-certificates, libssl3 and curl (for the health check) and creates the system user and group collector
simulatorruntime/app/sunspec-sim, runs as collector, exposes 1502, ENTRYPOINT ["/app/sunspec-sim"] with default arguments --bind 0.0.0.0:1502 --units 1
collectorruntime/app/collector-app, runs as collector with working directory /app/data (owned by collector), exposes 9090, a HEALTHCHECK on /healthz, ENTRYPOINT ["/app/collector-app"]; the default target
docker build -t sunspec-collector .                         # collector (default target)
docker build --target simulator -t sunspec-simulator .      # simulator

Build cache

The Dockerfile starts with # syntax=docker/dockerfile:1 and uses BuildKit cache mounts for the cargo registry (/usr/local/cargo/registry) and the target directory (/app/target), so rebuilds after a source change reuse the compiled dependencies, librdkafka included. Cache mounts need BuildKit, which is the default builder in current Docker Engine and Docker Desktop releases. Because the target directory is a cache mount and not part of the image, the binaries are copied to /out in the same step. Both images share the builder stage, which builds both binaries in one cargo invocation.

.dockerignore keeps target/, .git/, .github/, docs/, docker/ and SQLite files out of the build context.

Running the collector image

docker run -d --name sunspec-collector \
  -p 9090:9090 \
  -v sunspec-data:/app/data \
  -e SUNSPEC_STATIC_DEVICES=192.168.1.20:1 \
  -e SUNSPEC_KAFKA_BROKERS=kafka.example.internal:9092 \
  sunspec-collector
  • Buffer. The image does not set SUNSPEC_BUFFER_PATH. The collector runs in /app/data, so the default relative buffer.path (sunspec-buffer.sqlite) is /app/data/sunspec-buffer.sqlite, and a buffer.path from a config file or SUNSPEC_BUFFER_PATH is used as given (relative paths resolve against /app/data). Keep /app/data on a volume so buffered samples survive container replacement. A new named volume takes its ownership from the image, so it is writable by collector; a bind-mounted host directory must be made writable for that user yourself.

  • Upgrading from older images. Images that set SUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite stored the buffer under that name. To keep sending rows already buffered in an existing volume, set SUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite (or move the file and its -wal and -shm files to sunspec-buffer.sqlite while the collector is stopped).

  • Config file. Mount it and set SUNSPEC_CONFIG, or pass --config: the image's entrypoint is /app/collector-app, so arguments after the image name go to the collector:

    docker run -d -v "$PWD/collector.toml:/app/collector.toml:ro" -v sunspec-data:/app/data \
      -p 9090:9090 sunspec-collector --config /app/collector.toml
    
  • Environment files. With --env-file, put comments on their own lines: Docker does not strip trailing # ... from values, and the collector rejects values it cannot parse.

  • Health check. curl -fsS "http://localhost:${SUNSPEC_METRICS_PORT:-9090}/healthz" every 30 s (timeout 3 s, start period 20 s, 3 retries), so it follows SUNSPEC_METRICS_PORT. If you set the port only in a config file ([metrics] port), also set SUNSPEC_METRICS_PORT or override the check with --health-cmd.

  • Stopping. docker stop sends SIGTERM, which starts the graceful shutdown (pollers stop, queued samples are written, the uplink drains for up to buffer.shutdown_drain_timeout_ms). Docker sends SIGKILL after 10 s by default; docker stop -t 30 matches the 30 s the systemd unit allows.

  • Schema and version. docker run --rm sunspec-collector --print-schema prints the Avro schema of the published records, and --version the collector version.

Running the simulator image

docker run --rm -p 1502:1502 sunspec-simulator --units 1,2

Arguments replace the default --bind 0.0.0.0:1502 --units 1; the bind address defaults to 0.0.0.0:1502 anyway. See the Developer Guide for all flags.


Docker Compose stack

docker-compose.yml runs a complete local pipeline without hardware:

ServiceImageNotes
kafkaapache/kafka:3.9.1Single node in KRaft mode (broker and controller). Listeners: kafka:9092 inside the compose network, localhost:29092 from the host. Topics are created automatically with 3 partitions; data in the kafka-data volume.
simulatorsunspec-simulator:local (built from the simulator target)Units 1 and 2 on port 1502
collectorsunspec-collector:local (built from the collector target)Polls simulator:1 and simulator:2, publishes to kafka:9092, topic sunspec.telemetry; port 9090 on the host; buffer in the collector-data volume; starts once Kafka is healthy; restart: unless-stopped
prometheusprom/prometheus:v3.5.0localhost:9091 on the host; scrapes collector:9090 every 15 s (docker/prometheus.yml)
docker compose up --build            # build the images and start everything
curl localhost:9090/healthz          # 200 with a JSON body once the pipeline runs
curl -s localhost:9090/metrics | grep '^sunspec_uplink_records_total'

# Record keys on the topic (the values are binary Avro)
docker compose exec kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 --topic sunspec.telemetry --from-beginning \
  --max-messages 10 --property print.key=true --property print.value=false

docker compose down -v               # stop and remove the containers and volumes

docker compose down without -v keeps the volumes, and with them the collector's buffer and the Kafka data. docker compose stop gives containers 10 s before killing them; use docker compose stop --timeout 30 collector for a full graceful stop, as CI does.


Release builds

Release binaries are built with cross for x86_64-unknown-linux-gnu and aarch64-unknown-linux-gnu, in images derived from the cross-rs 0.2.5 images (Ubuntu 16.04). They need glibc 2.18 or newer and link nothing else that a target system might lack: OpenSSL is vendored, librdkafka, SQLite, zstd and zlib are compiled in, and the only shared libraries are glibc's and libgcc_s.

cargo install cross --locked
target=aarch64-unknown-linux-gnu
docker build -t "sunspec-cross-$target" -f docker/Dockerfile.cross docker
# x86_64: pass the pinned image from the release workflow's build matrix
# docker build -t sunspec-cross-x86_64-unknown-linux-gnu -f docker/Dockerfile.cross \
#   --build-arg BASE_IMAGE=ghcr.io/cross-rs/x86_64-unknown-linux-gnu:0.2.5@sha256:<digest> \
#   --build-arg DEB_ARCH=amd64 --build-arg MULTIARCH=x86_64-linux-gnu docker
cross build --release --locked --target $target -p collector-app -p sunspec-sim \
  --features collector-app/vendored-openssl
readelf -d target/$target/release/collector-app | grep NEEDED    # glibc and libgcc_s only
  • Cross.toml maps each target to the image tag sunspec-cross-<target>; build the image under that tag first.
  • docker/Dockerfile.cross starts from BASE_IMAGE, the target's ghcr.io/cross-rs/<target>:0.2.5 image pinned by digest (the default is the aarch64 one; the release workflow's build matrix lists both). It adds the target's Debian architecture if it is not the image's own, installs cmake, make, perl, pkg-config and libcurl4-openssl-dev for the target (the curl headers librdkafka needs at compile time), and points PKG_CONFIG_LIBDIR and PKG_CONFIG_PATH at the target's multiarch directory.
  • --features collector-app/vendored-openssl is required: the images' OpenSSL is older than the version librdkafka 2.12 needs. OpenSSL is compiled from source (hence make and perl).

Archives

scripts/package-release.sh <version> <target> <bin-dir> writes dist/sunspec-collector-<version>-<target>.tar.gz, one directory holding collector-app, sunspec-sim, README.md, CHANGELOG.md, LICENSE-MIT, LICENSE-APACHE, LICENSE-SUNSPEC-MODELS (the bundled model definitions), config.example.toml, sunspec-collector.env and sunspec-collector.service. It also copies the binaries to dist/bin/<amd64|arm64>/ for the release images.

scripts/install.sh installs an archive on a Linux machine; see Deployment. Its SUNSPEC_ROOT, SUNSPEC_TARGET and SUNSPEC_BASE_URL variables install under another directory, for another target, or from local files, which is how CI tests it.

Release images

docker build --build-arg BINARIES=prebuilt packages the binaries in dist/bin/<arch>/ instead of compiling them, so a multi-arch image needs no compilation under emulation:

docker buildx build --platform linux/amd64,linux/arm64 --build-arg BINARIES=prebuilt \
  --target collector -t sunspec-collector .

The published images are ghcr.io/imrans110/sunspec-collector and ghcr.io/imrans110/sunspec-simulator, tagged with the version (0.5.0), the minor series (0.5), latest for the newest non-prerelease, and sha-<commit>.

Release workflow

.github/workflows/release.yml runs on pushes to main, on pull requests that change the Dockerfiles, Cross.toml, scripts/ or the workflow, on manual dispatch, and on v* tags:

JobWhat it does
PrepareLints scripts/ with shellcheck; works out the version. On a tag it must equal the workspace version, point at a commit on main, and have a CHANGELOG.md section
BuildCross-compiles both targets; checks the ELF machine, that only glibc and libgcc_s are linked, and that no glibc symbol is newer than 2.18; packages the archives
AssembleAdds install.sh (with the version filled in), SHA256SUMS and the release notes; installs with sudo sh install.sh on the runner, starts the service under systemd against the simulator, checks it polls, and reinstalls to check the environment file is kept. On a tag (and on a manual run) it attests the archives, install.sh and SHA256SUMS, writes the signed bundle as sunspec-collector-<version>.intoto.jsonl and verifies each file against it
ImageBuilds each image for linux/amd64 and linux/arm64 from the archived binaries and runs --version on both platforms (under QEMU for arm64). On a tag it pushes the image, attests it (the attestation is pushed to GHCR too) and verifies the attestation
PublishOn a tag only: creates the GitHub Release with the archives, install.sh, SHA256SUMS and the provenance bundle

Every run except a tag push is a dry run: nothing is pushed or published. The archives and the release files are kept as the run's artifacts (archive-<target> and release). A manual run (gh workflow run release.yml --ref <branch>) also attests its release files, to test the attestation steps; pull request and main dry runs skip them.

Verifying a release

See Verifying a release.

Cutting a release

  1. Pick the version (the Kafka record format and the metric names are part of the public interface, so changing them needs a new minor version before 1.0).

  2. In a pull request, set version under [workspace.package] in Cargo.toml, run cargo check so Cargo.lock follows, and rename ## [Unreleased] in CHANGELOG.md to ## [<version>] - <date> with a new empty ## [Unreleased] above it.

  3. After it merges, tag the merge commit on main and push the tag:

    git switch main && git pull
    git tag -a v0.6.0 -m "v0.6.0"
    git push origin v0.6.0
    
  4. The workflow publishes the images and the release. The GHCR packages are linked to this public repository and take its visibility, so they can be pulled without logging in.


Running the binary

See Command-line options for the options, the environment, files, ports, signals and exit status.

Fuzzing

fuzz/ holds cargo-fuzz targets for the code that reads input the collector does not control:

TargetInputChecks
decode_modelA bundled model ID and arbitrary registersdecode_model_labeled does not panic, agrees with decode_model, names each point once, labels only existing points, and returns only finite floats
model_chainAn arbitrary register map at the configured or a standard basediscover_models ends with a chain or an error; an accepted chain starts with SunS, has at most 256 contiguous models and ends with 0xFFFF; each model it finds decodes without panicking
configAn arbitrary config file, loaded as TOML and as JSONCollectorConfig::from_file_contents, validate() and device_config() return errors instead of panicking

discover_models reads through the ReadRegisters trait, which the target implements with an in-memory register map, so the chain walk runs without a network. The config target skips inputs that could name a *_file setting, since loading reads those files.

fuzz/ is its own workspace (it needs nightly Rust and sanitizers), so the workspace commands above do not build it. It has no committed lockfile: builds copy the workspace's Cargo.lock into fuzz/ first, so the targets use the same dependency versions as the collector, and only one lockfile needs updates. To run a target locally:

rustup toolchain install nightly
cargo install cargo-fuzz --locked
cp Cargo.lock fuzz/                          # build against the workspace's locked versions
fuzz/seeds.py fuzz/seeds                     # seed corpora from fixtures/ and the example config
cargo +nightly fuzz run -O decode_model fuzz/corpus/decode_model fuzz/seeds/decode_model \
  -- -max_total_time=300

A crash is saved under fuzz/artifacts/<target>/; cargo +nightly fuzz run -O <target> <file> replays it and cargo +nightly fuzz tmin -O <target> <file> shrinks it. Turn a fixed crash into a regression test in the crate's tests (see values_that_are_not_finite_are_null in crates/sunspec-parser/tests/decode_tests.rs, found by decode_model).

In CI, .github/workflows/fuzz.yml uses ClusterFuzzLite: it builds the targets in the OSS-Fuzz Rust image (.clusterfuzzlite/) with AddressSanitizer and seed corpora from fuzz/seeds.py. It fuzzes for two minutes on pull requests that change the fuzzed code (crates/sunspec-parser/, crates/poller-actor/, crates/collector-app/src/config.rs) or the fuzzing setup (fuzz/, .clusterfuzzlite/, the workflow), and for half an hour every Sunday, which also covers dependency updates; the corpus is kept between runs as workflow artifacts. A crash fails the run and is uploaded with its input. When you add a target, add a [[bin]] to fuzz/Cargo.toml, add it to targets in .clusterfuzzlite/build.sh, add the code it fuzzes to the paths in fuzz.yml, and give it seeds in fuzz/seeds.py if the fixtures have useful input.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog.

[Unreleased]

Upgrade notes

  • A wrong setting now exits with status 78 instead of 1. Rerun install.sh to get the systemd unit with RestartPreventExitStatus=78 (an edited unit is saved as .service.bak). Scripts or supervisors that check for exit status 1 after a configuration error should accept 78

Added

  • The documentation site lists every supported SunSpec model with its points (type, units, label, description and enum and bitfield names), generated from the bundled definitions: Reference → Supported SunSpec models

Changed

  • Configuration errors (settings that do not load or validate, an invalid SUNSPEC_LOG_FORMAT, Kafka settings the producer rejects) exit with status 78 (EX_CONFIG), and the systemd unit does not restart on it (RestartPreventExitStatus=78): the service stops once and shows as failed with the error in the journal, instead of restarting every 5 s with the same error. Other startup errors still exit with 1 and are restarted
  • Quieter Kafka outages: librdkafka reported every reconnect attempt to a broker that was down, three error lines about every second. A failed broker connection is now logged at warn at most once per 5 minutes (Kafka broker connection failed), with the repeats at debug; librdkafka's other log lines keep their level under the librdkafka target, and other client errors are logged as Kafka client error. The uplink logs records delivered again (info) when an outage ends
  • After a Kafka outage the backlog starts draining within about 2 s of the brokers answering, instead of after up to 30 s of backoff: when a batch timed out while broker connections were failing, the uplink asks the brokers for the topic's metadata every 2 s and ends the backoff when they answer. Rejected records keep the full backoff
  • Discovery no longer warns on every reconnect about base addresses that hold no SunSpec map (for example 40000 on a device whose map is at 50000); those reads, and each failed Modbus attempt before a retry, are at debug. The poller still warns about a read that finally failed, and when no base has a map the error gives the device's last answer, e.g. (last answer: Gateway target device failed to respond)
  • The docs and the shipped environment file say how to run without Kafka (comment out SUNSPEC_KAFKA_BROKERS; an empty value is a configuration error) and suggest RUST_LOG=info,collector_app=debug,poller_actor=debug over a bare debug, which also turns on the libraries' debug output
  • The ignored Kafka decode test reads the device host from SUNSPEC_E2E_DEVICE_HOST (default simulator), so it also runs against a collector polling a simulator on the host
  • The documentation site is reorganized by task: Get started (quick start, deployment), Guides (Kafka security, serial devices, polling profiles, probing a device, verifying a release, monitoring and alerts, buffer maintenance, troubleshooting, upgrade notes), Reference (configuration, command-line options, HTTP endpoints, metrics, record format, models), How it works, Contributing and Project (with the changelog). The Operations page was split up; its old URL redirects to Deployment, and moved sections leave a pointer at their old place. The site now follows the system light or dark setting, and each page links to its source for editing

Fixed

  • A device closing its Modbus TCP connection is reported as connection closed by the device. tokio-modbus reports it as whatever OS error the thread saw last, so logs showed messages such as Operation now in progress (os error 36) or Resource temporarily unavailable (os error 35)
  • The deployment guide listed a modbus reconnected log line; the collector logs modbus connected

[0.5.0] - 2026-10-18

Upgrade notes

  • If you run the collector with the shipped systemd unit (install.sh installs it), upgrade: in 0.2.0–0.4.0 its watchdog pings never reached systemd, so WatchdogSec=30 killed and restarted the service about every 30 seconds (see Fixed). Rerun install.sh, then sudo systemctl restart sunspec-collector.
  • Records use Avro schema 2.1, which adds a labels map with an empty default. Readers resolve old and new records with either schema, but the schema fingerprint changes: consumers that look up the writer schema by fingerprint need the new schema (collector-app --print-schema) first.

Added

  • Enum and bitfield names: records carry labels, e.g. St: MPPT and Evt1: GROUND_FAULT,AC_DISCONNECT (set bits comma-separated, bit<n> for unnamed bits, empty when no bit is set), decoded with the symbol tables in the bundled definitions. /devices shows them too
  • Model catalog: collector-app --print-models prints, and GET /models and /models/{id} serve, every bundled model's points with type, units, label, description and enum/bitfield symbols; point names match records (module[*].DCA for repeating groups)
  • /devices and /devices/{host}/{unit} on the metrics port: each device's state, discovered and polled models, and the latest decoded reading of each polled model, as JSON. Only the latest reading per model is kept
  • Serial devices through serial-to-Ethernet converters: modbus.framing = "rtu" (also SUNSPEC_MODBUS_FRAMING and a per-device framing) speaks Modbus RTU frames over TCP. Units on the same converter share one connection, so their requests never overlap on the RS-485 bus; units sharing a converter must use the same request_timeout_ms and max_batch_size. --probe works through converters too
  • Signed build provenance for releases: the archives, install.sh, SHA256SUMS and both GHCR images are attested by the release workflow (SLSA provenance signed with Sigstore, verifiable with gh attestation verify), and each release includes the signed bundle sunspec-collector-<version>.intoto.jsonl
  • sunspec-sim --rtu and sunspec_sim::start_rtu_over_tcp simulate a converter (RTU framing with CRC; absent units stay silent); ModbusClient::new creates a client that connects on its first read

Changed

  • Every crate now declares the workspace MSRV (rust-version = "1.88"), and the workspace uses Cargo's resolver 3, so dependency updates keep to versions that build on Rust 1.88
  • The help text of sunspec_poll_duration_seconds now says it times the models due in one poll cycle (it has done so since polling profiles)

Fixed

  • The systemd watchdog pings now reach systemd. The readiness notification unset NOTIFY_SOCKET, so every watchdog ping after it went nowhere, and with the shipped unit's WatchdogSec=30 systemd killed and restarted the service about every 30 seconds. sd-notify is updated to 0.5, whose notify leaves the environment alone; the release workflow's install test now checks the service outlives its watchdog timeout
  • A scale factor far outside SunSpec's -10 to 10 no longer produces infinite or NaN values (for example WH_SF = 23901 made WH infinite); points whose value is not a finite number, including infinite float readings, are now null. Found by fuzzing

Security

  • The openssl crate (0.10.75, with eight advisories such as CVE-2026-41678) is no longer a dependency. It came in on Linux through the Prometheus exporter's push-gateway client (hyper-tls, native-tls), which the collector never used: /metrics is served by the collector's own HTTP server. The exporter's HTTP listener and push gateway are now disabled, removing about 30 crates; the advisories were in APIs the collector did not call. librdkafka's TLS (openssl-sys) is unaffected
  • The Docker images and release binaries are built from base images pinned by digest (rust, debian and the cross-rs images), and every GitHub Actions step is pinned to a commit SHA, so a re-pushed tag upstream cannot change what a release is built with. OpenSSF Scorecard runs weekly; its badge is in the README

[0.4.0] - 2026-10-17

Added

  • collector-app --probe HOST[:UNIT] reads one device's SunSpec map once and prints it as JSON (base address, model chain, registers, decoded points), using the service's configuration. Serial numbers and network addresses are blanked unless --keep-identifiers is given
  • sunspec-sim --fixture dump.json replays a probe dump; the library gains read_dump, UnitSpec::from_dump and SimHandle::load_dump, and types gains DeviceDump
  • fixtures/devices/: probe dumps that CI replays through discovery and decoding (fixture_replay_tests.rs); starts with a synthetic dump of the simulator's demo inverter
  • The device report form asks for a probe dump

[0.3.0] - 2026-10-17

Upgrade notes

  • A static device listed twice (same host and unit ID) now fails config validation; remove the duplicate entry.
  • Records for models outside the 31 bundled in 0.2.0 (for example the float meters 211–214) now carry decoded points instead of an empty map. The schema is unchanged.
  • The example config (docs/config.example.toml) now reads models 1 and 120 hourly and 121 every 10 minutes; remove [poller.model_intervals_s] from a copied config to read them every cycle.

Added

  • Every official SunSpec model definition is bundled: 112 models from github.com/sunspec/models (commit 90b4a331), up from 31. Newly decoded: network and security models (2–19), IEEE 1547-2003 era controls (125–145), float meters (211–214) and secure meter readings (220), environmental sensors (302–308), string combiners (401–404), solar modules (501–502), tracker (601), DER DC measurement and control (714–715), storage and lithium-ion/flow batteries (801, 803–809), and the test and vendor models in the official repository. The release binary grows by about 1 MB
  • Community files: CODE_OF_CONDUCT.md (Contributor Covenant 2.1), SECURITY.md, issue forms (bug report, feature request, device report) and a pull request template; a social preview image (docs/assets/social-preview.svg and .png)
  • Polling profiles: poller.model_intervals_s reads slow-changing models (for example common, nameplate and settings) at most once per interval instead of every cycle, and poller.include_models / poller.exclude_models choose which discovered models are polled (env SUNSPEC_MODEL_INTERVALS_S, SUNSPEC_INCLUDE_MODELS, SUNSPEC_EXCLUDE_MODELS). Cycles with nothing due do not contact the device and do not count as stalls
  • Per-device settings on [[discovery.static_devices]] entries: poll_interval_ms, request_timeout_ms, base_address, max_batch_size, include_models, exclude_models and model_intervals_s (merged model by model with the global intervals)
  • SimHandle::reads_at(unit, addr) in sunspec-sim, counting read requests per start address

Changed

  • sunspec-parser embeds every models/model_<id>.json through build.rs instead of a hand-maintained list
  • Breaking: a static device listed twice (same host and unit ID) is now a config error instead of being polled once
  • sunspec_poll_duration_seconds is recorded only for cycles that read at least one model

[0.2.0] - 2026-10-17

Upgrade notes

  • Kafka consumers must switch to the v2 record format (one Avro single-object record per sample, keyed by device); see crates/avro-kafka/schemas/sunspec_sample.avsc and docs/ops.md. Rows buffered by an older version are published with empty points, so let the old version drain its buffer before upgrading if possible.
  • Docker: the default buffer file is now /app/data/sunspec-buffer.sqlite (previously /app/data/buffer.sqlite via the image's SUNSPEC_BUFFER_PATH). To keep using an existing volume's unsent rows, set SUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite (or buffer.path).
  • Config is strict and several metric names changed; see docs/ops.md for the rename table and docs/configuration.md for deprecated keys.
  • Docker: the collector image's /app/collector-app is now the ENTRYPOINT, so arguments after the image name go to the collector (docker run sunspec-collector --config /app/collector.toml) instead of replacing the command.

Added

  • Release pipeline (.github/workflows/release.yml): portable x86_64 and aarch64 Linux archives (glibc 2.18+, OpenSSL linked statically) with the binaries, systemd unit, environment file, example config and licenses; SHA256SUMS; an install.sh that verifies and installs a release (curl -fsSL …/releases/latest/download/install.sh | sudo sh) and keeps an existing /etc/sunspec-collector.env; multi-arch ghcr.io/imrans110/sunspec-collector and sunspec-simulator images built from the same binaries; GitHub Release notes from this file. Every push to main runs it as a dry run, which installs the archive under systemd and smoke-tests both image platforms; a v* tag publishes
  • collector-app --version and --help, and sunspec-sim --version
  • Kafka connection security: TLS (ssl), mutual TLS, and SASL PLAIN / SCRAM-SHA-256 / SCRAM-SHA-512 (sasl_plaintext, sasl_ssl) via new kafka.security_protocol, kafka.ssl_* and kafka.sasl_* settings (and SUNSPEC_KAFKA_* variables). Passwords can be read from files (*_password_file) for Docker secrets and systemd credentials, are redacted in debug output, and mismatched settings (e.g. sasl_* with plaintext) fail validation. The compose stack now publishes over SASL/PLAIN with a Docker secret, which the CI Docker E2E job exercises
  • IEEE 1547-2018 DER models 701–713 (AC measurements, capacity, enter service, AC controls, volt-var, volt-watt, voltage/frequency trip curves, frequency droop, watt-var, storage capacity) bundled from the same pinned sunspec/models commit
  • vendored-openssl cargo feature (collector-app, avro-kafka): builds OpenSSL from source and links it statically; used by the aarch64 cross build so the binary does not depend on the target's OpenSSL
  • Docs: docs/build.md (building, Docker images, cross-compiling) and docs/buffer_maintenance.md (buffer semantics, retention, sizing, inspection); the October 2026 audit is recorded in docs/CODE_REVIEW.md
  • CI Docker E2E job: builds the images, runs Kafka + simulator + collector with docker compose, waits for /healthz, checks records are keyed per device, decodes the published records with the Rust consumer test, and verifies a clean SIGTERM stop
  • simulator Docker target and compose service (two units behind one gateway), so docker compose up --build produces data without hardware
  • /healthz endpoint: 200 while ingest and the uplink are checking in, 503 otherwise, with JSON detail (devices_up, devices_total, heartbeat ages); the systemd watchdog is pinged only while healthy
  • SUNSPEC_LOG_FORMAT=json for one JSON object per log line; RUST_LOG filtering is explicit
  • [metrics] port and [runtime] channel_capacity / respawn_delay_ms config file sections
  • Prometheus help text for every metric; *_seconds metrics are real histograms with buckets
  • Edge decoding with the official SunSpec model definitions (models 1, 101–103, 111–113, 120–124, 160, 201–204, 802, bundled from github.com/sunspec/models under Apache-2.0): scale factors applied, "not implemented" sentinels mapped to null, strings trimmed, repeating groups named group[i].Point
  • collector-app --print-schema prints the Avro schema of published records (crates/avro-kafka/schemas/sunspec_sample.avsc)
  • sunspec-sim writes named points and runs a live demo (changing power, current, voltage, frequency and energy) in the binary
  • Buffer retention: buffer.max_rows (default 1,000,000) and buffer.max_age_s (default 7 days) bound the buffer during long uplink outages; dropped rows are counted in sunspec_buffer_dropped_total{reason}, with sunspec_buffer_rows and sunspec_buffer_oldest_age_seconds gauges
  • buffer.shutdown_drain_timeout_ms (default 5 s): on stop, the uplink keeps draining to Kafka for up to this long after ingest has persisted everything
  • Per-device supervision: each device runs connect → discover → poll and is retried with exponential backoff (capped at 60 s) until shutdown, so devices that are offline at startup are picked up when they appear
  • Stall detection: a device session with no successful read for poller.stall_timeout_ms (default max(10 × poll interval, 30 s)) is restarted
  • Subnet mode rescans every discovery.rescan_interval_s (default 600) and starts pollers for new hosts; with no devices the collector stays up
  • Per-device metrics labelled device="host/unit": sunspec_poll_success_total, sunspec_poll_errors_total{kind}, sunspec_poll_duration_seconds, sunspec_device_up, sunspec_device_last_success_timestamp_seconds, sunspec_poller_restarts_total, sunspec_poller_stalls_total
  • sunspec-sim crate: a SunSpec Modbus TCP simulator (library and sunspec-sim binary) with per-unit register maps and fault injection, used by the test suite
  • CI pipeline with fmt, clippy, build, and test (.github/workflows/ci.yml)
  • Dependabot configuration for Cargo and GitHub Actions
  • Docker healthcheck and non-root user
  • systemd service hardening (NoNewPrivileges, ProtectSystem, etc.)
  • BufferStore::with_config() to respect max_connections setting
  • Per-device respawn backoff (exponential, capped at 60s)
  • Device-salted jitter to prevent correlated polling
  • debug!() log for zero-length model skip
  • SemaphoreClosed error variant in discovery
  • parse_unit_id_list() and discovery_unit_ids config field
  • Comprehensive documentation: Architecture, Developer Guide, Configuration, Operations
  • License files (MIT + Apache-2.0)
  • CONTRIBUTING.md

Fixed

  • Repeating groups whose count is a point in an enclosing group (e.g. the curve models' top-level NPt sizing Crv[i].Pt) decoded with no repetitions; counts are now resolved through enclosing groups like scale factors, and a not-implemented count (0xFFFF) means no repetitions
  • sunspec_device_up is published as 0 as soon as a device's poller starts, so devices that never answer are visible (previously they had no series)
  • sunspec_poll_errors_total also counts connect failures and Modbus errors during model discovery at session start
  • A metrics port that cannot be bound now stops the collector at startup (bind metrics port <port>) instead of running without /metrics and /healthz
  • The Docker image no longer sets SUNSPEC_BUFFER_PATH (which overrode buffer.path from a config file); the collector runs in /app/data, so the default buffer is /app/data/sunspec-buffer.sqlite. The HEALTHCHECK follows SUNSPEC_METRICS_PORT
  • The example env file had an inline comment that systemd and docker --env-file pass through as part of the value, which strict config rejected; tests now load the shipped example config and env file
  • docker-compose used bitnami/kafka:3.7, which no longer exists on Docker Hub; it now uses the official apache/kafka:3.9.1 (KRaft) with a host listener on localhost:29092
  • Uplink throughput was capped at batch_size / drain_interval (200 samples/s by default) because it paused between every batch; it now drains back to back while a backlog remains
  • Gateways: devices are keyed by host and unit ID, so every unit behind one IP is polled (previously only the last one)
  • Model discovery walks the model chain header by header instead of reading a fixed 200-register window, so models beyond register 200 (e.g. status, controls, MPPT) are no longer dropped; the standard bases 40000, 0 and 50000 are probed after the configured one
  • sunspec.base_address = 0 is accepted; 0 is a valid SunSpec base address
  • Samples still queued at shutdown are written to the buffer before exit
  • Kafka publishing failed at startup with the default zstd compression because librdkafka was built without zstd; rdkafka's zstd feature is now enabled
  • SIGTERM (sent by systemd and Docker on stop) now takes the graceful shutdown path instead of killing the process
  • Modbus client re-establishes the connection after transport errors and timeouts; Modbus exceptions no longer count as connection failures
  • Device hosts may be hostnames; every resolved address is tried in turn
  • Duplicate PrometheusBuilder::new().install_recorder() call (runtime crash)
  • Duplicate kafka_topic and kafka_enable_idempotence in Default impl (compile error)
  • Missing discovery_unit_ids field on CollectorConfig (compile error)
  • Uplink task: separated corrupt vs valid messages to prevent head-of-line blocking
  • Modbus backoff shift overflow (clamped to 63)
  • Removed unused retry_count column from buffer schema

Changed

  • All crates take their version from [workspace.package] in Cargo.toml, so a release bumps one line
  • Config validation rejects kafka.acks 0/1 while kafka.enable_idempotence is true (librdkafka refused to start) and modbus.max_batch_size above 125 (previously clamped silently)
  • modbus.timeout_ms / SUNSPEC_MODBUS_TIMEOUT_MS are deprecated and ignored with a warning: device sessions always used poller.request_timeout_ms, so the setting had no effect
  • README and docs rewritten against the code: removed claims that were not true ("zero data loss", "90%+" overhead reduction, "zombie poller detection", graceful SIGTERM before it existed) and dead links
  • CI runs on every push to main and every pull request (no path filters), so its checks can be required for merging
  • The systemd unit runs as a dynamic unprivileged user with StateDirectory=sunspec-collector (it ran as root), allows 30 s for the graceful stop, and adds further sandboxing
  • Docker HEALTHCHECK uses /healthz; images run as a non-root user; the cross-compilation image (now docker/Dockerfile.cross) drops the unused librdkafka-dev and libsqlite3-dev packages
  • Breaking: config is strict. Unknown keys in the config file, unparsable SUNSPEC_* values (the error names the variable), invalid static devices (e.g. unit 300) and unsupported kafka.compression / kafka.acks values fail at startup instead of silently falling back to defaults
  • Breaking: metrics renamed to the sunspec_ prefix with base units and _total counters: sunspec_buffer_writes_total, sunspec_uplink_records_total, sunspec_uplink_errors_total, sunspec_uplink_publish_seconds; the high-cardinality batch_size label is gone
  • Logs use ANSI colours only on a terminal; per-host discovery timeouts log at debug instead of warn
  • Kafka topic validation now matches Kafka's own rules
  • Breaking: Kafka records are now one Avro single-object message per sample (schema v2, com.rusty.sunspec.v2.SunspecSample), keyed by host/unit_id, with content-type and schema-fingerprint headers. Previously each message was an Avro container file mixing devices, with the schema embedded and no key. Records carry decoded points alongside the raw registers
  • Breaking: model_name uses the official SunSpec names (e.g. inverter_three_phase instead of three_phase_inverter)
  • Only records Kafka acknowledges are deleted from the buffer; undelivered records stay buffered and are retried first, and records that cannot be encoded are discarded and counted
  • The producer lingers 50 ms so records to the same partition are batched and compressed together
  • Samples are written to SQLite in transactions of up to 256 rows (one fsync per batch instead of per sample), reducing flash wear
  • Delivery is documented as at-least-once: rows are deleted only after Kafka acknowledges them, so a crash between publish and delete can produce duplicates
  • sunspec.discovery_register_count / SUNSPEC_DISCOVERY_REG_COUNT are deprecated and ignored (a warning is logged)
  • poller.jitter_ms now offsets each device's fixed polling schedule instead of stretching every interval
  • Orchestration moved from main.rs into collector_app::run and library modules so it can be tested end to end
  • tokio-modbus 0.9 → 0.17
  • The Kafka integration test is #[ignore]d with instructions instead of silently passing without a broker; the diagslave test is replaced by simulator-backed client tests
  • Docker base images upgraded from bullseye to bookworm
  • Tokio features narrowed from "full" to specific features
  • Removed #![allow(dead_code)] from all 7 library crates
  • Cargo.lock is now committed (removed from .gitignore and .dockerignore)
  • mdBook version pinned to 0.4.40 in deploy workflow
  • MSRV raised to 1.88 in workspace Cargo.toml and the Docker builder image (required by quick-xml 0.42 and apache-avro 0.21)
  • rustfmt.toml expanded with max_width, reorder_imports, reorder_modules
  • sqlx 0.7 → 0.8, built with only the sqlite and runtime-tokio features (no TLS stack or unused MySQL/Postgres drivers)
  • quick-xml 0.31 → 0.42

Removed

  • CollectorConfig::discovery_unit_ids (duplicate of discovery.unit_ids)
  • Unused model-catalog code in sunspec-parser (the id/name/length JSON and XML parsers, ModelCatalog, apply_scale); the decoder and bundled definitions replace them

Security

  • Resolved all cargo audit vulnerabilities: RUSTSEC-2024-0363 (sqlx), RUSTSEC-2023-0071 (rsa, via the unused sqlx MySQL driver), RUSTSEC-2026-0346/0349 (rustls 0.21), RUSTSEC-2026-0098/0099/0104 (rustls-webpki 0.101), RUSTSEC-2026-0194/0195 (quick-xml), RUSTSEC-2026-0007 (bytes), RUSTSEC-2026-0204 (crossbeam-epoch)

[0.1.0] - 2024-12-23

Added

  • Initial implementation
  • Modbus TCP client with retry and exponential backoff
  • SunSpec model parsing (JSON, XML, register map)
  • Per-device poller actors with supervision
  • SQLite-backed durable message buffer (WAL mode)
  • Avro OCF serialization with Kafka producer
  • CIDR subnet discovery and static device lists
  • Prometheus metrics endpoint via Axum
  • systemd watchdog integration (Linux)
  • Docker multi-stage build
  • ARM64 cross-compilation via cross
  • mdBook documentation site with GitHub Pages deployment

Design Plan

This is the original design document written before implementation. For the current architecture, see Architecture Guide. For the post-implementation audit, see Code Review.

Rusty SunSpec Collector — Design Plan

1. Executive summary and strategic imperative

The renewable energy sector is moving toward decentralized generation and real-time telemetry. Solar PV, BESS, and smart inverters at the grid edge require reliable, low-latency data collection. A Rust-based SunSpec collector targets high-assurance, low-overhead edge compute by avoiding GC pauses, reducing memory usage, and enforcing memory safety at compile time. The design focuses on a production-grade collector that discovers SunSpec models, normalizes scale factors, buffers data durably, and forwards telemetry securely.

The architecture centers on a collect-buffer-forward pipeline using Rust for reliability and performance, tokio for async execution, SQLite for durable buffering, and Kafka for high-throughput uplink. This plan is a detailed execution blueprint for engineering and operations.

2. Architectural philosophy and design principles

2.1 The case for Rust at the edge

Edge gateways operate with tight CPU and memory budgets. Rust's zero-cost abstractions and memory safety provide strong correctness guarantees without a garbage collector. These properties reduce the risk of runtime failures and data corruption, which are costly in remote deployments.

2.2 System architecture: collect-buffer-forward

2.2.1 Ingestion layer (SunSpec poller)

  • Modbus TCP connections per device.
  • SunSpec model discovery based on the sentinel and model list.
  • Async actor model so timeouts on one device do not block others.

2.2.2 Persistence layer (resilient buffer)

  • Durable, on-disk buffer to handle backhaul interruptions.
  • SQLite with WAL mode for ACID durability and operational tooling.
  • Reads normalized data from SQLite and sends to Kafka.
  • Handles batching, compression, and backpressure.

2.3 Feature matrix and technology selection

ComponentSelected technologyJustificationAlternatives
LanguageRust (2021)Memory safety, zero-GC latency, high performancePython, C++, Go
Async runtimeTokioMature ecosystem, robust async patternsasync-std, smol
Modbus stacksunspec + tokio-modbusType-safe model discovery and scale factorsraw tokio-modbus
Local bufferSQLite (sqlx)ACID, file-based, strong toolingSled, RocksDB
MessagingKafka (rust-rdkafka)High throughput, decoupled producer/consumerMQTT, HTTP
SerializationAvroCompact binary with schema evolutionJSON, Protobuf
Resiliencesd-notify (systemd)Native watchdog integrationcustom timers

3. Detailed component implementation

3.1 SunSpec polling engine

3.1.1 Device model discovery

  • Connect via Modbus TCP.
  • Read the SunSpec sentinel and iterate models.
  • Build a device model catalog (e.g., model 103, 160, 201).
  • Use generated Rust structs from SunSpec JSON definitions.

3.1.2 Scale factor normalization

  • Raw register values represent fixed-point data.
  • Apply scale factors: Value * 10^ScaleFactor.
  • Treat sentinel values (e.g., 0x8000, NaN) as None.
  • Convert into normalized domain types (e.g., NormalizedInverterData).

3.1.3 Concurrency and actor model

  • One ConnectionActor per device.
  • Bounded mpsc channels for messaging.
  • Failures isolated per device with supervision and restart.

3.2 Persistence subsystem: SQLite vs. Sled

3.2.1 Technology evaluation

  • SQLite provides ACID durability and inspection tooling.
  • Sled offers higher raw throughput but less operational tooling.

3.2.2 Implementation strategy

  • Use sqlx for SQLite with compile-time query checking.
  • WAL mode and synchronous=NORMAL for performance on flash media.

Schema:

CREATE TABLE IF NOT EXISTS telemetry_queue (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    topic TEXT NOT NULL,
    payload BLOB NOT NULL,
    created_at INTEGER NOT NULL
);

3.3.1 Kafka producer configuration

  • Compression: zstd.
  • Micro-batching with short buffering windows.
  • Backpressure handling to stop draining SQLite when queue is full.

3.3.2 Serialization with Apache Avro

  • Schema-first format with evolution support.
  • Use serde + apache-avro derive.
  • Ensure Option maps to Avro unions.

4. Implementation and execution guide

4.1 Phase 1: environment setup and cross-compilation

4.1.1 The cross-compilation challenge

Cross-compiling with C dependencies (librdkafka, libsqlite3) requires a target sysroot. Direct cargo build --target aarch64-unknown-linux-gnu is insufficient without target libs.

4.1.2 Dockerized solution

Use cross with a custom Docker image.

Dockerfile (docker/Dockerfile.arm64):

FROM ghcr.io/cross-rs/aarch64-unknown-linux-gnu:latest

RUN dpkg --add-architecture arm64
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
    libssl-dev:arm64 \
    libsqlite3-dev:arm64 \
    librdkafka-dev:arm64 \
    pkg-config

ENV PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig
ENV PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig

Cross config (Cross.toml):

[target.aarch64-unknown-linux-gnu]
image = "my-sunspec-builder"

4.2 Phase 2: core application logic

4.2.1 Project structure

The project uses a Cargo workspace with domain-separated crates:

  • crates/types/: shared data types (PointValue, DeviceIdentity).
  • crates/discovery/: network scanning and device enumeration.
  • crates/modbus-client/: Modbus TCP client with retry and backoff.
  • crates/sunspec-parser/: SunSpec model parsing (JSON, XML, registers).
  • crates/poller-actor/: per-device async polling loop.
  • crates/buffer/: SQLite-backed durable message queue.
  • crates/avro-kafka/: Avro serialization and Kafka producer.
  • crates/collector-app/: main binary orchestrator (main.rs, config.rs).

4.2.2 Main event loop

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt::init();

    let _ = sd_notify::notify(true, &);

    let (tx, rx) = mpsc::channel(100);
    tokio::spawn(buffer_task(rx));
    tokio::spawn(poller_task(tx));

    loop {
        sleep(Duration::from_secs(5)).await;
        let _ = sd_notify::notify(true, &);
    }
}

4.3 Phase 3: resilience and observability

  • systemd watchdog integration (READY=1, WATCHDOG=1).
  • structured logs via tracing.
  • optional OpenTelemetry metrics.

4.4 Phase 4: testing and validation

  • Use a Modbus simulator for CI (e.g., pymodbus-sim).
  • Integration tests validate discovery and scale-factor math.
  • Benchmarks with criterion for serialization and memory use.

5. Security considerations

  • Rust prevents buffer overflows and data races.
  • Kafka communication uses TLS (security.protocol=SSL).
  • Prefer encrypted storage (LUKS) on edge devices.

6. Execution roadmap

All core phases are complete:

  • Foundation: Cross-compilation setup, dependency selection, Modbus actor.
  • Core logic: SunSpec discovery, poller actors, model parsing.
  • Persistence and uplink: SQLite buffer, Kafka producer, Avro serialization.
  • Hardening: Watchdog, structured logging, Docker, CI/CD, code audit.

See CODE_REVIEW.md for the full audit report and remaining future work.

7. Conclusion

A Rust-based SunSpec collector provides a reliable, high-performance edge data pipeline. The architecture and execution plan emphasize resilience, operational safety, and data correctness for modern grid analytics.

8. Detailed technical implementation specification

8.1 Dependency management and crate selection

  • tokio with specific features: rt-multi-thread, macros, io-util, net, time, sync, signal.
  • tokio-modbus for async Modbus TCP.
  • sqlx with sqlite + runtime-tokio-rustls + macros.
  • rdkafka with cmake-build + ssl.
  • apache-avro with derive.

8.2 Error handling

  • thiserror for library errors.
  • anyhow for top-level context.

8.3 Poller actor state machine

States: Disconnected -> Connected -> Discovering -> Polling -> Backoff.

#![allow(unused)]
fn main() {
pub struct DeviceActor {
    client: Option<AsyncClient>,
    config: DeviceConfig,
    sender: mpsc::Sender<TelemetryData>,
}

impl DeviceActor {
    pub async fn run(&mut self) {
        loop {
            if let Err(e) = self.cycle().await {
                error!("Device cycle failed: {:?}", e);
                self.client = None;
                sleep(self.backoff_duration()).await;
            }
        }
    }
}
}

8.4 Buffer schema and operations

CREATE TABLE IF NOT EXISTS telemetry_queue (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    topic TEXT NOT NULL,
    payload BLOB NOT NULL,
    created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_created_at ON telemetry_queue(created_at);

Operational logic:

  • Insert JSON-serialized PollSample on each poll cycle.
  • Drain ordered by id in configurable batch sizes.
  • Separate corrupt messages (delete immediately) from valid messages.
  • Delete valid messages only after successful Kafka publish.
  • Failed publishes leave messages in buffer for retry next cycle.
  • message.timeout.ms=5000.
  • queue.buffering.max.messages=10000.
  • compression.codec=zstd.
  • acks=all.
  • Pause drains on QueueFull errors.

8.6 Cross-compilation workflow

  • Build custom cross image.
  • Configure Cross.toml.
  • Build with cross build --release --target aarch64-unknown-linux-gnu.

8.7 Resilience engineering

See sunspec-collector.service for the full systemd unit file with security hardening.

Key systemd features:

  • Type=notify with WatchdogSec for health monitoring.
  • Restart=always with RestartSec=5 for automatic recovery.
  • Security directives: NoNewPrivileges, ProtectSystem, ProtectHome.

Watchdog task:

#![allow(unused)]
fn main() {
tokio::spawn(async {
    loop {
        let _ = sd_notify::notify(true, &);
        sleep(Duration::from_secs(5)).await;
    }
});
}

Implementation tracker

  • Modbus client connect/read with timeout, retry/backoff, batching, inter-read delay.
  • Modbus integration test against diagslave.
  • Poller actor loop reads model ranges and publishes payloads.
  • Orchestrator supervision (JoinSet), bounded channels, graceful shutdown, loop lag metrics.
  • SunSpec parser JSON model definitions + register map discovery helper.
  • SunSpec XML model definitions parsing.
  • Model metadata expanded with start address for polling.
  • Lenient model read handling + cached parsed models + golden tests.
  • Avro schemas + Kafka producer wrapper scaffold.
  • Kafka producer integration (rdkafka-backed publish).
  • End-to-end harness from synthetic payloads to serialized output.
  • Discovery subnet scanner with concurrency cap and per-host timeout.
  • static_config discovery mode.
  • SQLite buffer layer (enqueue, dequeue, delete) with WAL mode.
  • systemd READY/WATCHDOG integration.
  • systemd unit template + env file.

Backlog (future scope)

Updated October 2026 after the audit fixes; see Code Review.

  • Decode SunSpec readings at the edge (scale factors, sentinel rules, named points) using the official model definitions.
  • Add repeating group support for SunSpec parser (e.g. MPPT modules, group[i].Point).
  • Add per-device polling profiles (model filters, interval overrides, device-specific quirks).
  • Add backpressure from buffer depth to pollers (today: a full channel drops and counts samples, and retention bounds the buffer).
  • Add Kafka TLS/SASL configuration + secret loading strategy (TLS, mutual TLS, SASL PLAIN/SCRAM; passwords from files for Docker secrets and systemd credentials).
  • Add Schema Registry integration + schema evolution policy docs (records use Avro single-object encoding with the schema fingerprint today).
  • Add Prometheus metrics exporter (poll latency, queue depth, uplink throughput) with histograms and help text.
  • Add config reload support (SIGHUP) with validation + safe rollback.
  • Add buffer retention policy (size/age-based pruning) + scheduled maintenance job.
  • Add buffer compaction and integrity checks for flash endurance.
  • Add certificate rotation workflow for Kafka TLS credentials (today: certificates are read at startup; restart after rotating).
  • Add structured error taxonomy + error-to-metric mapping (partly done: sunspec_poll_errors_total{kind} and sunspec_buffer_dropped_total{reason}).
  • Add data quality flags (stale, partial, invalid, retries) in uplink payloads.
  • Add device health checks (sunspec_device_up, last-successful poll, stall detection; models are rediscovered on every reconnect).
  • Add simulator-driven integration suite (sunspec-sim crate, end-to-end tests, CI Docker E2E job).
  • Add packaging pipeline (deb/rpm/container) with upgrade-safe config paths.
  • Add audit log for config changes and runtime control events.
  • Add support for multiple Kafka topics per device/model.
  • Model definitions: bundle the official SunSpec JSON definitions and decode generically in-tree, instead of generated per-model types, so new models need no code changes.

Code Review & Audit Report

This page records two reviews. The October 2026 audit is current. The February 2026 review below it is kept for history. Its fixes were committed without CI being green, and main did not compile again until #9, so treat its "resolved" status with care.


October 2026 audit

Scope: the whole system: code, data path, data contract, configuration, observability, deployment, CI and docs. Method: code reading, plus running the collector against a SunSpec Modbus simulator to reproduce each failure. Every fix comes with a test that reproduces the original problem. Status: all 17 findings resolved in #9–#17.

Before the audit, main did not compile, and CI had been red since February 2026 without anyone noticing (there was no branch protection). #9 restored the build and fixed the first runtime bugs. #10 cleared all cargo audit vulnerabilities. The 8 Dependabot PRs were then merged. The 17 findings below were fixed in #11–#17.

Critical: blocked the core use case

#FindingFix
1With any broker configured, Kafka publishing failed at startup: the default zstd compression wasn't compiled into librdkafka.#11: rdkafka zstd feature, plus a regression test that creates a producer for every codec
2Gateways: pollers were keyed by IP, so only the last unit ID behind a host was polled.#12: devices keyed by host and unit ID, plus an end-to-end gateway test
3Discovery read a fixed 200-register window and silently dropped every model past it (status, controls, MPPT).#12: walk the model chain one header at a time, probing bases 40000, 0 and 50000
4A device that was offline at startup was never polled.#12: per-device supervision with backoff, plus subnet rescans
5SIGTERM (systemd/Docker stop) killed the process with no graceful shutdown.#11: SIGTERM handled, plus a process-level test

High: data path and durability

#FindingFix
6Upload throughput was capped at batch_size / drain_interval (200 samples/s).#13: drain back to back while a backlog exists
7The buffer was unbounded, so samples were lost once the disk filled; queued samples were lost on stop; every sample cost one fsync.#13: max_rows/max_age_s retention, shutdown flush with a bounded final drain, batched transactions
8Only raw registers were shipped. Scale factors, sentinels and point names were left to every consumer; the parser's scaling code was unused.#14: edge decoding with the official SunSpec definitions
9Each Kafka message was an Avro container mixing devices, with no key, the schema embedded every time, and duplicates after a crash.#14: one keyed single-object record per sample; only acknowledged rows are deleted

Medium: operations and config

#FindingFix
10Dropped Modbus connections were never re-established, so data was lost until the poller gave up.#11: lazy reconnect on transport errors and timeouts; Modbus exceptions keep the connection
11Config errors were silently ignored: unparsable env values, a bad unit ID became 1, unknown keys.#15: strict config (deny_unknown_fields, errors name the variable); #17 deprecates the ineffective modbus.timeout_ms
12About 430k INFO lines a day per device, and ANSI colour codes in journald/Docker logs.#12 and #15: per-cycle logs at debug, colours only on a terminal, JSON format option
13Metrics couldn't show device health: a high-cardinality batch_size label, summaries instead of histograms, no per-device liveness, and no real stall detection.#12 and #15: sunspec_* metrics with per-device gauges, histograms, /healthz, a health-gated watchdog, stall detection
14docker-compose couldn't pull its Kafka image and had no device to poll; the systemd unit ran as root; images were never built in CI; unused arm64 packages.#16: official Kafka image, simulator service, DynamicUser unit, Docker E2E and cross-compile CI

Quality and process

#FindingFix
1513 tests in total; discovery, the poller and the orchestration had none; the integration tests passed without doing anything.#11–#16: the sunspec-sim crate and simulator-backed unit, integration and end-to-end tests (56 plus 2 broker tests run in CI)
16No branch protection, so a broken main went unnoticed for 8 months.Branch protection requires Check & Lint, Test, Security Audit and Docker E2E
17Docs overclaimed ("zero data loss", "90%+", "zombie poller detection", graceful SIGTERM) and linked to missing pages.#17: docs rewritten against the code; build.md and buffer_maintenance.md added

Remaining limitations

  • Kafka authentication is limited to SASL PLAIN and SCRAM (TLS, mutual TLS and SASL were added after the audit).
  • There's no Schema Registry integration. Records carry the schema's fingerprint instead.
  • Delivery is at-least-once. Consumers should deduplicate on (device_key, model_id, collected_at_ms).
  • There's no config reload and no per-device polling profiles.

February 2026 review (historical)

Executive Summary

Rusty SunSpec Collector is a well-architected Rust workspace with clean domain boundaries across 8 crates. The core design — actor-based polling, durable SQLite buffering, Avro/Kafka uplink — is sound.

The initial review uncovered 3 compilation/runtime bugs, 8 high-severity issues, and 10 medium-severity issues. All have been addressed in the accompanying commits.


1. Critical Bugs — RESOLVED

1.1 config.rs — Non-existent field discovery_unit_ids ✅

Problem: The Default impl, apply_env_overrides, and apply_file_config all referenced a discovery_unit_ids field that did not exist on CollectorConfig. Additionally, parse_unit_id_list() was called but never defined, and FileDiscoveryConfig was missing a unit_ids field.

Fix: Added discovery_unit_ids: Vec<u8> to the struct, added unit_ids to FileDiscoveryConfig, and implemented the missing parse_unit_id_list() function.

1.2 config.rs — Duplicate fields in Default impl ✅

Problem: kafka_topic and kafka_enable_idempotence were set twice in the struct literal.

Fix: Removed the duplicate lines.

1.3 main.rs — Prometheus recorder installed twice ✅

Problem: PrometheusBuilder::new().install_recorder() was called twice. The metrics crate only allows a single global recorder, so the second call would crash the app on startup.

Fix: Removed the duplicate builder + install.


2. High-Severity Issues — RESOLVED

2.1 No CI/CD for build, test, or lint ✅

Fix: Added .github/workflows/ci.yml with cargo fmt --check, cargo clippy, cargo build, and cargo test jobs.

2.2 Docker container runs as root ✅

Fix: Added non-root collector user/group in Dockerfile. Process now runs as USER collector:collector.

2.3 No Docker HEALTHCHECK ✅

Fix: Added HEALTHCHECK instruction that curls the /metrics endpoint.

2.4 Outdated base images ✅

Fix: Migrated from bullseye (Debian 11) to bookworm (Debian 12) for both builder and runtime stages. Updated libssl1.1 to libssl3.

2.5 .dockerignore excludes Cargo.lock ✅

Fix: Removed Cargo.lock from .dockerignore. --locked builds now work correctly in Docker.

2.6 .gitignore excludes Cargo.lock ✅

Fix: Removed Cargo.lock from .gitignore. Added missing patterns: .DS_Store, .env, *.sqlite*, .vscode/, .idea/, editor swap files.

2.7 discovery.rs — expect() on Semaphore can panic ✅

Fix: Replaced .expect("semaphore closed") with .map_err(|_| DiscoveryError::SemaphoreClosed)?. Added SemaphoreClosed variant to DiscoveryError.

Fix: Separated valid_ids from corrupt_ids. Corrupt messages are always deleted immediately (preventing head-of-line blocking). Valid messages are only deleted after successful Kafka publish. Failed publishes leave valid messages in the buffer for retry.


3. Medium-Severity Issues — RESOLVED

3.1 buffer.rs — retry_count column never used ✅

Fix: Removed the unused retry_count column from the schema.

3.2 buffer.rs — BufferConfig.max_connections ignored ✅

Fix: Added BufferStore::with_config(config: BufferConfig) that respects max_connections. The existing new(path) method now delegates to with_config with defaults.

3.3 modbus_client.rs — Exponential backoff overflow ✅

Fix: Clamped shift to 63 with u32::try_from(attempt).unwrap_or(63).min(63) instead of using checked_shl with unwrap_or(u64::MAX).

3.4 poller_actor.rs — Jitter correlates across devices ✅

Fix: Added a device_salt derived from hashing (ip, unit_id). The jitter seed now includes this salt, ensuring different devices get different jitter patterns even when starting simultaneously.

3.5 poller_actor.rs — Silent skip of zero-length models ✅

Fix: Added debug!() log when skipping zero-length models.

3.6 avro_kafka.rs — Error context lost in map_err ⚠️

Status: Not changed. The apache_avro::Error type does not implement Clone and wrapping it in PublishError would require boxing. The to_string() approach is acceptable for now; the error message content is preserved.

3.7 avro_kafka.rs — Avro schema type mismatch ⚠️

Status: Noted but not changed. Avro "int" safely widens u8 and u16 values. No data loss occurs in practice. A future schema evolution (via Schema Registry) can use more precise types.

3.8 sunspec_parser.rs — Limited model name lookup ⚠️

Status: Noted for future work. Only 5 models are named. Loading from a resource file is a good enhancement but out of scope for this review.

3.9 main.rs — Infinite respawn without backoff ✅

Fix: Added per-device respawn_counts tracking and a respawn_delay() function with exponential backoff (base delay * 2^failures, capped at 60 seconds). Successful exits reset the counter.

3.10 #![allow(dead_code)] in every crate ✅

Fix: Removed #![allow(dead_code)] from all 7 library crates: types, discovery, modbus-client, sunspec-parser, poller-actor, avro-kafka, and buffer.


4. Low-Severity Issues — RESOLVED

4.1 .gitignore missing common patterns ✅

Fix: Added .DS_Store, .env, *.sqlite*, .vscode/, .idea/, swap files.

4.2 rustfmt.toml is minimal ✅

Fix: Added max_width = 100, reorder_imports = true, reorder_modules = true.

4.3 Cargo.toml — Tokio "full" feature ✅

Fix: Replaced features = ["full"] with specific features: ["rt-multi-thread", "macros", "io-util", "net", "time", "sync", "signal"].

4.4 deploy-docs.yml — mdBook version not pinned ✅

Fix: Changed from mdbook-version: 'latest' to mdbook-version: '0.4.40'.

4.5 No MSRV specified ✅

Fix: Added rust-version = "1.80" to [workspace.package] in root Cargo.toml.

4.6 ARM64 Dockerfile uses :latest tag ✅

Fix: docker/Dockerfile.cross (formerly Dockerfile.arm64) pins ghcr.io/cross-rs/<target>:0.2.5; the release workflow builds with it and checks the resulting glibc floor (2.18).


5. Remaining Future Work

These items were identified but are enhancements, not bugs:

ItemPriorityNotes
Kafka TLS/SASL config exposureMediumrdkafka supports it; need config fields
Schema Registry integrationMediumHardcoded Avro schema should evolve
SunSpec model name resource fileLowOnly 5 models named currently
docker-compose.yml for devLowKafka + collector local setup
cargo audit / cargo deny CIMediumSecurity scanning workflow
Dependabot configurationLowAutomated dependency updates
CONTRIBUTING.md, CHANGELOG.mdLowCommunity documentation
Avro error wrapping (box error)LowPreserves full error chain
ARM64 Dockerfile version pinLowReproducibility

6. Security Observations

AreaStatusNotes
SQL injectionSafeParameterized queries throughout
DockerFixedNon-root user, healthcheck added
Modbus TCPNo TLSStandard for Modbus protocol
KafkaNo TLS/SASLConfig supports it via rdkafka but not exposed
Config credentialsRiskEnvironment variables could leak in logs
systemd serviceFixedAdded NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp

7. Performance Observations

ComponentObservationImpact
ModbusClientSingle Mutex<Context> serializes all readsBottleneck under high model count
BufferStoreNow configurable via BufferConfigFixed — was hardcoded to 5
uplink_taskJSON deserialize + Avro re-serialize every cycleCPU overhead on each batch
DiscoveryFresh TCP connection per deviceNetwork overhead at startup
TokioSpecific features onlyFixed — was "full"

8. Test Coverage Assessment

CrateUnit TestsIntegration TestsGaps
typesNoneNoneSerialization round-trip
sunspec-parserYesFixture-basedEdge cases
modbus-clientNonediagslaveRetry/backoff logic
discoveryNoneNoneCIDR parsing edges
poller-actorNoneNoneError recovery, jitter
avro-kafkaSerializationKafka integrationMock publisher
bufferSQLite opsNoneConcurrent access
collector-appConfig validationE2E harnessuplink/buffer tasks

Files Modified

FileChanges
crates/collector-app/src/config.rsAdded discovery_unit_ids field, unit_ids to FileDiscoveryConfig, parse_unit_id_list() fn, removed duplicate Default fields
crates/collector-app/src/main.rsRemoved duplicate Prometheus install, separated corrupt/valid IDs in uplink, added respawn backoff
crates/discovery/src/lib.rsAdded SemaphoreClosed error, replaced expect(), removed dead_code
crates/modbus-client/src/lib.rsClamped backoff shift to 63, removed dead_code
crates/poller-actor/src/lib.rsDevice-salted jitter, debug log for zero-length models, removed dead_code
crates/buffer/src/lib.rsAdded with_config(), removed unused retry_count column, removed dead_code
crates/avro-kafka/src/lib.rsRemoved dead_code
crates/sunspec-parser/src/lib.rsRemoved dead_code
crates/types/src/lib.rsRemoved dead_code
DockerfileBookworm base, non-root user, healthcheck, --no-install-recommends
.gitignoreRemoved Cargo.lock, added .DS_Store, .env, *.sqlite*, IDE patterns
.dockerignoreRemoved Cargo.lock, added docs/, .github/, .env
Cargo.tomlSpecific tokio features, rust-version = "1.80"
rustfmt.tomlAdded max_width, reorder_imports, reorder_modules
.github/workflows/deploy-docs.ymlPinned mdBook to 0.4.40
.github/workflows/ci.ymlNew — fmt, clippy, build, test