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
- Device set. Either a static list of hosts (
discovery.static_devices) or a subnet scan (discovery.subnet, rescanned everydiscovery.rescan_interval_s). Each device is identified by host and Modbus unit ID, so every unit behind a gateway is polled. - Per-device poller. Each device runs in its own supervised task. A session connects, finds
the
SunSmarker (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. - 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.
- Buffer. SQLite in WAL mode. A retention task runs every 10 s and drops rows older than
buffer.max_age_s(7 days) or beyondbuffer.max_rows(1,000,000). - 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.
- 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
| Section | Pages |
|---|---|
| Get started | Quick start with simulated devices; Deployment with Docker or systemd, shutdown and logs |
| Guides | Securing the Kafka connection, Serial devices through a converter, 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, Supported SunSpec models |
| How it works | Architecture: crates, tasks, data flow and design decisions |
| Contributing | Developer Guide, Building & Packaging, Fuzzing |
| Project | Changelog, Design Plan, Code Review & Audit |
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
| Option | Use it for | Buffer location |
|---|---|---|
| Docker image | Gateways and servers that run containers | /app/data/sunspec-buffer.sqlite in a volume |
| Docker Compose stack | Local testing with Kafka, a simulator and Prometheus | collector-data volume |
| systemd service | Bare-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 setSUNSPEC_BUFFER_PATH, sobuffer.pathfrom a config file applies; keep it relative or under/app/dataso the buffer stays in the volume; - exposes port 9090;
- has a
HEALTHCHECKthat runscurl -fsS "http://localhost:${SUNSPEC_METRICS_PORT:-9090}/healthz"every 30 s (3 s timeout, 20 s start period, 3 retries). It followsSUNSPEC_METRICS_PORT, but notmetrics.portfrom 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:
| Service | Image | Notes |
|---|---|---|
kafka | apache/kafka:3.9.1 (KRaft) | Plaintext from the host at localhost:29092; SASL/PLAIN listener on kafka:9094 inside the network |
simulator | built from the simulator target | Units 1 and 2 on port 1502 |
collector | built from the collector target | Polls simulator:1 and simulator:2; publishes over SASL/PLAIN with its password from a Docker secret; port 9090; data in volume collector-data |
prometheus | prom/prometheus:v3.5.0 | http://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:
| Path | Notes |
|---|---|
/usr/local/bin/collector-app, /usr/local/bin/sunspec-sim | Replaced atomically, so a running service keeps its old binary until restarted |
/etc/systemd/system/sunspec-collector.service | Replaced 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.env | Installed 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
| Setting | Effect |
|---|---|
Type=notify | systemd marks the service started once the collector has opened the buffer and started its tasks (READY=1). |
WatchdogSec=30 | The 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=5 | Restarts 5 s after any exit except a configuration error. |
RestartPreventExitStatus=78 | A 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=30 | Time allowed for the graceful stop after SIGTERM before SIGKILL. |
DynamicUser=yes | Runs as a transient unprivileged user; no account needs to be created. |
StateDirectory=sunspec-collector, StateDirectoryMode=0750 | systemd 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.env | Loads the environment file; the leading - means a missing file is not an error. |
LimitNOFILE=4096 | File descriptor limit. |
| Hardening | NoNewPrivileges, 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:
- The log shows
shutdown signal received. Device pollers stop; reads in progress are abandoned. Pollers get 10 s to finish before they are aborted. - The ingest task writes every sample still queued in the channel to SQLite, then logs
ingest stopped. - The uplink finishes the batch it is publishing, then keeps draining the buffer for up to
buffer.shutdown_drain_timeout_ms(default 5 s;0skips this step). If time runs out, it logsfinal uplink drain timed out; remaining rows stay buffered for the next start. - Retention, the HTTP server and the watchdog stop. The log shows
collector stoppedand 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:
| Message | Level | Meaning |
|---|---|---|
starting device poller | info | A device was added (device=host/unit). |
discovered SunSpec models | info | Session started; shows the base address and model IDs found. |
device session ended | warn | A session failed; error gives the reason, retry_in_ms the backoff. |
modbus read failed | warn | A model could not be read after the client's retries (poller_actor). Each failed attempt is logged at debug by modbus_client. |
modbus connected | info | The client opened a connection: a shared client's first read, or a reconnect after a transport error or timeout. |
telemetry channel full, sample dropped | warn | Ingest is not keeping up. |
no devices found | warn | The device set is empty; subnet mode retries after rescan_interval_s. |
records not delivered; they stay buffered | warn | Kafka did not acknowledge some records. |
Kafka broker connection failed; repeats are logged at debug for 5 minutes | warn | The Kafka client could not connect to a broker; reason names it. Logged at warn at most once per 5 minutes. |
records delivered again | info | Kafka accepted records after failed attempts (failed_attempts); a backlog drains from here. |
Kafka client error | error | librdkafka reported another error, for example a failed authentication. |
buffer write failed; samples dropped | warn | SQLite write failed; samples lost. |
buffer retention: dropped rows past max age | warn | Retention deleted rows older than buffer.max_age_s. |
buffer retention: dropped oldest rows over max rows | warn | Retention deleted rows beyond buffer.max_rows. |
corrupt buffered sample, discarding / sample cannot be encoded, discarding | warn | A row could never be published and was deleted. |
pipeline unhealthy; withholding systemd watchdog ping | warn | Ingest or uplink stopped checking in. |
final uplink drain timed out; ... | warn | Shutdown 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-passwordThe file is readable only by the service, which also works with
DynamicUser=yes. -
Docker / Compose: mount the password as a secret and point the
_FILEvariable 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-passwordThe 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_msandmax_batch_sizemust 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_msand, if the converter buffers little, lowermodbus.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 --probeuses 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 = 600reads 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_modelsandexclude_modelsfilter 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 withnone of the discovered models [...] is selected for pollingand 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 modelslog 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:
| Key | Overrides | Notes |
|---|---|---|
poll_interval_ms | poller.poll_interval_ms | ≥ 1. The device's default stall timeout follows it. |
request_timeout_ms | poller.request_timeout_ms | ≥ 1. |
base_address | sunspec.base_address | Probed first; the standard bases follow. |
max_batch_size | modbus.max_batch_size | 1–125. |
framing | modbus.framing | tcp or rtu. |
include_models | poller.include_models | Replaces the global list. |
exclude_models | poller.exclude_models | Replaces the global list; [] clears it for this device. |
model_intervals_s | poller.model_intervals_s | Merged 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_DEVICESreplaces 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_modelsandexclude_modelsmay 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.
Recommended alert rules
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_rowsandbuffer.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
| Deployment | Path | Notes |
|---|---|---|
| Local run | sunspec-buffer.sqlite | Default; relative to the working directory. |
| Docker image | /app/data/sunspec-buffer.sqlite | The 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.sqlite | Set 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):
| File | Contents |
|---|---|
buffer.sqlite | The database. |
buffer.sqlite-wal | Write-ahead log. Holds recently committed transactions that have not yet been copied into the main file. |
buffer.sqlite-shm | Shared-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);
| Column | Contents |
|---|---|
id | Increasing row ID. The uplink publishes in id order, oldest first. |
topic | Kafka 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. |
payload | The sample as UTF-8 JSON (stored as a BLOB). It is converted to Avro only when published. |
created_at | Time 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]]
}
startis the register address of the model header;registersare the raw values, including the two header registers.collected_at_msis the time the model was read. It differs fromcreated_at, which is when the row was written.pointsare the decoded points as[name, value]pairs in definition order. A value is a number, a string ornull(not implemented). Models without a bundled definition have an empty list. Rows written by earlier versions have nopointsfield and are published with an emptypointsmap.
SQLite settings
- WAL journal mode. Readers and the writer do not block each other, so the uplink can read
while ingest writes, and a
sqlite3session 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.
- The uplink reads up to
buffer.batch_sizerows, oldest first. - 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. - Rows that Kafka acknowledged are deleted.
- Rows that Kafka did not acknowledge within
kafka.timeout_msstay in the buffer. They are still the oldest rows, so they are retried first. Failed records count insunspec_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. - 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:
- Deletes rows whose
created_atis older thanbuffer.max_age_s(default 604800 s, 7 days). Counted insunspec_buffer_dropped_total{reason="max_age"}. - Deletes the oldest rows beyond
buffer.max_rows(default 1,000,000). Counted insunspec_buffer_dropped_total{reason="max_rows"}. - Updates
sunspec_buffer_rowsandsunspec_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 second | 10 × 4 ÷ 1 = 40 |
| Rows per hour | 144,000 (about 100 MB) |
| Rows per day | 3,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 outage | 40 × 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_msfor acknowledgements.sunspec_uplink_errors_totalincreases with each attempt, and each attempt logsrecords 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_rowsandsunspec_buffer_oldest_age_secondsrise; - once the limits are reached, retention drops the oldest rows
(
sunspec_buffer_dropped_total{reason="max_rows"|"max_age"}); /healthzstays 200, because the pipeline itself is working.
When Kafka is reachable again:
-
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 forbuffer.drain_interval_msonly once a batch is not full, that is, when the backlog is gone. -
Rows are sent oldest first, so the newest data arrives last. Consumers receive records with old
collected_at_msvalues for a while. -
Watch
sunspec_buffer_rowsandsunspec_buffer_oldest_age_secondsfall. 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])) -
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. -
Data that retention dropped during the outage is gone;
sunspec_buffer_dropped_totalshows how many rows. -
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.
| Error | Cause | Fix |
|---|---|---|
load config failed ... unknown field | Misspelled or removed key in the config file | Compare with the reference. |
load config failed ... SUNSPEC_X="..." is invalid | Unparsable environment value, including inline comments in an env file | Fix the value; put comments on their own lines. |
load config failed ... read config file | --config / SUNSPEC_CONFIG path missing or unreadable | Check the path and permissions (mode 0644 for systemd). |
config validation failed | Value out of range | The message names the key and the allowed values. |
config validation failed ... kafka.acks must be all (or -1) while kafka.enable_idempotence is true | acks of 0 or 1 with idempotence on (the default) | Set kafka.enable_idempotence = false or acks = "all". |
config validation failed ... modbus.max_batch_size must be between 1 and 125 | Batch size above the Modbus limit | Use a value from 1 to 125. |
config validation failed ... kafka.brokers must be non-empty when set | SUNSPEC_KAFKA_BROKERS= with no value | Set the brokers, or comment the line out to run without Kafka (records are then only logged). |
kafka publisher init failed | librdkafka rejected a setting when the producer was created | The message gives librdkafka's reason. |
bind metrics port <port> | The port is in use (often a second collector) or not permitted | Stop the other process or change metrics.port / SUNSPEC_METRICS_PORT. |
buffer init failed | Buffer path not writable | systemd: keep the buffer under /var/lib/sunspec-collector. Docker: mount a volume at /app/data and keep buffer.path relative or under /app/data. |
SUNSPEC_LOG_FORMAT must be text or json | Unsupported log format | Use text or json. |
Serial devices through a converter
See Serial devices through a converter.
Probing a device
See Probing a device.
A device is not polled
- Check that the collector knows the device:
curl localhost:9090/deviceslists every supervised device with its state and latest readings;sunspec_devices_known,devices_totalin/healthz, and astarting device poller device=host/unitlog line say the same. In subnet mode the host must accept a TCP connection on the Modbus port withindiscovery.per_host_timeout_ms. - Check
sunspec_device_up{device="host/unit"}. A value of 0 means the device is not being read successfully; ifsunspec_device_last_success_timestamp_secondshas no series for it, it has not been read successfully since the collector started. Ifsunspec_poller_restarts_totalkeeps increasing, its sessions keep failing;sunspec_poll_errors_total{kind}shows whether connects or reads fail. - Find the reason in the logs:
journalctl -u sunspec-collector | grep 'device session ended', or probe the device directly withcollector-app --probe host:unit(see Probing a device).
error= in device session ended | Likely cause |
|---|---|
could not resolve host:port | Hostname does not resolve. |
connect to ... failed / request timed out after ...ms | Wrong host or port, firewall, device offline, or poller.request_timeout_ms too short. Test with nc -zv <host> <port>. |
no SunSpec register map at base addresses [...] | The SunS marker was not found at any base. The device may not be SunSpec, may use another base address (set sunspec.base_address), or the unit ID is wrong. (last answer: ...) gives the device's last Modbus exception. |
connection closed by the device | The device or gateway closed the TCP connection. Many devices accept only one Modbus TCP client at a time, or close idle connections; check for another client polling the same device. |
SunSpec model chain is malformed: ... | The model headers do not form a valid chain ending in 0xFFFF. |
10 consecutive poll cycles failed | Reads keep failing after discovery; check sunspec_poll_errors_total{kind}. Devices that reject large reads need a lower modbus.max_batch_size. |
no successful read for ... | Stall detection; see Stalls. |
none of the discovered models [...] is selected for polling | include_models / exclude_models (global or on the device entry) leave nothing to poll on this device. The list shows the models it has. |
If discovery reports (last answer: Gateway target device failed to respond), or the
modbus read failed lines before a session ends show that exception, a gateway answered but has no
device at that unit ID. Check the unit IDs. Each failed attempt before a retry is logged at debug
(RUST_LOG=info,modbus_client=debug).
Failed sessions are retried with exponential backoff capped at 60 s, so a device that comes back online is picked up within about a minute without restarting the collector.
No records in Kafka
- Check that
kafka.brokers/SUNSPEC_KAFKA_BROKERSis set. Without it, records are only logged at debug level (record published (log sink)withRUST_LOG=info,avro_kafka=debug) and then removed from the buffer. - Check
sunspec_uplink_errors_totaland therecords not delivered; they stay bufferedlog line. Rising errors mean the brokers are unreachable or are rejecting the records (wrong address, missing topic with auto-creation disabled, ACLs, or a security mismatch).Kafka broker connection failedgives librdkafka's reason, naming the broker; it is logged at warn at most once per 5 minutes, and every attempt at debug (RUST_LOG=info,avro_kafka=debug).records delivered againmarks the end of an outage. Authentication problems show up asKafka client errorwithSASL authentication errororSSL handshake failed. Checkkafka.security_protocol, the SASL mechanism and credentials, and thatkafka.ssl_ca_locationholds the CA that signed the broker certificate. - Check
sunspec_buffer_oldest_age_secondsandsunspec_buffer_rows. If they grow whilesunspec_uplink_records_totalis flat, nothing is being delivered. - If records arrive but consumers fail to read them, the consumers may still expect the old format; see Upgrade notes.
The buffer keeps growing
The buffer grows when samples are written faster than they are delivered. Compare:
rate(sunspec_buffer_writes_total[5m]) # samples in
rate(sunspec_uplink_records_total[5m]) # records out
- Kafka down or rejecting records: see the previous section. The buffer holds the data until
retention starts dropping it (
sunspec_buffer_dropped_total{reason="max_rows"|"max_age"}). - Kafka up but slow: check
sunspec_uplink_publish_seconds. Each batch waits for the producer's 50 ms linger plus the broker round trip, so a largerbuffer.batch_sizeraises throughput. - More data than expected: every polled model of every device produces one row per poll interval. Read slow-changing models less often or skip unneeded ones with a polling profile; see also Sizing.
Stalls
sunspec_poller_stalls_total increases when a session goes poller.stall_timeout_ms (default
max(10 × poll interval, 30 s)) without a successful read. This usually means every read of the
device timed out or failed for that long, for example when a device keeps its TCP connection open
but stops answering. The session is ended and retried with backoff. The modbus read failed lines
for the device show the errors. Occasional stalls point to the device, the gateway or the network;
a device that stalls repeatedly also triggers the SunspecDeviceRestarting alert.
If /healthz returns 503, the ingest task or the uplink itself has stopped checking in. That is
a collector problem rather than a device problem; the systemd watchdog restarts the service. Keep
the logs from before the restart.
Samples dropped with reason="channel_full"
The ingest task could not take a sample within one poll interval, usually because SQLite writes are
slow (slow or failing storage). Check disk health and latency. runtime.channel_capacity absorbs
short bursts but does not fix slow storage.
Poll cycles take longer than the poll interval
Compare sunspec_poll_duration_seconds with poller.poll_interval_ms. When a cycle overruns, the
next tick is skipped, so samples arrive less often than configured. Increase the poll interval, or
reduce per-request latency (lower poller.request_timeout_ms, fewer retries).
"database is locked"
Two collectors are using the same buffer file, or a manual sqlite3 session holds a write lock.
Run one collector per buffer file and follow
Buffer Maintenance for manual work.
Upgrade notes for this release
This release contains breaking changes. Read all of this section before upgrading.
Kafka record format v2 (breaking)
| Previous format | This release (v2) | |
|---|---|---|
| Records | One Avro Object Container File per message, mixing devices | One record per sample |
| Key | None | device_key, e.g. 192.168.1.20/1 |
| Value | Container file with the schema embedded in every message | Avro single-object encoding: bytes C3 01, the schema's 8-byte CRC-64-AVRO (Rabin) fingerprint (little-endian), then the Avro binary datum |
| Headers | None | content-type: application/avro; encoding=single-object, schema-fingerprint: <16 hex chars> |
| Schema | Raw registers only | com.rusty.sunspec.v2.SunspecSample: decoded points and raw registers |
model_name | Project names, e.g. three_phase_inverter | Official 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 metric | New metric | Change |
|---|---|---|
buffer_enqueue_success | sunspec_buffer_writes_total | Renamed. |
buffer_enqueue_error | sunspec_buffer_dropped_total{reason="write_error"} | Now one counter with a reason label. |
buffer_size | sunspec_buffer_rows | Renamed; updated every 10 s. |
uplink_messages_sent | sunspec_uplink_records_total | batch_size label removed. Counts acknowledged records. |
uplink_publish_error | sunspec_uplink_errors_total | Counts records, not failed batches. |
uplink_publish_latency | sunspec_uplink_publish_seconds | Histogram 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: usepoller.request_timeout_ms/SUNSPEC_REQUEST_TIMEOUT_MS.
Deployment changes
- systemd: the unit now runs as a dynamic unprivileged user with
StateDirectory=sunspec-collector,Type=notifywith a health-gated watchdog, andTimeoutStopSec=30. Install the new unit file and runsystemctl daemon-reload. The previous guide had you create/var/lib/sunspec-collectoras root; after the first start with the new unit, check that the log showsbuffer initializedand no write errors. - Docker: the
HEALTHCHECKnow calls/healthzinstead of/metrics, on the port inSUNSPEC_METRICS_PORT(default 9090). The image's working directory is/app/data, so withoutSUNSPEC_BUFFER_PATHorbuffer.paththe buffer is/app/data/sunspec-buffer.sqlite. Deployments that setSUNSPEC_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
/metricsand/healthz. - SIGTERM now takes the graceful shutdown path.
Upgrade procedure
- Update Kafka consumers to read v2 records (or prepare a new topic).
- Let the old collector drain its buffer, then stop it.
- Install the new binary or image, the new unit file and an updated config / environment file.
- Start the collector and check
/healthz,sunspec_device_upandsunspec_uplink_records_total. - 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:
config.example.toml: a config file with every section.sunspec-collector.env: an environment file, used by the systemd unit as/etc/sunspec-collector.env.
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.compressionorkafka.acksvalues outside the supported lists, andkafka.acksof0or1whilekafka.enable_idempotenceis true (the default).- Out-of-range values: zero ports, intervals, timeouts and sizes where at least 1 is required,
modbus.max_batch_sizeoutside 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 bothinclude_modelsandexclude_models, amodel_intervals_sentry of 0, and a static device listed twice. - A
framingother thantcporrtu, and static devices behind the same RTU converter (same host and port,framing = "rtu") with differentrequest_timeout_msormax_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 key | Env var | Replacement |
|---|---|---|
sunspec.discovery_register_count | SUNSPEC_DISCOVERY_REG_COUNT | None. Models are found by walking the SunSpec model chain one header at a time. |
modbus.timeout_ms | SUNSPEC_MODBUS_TIMEOUT_MS | poller.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 key | Env var | Default | Notes |
|---|---|---|---|
discovery.subnet | SUNSPEC_SUBNET | 192.168.1.0/24 | IPv4 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.port | SUNSPEC_PORT | 502 | Modbus TCP port for probing and polling, 1–65535. Same setting as modbus.port. |
discovery.max_concurrency | — | 64 | Probes in flight at once during a scan. ≥ 1. |
discovery.per_host_timeout_ms | — | 200 | TCP connect timeout for each probe. ≥ 1. Raise it on high-latency links. |
discovery.unit_ids | SUNSPEC_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_s | SUNSPEC_RESCAN_INTERVAL_S | 600 | Time between scans. New hosts get pollers; hosts that disappear keep theirs and are retried. ≥ 1. |
discovery.static_devices | SUNSPEC_STATIC_DEVICES | empty | Fixed device list; see Static devices. Started once at startup. |
[poller]
| File key | Env var | Default | Notes |
|---|---|---|---|
poller.poll_interval_ms | SUNSPEC_POLL_INTERVAL_MS | 1000 | Time 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_ms | SUNSPEC_REQUEST_TIMEOUT_MS | 1000 | TCP connect timeout and per-request Modbus read timeout for device sessions. ≥ 1. |
poller.jitter_ms | SUNSPEC_JITTER_MS | 0 | Each 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_ms | SUNSPEC_STALL_TIMEOUT_MS | max(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_models | SUNSPEC_INCLUDE_MODELS | unset (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_models | SUNSPEC_EXCLUDE_MODELS | empty | Never poll these models. A model cannot also be in include_models. Env form: comma list. |
poller.model_intervals_s | SUNSPEC_MODEL_INTERVALS_S | empty | Read 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 key | Env var | Default | Notes |
|---|---|---|---|
modbus.port | SUNSPEC_PORT | 502 | Same setting as discovery.port. If both are in the file, modbus.port wins; SUNSPEC_PORT overrides both. |
modbus.max_batch_size | SUNSPEC_MAX_BATCH_SIZE | 125 | Most 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_ms | SUNSPEC_MODBUS_TIMEOUT_MS | — | Deprecated, ignored with a warning. Use poller.request_timeout_ms. |
modbus.retry_count | — | 2 | Retries 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 | — | 100 | Delay before the first retry; doubles on each further retry. ≥ 1. |
modbus.retry_max_backoff_ms | — | 2000 | Upper bound for the retry delay. ≥ 1. |
modbus.inter_read_delay_ms | — | unset | Pause between the requests of one split read, for devices that need it. ≥ 1 when set. |
modbus.framing | SUNSPEC_MODBUS_FRAMING | tcp | tcp 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 key | Env var | Default | Notes |
|---|---|---|---|
sunspec.base_address | SUNSPEC_BASE_ADDRESS | 40000 | Register address probed first for the SunS marker. The other standard bases (40000, 0, 50000) are probed after it. 0–65535. |
sunspec.discovery_register_count | SUNSPEC_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 key | Env var | Default | Notes |
|---|---|---|---|
buffer.path | SUNSPEC_BUFFER_PATH | sunspec-buffer.sqlite | SQLite 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_size | SUNSPEC_BUFFER_BATCH_SIZE | 100 | Rows read and published per uplink batch. ≥ 1. |
buffer.drain_interval_ms | SUNSPEC_BUFFER_DRAIN_MS | 500 | Pause 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_rows | SUNSPEC_BUFFER_MAX_ROWS | 1000000 | Retention: the oldest rows beyond this count are deleted. Checked every 10 s. ≥ 1. |
buffer.max_age_s | SUNSPEC_BUFFER_MAX_AGE_S | 604800 (7 days) | Retention: rows written longer ago than this are deleted. Checked every 10 s. ≥ 1. |
buffer.shutdown_drain_timeout_ms | SUNSPEC_SHUTDOWN_DRAIN_TIMEOUT_MS | 5000 | On 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 key | Env var | Default | Notes |
|---|---|---|---|
kafka.brokers | SUNSPEC_KAFKA_BROKERS | unset | Comma-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.topic | SUNSPEC_KAFKA_TOPIC | sunspec.telemetry | 1–249 characters from A-Z a-z 0-9 . _ -, not . or ... |
kafka.client_id | SUNSPEC_KAFKA_CLIENT_ID | sunspec-collector | librdkafka client.id. |
kafka.acks | SUNSPEC_KAFKA_ACKS | all | One of 0, 1, -1, all. 0 and 1 require enable_idempotence = false; otherwise validation fails. |
kafka.compression | SUNSPEC_KAFKA_COMPRESSION | zstd | One of none, gzip, snappy, lz4, zstd. |
kafka.timeout_ms | SUNSPEC_KAFKA_TIMEOUT_MS | 5000 | librdkafka 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_idempotence | SUNSPEC_KAFKA_IDEMPOTENCE | true | librdkafka 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 key | Env var | Default | Notes |
|---|---|---|---|
kafka.security_protocol | SUNSPEC_KAFKA_SECURITY_PROTOCOL | plaintext | One of plaintext, ssl, sasl_plaintext, sasl_ssl (lowercase). sasl_plaintext logs a warning: credentials and data are not encrypted. |
kafka.ssl_ca_location | SUNSPEC_KAFKA_SSL_CA_LOCATION | system CAs | PEM file or directory used to verify the broker. Must exist. |
kafka.ssl_certificate_location | SUNSPEC_KAFKA_SSL_CERTIFICATE_LOCATION | unset | Client certificate (PEM) for mutual TLS. Must exist; set together with the key. |
kafka.ssl_key_location | SUNSPEC_KAFKA_SSL_KEY_LOCATION | unset | Client private key (PEM) for mutual TLS. Must exist; set together with the certificate. |
kafka.ssl_key_password / kafka.ssl_key_password_file | SUNSPEC_KAFKA_SSL_KEY_PASSWORD / SUNSPEC_KAFKA_SSL_KEY_PASSWORD_FILE | unset | Passphrase of an encrypted private key, given directly or as a file. |
kafka.sasl_mechanism | SUNSPEC_KAFKA_SASL_MECHANISM | unset | PLAIN, SCRAM-SHA-256 or SCRAM-SHA-512. Required with a sasl_* protocol. |
kafka.sasl_username | SUNSPEC_KAFKA_SASL_USERNAME | unset | Required with a sasl_* protocol. |
kafka.sasl_password / kafka.sasl_password_file | SUNSPEC_KAFKA_SASL_PASSWORD / SUNSPEC_KAFKA_SASL_PASSWORD_FILE | unset | Required with a sasl_* protocol. Prefer the _file form. |
Rules checked at startup:
ssl_*settings needsslorsasl_ssl;sasl_*settings needsasl_plaintextorsasl_ssl. Setting them with another protocol is an error, so a half-applied security configuration cannot silently fall back to plaintext.*_filesettings read a file that contains only the secret (a trailing newline is removed). Setting both a secret and its_fileform 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 key | Env var | Default | Notes |
|---|---|---|---|
metrics.port | SUNSPEC_METRICS_PORT | 9090 | Port 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 key | Env var | Default | Notes |
|---|---|---|---|
runtime.channel_capacity | SUNSPEC_CHANNEL_CAPACITY | 256 | Samples that can wait between the pollers and the ingest task. ≥ 1. |
runtime.respawn_delay_ms | SUNSPEC_RESPAWN_DELAY_MS | 1000 | Base 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 var | Default | Notes |
|---|---|---|
SUNSPEC_CONFIG | unset | Path of the config file, if --config is not given. |
RUST_LOG | info | Log filter; see Logging. |
SUNSPEC_LOG_FORMAT | text | text 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.
| Variable | Values | Notes |
|---|---|---|
RUST_LOG | tracing filter directives | Default 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_FORMAT | text, json | Default 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. Atinfoa 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
| Option | Effect |
|---|---|
--config PATH, --config=PATH | Read this config file. Overrides SUNSPEC_CONFIG. |
--print-schema | Print the Avro schema of the published records as JSON and exit. No configuration is loaded. |
--print-models | Print 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-identifiers | With --probe: keep serial numbers and network addresses instead of blanking them. |
--version, -V | Print collector-app <version> and exit. |
--help, -h | Print a short usage summary and exit. |
Other arguments are ignored.
Running the binary
collector-app [--config PATH] [--print-schema]
| Argument | Effect |
|---|---|
--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-schema | Print 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_LOGsets the filter (defaultinfo, for exampleinfo,poller_actor=debug);SUNSPEC_LOG_FORMAT=jsonwrites one JSON object per line (textis the default; any other value fails at startup). Colours are used only when stdout is a terminal. - Files: the buffer is
sunspec-buffer.sqlitein the working directory by default, with SQLite's-waland-shmfiles 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:
/metricsand/healthzonmetrics.port(default 9090). If the port cannot be bound, the collector exits at startup with an error containingbind 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 invalidSUNSPEC_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.
- 78 (
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}
| Field | Meaning |
|---|---|
healthy | true when both ages are below 120 s. |
ingest_age_s | Seconds since the ingest task last checked in (at least every 5 s); null before the first check-in. |
uplink_age_s | Seconds 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_up | Devices whose current session has read successfully. |
devices_total | Devices 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}}
]
}]}
| Field | Meaning |
|---|---|
up, last_success_ms, restarts | The 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, models | Where the SunS marker was found and the model chain, from the latest discovery; null and empty until a session has discovered the device. |
polled_models | The models the polling profile selects. |
readings | The 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
| Metric | Type | Labels | Meaning |
|---|---|---|---|
sunspec_poll_success_total | counter | device | Successful model reads. |
sunspec_poll_errors_total | counter | device, kind | Failed Modbus operations: connects at session start, reads during model discovery, and model reads during polling. kind: exception, timeout, transport, connect, address. |
sunspec_poll_duration_seconds | histogram | device | Time to read the models due in one poll cycle; cycles with nothing due are not recorded. |
sunspec_device_up | gauge | device | 0 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_seconds | gauge | device | Unix time of the last successful read. |
sunspec_poller_restarts_total | counter | device | Device sessions that ended with an error and were scheduled for retry. |
sunspec_poller_stalls_total | counter | device | Sessions ended by stall detection (no successful read within poller.stall_timeout_ms). |
sunspec_devices_known | gauge | — | Devices being supervised. |
sunspec_samples_dropped_total | counter | reason = channel_full | Samples dropped before reaching the buffer. |
sunspec_buffer_writes_total | counter | — | Samples written to SQLite. |
sunspec_buffer_dropped_total | counter | reason = max_rows, max_age, corrupt, write_error | Rows or samples dropped without being published. |
sunspec_buffer_rows | gauge | — | Rows waiting in the buffer. Updated every 10 s. |
sunspec_buffer_oldest_age_seconds | gauge | — | Age of the oldest buffered row; 0 when empty. Updated every 10 s. |
sunspec_uplink_records_total | counter | — | Records acknowledged by Kafka. |
sunspec_uplink_errors_total | counter | — | Delivery attempts Kafka did not acknowledge. The records stay buffered and are retried, so one record can count more than once. |
sunspec_uplink_publish_seconds | histogram | — | Time to publish one batch and receive its acknowledgements. |
Notes:
- The
devicelabel ishost/unit, for example192.168.1.20/1. sunspec_device_upis 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 particularsunspec_device_last_success_timestamp_secondsappears only after a device's first successful read.sunspec_poll_errors_totalcounts 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 onlysunspec_poller_restarts_totaland adevice session endedlog line.kindvalues: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_totalcounts per attempt: a record that fails three times counts three times. During a Kafka outage it grows by up tobuffer.batch_sizeper attempt.- Without
kafka.brokers, records handed to the log-only sink count insunspec_uplink_records_total. sunspec_uplink_publish_secondsis 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(defaultsunspec.telemetry). -
One record per sample, that is per model read from one device.
-
Key:
device_key,host/unit_id, for example192.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-objectschema-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.
| Field | Avro type | Meaning |
|---|---|---|
device_key | string | host/unit_id; also the record key |
ip | string | Device host (IP address or hostname as configured) |
unit_id | int | Modbus unit ID |
model_id | int | SunSpec model ID, e.g. 103 |
model_name | string | SunSpec model name, e.g. inverter_three_phase, or model_<id> |
start | int | Register address of the model header |
collected_at_ms | long (timestamp-millis) | When the registers were read |
points | map of ["null", "double", "long", "string"] | Decoded points by SunSpec name |
labels | map of string, default {} | Enum values and set bitfield bits by name (schema 2.1) |
registers | array of int | Raw 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-groupsgroup.Point; - scaled values are
double; enums, bitfields and unscaled integers arelong; strings arestring, 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
pointsmap; onlyregistersis 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 aSample(points sorted by name). - Other languages: check that the value starts with
C3 01and 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 exampleBinaryMessageDecoderin 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
| Model | Name in records | Label | Points |
|---|---|---|---|
| 1 | common | Common | 6 |
| 2 | aggregator | Basic Aggregator | 10 |
| 3 | model_3 | Secure Dataset Read Request | 58 |
| 4 | model_4 | Secure Dataset Read Response | 60 |
| 5 | model_5 | Secure Write Request | 88 |
| 6 | model_6 | Secure Write Sequential Request | 89 |
| 7 | model_7 | Secure Write Response Model (DRAFT 1) | 9 |
| 8 | model_8 | Get Device Security Certificate | 3 |
| 9 | model_9 | Set Operator Security Certificate | 92 |
| 10 | model_10 | Communication Interface Header | 3 |
| 11 | model_11 | Ethernet Link Layer | 7 |
| 12 | model_12 | IPv4 | 15 |
| 13 | model_13 | IPv6 | 15 |
| 14 | model_14 | Proxy Server | 8 |
| 15 | model_15 | Interface Counters Model | 12 |
| 16 | model_16 | Simple IP Network | 10 |
| 17 | model_17 | Serial Interface | 8 |
| 18 | model_18 | Cellular Link | 5 |
| 19 | model_19 | PPP Link | 9 |
Inverters and inverter controls
| Model | Name in records | Label | Points |
|---|---|---|---|
| 101 | inverter_single_phase | Inverter (Single Phase) | 31 |
| 102 | inverter_split_phase | Inverter (Split-Phase) | 31 |
| 103 | inverter_three_phase | Inverter (Three Phase) | 31 |
| 111 | inverter_single_phase_float | Inverter (Single Phase) FLOAT | 31 |
| 112 | inverter_split_phase_float | Inverter (Split Phase) FLOAT | 31 |
| 113 | inverter_three_phase_float | Inverter (Three Phase) FLOAT | 31 |
| 120 | nameplate | Nameplate | 16 |
| 121 | settings | Basic Settings | 20 |
| 122 | status | Measurements_Status | 17 |
| 123 | controls | Immediate Controls | 21 |
| 124 | storage_basic | Storage | 16 |
| 125 | pricing | Pricing | 6 |
| 126 | volt_var | Static Volt-VAR | 54 |
| 127 | freq_watt_param | Freq-Watt Param | 6 |
| 128 | reactive_current | Dynamic Reactive Current | 11 |
| 129 | lvrt | LVRTD | 50 |
| 130 | hvrt | HVRTD | 50 |
| 131 | watt_pf | Watt-PF | 53 |
| 132 | volt_watt | Volt-Watt | 54 |
| 133 | schedule | Basic Scheduling | 34 |
| 134 | freq_watt | Freq-Watt Crv | 58 |
| 135 | lfrt | LFRT | 50 |
| 136 | hfrt | HFRT | 50 |
| 137 | lvrtc | LVRTC | 50 |
| 138 | hvrtc | HVRTC | 50 |
| 139 | lvrtx | LVRTX | 51 |
| 140 | hvrtx | HVRTX | 51 |
| 141 | lfrtc | LFRTC | 50 |
| 142 | hfrtc | HFRTC | 50 |
| 143 | lfrtx | LFRTX | 51 |
| 144 | hfrtx | HFRTX | 51 |
| 145 | ext_settings | Extended Settings | 7 |
| 160 | mppt | Multiple MPPT Inverter Extension Model | 13 |
Meters
| Model | Name in records | Label | Points |
|---|---|---|---|
| 201 | ac_meter_an_or_ab | Meter (Single Phase) single phase (AN or AB) meter | 62 |
| 202 | ac_meter_abn | split single phase (ABN) meter | 62 |
| 203 | ac_meter_abcn | wye-connect three phase (abcn) meter | 62 |
| 204 | ac_meter_abc | delta-connect three phase (abc) meter | 62 |
| 211 | ac_meter_an_or_ab_float | single phase (AN or AB) meter | 62 |
| 212 | ac_meter_abn_float | split single phase (ABN) meter | 62 |
| 213 | ac_meter_abcn_float | wye-connect three phase (abcn) meter | 62 |
| 214 | ac_meter_abc_float | delta-connect three phase (abc) meter | 62 |
| 220 | ac_meter_secure | Secure AC Meter Selected Readings | 22 |
Environmental sensors
| Model | Name in records | Label | Points |
|---|---|---|---|
| 302 | irradiance | Irradiance Model | 5 |
| 303 | bom_temp | Back of Module Temperature Model | 1 |
| 304 | inclinometer | Inclinometer Model | 3 |
| 305 | location | GPS | 6 |
| 306 | ref_point | Reference Point Model | 4 |
| 307 | base_met | Base Met | 11 |
| 308 | mini_met | Mini Met Model | 4 |
String combiners
| Model | Name in records | Label | Points |
|---|---|---|---|
| 401 | string_combiner_current | String Combiner (Current) | 13 |
| 402 | string_combiner_advanced | String Combiner (Advanced) | 21 |
| 403 | string_combiner_current_input | String Combiner (Current) | 13 |
| 404 | string_combiner_advanced_inputs | String Combiner (Advanced) | 21 |
Solar modules
Trackers
| Model | Name in records | Label | Points |
|---|---|---|---|
| 601 | tracker_controller | Tracker Controller DRAFT 2 | 19 |
DER (IEEE 1547-2018)
| Model | Name in records | Label | Points |
|---|---|---|---|
| 701 | DERMeasureAC | DER AC Measurement | 60 |
| 702 | DERCapacity | DER Capacity | 42 |
| 703 | DEREnterService | Enter Service | 9 |
| 704 | DERCtlAC | DER AC Controls | 45 |
| 705 | DERVoltVar | DER Volt-Var | 19 |
| 706 | DERVoltWatt | DER Volt-Watt | 14 |
| 707 | DERTripLV | DER Trip LV | 15 |
| 708 | DERTripHV | DER Trip HV | 15 |
| 709 | DERTripLF | DER Trip LF | 15 |
| 710 | DERTripHF | DER Trip HF | 15 |
| 711 | DERFreqDroop | DER Frequency Droop | 14 |
| 712 | DERWattVar | DER Watt-Var | 14 |
| 713 | DERStorageCapacity | DER Storage Capacity | 5 |
| 714 | DERMeasureDC | DER DC Measurement | 17 |
| 715 | DERCtl | DERCtl | 5 |
Storage
| Model | Name in records | Label | Points |
|---|---|---|---|
| 801 | storage | Energy Storage Base Model (DEPRECATED) | 1 |
| 802 | battery | Battery Base Model | 44 |
| 803 | lithium_ion_bank | Lithium-Ion Battery Bank Model | 44 |
| 804 | lithium_ion_string | Lithium-Ion String Model | 41 |
| 805 | lithium-ion-module | Lithium-Ion Module Model | 23 |
| 806 | flow_battery | Flow Battery Model | 2 |
| 807 | flow_battery_string | Flow Battery String Model | 44 |
| 808 | flow_battery_module | Flow Battery Module Model | 2 |
| 809 | flow_battery_stack | Flow Battery Stack Model | 2 |
Test and vendor models
| Model | Name in records | Label | Points |
|---|---|---|---|
| 63001 | model_63001 | SunSpec Test Model 1 | 59 |
| 63002 | model_63002 | SunSpec Test Model 2 | 2 |
| 64001 | model_64001 | Veris Status and Configuration | 35 |
| 64020 | model_64020 | Mersen GreenString | 30 |
| 64101 | model_64101 | Eltek Inverter Extension | 7 |
| 64111 | model_64111 | Basic Charge Controller | 18 |
| 64112 | model_64112 | OutBack FM Charge Controller | 58 |
| 64410 | DCSimInterface | DC Simulator Control Interface | 32 |
| 64411 | ACSimInterface | AC Simulator Control Interface | 51 |
| 64412 | DERExploitation | DER Cyber Exploitation | 43 |
| 64413 | PVSimCurves | PV Curves | 5 |
| 64414 | DERSimControls | DER Simulation Controls | 9 |
| 64415 | CSIPControl | CSIP Client Control | 5 |
Common and network
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 1 | common | 6 |
| 2 | aggregator | 10 |
| 3 | model_3 | 58 |
| 4 | model_4 | 60 |
| 5 | model_5 | 88 |
| 6 | model_6 | 89 |
| 7 | model_7 | 9 |
| 8 | model_8 | 3 |
| 9 | model_9 | 92 |
| 10 | model_10 | 3 |
| 11 | model_11 | 7 |
| 12 | model_12 | 15 |
| 13 | model_13 | 15 |
| 14 | model_14 | 8 |
| 15 | model_15 | 12 |
| 16 | model_16 | 10 |
| 17 | model_17 | 8 |
| 18 | model_18 | 5 |
| 19 | model_19 | 9 |
Model 1: Common
Name in records: common.
All SunSpec compliant devices must include this as the first model
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Mn | string | Manufacturer | Well known value registered with SunSpec for compliance | |
Md | string | Model | Manufacturer specific value (32 chars) | |
Opt | string | Options | Manufacturer specific value (16 chars) | |
Vr | string | Version | Manufacturer specific value (16 chars) | |
SN | string | Serial Number | Manufacturer specific value (32 chars) | |
DA | uint16 | Device Address | Modbus device address |
Model 2: Basic Aggregator
Name in records: aggregator.
Aggregates a collection of models for a given model id
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
AID | uint16 | AID | Aggregated model id | |
N | uint16 | N | Number of aggregated models | |
UN | uint16 | UN | Update 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 | |
St | enum16 | Status | Enumerated status code | |
StVnd | enum16 | Vendor Status | Vendor specific status code | |
Evt | bitfield32 | Event Code | Bitmask event code | |
EvtVnd | bitfield32 | Vendor Event Code | Vendor specific event code | |
Ctl | enum16 | Control | Control register for all aggregated devices | |
CtlVnd | enum32 | Vendor Control | Vendor control register for all aggregated devices | |
CtlVl | enum32 | Control Value | Numerical value used as a parameter to the control |
Value names:
St(enum16): 1OFF, 2ON, 3FULL, 4FAULTEvt(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 2RESERVED_2, bit 3DC_DISCONNECT, bit 4RESERVED_4, bit 5RESERVED_5, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMPERATURE, bit 8RESERVED_8, bit 9RESERVED_9, bit 10RESERVED_10, bit 11RESERVED_11, bit 12BLOWN_FUSE, bit 13UNDER_TEMPERATURE, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 16THEFT_DETECTION, bit 17OUTPUT_OVER_CURRENT, bit 18OUTPUT_OVER_VOLTAGE, bit 19OUTPUT_UNDER_VOLTAGE, bit 20TEST_FAILEDCtl(enum16): 0NONE, 1AUTOMATIC, 2FORCE_OFF, 3TEST, 4THROTTLE
Model 3: Secure Dataset Read Request
Name in records: model_3.
Request a digital signature over a specified set of data registers
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
X | uint16 | X | Number of registers being requested | |
Off1 | uint16 | Offset1 | Offset of value to read | |
Off2 | uint16 | |||
Off3 | uint16 | |||
Off4 | uint16 | |||
Off5 | uint16 | |||
Off6 | uint16 | |||
Off7 | uint16 | |||
Off8 | uint16 | |||
Off9 | uint16 | |||
Off10 | uint16 | |||
Off11 | uint16 | |||
Off12 | uint16 | |||
Off13 | uint16 | |||
Off14 | uint16 | |||
Off15 | uint16 | |||
Off16 | uint16 | |||
Off17 | uint16 | |||
Off18 | uint16 | |||
Off19 | uint16 | |||
Off20 | uint16 | |||
Off21 | uint16 | |||
Off22 | uint16 | |||
Off23 | uint16 | |||
Off24 | uint16 | |||
Off25 | uint16 | |||
Off26 | uint16 | |||
Off27 | uint16 | |||
Off28 | uint16 | |||
Off29 | uint16 | |||
Off30 | uint16 | |||
Off31 | uint16 | |||
Off32 | uint16 | |||
Off33 | uint16 | |||
Off34 | uint16 | |||
Off35 | uint16 | |||
Off36 | uint16 | |||
Off37 | uint16 | |||
Off38 | uint16 | |||
Off39 | uint16 | |||
Off40 | uint16 | |||
Off41 | uint16 | |||
Off42 | uint16 | |||
Off43 | uint16 | |||
Off44 | uint16 | |||
Off45 | uint16 | |||
Off46 | uint16 | |||
Off47 | uint16 | |||
Off48 | uint16 | |||
Off49 | uint16 | |||
Off50 | uint16 | |||
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of request | |
Role | uint16 | Role | Digital Signature ID | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 | DS | Digital Signature |
Value names:
Alg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 4: Secure Dataset Read Response
Name in records: model_4.
Compute a digital signature over a specified set of data registers
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
RqSeq | uint16 | Request Sequence | Sequence number from the request | |
Sts | enum16 | Status | Status of last read operation | |
X | uint16 | X | Number of values from the request | |
Val1 | uint16 | Value1 | Copy of value from register Off1. | |
Val2 | uint16 | |||
Val3 | uint16 | |||
Val4 | uint16 | |||
Val5 | uint16 | |||
Val6 | uint16 | |||
Val7 | uint16 | |||
Val8 | uint16 | |||
Val9 | uint16 | |||
Val10 | uint16 | |||
Val11 | uint16 | |||
Val12 | uint16 | |||
Val13 | uint16 | |||
Val14 | uint16 | |||
Val15 | uint16 | |||
Val16 | uint16 | |||
Val17 | uint16 | |||
Val18 | uint16 | |||
Val19 | uint16 | |||
Val20 | uint16 | |||
Val21 | uint16 | |||
Val22 | uint16 | |||
Val23 | uint16 | |||
Val24 | uint16 | |||
Val25 | uint16 | |||
Val26 | uint16 | |||
Val27 | uint16 | |||
Val28 | uint16 | |||
Val29 | uint16 | |||
Val30 | uint16 | |||
Val31 | uint16 | |||
Val32 | uint16 | |||
Val33 | uint16 | |||
Val34 | uint16 | |||
Val35 | uint16 | |||
Val36 | uint16 | |||
Val37 | uint16 | |||
Val38 | uint16 | |||
Val39 | uint16 | |||
Val40 | uint16 | |||
Val41 | uint16 | |||
Val42 | uint16 | |||
Val43 | uint16 | |||
Val44 | uint16 | |||
Val45 | uint16 | |||
Val46 | uint16 | |||
Val47 | uint16 | |||
Val48 | uint16 | |||
Val49 | uint16 | |||
Val50 | uint16 | |||
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of response | |
Alm | enum16 | Alarm | Bitmask alarm code | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 | DS | Digital Signature |
Value names:
Sts(enum16): 0SUCCESS, 1DS, 2ACL, 3OFFAlm(enum16): 0NONE, 1ALMAlg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 5: Secure Write Request
Name in records: model_5.
Include a digital signature along with the control data
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
X | uint16 | X | Number of (offset, value) pairs being written | |
Off1 | uint16 | Offset1 | Offset of control register to write value to | |
Val1 | uint16 | Value1 | Value to write to control register at offset | |
Off2 | uint16 | |||
Val2 | uint16 | |||
Off3 | uint16 | |||
Val3 | uint16 | |||
Off4 | uint16 | |||
Val4 | uint16 | |||
Off5 | uint16 | |||
Val5 | uint16 | |||
Off6 | uint16 | |||
Val6 | uint16 | |||
Off7 | uint16 | |||
Val7 | uint16 | |||
Off8 | uint16 | |||
Val8 | uint16 | |||
Off9 | uint16 | |||
Val9 | uint16 | |||
Off10 | uint16 | |||
Val10 | uint16 | |||
Off11 | uint16 | |||
Val11 | uint16 | |||
Off12 | uint16 | |||
Val12 | uint16 | |||
Off13 | uint16 | |||
Val13 | uint16 | |||
Off14 | uint16 | |||
Val14 | uint16 | |||
Off15 | uint16 | |||
Val15 | uint16 | |||
Off16 | uint16 | |||
Val16 | uint16 | |||
Off17 | uint16 | |||
Val17 | uint16 | |||
Off18 | uint16 | |||
Val18 | uint16 | |||
Off19 | uint16 | |||
Val19 | uint16 | |||
Off20 | uint16 | |||
Val20 | uint16 | |||
Off21 | uint16 | |||
Val21 | uint16 | |||
Off22 | uint16 | |||
Val22 | uint16 | |||
Off23 | uint16 | |||
Val23 | uint16 | |||
Off24 | uint16 | |||
Val24 | uint16 | |||
Off25 | uint16 | |||
Val25 | uint16 | |||
Off26 | uint16 | |||
Val26 | uint16 | |||
Off27 | uint16 | |||
Val27 | uint16 | |||
Off28 | uint16 | |||
Val28 | uint16 | |||
Off29 | uint16 | |||
Val29 | uint16 | |||
Off30 | uint16 | |||
Val30 | uint16 | |||
Off31 | uint16 | |||
Val31 | uint16 | |||
Off32 | uint16 | |||
Val32 | uint16 | |||
Off33 | uint16 | |||
Val33 | uint16 | |||
Off34 | uint16 | |||
Val34 | uint16 | |||
Off35 | uint16 | |||
Val35 | uint16 | |||
Off36 | uint16 | |||
Val36 | uint16 | |||
Off37 | uint16 | |||
Val37 | uint16 | |||
Off38 | uint16 | |||
Val38 | uint16 | |||
Off39 | uint16 | |||
Val39 | uint16 | |||
Off40 | uint16 | |||
Val40 | uint16 | |||
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of request | |
Role | uint16 | Role | Signing key used 0-5 | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 | DS | Digital Signature |
Value names:
Alg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 6: Secure Write Sequential Request
Name in records: model_6.
Include a digital signature along with the control data
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
X | uint16 | X | Number of (offset, value) pairs being written | |
Off | uint16 | Offset | Starting offset for write operation | |
Val1 | uint16 | Value1 | Value to write to control register at offset | |
Val2 | uint16 | |||
Val3 | uint16 | |||
Val4 | uint16 | |||
Val5 | uint16 | |||
Val6 | uint16 | |||
Val7 | uint16 | |||
Val8 | uint16 | |||
Val9 | uint16 | |||
Val10 | uint16 | |||
Val11 | uint16 | |||
Val12 | uint16 | |||
Val13 | uint16 | |||
Val14 | uint16 | |||
Val15 | uint16 | |||
Val16 | uint16 | |||
Val17 | uint16 | |||
Val18 | uint16 | |||
Val19 | uint16 | |||
Val20 | uint16 | |||
Val21 | uint16 | |||
Val22 | uint16 | |||
Val23 | uint16 | |||
Val24 | uint16 | |||
Val25 | uint16 | |||
Val26 | uint16 | |||
Val27 | uint16 | |||
Val28 | uint16 | |||
Val29 | uint16 | |||
Val30 | uint16 | |||
Val31 | uint16 | |||
Val32 | uint16 | |||
Val33 | uint16 | |||
Val34 | uint16 | |||
Val35 | uint16 | |||
Val36 | uint16 | |||
Val37 | uint16 | |||
Val38 | uint16 | |||
Val39 | uint16 | |||
Val40 | uint16 | |||
Val41 | uint16 | |||
Val42 | uint16 | |||
Val43 | uint16 | |||
Val44 | uint16 | |||
Val45 | uint16 | |||
Val46 | uint16 | |||
Val47 | uint16 | |||
Val48 | uint16 | |||
Val49 | uint16 | |||
Val50 | uint16 | |||
Val51 | uint16 | |||
Val52 | uint16 | |||
Val53 | uint16 | |||
Val54 | uint16 | |||
Val55 | uint16 | |||
Val56 | uint16 | |||
Val57 | uint16 | |||
Val58 | uint16 | |||
Val59 | uint16 | |||
Val60 | uint16 | |||
Val61 | uint16 | |||
Val62 | uint16 | |||
Val63 | uint16 | |||
Val64 | uint16 | |||
Val65 | uint16 | |||
Val66 | uint16 | |||
Val67 | uint16 | |||
Val68 | uint16 | |||
Val69 | uint16 | |||
Val70 | uint16 | |||
Val71 | uint16 | |||
Val72 | uint16 | |||
Val73 | uint16 | |||
Val74 | uint16 | |||
Val75 | uint16 | |||
Val76 | uint16 | |||
Val77 | uint16 | |||
Val78 | uint16 | |||
Val79 | uint16 | |||
Val80 | uint16 | |||
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of request | |
Role | uint16 | Role | Signing key used 0-5 | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 | DS | Digital Signature |
Value names:
Alg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 7: Secure Write Response Model (DRAFT 1)
Name in records: model_7.
Include a digital signature over the response
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
RqSeq | uint16 | Request Sequence | Sequence number from the request | |
Sts | enum16 | Status | Status of last write operation | |
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of response | |
Alm | enum16 | Alarm | Bitmask alarm code | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 | DS | Digital Signature |
Value names:
Sts(enum16): 0SUCCESS, 1DS, 2ACL, 3OFF, 4VALAlm(enum16): 0NONE, 1ALMAlg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 8: Get Device Security Certificate
Name in records: model_8.
Security model for PKI
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Fmt | enum16 | Format | X.509 format of the certificate. DER or PEM. | |
N | uint16 | N | Number of registers to follow for the certificate | |
repeating[*].Cert | uint16 | Cert | X.509 Certificate of the device |
Value names:
Fmt(enum16): 0NONE, 1X509_PEM, 2X509_DER
Model 9: Set Operator Security Certificate
Name in records: model_9.
Security model for PKI
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
CertUID | uint16 | Cert_UID | User ID for this certificate | |
CertRole | uint16 | Cert_Role | Role for this certificate | |
Fmt | enum16 | Format | Format of this certificate | |
Typ | enum16 | Type | Type of this certificate | |
TotLn | uint16 | Total Length | Total Length of the Certificate | |
FrgLn | uint16 | Fragment length | Length of this fragment | |
Frg1 | uint16 | Frag1 | First word of this fragment | |
Frg2 | uint16 | |||
Frg3 | uint16 | |||
Frg4 | uint16 | |||
Frg5 | uint16 | |||
Frg6 | uint16 | |||
Frg7 | uint16 | |||
Frg8 | uint16 | |||
Frg9 | uint16 | |||
Frg10 | uint16 | |||
Frg11 | uint16 | |||
Frg12 | uint16 | |||
Frg13 | uint16 | |||
Frg14 | uint16 | |||
Frg15 | uint16 | |||
Frg16 | uint16 | |||
Frg17 | uint16 | |||
Frg18 | uint16 | |||
Frg19 | uint16 | |||
Frg20 | uint16 | |||
Frg21 | uint16 | |||
Frg22 | uint16 | |||
Frg23 | uint16 | |||
Frg24 | uint16 | |||
Frg25 | uint16 | |||
Frg26 | uint16 | |||
Frg27 | uint16 | |||
Frg28 | uint16 | |||
Frg29 | uint16 | |||
Frg30 | uint16 | |||
Frg31 | uint16 | |||
Frg32 | uint16 | |||
Frg33 | uint16 | |||
Frg34 | uint16 | |||
Frg35 | uint16 | |||
Frg36 | uint16 | |||
Frg37 | uint16 | |||
Frg38 | uint16 | |||
Frg39 | uint16 | |||
Frg40 | uint16 | |||
Frg41 | uint16 | |||
Frg42 | uint16 | |||
Frg43 | uint16 | |||
Frg44 | uint16 | |||
Frg45 | uint16 | |||
Frg46 | uint16 | |||
Frg47 | uint16 | |||
Frg48 | uint16 | |||
Frg49 | uint16 | |||
Frg50 | uint16 | |||
Frg51 | uint16 | |||
Frg52 | uint16 | |||
Frg53 | uint16 | |||
Frg54 | uint16 | |||
Frg55 | uint16 | |||
Frg56 | uint16 | |||
Frg57 | uint16 | |||
Frg58 | uint16 | |||
Frg59 | uint16 | |||
Frg60 | uint16 | |||
Frg61 | uint16 | |||
Frg62 | uint16 | |||
Frg63 | uint16 | |||
Frg64 | uint16 | |||
Frg65 | uint16 | |||
Frg66 | uint16 | |||
Frg67 | uint16 | |||
Frg68 | uint16 | |||
Frg69 | uint16 | |||
Frg70 | uint16 | |||
Frg71 | uint16 | |||
Frg72 | uint16 | |||
Frg73 | uint16 | |||
Frg74 | uint16 | |||
Frg75 | uint16 | |||
Frg78 | uint16 | |||
Frg79 | uint16 | |||
Frg80 | uint16 | Frag80 | Last word of this fragment | |
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of request | |
UID | uint16 | UID | User ID for the request signature | |
Role | uint16 | Role | Signing key used 0-5 | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers to follow for the certificate | |
repeating[*].Cert | uint16 |
Value names:
Fmt(enum16): 0NONE, 1X509_PEM, 2X509_DERTyp(enum16): 0DEV_KEY_PAIR, 1DEV_SHARED_KEY, 2OPERATOR_PUB, 3OPERATOR_SHARED, 4CA_PUBAlg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Model 10: Communication Interface Header
Name in records: model_10.
To be included first for a complete interface description
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
St | enum16 | Interface Status | Overall interface status | |
Ctl | uint16 | Interface Control | Overall interface control (TBD) | |
Typ | enum16 | Physical Access Type | Type of physical media |
Value names:
St(enum16): 0DOWN, 1UP, 2FAULTTyp(enum16): 0UNKNOWN, 1INTERNAL, 2TWISTED_PAIR, 3FIBER, 4WIRELESS
Model 11: Ethernet Link Layer
Name in records: model_11.
Include to support a wired ethernet port
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Spd | uint16 | Mbps | Ethernet Link Speed | Interface speed in Mb/s |
CfgSt | bitfield16 | Interface Status Flags | Interface flags. | |
St | enum16 | Link State | State information for this interface | |
MAC | eui48 | MAC | IEEE MAC address of this interface | |
Nam | string | Name | Interface name (8 chars) | |
Ctl | bitfield16 | Control | Control flags | |
FrcSpd | uint16 | Mbps | Forced Speed | Forced interface speed in Mb/s when AUTO is disabled |
Value names:
CfgSt(bitfield16): bit 0LINK, bit 1FULL_DUPLEX, bit 2AUTO_NEG1, bit 3AUTO_NEG2, bit 4AUTO_NEG3, bit 5RESET_REQUIRED, bit 6HW_FAULTSt(enum16): 0UNKNOWN, 1ENABLED, 2DISABLED, 3TESTINGCtl(bitfield16): bit 0AUTO, bit 1FULL_DUPLEX
Model 12: IPv4
Name in records: model_12.
Include to support an IPv4 protocol stack on this interface
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name | |
CfgSt | enum16 | Config Status | Configuration status | |
ChgSt | bitfield16 | Change Status | A configuration change is pending | |
Cap | bitfield16 | Config Capability | Identify capable sources of configuration | |
Cfg | enum16 | IPv4 Config | Configuration method used. | |
Ctl | enum16 | Control | Configure use of services | |
Addr | string | IP | IPv4 numeric address as a dotted string xxx.xxx.xxx.xxx | |
Msk | string | Netmask | IPv4 numeric netmask as a dotted string xxx.xxx.xxx.xxx | |
Gw | string | Gateway | IPv4 numeric gateway address as a dotted string xxx.xxx.xxx.xxx | |
DNS1 | string | DNS1 | IPv4 numeric DNS address as a dotted string xxx.xxx.xxx.xxx | |
DNS2 | string | DNS2 | IPv4 numeric DNS address as a dotted string xxx.xxx.xxx.xxx | |
NTP1 | string | NTP1 | IPv4 numeric NTP address as a dotted string xxx.xxx.xxx.xxx | |
NTP2 | string | NTP2 | IPv4 numeric NTP address as a dotted string xxx.xxx.xxx.xxx | |
DomNam | string | Domain | Domain name (24 chars max) | |
HostNam | string | Host Name | Host name (24 chars max) |
Value names:
CfgSt(enum16): 0NOT_CONFIGURED, 1VALID_SETTING, 2VALID_HWChgSt(bitfield16): bit 0PENDINGCap(bitfield16): bit 0DHCP, bit 1BOOTP, bit 2ZEROCONF, bit 3DNS, bit 4CFG_SETTABLE, bit 5HW_CONFIG, bit 6NTP_CLIENT, bit 7RESET_REQUIREDCfg(enum16): 0STATIC, 1DHCP, 2BOOTP, 3ZEROCONFCtl(enum16): 0ENABLE_DNS, 1ENABLE_NTP
Model 13: IPv6
Name in records: model_13.
Include to support an IPv6 protocol stack on this interface
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name | |
CfgSt | enum16 | Config Status | Configuration status | |
ChgSt | bitfield16 | Change Status | A configuration change is pending | |
Cap | bitfield16 | Config Capability | Identify capable sources of configuration | |
Cfg | enum16 | IPv6 Config | Configuration method used. | |
Ctl | enum16 | Control | Configure use of services | |
Addr | string | IP | IPv6 numeric address as a dotted string xxxx.xxxx.xxxx.xxxx | |
CIDR | string | CIDR | Classless Inter-Domain Routing Number | |
Gw | string | Gateway | IPv6 numeric address as a dotted string xxxx.xxxx.xxxx.xxxx | |
DNS1 | string | DNS1 | IPv6 numeric DNS address as a dotted string xxxx.xxxx.xxxx.xxxx | |
DNS2 | string | DNS2 | IPv6 numeric DNS address as a dotted string xxxx.xxxx.xxxx.xxxx | |
NTP1 | string | NTP1 | IPv6 numeric NTP address as a name or dotted string xxxx.xxxx.xxxx.xxxx | |
NTP2 | string | NTP2 | IPv6 numeric NTP address as a name or dotted string xxxx.xxxx.xxxx.xxxx | |
DomNam | string | Domain | Domain name (24 chars max) | |
HostNam | string | Host Name | Host name (24 chars max) |
Value names:
CfgSt(enum16): 0NOT_CONFIGURED, 1VALID_SETTING, 2VALID_HWChgSt(bitfield16): bit 0PENDINGCap(bitfield16): bit 0DHCP, bit 1BOOTP, bit 2ZEROCONF, bit 3DNS, bit 4CFG_SETTABLE, bit 5HW_CONFIG, bit 6NTP_CLIENT, bit 7RESET_REQUIREDCfg(enum16): 0STATIC, 1DHCP, 2BOOTP, 3ZEROCONFCtl(enum16): 0ENABLE_DNS, 1ENABLE_NTP
Model 14: Proxy Server
Name in records: model_14.
Include this block to allow for a proxy server
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | name | Interface name (8 chars) | |
Cap | bitfield16 | Capabilities | Proxy configuration capabilities | |
Cfg | enum16 | Config | Set proxy address type | |
Typ | bitfield16 | Type | Enumerate value. Proxy server type | |
Addr | string | Address | IPv4 or IPv6 proxy hostname or dotted address (40 chars) | |
Port | uint16 | Port | Proxy port number | |
User | string | Username | Proxy user name | |
Pw | string | Password | Proxy password |
Value names:
Cap(bitfield16): bit 0NO_PROXY, bit 1IPV4_PROXY, bit 2IPV6_PROXY
Model 15: Interface Counters Model
Name in records: model_15.
Interface counters
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Clr | uint16 | Clear | Write a "1" to clear all counters | |
InCnt | acc32 | Input Count | Number of bytes received | |
InUcCnt | acc32 | Input Unicast Count | Number of Unicast packets received | |
InNUcCnt | acc32 | Input Non-Unicast Count | Number of non-Unicast packets received | |
InDscCnt | acc32 | Input Discarded Count | Number of inbound packets received on the interface but discarded | |
InErrCnt | acc32 | Input Error Count | Number of inbound packets that contain errors (excluding discards) | |
InUnkCnt | acc32 | Input Unknown Count | Number of inbound packets with unknown protocol | |
OutCnt | acc32 | Output Count | Total number of bytes transmitted on this interface | |
OutUcCnt | acc32 | Output Unicast Count | Number of Unicast packets transmitted | |
OutNUcCnt | acc32 | Output Non-Unicast Count | Number of Non-Unicast packets transmitted | |
OutDscCnt | acc32 | Output Discarded Count | Number of Discarded output packets | |
OutErrCnt | acc32 | Output Error Count | Number of outbound error packets |
Model 16: Simple IP Network
Name in records: model_16.
Include this model for a simple IPv4 network stack
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name. (8 chars) | |
Cfg | enum16 | Config | Force IPv4 configuration method | |
Ctl | bitfield16 | Control | Configure use of services | |
Addr | string | Address | IP address | |
Msk | string | Netmask | Netmask | |
Gw | string | Gateway | Gateway IP address | |
DNS1 | string | DNS1 | 32 bit IP address of DNS server | |
DNS2 | string | DNS2 | 32 bit IP address of DNS server | |
MAC | eui48 | MAC | IEEE MAC address of this interface | |
LnkCtl | bitfield16 | Link Control | Link control flags |
Value names:
Cfg(enum16): 0STATIC, 1DHCPCtl(bitfield16): bit 0ENABLE_DNS, bit 1ENABLE_NTPLnkCtl(bitfield16): bit 0AUTONEGOTIATE, bit 1FULL_DUPLEX, bit 2FORCE_10MB, bit 3FORCE_100MB, bit 4FORCE_1GB
Model 17: Serial Interface
Name in records: model_17.
Include this model for serial interface configuration support
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name (8 chars) | |
Rte | uint32 | bps | Rate | Interface baud rate in bits per second |
Bits | uint16 | Bits | Number of data bits per character | |
Pty | enum16 | Parity | Parity setting | |
Dup | enum16 | Duplex | Duplex mode | |
Flw | enum16 | Flow Control | Flow Control Method | |
Typ | enum16 | Interface Type | Interface type | |
Pcol | enum16 | Protocol | Serial protocol selection |
Value names:
Pty(enum16): 0NONE, 1ODD, 2EVENDup(enum16): 0FULL, 1HALFFlw(enum16): 0NONE, 1HW, 2XONXOFFTyp(enum16): 0UNKNOWN, 1RS232, 2RS485Pcol(enum16): 0UNKNOWN, 1MODBUS, 2VENDOR
Model 18: Cellular Link
Name in records: model_18.
Include this model to support a cellular interface link
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name | |
IMEI | uint32 | IMEI | International Mobile Equipment Identifier for the interface | |
APN | string | APN | Access Point Name for the interface | |
Num | string | Number | Phone number for the interface | |
Pin | string | PIN | Personal Identification Number for the interface |
Model 19: PPP Link
Name in records: model_19.
Include this model to configure a Point-to-Point Protocol link
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Name | Interface name | |
Rte | uint32 | bps | Rate | Interface baud rate in bits per second |
Bits | uint16 | Bits | Number of data bits per character | |
Pty | enum16 | Parity | Parity setting | |
Dup | enum16 | Duplex | Duplex mode | |
Flw | enum16 | Flow Control | Flow Control Method | |
Auth | enum16 | Authentication | Authentication method | |
UsrNam | string | Username | Username for authentication | |
Pw | string | Password | Password for authentication |
Value names:
Pty(enum16): 0NONE, 1ODD, 2EVENDup(enum16): 0FULL, 1HALFFlw(enum16): 0NONE, 1HW, 2XONXOFFAuth(enum16): 0NONE, 1PAP, 2CHAP
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.
| Model | Name in records | Points |
|---|---|---|
| 101 | inverter_single_phase | 31 |
| 102 | inverter_split_phase | 31 |
| 103 | inverter_three_phase | 31 |
| 111 | inverter_single_phase_float | 31 |
| 112 | inverter_split_phase_float | 31 |
| 113 | inverter_three_phase_float | 31 |
| 120 | nameplate | 16 |
| 121 | settings | 20 |
| 122 | status | 17 |
| 123 | controls | 21 |
| 124 | storage_basic | 16 |
| 125 | pricing | 6 |
| 126 | volt_var | 54 |
| 127 | freq_watt_param | 6 |
| 128 | reactive_current | 11 |
| 129 | lvrt | 50 |
| 130 | hvrt | 50 |
| 131 | watt_pf | 53 |
| 132 | volt_watt | 54 |
| 133 | schedule | 34 |
| 134 | freq_watt | 58 |
| 135 | lfrt | 50 |
| 136 | hfrt | 50 |
| 137 | lvrtc | 50 |
| 138 | hvrtc | 50 |
| 139 | lvrtx | 51 |
| 140 | hvrtx | 51 |
| 141 | lfrtc | 50 |
| 142 | hfrtc | 50 |
| 143 | lfrtx | 51 |
| 144 | hfrtx | 51 |
| 145 | ext_settings | 7 |
| 160 | mppt | 13 |
Model 101: Inverter (Single Phase)
Name in records: inverter_single_phase.
Include this model for single phase inverter monitoring
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | uint16 | A | Amps | AC Current |
AphA | uint16 | A | Amps PhaseA | Phase A Current |
AphB | uint16 | A | Amps PhaseB | Phase B Current |
AphC | uint16 | A | Amps PhaseC | Phase C Current |
PPVphAB | uint16 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | uint16 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | uint16 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | uint16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | uint16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | uint16 | V | Phase Voltage CN | Phase Voltage CN |
W | int16 | W | Watts | AC Power |
Hz | uint16 | Hz | Hz | Line Frequency |
VA | int16 | VA | VA | AC Apparent Power |
VAr | int16 | var | VAr | AC Reactive Power |
PF | int16 | Pct | PF | AC Power Factor |
WH | acc32 | Wh | WattHours | AC Energy |
DCA | uint16 | A | DC Amps | DC Current |
DCV | uint16 | V | DC Voltage | DC Voltage |
DCW | int16 | W | DC Watts | DC Power |
TmpCab | int16 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | int16 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | int16 | C | Transformer Temperature | Transformer Temperature |
TmpOt | int16 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_TEST_FAILURE
Model 102: Inverter (Split-Phase)
Name in records: inverter_split_phase.
Include this model for split phase inverter monitoring
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | uint16 | A | Amps | AC Current |
AphA | uint16 | A | Amps PhaseA | Phase A Current |
AphB | uint16 | A | Amps PhaseB | Phase B Current |
AphC | uint16 | A | Amps PhaseC | Phase C Current |
PPVphAB | uint16 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | uint16 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | uint16 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | uint16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | uint16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | uint16 | V | Phase Voltage CN | Phase Voltage CN |
W | int16 | W | Watts | AC Power |
Hz | uint16 | Hz | Hz | Line Frequency |
VA | int16 | VA | VA | AC Apparent Power |
VAr | int16 | var | VAr | AC Reactive Power |
PF | int16 | Pct | PF | AC Power Factor |
WH | acc32 | Wh | WattHours | AC Energy |
DCA | uint16 | A | DC Amps | DC Current |
DCV | uint16 | V | DC Voltage | DC Voltage |
DCW | int16 | W | DC Watts | DC Power |
TmpCab | int16 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | int16 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | int16 | C | Transformer Temperature | Transformer Temperature |
TmpOt | int16 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_TEST_FAILURE
Model 103: Inverter (Three Phase)
Name in records: inverter_three_phase.
Include this model for three phase inverter monitoring
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | uint16 | A | Amps | AC Current |
AphA | uint16 | A | Amps PhaseA | Phase A Current |
AphB | uint16 | A | Amps PhaseB | Phase B Current |
AphC | uint16 | A | Amps PhaseC | Phase C Current |
PPVphAB | uint16 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | uint16 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | uint16 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | uint16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | uint16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | uint16 | V | Phase Voltage CN | Phase Voltage CN |
W | int16 | W | Watts | AC Power |
Hz | uint16 | Hz | Hz | Line Frequency |
VA | int16 | VA | VA | AC Apparent Power |
VAr | int16 | var | VAr | AC Reactive Power |
PF | int16 | Pct | PF | AC Power Factor |
WH | acc32 | Wh | WattHours | AC Energy |
DCA | uint16 | A | DC Amps | DC Current |
DCV | uint16 | V | DC Voltage | DC Voltage |
DCW | int16 | W | DC Watts | DC Power |
TmpCab | int16 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | int16 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | int16 | C | Transformer Temperature | Transformer Temperature |
TmpOt | int16 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_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
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
W | float32 | W | Watts | AC Power |
Hz | float32 | Hz | Hz | Line Frequency |
VA | float32 | VA | VA | AC Apparent Power |
VAr | float32 | var | VAr | AC Reactive Power |
PF | float32 | Pct | PF | AC Power Factor |
WH | float32 | Wh | WattHours | AC Energy |
DCA | float32 | A | DC Amps | DC Current |
DCV | float32 | V | DC Voltage | DC Voltage |
DCW | float32 | W | DC Watts | DC Power |
TmpCab | float32 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | float32 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | float32 | C | Transformer Temperature | Transformer Temperature |
TmpOt | float32 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1ggOFF, 2ggSLEEPING, 3ggSTARTING, 4ggMPPT, 5ggTHROTTLED, 6ggSHUTTING_DOWN, 7ggFAULT, 8ggSTANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_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
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
W | float32 | W | Watts | AC Power |
Hz | float32 | Hz | Hz | Line Frequency |
VA | float32 | VA | VA | AC Apparent Power |
VAr | float32 | var | VAr | AC Reactive Power |
PF | float32 | Pct | PF | AC Power Factor |
WH | float32 | Wh | WattHours | AC Energy |
DCA | float32 | A | DC Amps | DC Current |
DCV | float32 | V | DC Voltage | DC Voltage |
DCW | float32 | W | DC Watts | DC Power |
TmpCab | float32 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | float32 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | float32 | C | Transformer Temperature | Transformer Temperature |
TmpOt | float32 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_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
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
W | float32 | W | Watts | AC Power |
Hz | float32 | Hz | Hz | Line Frequency |
VA | float32 | VA | VA | AC Apparent Power |
VAr | float32 | var | VAr | AC Reactive Power |
PF | float32 | Pct | PF | AC Power Factor |
WH | float32 | Wh | WattHours | AC Energy |
DCA | float32 | A | DC Amps | DC Current |
DCV | float32 | V | DC Voltage | DC Voltage |
DCW | float32 | W | DC Watts | DC Power |
TmpCab | float32 | C | Cabinet Temperature | Cabinet Temperature |
TmpSnk | float32 | C | Heat Sink Temperature | Heat Sink Temperature |
TmpTrns | float32 | C | Transformer Temperature | Transformer Temperature |
TmpOt | float32 | C | Other Temperature | Other Temperature |
St | enum16 | Operating State | Operating state | |
StVnd | enum16 | Vendor Operating State | Vendor specific operating state code | |
Evt1 | bitfield32 | Event1 | Event fields | |
Evt2 | bitfield32 | Event Bitfield 2 | Reserved for future use | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events | |
EvtVnd3 | bitfield32 | Vendor Event Bitfield 3 | Vendor defined events | |
EvtVnd4 | bitfield32 | Vendor Event Bitfield 4 | Vendor defined events |
Value names:
St(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBYEvt1(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_TEST_FAILURE
Model 120: Nameplate
Name in records: nameplate.
Inverter Controls Nameplate Ratings
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DERTyp | enum16 | DERTyp | Type of DER device. Default value is 4 to indicate PV device. | |
WRtg | uint16 | W | WRtg | Continuous power output capability of the inverter. |
VARtg | uint16 | VA | VARtg | Continuous Volt-Ampere capability of the inverter. |
VArRtgQ1 | int16 | var | VArRtgQ1 | Continuous VAR capability of the inverter in quadrant 1. |
VArRtgQ2 | int16 | var | VArRtgQ2 | Continuous VAR capability of the inverter in quadrant 2. |
VArRtgQ3 | int16 | var | VArRtgQ3 | Continuous VAR capability of the inverter in quadrant 3. |
VArRtgQ4 | int16 | var | VArRtgQ4 | Continuous VAR capability of the inverter in quadrant 4. |
ARtg | uint16 | A | ARtg | Maximum RMS AC current level capability of the inverter. |
PFRtgQ1 | int16 | cos() | PFRtgQ1 | Minimum power factor capability of the inverter in quadrant 1. |
PFRtgQ2 | int16 | cos() | PFRtgQ2 | Minimum power factor capability of the inverter in quadrant 2. |
PFRtgQ3 | int16 | cos() | PFRtgQ3 | Minimum power factor capability of the inverter in quadrant 3. |
PFRtgQ4 | int16 | cos() | PFRtgQ4 | Minimum power factor capability of the inverter in quadrant 4. |
WHRtg | uint16 | Wh | WHRtg | Nominal energy rating of storage device. |
AhrRtg | uint16 | AH | AhrRtg | The usable capacity of the battery. Maximum charge minus minimum charge from a technology capability perspective (Amp-hour capacity rating). |
MaxChaRte | uint16 | W | MaxChaRte | Maximum rate of energy transfer into the storage device. |
MaxDisChaRte | uint16 | W | MaxDisChaRte | Maximum rate of energy transfer out of the storage device. |
Value names:
DERTyp(enum16): 4PV, 82PV_STOR
Model 121: Basic Settings
Name in records: settings.
Inverter Controls Basic Settings
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
WMax | uint16 | W | WMax | Setting for maximum power output. Default to WRtg. |
VRef | uint16 | V | VRef | Voltage at the PCC. |
VRefOfs | int16 | V | VRefOfs | Offset from PCC to inverter. |
VMax | uint16 | V | VMax | Setpoint for maximum voltage. |
VMin | uint16 | V | VMin | Setpoint for minimum voltage. |
VAMax | uint16 | VA | VAMax | Setpoint for maximum apparent power. Default to VARtg. |
VArMaxQ1 | int16 | var | VArMaxQ1 | Setting for maximum reactive power in quadrant 1. Default to VArRtgQ1. |
VArMaxQ2 | int16 | var | VArMaxQ2 | Setting for maximum reactive power in quadrant 2. Default to VArRtgQ2. |
VArMaxQ3 | int16 | var | VArMaxQ3 | Setting for maximum reactive power in quadrant 3. Default to VArRtgQ3. |
VArMaxQ4 | int16 | var | VArMaxQ4 | Setting for maximum reactive power in quadrant 4. Default to VArRtgQ4. |
WGra | uint16 | % WMax/sec | WGra | Default ramp rate of change of active power due to command or internal action. |
PFMinQ1 | int16 | cos() | PFMinQ1 | Setpoint for minimum power factor value in quadrant 1. Default to PFRtgQ1. |
PFMinQ2 | int16 | cos() | PFMinQ2 | Setpoint for minimum power factor value in quadrant 2. Default to PFRtgQ2. |
PFMinQ3 | int16 | cos() | PFMinQ3 | Setpoint for minimum power factor value in quadrant 3. Default to PFRtgQ3. |
PFMinQ4 | int16 | cos() | PFMinQ4 | Setpoint for minimum power factor value in quadrant 4. Default to PFRtgQ4. |
VArAct | enum16 | VArAct | VAR action on change between charging and discharging: 1=switch 2=maintain VAR characterization. | |
ClcTotVA | enum16 | ClcTotVA | Calculation method for total apparent power. 1=vector 2=arithmetic. | |
MaxRmpRte | uint16 | % WGra | MaxRmpRte | Setpoint 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. |
ECPNomHz | uint16 | Hz | ECPNomHz | Setpoint for nominal frequency at the ECP. |
ConnPh | enum16 | ConnPh | Identity of connected phase for single phase inverters. A=1 B=2 C=3. |
Value names:
VArAct(enum16): 1SWITCH, 2MAINTAINClcTotVA(enum16): 1VECTOR, 2ARITHMETICConnPh(enum16): 1A, 2B, 3C
Model 122: Measurements_Status
Name in records: status.
Inverter Controls Extended Measurements and Status
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
PVConn | bitfield16 | PVConn | PV inverter present/available status. | |
StorConn | bitfield16 | StorConn | Storage inverter present/available status. | |
ECPConn | bitfield16 | ECPConn | ECP connection status: disconnected=0 connected=1. | |
ActWh | acc64 | Wh | ActWh | AC lifetime active (real) energy output. |
ActVAh | acc64 | VAh | ActVAh | AC lifetime apparent energy output. |
ActVArhQ1 | acc64 | varh | ActVArhQ1 | AC lifetime reactive energy output in quadrant 1. |
ActVArhQ2 | acc64 | varh | ActVArhQ2 | AC lifetime reactive energy output in quadrant 2. |
ActVArhQ3 | acc64 | varh | ActVArhQ3 | AC lifetime negative energy output in quadrant 3. |
ActVArhQ4 | acc64 | varh | ActVArhQ4 | AC lifetime reactive energy output in quadrant 4. |
VArAval | int16 | var | VArAval | Amount of VARs available without impacting watts output. |
WAval | uint16 | W | WAval | Amount of Watts available. |
StSetLimMsk | bitfield32 | StSetLimMsk | Setpoint limit(s) reached. | |
StActCtl | bitfield32 | StActCtl | Which inverter controls are currently active. | |
TmSrc | string | TmSrc | Source of time synchronization. | |
Tms | uint32 | Secs | Tms | Seconds since 01-01-2000 00:00 UTC |
RtSt | bitfield16 | RtSt | Active ride-through status. | |
Ris | uint16 | ohms | Ris | Isolation resistance. |
Value names:
PVConn(bitfield16): bit 0CONNECTED, bit 1AVAILABLE, bit 2OPERATING, bit 3TESTStorConn(bitfield16): bit 0CONNECTED, bit 1AVAILABLE, bit 2OPERATING, bit 3TESTECPConn(bitfield16): bit 0DISCONNECTED, bit 1CONNECTEDStSetLimMsk(bitfield32): bit 0WMax, bit 1VAMax, bit 2VArAval, bit 3VArMaxQ1, bit 4VArMaxQ2, bit 5VArMaxQ3, bit 6VArMaxQ4, bit 7PFMinQ1, bit 8PFMinQ2, bit 9PFMinQ3, bit 10PFMinQ4StActCtl(bitfield32): bit 0FixedW, bit 1FixedVAR, bit 2FixedPF, bit 3Volt-VAr, bit 4Freq-Watt-Param, bit 5Freq-Watt-Curve, bit 6Dyn-Reactive-Current, bit 7LVRT, bit 8HVRT, bit 9Watt-PF, bit 10Volt-Watt, bit 12Scheduled, bit 13LFRT, bit 14HFRTRtSt(bitfield16): bit 0LVRT_ACTIVE, bit 1HVRT_ACTIVE, bit 2LFRT_ACTIVE, bit 3HFRT_ACTIVE
Model 123: Immediate Controls
Name in records: controls.
Immediate Inverter Controls
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Conn_WinTms | uint16 | Secs | Conn_WinTms | Time window for connect/disconnect. |
Conn_RvrtTms | uint16 | Secs | Conn_RvrtTms | Timeout period for connect/disconnect. |
Conn | enum16 | Conn | Connection control. | |
WMaxLimPct | uint16 | % WMax | WMaxLimPct | Set power output to specified level. |
WMaxLimPct_WinTms | uint16 | Secs | WMaxLimPct_WinTms | Time window for power limit change. |
WMaxLimPct_RvrtTms | uint16 | Secs | WMaxLimPct_RvrtTms | Timeout period for power limit. |
WMaxLimPct_RmpTms | uint16 | Secs | WMaxLimPct_RmpTms | Ramp time for moving from current setpoint to new setpoint. |
WMaxLim_Ena | enum16 | WMaxLim_Ena | Throttle enable/disable control. | |
OutPFSet | int16 | cos() | OutPFSet | Set power factor to specific value - cosine of angle. |
OutPFSet_WinTms | uint16 | Secs | OutPFSet_WinTms | Time window for power factor change. |
OutPFSet_RvrtTms | uint16 | Secs | OutPFSet_RvrtTms | Timeout period for power factor. |
OutPFSet_RmpTms | uint16 | Secs | OutPFSet_RmpTms | Ramp time for moving from current setpoint to new setpoint. |
OutPFSet_Ena | enum16 | OutPFSet_Ena | Fixed power factor enable/disable control. | |
VArWMaxPct | int16 | % WMax | VArWMaxPct | Reactive power in percent of WMax. |
VArMaxPct | int16 | % VArMax | VArMaxPct | Reactive power in percent of VArMax. |
VArAvalPct | int16 | % VArAval | VArAvalPct | Reactive power in percent of VArAval. |
VArPct_WinTms | uint16 | Secs | VArPct_WinTms | Time window for VAR limit change. |
VArPct_RvrtTms | uint16 | Secs | VArPct_RvrtTms | Timeout period for VAR limit. |
VArPct_RmpTms | uint16 | Secs | VArPct_RmpTms | Ramp time for moving from current setpoint to new setpoint. |
VArPct_Mod | enum16 | VArPct_Mod | VAR percent limit mode. | |
VArPct_Ena | enum16 | VArPct_Ena | Percent limit VAr enable/disable control. |
Value names:
Conn(enum16): 0DISCONNECT, 1CONNECTWMaxLim_Ena(enum16): 0DISABLED, 1ENABLEDOutPFSet_Ena(enum16): 0DISABLED, 1ENABLEDVArPct_Mod(enum16): 0NONE, 1WMax, 2VArMax, 3VArAvalVArPct_Ena(enum16): 0DISABLED, 1ENABLED
Model 124: Storage
Name in records: storage_basic.
Basic Storage Controls
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
WChaMax | uint16 | W | WChaMax | Setpoint for maximum charge. |
WChaGra | uint16 | % WChaMax/sec | WChaGra | Setpoint for maximum charging rate. Default is MaxChaRte. |
WDisChaGra | uint16 | % WChaMax/sec | WDisChaGra | Setpoint for maximum discharge rate. Default is MaxDisChaRte. |
StorCtl_Mod | bitfield16 | StorCtl_Mod | Activate hold/discharge/charge storage control mode. | |
VAChaMax | uint16 | VA | VAChaMax | Setpoint for maximum charging VA. |
MinRsvPct | uint16 | % WChaMax | MinRsvPct | Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage. |
ChaState | uint16 | % AhrRtg | ChaState | Currently available energy as a percent of the capacity rating. |
StorAval | uint16 | AH | StorAval | State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg). |
InBatV | uint16 | V | InBatV | Internal battery voltage. |
ChaSt | enum16 | ChaSt | Charge status of storage device. | |
OutWRte | int16 | % WDisChaMax | OutWRte | Percent of max discharge rate. |
InWRte | int16 | % WChaMax | InWRte | Percent of max charging rate. |
InOutWRte_WinTms | uint16 | Secs | InOutWRte_WinTms | Time window for charge/discharge rate change. |
InOutWRte_RvrtTms | uint16 | Secs | InOutWRte_RvrtTms | Timeout period for charge/discharge rate. |
InOutWRte_RmpTms | uint16 | Secs | InOutWRte_RmpTms | Ramp time for moving from current setpoint to new setpoint. |
ChaGriSet | enum16 |
Value names:
StorCtl_Mod(bitfield16): bit 0CHARGE, bit 1DiSCHARGEChaSt(enum16): 1OFF, 2EMPTY, 3DISCHARGING, 4CHARGING, 5FULL, 6HOLDING, 7TESTINGChaGriSet(enum16): 0PV, 1GRID
Model 125: Pricing
Name in records: pricing.
Pricing Signal
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ModEna | bitfield16 | ModEna | Is price-based charge/discharge mode active? | |
SigType | enum16 | SigType | Meaning of the pricing signal. When a Price schedule is used, type must match the schedule range variable description. | |
Sig | int16 | Sig | Utility/ESP specific pricing signal. Content depends on pricing signal type. When H/M/L type is specified. Low=0; Med=1; High=2. | |
WinTms | uint16 | Secs | WinTms | Time window for charge/discharge pricing change. |
RvtTms | uint16 | Secs | RvtTms | Timeout period for charge/discharge pricing change. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current charge or discharge level to new level. |
Value names:
ModEna(bitfield16): bit 0ENABLESigType(enum16): 0UNKNOWN, 1ABSOLUTE, 2RELATIVE, 3MULTIPLIER, 4LEVEL
Model 126: Static Volt-VAR
Name in records: volt_var.
Static Volt-VAR Arrays
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | Is Volt-VAR control active. | |
WinTms | uint16 | Secs | WinTms | Time window for volt-VAR change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for volt-VAR curve selection. |
RmpTms | uint16 | Secs | RmpTms | The time of the PT1 in seconds (time to accomplish a change of 95%). |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].DeptRef | enum16 | DeptRef | Meaning of dependent variable: 1=%WMax 2=%VArMax 3=%VArAval. | |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 Volts. |
curve[*].VAr1 | int16 | VAr1 | Point 1 VARs. | |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 Volts. |
curve[*].VAr2 | int16 | VAr2 | Point 2 VARs. | |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 Volts. |
curve[*].VAr3 | int16 | VAr3 | Point 3 VARs. | |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 Volts. |
curve[*].VAr4 | int16 | VAr4 | Point 4 VARs. | |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 Volts. |
curve[*].VAr5 | int16 | VAr5 | Point 5 VARs. | |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 Volts. |
curve[*].VAr6 | int16 | VAr6 | Point 6 VARs. | |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 Volts. |
curve[*].VAr7 | int16 | VAr7 | Point 7 VARs. | |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 Volts. |
curve[*].VAr8 | int16 | VAr8 | Point 8 VARs. | |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 Volts. |
curve[*].VAr9 | int16 | VAr9 | Point 9 VARs. | |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 Volts. |
curve[*].VAr10 | int16 | VAr10 | Point 10 VARs. | |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 Volts. |
curve[*].VAr11 | int16 | VAr11 | Point 11 VARs. | |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 Volts. |
curve[*].VAr12 | int16 | VAr12 | Point 12 VARs. | |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 Volts. |
curve[*].VAr13 | int16 | VAr13 | Point 13 VARs. | |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 Volts. |
curve[*].VAr14 | int16 | VAr14 | Point 14 VARs. | |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 Volts. |
curve[*].VAr15 | int16 | VAr15 | Point 15 VARs. | |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 Volts. |
curve[*].VAr16 | int16 | VAr16 | Point 16 VARs. | |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 Volts. |
curve[*].VAr17 | int16 | VAr17 | Point 17 VARs. | |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 Volts. |
curve[*].VAr18 | int16 | VAr18 | Point 18 VARs. | |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 Volts. |
curve[*].VAr19 | int16 | VAr19 | Point 19 VARs. | |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 Volts. |
curve[*].VAr20 | int16 | VAr20 | Point 20 VARs. | |
curve[*].CrvNam | string | CrvNam | Optional description for curve. (Max 16 chars) | |
curve[*].RmpTms | uint16 | Secs | RmpTms | The time of the PT1 in seconds (time to accomplish a change of 95%). |
curve[*].RmpDecTmm | uint16 | % ref_value/min | RmpDecTmm | The 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[*].RmpIncTmm | uint16 | % ref_value/min | RmpIncTmm | The 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[*].ReadOnly | enum16 | ReadOnly | Boolean flag indicates if curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].DeptRef(enum16): 1WMax, 2VArMax, 3VArAvalcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 127: Freq-Watt Param
Name in records: freq_watt_param.
Parameterized Frequency-Watt
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
WGra | uint16 | % PM/Hz | WGra | The slope of the reduction in the maximum allowed watts output as a function of frequency. |
HzStr | int16 | Hz | HzStr | The 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. |
HzStop | int16 | Hz | HzStop | The 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. |
HysEna | bitfield16 | HysEna | Enable hysteresis | |
ModEna | bitfield16 | ModEna | Is Parameterized Frequency-Watt control active. | |
HzStopWGra | uint16 | % WMax/min | HzStopWGra | The 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 0ENABLEDModEna(bitfield16): bit 0ENABLED
Model 128: Dynamic Reactive Current
Name in records: reactive_current.
Dynamic Reactive Current
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ArGraMod | enum16 | ArGraMod | Indicates if gradients trend toward zero at the edges of the deadband or trend toward zero at the center of the deadband. | |
ArGraSag | uint16 | %ARtg/%dV | ArGraSag | The gradient used to increase capacitive dynamic current. A value of 0 indicates no additional reactive current support. |
ArGraSwell | uint16 | %ARtg/%dV | ArGraSwell | The gradient used to increase inductive dynamic current. A value of 0 indicates no additional reactive current support. |
ModEna | bitfield16 | ModEna | Activate dynamic reactive current model | |
FilTms | uint16 | Secs | FilTms | The time window used to calculate the moving average voltage. |
DbVMin | uint16 | % VRef | DbVMin | The lower delta voltage limit for which negative voltage deviations less than this value no dynamic vars are produced. |
DbVMax | uint16 | % VRef | DbVMax | The upper delta voltage limit for which positive voltage deviations less than this value no dynamic current produced. |
BlkZnV | uint16 | % VRef | BlkZnV | Block zone voltage which defines a lower voltage boundary below which no dynamic current is produced. |
HysBlkZnV | uint16 | % VRef | HysBlkZnV | Hysteresis voltage used with BlkZnV. |
BlkZnTmms | uint16 | mSecs | BlkZnTmms | Block zone time the time before which reactive current support remains active regardless of how low the voltage drops. |
HoldTmms | uint16 | mSecs | HoldTmms | Hold time during which reactive current support continues after the average voltage has entered the dead zone. |
Value names:
ArGraMod(enum16): 0EDGE, 1CENTERModEna(bitfield16): bit 0ENABLED
Model 129: LVRTD
Name in records: lvrt.
LVRT Must Disconnect
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must disconnect duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 must disconnect voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must disconnect duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 must disconnect voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must disconnect duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 must disconnect voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must disconnect duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 must disconnect voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must disconnect duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 must disconnect voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must disconnect duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 must disconnect voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must disconnect duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 must disconnect voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must disconnect duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 must disconnect voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must disconnect duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 must disconnect voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must disconnect duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 must disconnect voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must disconnect duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 must disconnect voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must disconnect duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 must disconnect voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must disconnect duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 must disconnect voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must disconnect duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 must disconnect voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must disconnect duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 must disconnect voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must disconnect duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 must disconnect voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must disconnect duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 must disconnect voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must disconnect duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 must disconnect voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must disconnect duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 must disconnect voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must disconnect duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 must disconnect voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 130: HVRTD
Name in records: hvrt.
HVRT Must Disconnect
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | HVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for HVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for HVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must disconnect duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 must disconnect voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must disconnect duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 must disconnect voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must disconnect duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 must disconnect voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must disconnect duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 must disconnect voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must disconnect duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 must disconnect voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must disconnect duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 must disconnect voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must disconnect duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 must disconnect voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must disconnect duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 must disconnect voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must disconnect duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 must disconnect voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must disconnect duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 must disconnect voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must disconnect duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 must disconnect voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must disconnect duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 must disconnect voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must disconnect duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 must disconnect voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must disconnect duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 must disconnect voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must disconnect duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 must disconnect voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must disconnect duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 must disconnect voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must disconnect duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 must disconnect voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must disconnect duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 must disconnect voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must disconnect duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 must disconnect voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must disconnect duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 must disconnect voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 131: Watt-PF
Name in records: watt_pf.
Watt-Power Factor
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | Is watt-PF mode active. | |
WinTms | uint16 | Secs | WinTms | Time window for watt-PF change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for watt-PF curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Max number of points in array. | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].W1 | int16 | % WMax | W1 | Point 1 Watts. |
curve[*].PF1 | int16 | cos() | PF1 | Point 1 PF in EEI notation. |
curve[*].W2 | int16 | % WMax | W2 | Point 2 Watts. |
curve[*].PF2 | int16 | cos() | PF2 | Point 2 PF in EEI notation. |
curve[*].W3 | int16 | % WMax | W3 | Point 3 Watts. |
curve[*].PF3 | int16 | cos() | PF3 | Point 3 PF in EEI notation. |
curve[*].W4 | int16 | % WMax | W4 | Point 4 Watts. |
curve[*].PF4 | int16 | cos() | PF4 | Point 4 PF in EEI notation. |
curve[*].W5 | int16 | % WMax | W5 | Point 5 Watts. |
curve[*].PF5 | int16 | cos() | PF5 | Point 5 PF in EEI notation. |
curve[*].W6 | int16 | % WMax | W6 | Point 6 Watts. |
curve[*].PF6 | int16 | cos() | PF6 | Point 6 PF in EEI notation. |
curve[*].W7 | int16 | % WMax | W7 | Point 7 Watts. |
curve[*].PF7 | int16 | cos() | PF7 | Point 7 PF in EEI notation. |
curve[*].W8 | int16 | % WMax | W8 | Point 8 Watts. |
curve[*].PF8 | int16 | cos() | PF8 | Point 8 PF in EEI notation. |
curve[*].W9 | int16 | % WMax | W9 | Point 9 Watts. |
curve[*].PF9 | int16 | cos() | PF9 | Point 9 PF in EEI notation. |
curve[*].W10 | int16 | % WMax | W10 | Point 10 Watts. |
curve[*].PF10 | int16 | cos() | PF10 | Point 10 PF in EEI notation. |
curve[*].W11 | int16 | % WMax | W11 | Point 11 Watts. |
curve[*].PF11 | int16 | cos() | PF11 | Point 11 PF in EEI notation. |
curve[*].W12 | int16 | % WMax | W12 | Point 12 Watts. |
curve[*].PF12 | int16 | cos() | PF12 | Point 12 PF in EEI notation. |
curve[*].W13 | int16 | % WMax | W13 | Point 13 Watts. |
curve[*].PF13 | int16 | cos() | PF13 | Point 13 PF in EEI notation. |
curve[*].W14 | int16 | % WMax | W14 | Point 14 Watts. |
curve[*].PF14 | int16 | cos() | PF14 | Point 14 PF in EEI notation. |
curve[*].W15 | int16 | % WMax | W15 | Point 15 Watts. |
curve[*].PF15 | int16 | cos() | PF15 | Point 15 PF in EEI notation. |
curve[*].W16 | int16 | % WMax | W16 | Point 16 Watts. |
curve[*].PF16 | int16 | cos() | PF16 | Point 16 PF in EEI notation. |
curve[*].W17 | int16 | % WMax | W17 | Point 17 Watts. |
curve[*].PF17 | int16 | cos() | PF17 | Point 17 PF in EEI notation. |
curve[*].W18 | int16 | % WMax | W18 | Point 18 Watts. |
curve[*].PF18 | int16 | cos() | PF18 | Point 18 PF in EEI notation. |
curve[*].W19 | int16 | % WMax | W19 | Point 19 Watts. |
curve[*].PF19 | int16 | cos() | PF19 | Point 19 PF in EEI notation. |
curve[*].W20 | int16 | % WMax | W20 | Point 20 Watts. |
curve[*].PF20 | int16 | cos() | PF20 | Point 20 PF in EEI notation. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].RmpPT1Tms | uint16 | Secs | RmpPT1Tms | The time of the PT1 in seconds (time to accomplish a change of 95%). |
curve[*].RmpDecTmm | uint16 | % PF/min | RmpDecTmm | The maximum rate at which the power factor may be reduced in response to changes in the power value. |
curve[*].RmpIncTmm | uint16 | % PF/min | RmpIncTmm | The maximum rate at which the power factor may be increased in response to changes in the power value. |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 132: Volt-Watt
Name in records: volt_watt.
Volt-Watt
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | Is Volt-Watt control active. | |
WinTms | uint16 | Secs | WinTms | Time window for volt-watt change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for volt-watt curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend min. 4). | |
NPt | uint16 | NPt | Number of points in array (maximum 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].DeptRef | enum16 | DeptRef | Defines the meaning of the Watts DeptRef. 1=% WMax 2=% WAvail | |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 Volts. |
curve[*].W1 | int16 | % VRef | W1 | Point 1 Watts. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 Volts. |
curve[*].W2 | int16 | % VRef | W2 | Point 2 Watts. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 Volts. |
curve[*].W3 | int16 | % VRef | W3 | Point 3 Watts. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 Volts. |
curve[*].W4 | int16 | % VRef | W4 | Point 4 Watts. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 Volts. |
curve[*].W5 | int16 | % VRef | W5 | Point 5 Watts. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 Volts. |
curve[*].W6 | int16 | % VRef | W6 | Point 6 Watts. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 Volts. |
curve[*].W7 | int16 | % VRef | W7 | Point 7 Watts. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 Volts. |
curve[*].W8 | int16 | % VRef | W8 | Point 8 Watts. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 Volts. |
curve[*].W9 | int16 | % VRef | W9 | Point 9 Watts. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 Volts. |
curve[*].W10 | int16 | % VRef | W10 | Point 10 Watts. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 Volts. |
curve[*].W11 | int16 | % VRef | W11 | Point 11 Watts. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 Volts. |
curve[*].W12 | int16 | % VRef | W12 | Point 12 Watts. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 Volts. |
curve[*].W13 | int16 | % VRef | W13 | Point 13 Watts. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 Volts. |
curve[*].W14 | int16 | % VRef | W14 | Point 14 Watts. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 Volts. |
curve[*].W15 | int16 | % VRef | W15 | Point 15 Watts. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 Volts. |
curve[*].W16 | int16 | % VRef | W16 | Point 16 Watts. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 Volts. |
curve[*].W17 | int16 | % VRef | W17 | Point 17 Watts. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 Volts. |
curve[*].W18 | int16 | % VRef | W18 | Point 18 Watts. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 Volts. |
curve[*].W19 | int16 | % VRef | W19 | Point 19 Watts. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 Volts. |
curve[*].W20 | int16 | % VRef | W20 | Point 20 Watts. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].RmpPt1Tms | uint16 | Secs | RmpPt1Tms | The time of the PT1 in seconds (time to accomplish a change of 95%). |
curve[*].RmpDecTmm | uint16 | % WMax/min | RmpDecTmm | The maximum rate at which the watt value may be reduced in response to changes in the voltage value. |
curve[*].RmpIncTmm | uint16 | % WMax/min | RmpIncTmm | The maximum rate at which the watt value may be increased in response to changes in the voltage value. |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].DeptRef(enum16): 1%WMax, 2%WAvalcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 133: Basic Scheduling
Name in records: schedule.
Basic Scheduling
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActSchd | bitfield32 | ActSchd | Bitfield of active schedules | |
ModEna | bitfield16 | ModEna | Is basic scheduling active. | |
NSchd | uint16 | NSchd | Number of schedules supported (recommend min. 4, max 32) | |
NPts | uint16 | NPts | Number of schedule entries supported (maximum of 10). | |
repeating[*].ActPts | uint16 | ActPts | Number of active entries in schedule. | |
repeating[*].StrTms | uint32 | Secs | StrTms | Schedule start in seconds since 2000 JAN 01 00:00:00 UTC. |
repeating[*].RepPer | uint16 | RepPer | The repetition count for time-based schedules (0=repeat forever) | |
repeating[*].IntvTyp | enum16 | SchdTyp | The repetition frequency for time-based schedules: no repeat=0 | |
repeating[*].XTyp | enum16 | XTyp | The meaning of the X-values in the array. | |
repeating[*].YTyp | enum16 | YTyp | The meaning of the Y-values in the array. | |
repeating[*].X1 | int32 | X1 | Entry 1 range. | |
repeating[*].Y1 | int32 | Y1 | Entry 1 target. | |
repeating[*].X2 | int32 | X2 | Entry 2 range. | |
repeating[*].Y2 | int32 | Y2 | Entry 2 target. | |
repeating[*].X3 | int32 | X3 | Entry 3 range. | |
repeating[*].Y3 | int32 | Y3 | Entry 3 target. | |
repeating[*].X4 | int32 | X4 | Entry 4 range. | |
repeating[*].Y4 | int32 | Y4 | Entry 4 target. | |
repeating[*].X5 | int32 | X5 | Entry 15range. | |
repeating[*].Y5 | int32 | Y5 | Entry 5 target. | |
repeating[*].X6 | int32 | X6 | Entry 6 range. | |
repeating[*].Y6 | int32 | Y6 | Entry 6 target. | |
repeating[*].X7 | int32 | X7 | Entry 7 range. | |
repeating[*].Y7 | int32 | Y7 | Entry 7 target. | |
repeating[*].X8 | int32 | X8 | Entry 8 range. | |
repeating[*].Y8 | int32 | Y8 | Entry 8 target. | |
repeating[*].X9 | int32 | X9 | Entry 9 range. | |
repeating[*].Y9 | int32 | Y9 | Entry 9 target. | |
repeating[*].X10 | int32 | X10 | Entry 10 range. | |
repeating[*].Y10 | int32 | Y10 | Entry 10 target. | |
repeating[*].Nam | string | Nam | Optional description for schedule. | |
repeating[*].WinTms | uint16 | Secs | WinTms | Time window for schedule entry change. |
repeating[*].RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current target to new target. |
repeating[*].ActIndx | uint16 | ActIndx | Index of active entry in the active schedule. |
Value names:
ActSchd(bitfield32): bit 0SCHED1, bit 1SCHED2, bit 2SCHED3, bit 3SCHED4, bit 4SCHED5, bit 5SCHED6, bit 6SCHED7, bit 7SCHED8, bit 8SCHED9, bit 9SCHED10, bit 10SCHED12, bit 11SCHED13, bit 12SCHED14, bit 13SCHED15, bit 14SCHED16, bit 15SCHED17, bit 16SCHED18, bit 17SCHED19, bit 18SCHED20, bit 19SCHED21, bit 21SCHED22, bit 22SCHED23, bit 23SCHED24, bit 24SCHED25, bit 25SCHED26, bit 26SCHED27, bit 27SCHED28, bit 28SCHED29, bit 29SCHED30, bit 30SCHED31, bit 31SCHED32ModEna(bitfield16): bit 0ENABLEDrepeating[*].IntvTyp(enum16): 0ONETIME, 1DAILY, 2WEEKLY, 3MONTHLY, 4WEEKDAY, 5HOLIDAY, 6WEEKEND, 7YEARLYrepeating[*].XTyp(enum16): 0UNSET, 1TIME, 2TEMP, 3PRICE, 99OTHERrepeating[*].YTyp(enum16): 0UNSET, 1WMax, 2RSRVD2, 3PF, 4RSRVD4, 5WATT_PRICE, 6VAR_PRICE, 7RSRVD7, 8VOLT_VAR_ARRAY, 9WChaGra, 10WDisChaGra, 11VArAval, 12Schedule, 99OTHER
Model 134: Freq-Watt Crv
Name in records: freq_watt.
Curve-Based Frequency-Watt
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | Is curve-based Frequency-Watt control active. | |
WinTms | uint16 | Secs | WinTms | Time window for freq-watt change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for freq-watt curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend min. 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 Hertz. |
curve[*].W1 | int16 | % WRef | W1 | Point 1 Watts. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 Hertz. |
curve[*].W2 | int16 | % WRef | W2 | Point 2 Watts. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 Hertz. |
curve[*].W3 | int16 | % WRef | W3 | Point 3 Watts. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 Hertz. |
curve[*].W4 | int16 | % WRef | W4 | Point 4 Watts. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 Hertz. |
curve[*].W5 | int16 | % WRef | W5 | Point 5 Watts. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 Hertz. |
curve[*].W6 | int16 | % WRef | W6 | Point 6 Watts. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 Hertz. |
curve[*].W7 | int16 | % WRef | W7 | Point 7 Watts. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 Hertz. |
curve[*].W8 | int16 | % WRef | W8 | Point 8 Watts. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 Hertz. |
curve[*].W9 | int16 | % WRef | W9 | Point 9 Watts. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 Hertz. |
curve[*].W10 | int16 | % WRef | W10 | Point 10 Watts. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 Hertz. |
curve[*].W11 | int16 | % WRef | W11 | Point 11 Watts. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 Hertz. |
curve[*].W12 | int16 | % WRef | W12 | Point 12 Watts. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 Hertz. |
curve[*].W13 | int16 | % WRef | W13 | Point 13 Watts. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 Hertz. |
curve[*].W14 | int16 | % WRef | W14 | Point 14 Watts. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 Hertz. |
curve[*].W15 | int16 | % WRef | W15 | Point 15 Watts. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 Hertz. |
curve[*].W16 | int16 | % WRef | W16 | Point 16 Watts. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 Hertz. |
curve[*].W17 | int16 | % WRef | W17 | Point 17 Watts. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 Hertz. |
curve[*].W18 | int16 | % WRef | W18 | Point 18 Watts. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 Hertz. |
curve[*].W19 | int16 | % WRef | W19 | Point 19 Watts. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 Hertz. |
curve[*].W20 | int16 | % WRef | W20 | Point 20 Watts. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. (Max 16 chars) | |
curve[*].RmpPT1Tms | uint16 | Secs | RmpPT1Tms | The time of the PT1 in seconds (time to accomplish a change of 95%). |
curve[*].RmpDecTmm | uint16 | % WMax/min | RmpDecTmm | The maximum rate at which the power value may be reduced in response to changes in the frequency value. |
curve[*].RmpIncTmm | uint16 | % WMax/min | RmpIncTmm | The maximum rate at which the power value may be increased in response to changes in the frequency value. |
curve[*].RmpRsUp | uint16 | % WMax/min | RmpRsUp | The maximum rate at which the power may be increased after releasing the frozen value of snap shot function. |
curve[*].SnptW | bitfield16 | SnptW | 1=enable snapshot/capture mode | |
curve[*].WRef | uint16 | W | WRef | Reference active power (default = WMax). |
curve[*].WRefStrHz | uint16 | Hz | WRefStrHz | Frequency deviation from nominal frequency at the time of the snapshot to start constraining power output. |
curve[*].WRefStopHz | uint16 | Hz | WRefStopHz | Frequency deviation from nominal frequency at which to release the power output. |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 135: LFRT
Name in records: lfrt.
Low Frequency Ride-through
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LHzRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must disconnect duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 must disconnect frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must disconnect duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 must disconnect frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must disconnect duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 must disconnect frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must disconnect duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 must disconnect frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must disconnect duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 must disconnect frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must disconnect duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 must disconnect frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must disconnect duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 must disconnect frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must disconnect duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 must disconnect frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must disconnect duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 must disconnect frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must disconnect duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 must disconnect frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must disconnect duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 must disconnect frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must disconnect duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 must disconnect frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must disconnect duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 must disconnect frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must disconnect duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 must disconnect frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must disconnect duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 must disconnect frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must disconnect duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 must disconnect frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must disconnect duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 must disconnect frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must disconnect duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 must disconnect frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must disconnect duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 must disconnect frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must disconnect duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 must disconnect frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 136: HFRT
Name in records: hfrt.
High Frequency Ride-through
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | HFRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for HFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for HFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must disconnect duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 must disconnect frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must disconnect duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 must disconnect frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must disconnect duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 must disconnect frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must disconnect duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 must disconnect frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must disconnect duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 must disconnect frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must disconnect duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 must disconnect frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must disconnect duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 must disconnect frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must disconnect duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 must disconnect frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must disconnect duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 must disconnect frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must disconnect duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 must disconnect frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must disconnect duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 must disconnect frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must disconnect duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 must disconnect frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must disconnect duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 must disconnect frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must disconnect duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 must disconnect frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must disconnect duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 must disconnect frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must disconnect duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 must disconnect frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must disconnect duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 must disconnect frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must disconnect duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 must disconnect frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must disconnect duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 must disconnect frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must disconnect duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 must disconnect frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 137: LVRTC
Name in records: lvrtc.
LVRT must remain connected
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must remain connected duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 must remain connected voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must remain connected duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 must remain connected voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must remain connected duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 must remain connected voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must remain connected duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 must remain connected voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must remain connected duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 must remain connected voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must remain connected duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 must remain connected voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must remain connected duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 must remain connected voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must remain connected duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 must remain connected voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must remain connected duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 must remain connected voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must remain connected duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 must remain connected voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must remain connected duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 must remain connected voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must remain connected duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 must remain connected voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must remain connected duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 must remain connected voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must remain connected duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 must remain connected voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must remain connected duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 must remain connected voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must remain connected duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 must remain connected voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must remain connected duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 must remain connected voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must remain connected duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 must remain connected voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must remain connected duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 must remain connected voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must remain connected duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 must remain connected voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 138: HVRTC
Name in records: hvrtc.
HVRT must remain connected
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | HVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for HVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for HVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must remain connected duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 must remain connected voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must remain connected duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 must remain connected voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must remain connected duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 must remain connected voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must remain connected duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 must remain connected voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must remain connected duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 must remain connected voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must remain connected duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 must remain connected voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must remain connected duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 must remain connected voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must remain connected duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 must remain connected voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must remain connected duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 must remain connected voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must remain connected duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 must remain connected voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must remain connected duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 must remain connected voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must remain connected duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 must remain connected voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must remain connected duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 must remain connected voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must remain connected duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 must remain connected voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must remain connected duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 must remain connected voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must remain connected duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 must remain connected voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must remain connected duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 must remain connected voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must remain connected duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 must remain connected voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must remain connected duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 must remain connected voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must remain connected duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 must remain connected voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 139: LVRTX
Name in records: lvrtx.
LVRT extended curve
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
CrvType | enum16 | |||
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDCrvType(enum16): 1CEASE_TO_ENERGIZEcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 140: HVRTX
Name in records: hvrtx.
HVRT extended curve
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LVRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LVRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LVRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
CrvType | enum16 | |||
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 duration. |
curve[*].V1 | uint16 | % VRef | V1 | Point 1 voltage. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 duration. |
curve[*].V2 | uint16 | % VRef | V2 | Point 2 voltage. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 duration. |
curve[*].V3 | uint16 | % VRef | V3 | Point 3 voltage. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 duration. |
curve[*].V4 | uint16 | % VRef | V4 | Point 4 voltage. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 duration. |
curve[*].V5 | uint16 | % VRef | V5 | Point 5 voltage. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 duration. |
curve[*].V6 | uint16 | % VRef | V6 | Point 6 voltage. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 duration. |
curve[*].V7 | uint16 | % VRef | V7 | Point 7 voltage. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 duration. |
curve[*].V8 | uint16 | % VRef | V8 | Point 8 voltage. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 duration. |
curve[*].V9 | uint16 | % VRef | V9 | Point 9 voltage. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 duration. |
curve[*].V10 | uint16 | % VRef | V10 | Point 10 voltage. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 duration. |
curve[*].V11 | uint16 | % VRef | V11 | Point 11 voltage. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 duration. |
curve[*].V12 | uint16 | % VRef | V12 | Point 12 voltage. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 duration. |
curve[*].V13 | uint16 | % VRef | V13 | Point 13 voltage. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 duration. |
curve[*].V14 | uint16 | % VRef | V14 | Point 14 voltage. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 duration. |
curve[*].V15 | uint16 | % VRef | V15 | Point 15 voltage. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 duration. |
curve[*].V16 | uint16 | % VRef | V16 | Point 16 voltage. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 duration. |
curve[*].V17 | uint16 | % VRef | V17 | Point 17 voltage. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 duration. |
curve[*].V18 | uint16 | % VRef | V18 | Point 18 voltage. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 duration. |
curve[*].V19 | uint16 | % VRef | V19 | Point 19 voltage. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 duration. |
curve[*].V20 | uint16 | % VRef | V20 | Point 20 voltage. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDCrvType(enum16): 1CEASE_TO_ENERGIZEcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 141: LFRTC
Name in records: lfrtc.
LFRT must remain connected
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LHzRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must remain connected duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 must remain connected frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must remain connected duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 must remain connected frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must remain connected duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 must remain connected frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must remain connected duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 must remain connected frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must remain connected duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 must remain connected frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must remain connected duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 must remain connected frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must remain connected duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 must remain connected frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must remain connected duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 must remain connected frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must remain connected duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 must remain connected frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must remain connected duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 must remain connected frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must remain connected duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 must remain connected frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must remain connected duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 must remain connected frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must remain connected duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 must remain connected frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must remain connected duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 must remain connected frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must remain connected duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 must remain connected frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must remain connected duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 must remain connected frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must remain connected duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 must remain connected frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must remain connected duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 must remain connected frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must remain connected duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 must remain connected frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must remain connected duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 must remain connected frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 142: HFRTC
Name in records: hfrtc.
HFRT must remain connected
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LHzRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 must remain connected duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 must remain connected frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 must remain connected duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 must remain connected frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 must remain connected duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 must remain connected frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 must remain connected duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 must remain connected frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 must remain connected duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 must remain connected frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 must remain connected duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 must remain connected frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 must remain connected duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 must remain connected frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 must remain connected duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 must remain connected frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 must remain connected duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 must remain connected frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 must remain connected duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 must remain connected frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 must remain connected duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 must remain connected frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 must remain connected duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 must remain connected frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 must remain connected duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 must remain connected frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 must remain connected duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 must remain connected frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 must remain connected duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 must remain connected frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 must remain connected duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 must remain connected frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 must remain connected duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 must remain connected frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 must remain connected duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 must remain connected frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 must remain connected duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 must remain connected frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 must remain connected duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 must remain connected frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 143: LFRTX
Name in records: lfrtx.
LFRT extended curve
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LHzRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
CrvType | enum16 | |||
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDCrvType(enum16): 1CEASE_TO_ENERGIZEcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 144: HFRTX
Name in records: hfrtx.
HFRT extended curve
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ActCrv | uint16 | ActCrv | Index of active curve. 0=no active curve. | |
ModEna | bitfield16 | ModEna | LHzRT control mode. Enable active curve. | |
WinTms | uint16 | Secs | WinTms | Time window for LFRT change. |
RvrtTms | uint16 | Secs | RvrtTms | Timeout period for LFRT curve selection. |
RmpTms | uint16 | Secs | RmpTms | Ramp time for moving from current mode to new mode. |
NCrv | uint16 | NCrv | Number of curves supported (recommend 4). | |
NPt | uint16 | NPt | Number of curve points supported (maximum of 20). | |
CrvType | enum16 | |||
curve[*].ActPt | uint16 | ActPt | Number of active points in array. | |
curve[*].Tms1 | uint16 | Secs | Tms1 | Point 1 duration. |
curve[*].Hz1 | uint16 | Hz | Hz1 | Point 1 frequency. |
curve[*].Tms2 | uint16 | Secs | Tms2 | Point 2 duration. |
curve[*].Hz2 | uint16 | Hz | Hz2 | Point 2 frequency. |
curve[*].Tms3 | uint16 | Secs | Tms3 | Point 3 duration. |
curve[*].Hz3 | uint16 | Hz | Hz3 | Point 3 frequency. |
curve[*].Tms4 | uint16 | Secs | Tms4 | Point 4 duration. |
curve[*].Hz4 | uint16 | Hz | Hz4 | Point 4 frequency. |
curve[*].Tms5 | uint16 | Secs | Tms5 | Point 5 duration. |
curve[*].Hz5 | uint16 | Hz | Hz5 | Point 5 frequency. |
curve[*].Tms6 | uint16 | Secs | Tms6 | Point 6 duration. |
curve[*].Hz6 | uint16 | Hz | Hz6 | Point 6 frequency. |
curve[*].Tms7 | uint16 | Secs | Tms7 | Point 7 duration. |
curve[*].Hz7 | uint16 | Hz | Hz7 | Point 7 frequency. |
curve[*].Tms8 | uint16 | Secs | Tms8 | Point 8 duration. |
curve[*].Hz8 | uint16 | Hz | Hz8 | Point 8 frequency. |
curve[*].Tms9 | uint16 | Secs | Tms9 | Point 9 duration. |
curve[*].Hz9 | uint16 | Hz | Hz9 | Point 9 frequency. |
curve[*].Tms10 | uint16 | Secs | Tms10 | Point 10 duration. |
curve[*].Hz10 | uint16 | Hz | Hz10 | Point 10 frequency. |
curve[*].Tms11 | uint16 | Secs | Tms11 | Point 11 duration. |
curve[*].Hz11 | uint16 | Hz | Hz11 | Point 11 frequency. |
curve[*].Tms12 | uint16 | Secs | Tms12 | Point 12 duration. |
curve[*].Hz12 | uint16 | Hz | Hz12 | Point 12 frequency. |
curve[*].Tms13 | uint16 | Secs | Tms13 | Point 13 duration. |
curve[*].Hz13 | uint16 | Hz | Hz13 | Point 13 frequency. |
curve[*].Tms14 | uint16 | Secs | Tms14 | Point 14 duration. |
curve[*].Hz14 | uint16 | Hz | Hz14 | Point 14 frequency. |
curve[*].Tms15 | uint16 | Secs | Tms15 | Point 15 duration. |
curve[*].Hz15 | uint16 | Hz | Hz15 | Point 15 frequency. |
curve[*].Tms16 | uint16 | Secs | Tms16 | Point 16 duration. |
curve[*].Hz16 | uint16 | Hz | Hz16 | Point 16 frequency. |
curve[*].Tms17 | uint16 | Secs | Tms17 | Point 17 duration. |
curve[*].Hz17 | uint16 | Hz | Hz17 | Point 17 frequency. |
curve[*].Tms18 | uint16 | Secs | Tms18 | Point 18 duration. |
curve[*].Hz18 | uint16 | Hz | Hz18 | Point 18 frequency. |
curve[*].Tms19 | uint16 | Secs | Tms19 | Point 19 duration. |
curve[*].Hz19 | uint16 | Hz | Hz19 | Point 19 frequency. |
curve[*].Tms20 | uint16 | Secs | Tms20 | Point 20 duration. |
curve[*].Hz20 | uint16 | Hz | Hz20 | Point 20 frequency. |
curve[*].CrvNam | string | CrvNam | Optional description for curve. | |
curve[*].ReadOnly | enum16 | ReadOnly | Curve is read-only or can be modified. |
Value names:
ModEna(bitfield16): bit 0ENABLEDCrvType(enum16): 1CEASE_TO_ENERGIZEcurve[*].ReadOnly(enum16): 0READWRITE, 1READONLY
Model 145: Extended Settings
Name in records: ext_settings.
Inverter controls extended settings
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
NomRmpUpRte | uint16 | Pct | Ramp Up Rate | Ramp up rate as a percentage of max current. |
NomRmpDnRte | uint16 | Pct | NomRmpDnRte | Ramp down rate as a percentage of max current. |
EmgRmpUpRte | uint16 | Pct | Emergency Ramp Up Rate | Emergency ramp up rate as a percentage of max current. |
EmgRmpDnRte | uint16 | Pct | Emergency Ramp Down Rate | Emergency ramp down rate as a percentage of max current. |
ConnRmpUpRte | uint16 | Pct | Connect Ramp Up Rate | Connect ramp up rate as a percentage of max current. |
ConnRmpDnRte | uint16 | Pct | Connect Ramp Down Rate | Connect ramp down rate as a percentage of max current. |
AGra | uint16 | Pct | Default Ramp Rate | Ramp rate specified in percent of max current. |
Model 160: Multiple MPPT Inverter Extension Model
Name in records: mppt.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Evt | bitfield32 | Global Events | ||
N | count | Number of Modules | ||
TmsPer | uint16 | Timestamp Period | ||
module[*].ID | uint16 | Input ID | ||
module[*].IDStr | string | Input ID String | ||
module[*].DCA | uint16 | A | DC Current | |
module[*].DCV | uint16 | V | DC Voltage | |
module[*].DCW | uint16 | W | DC Power | |
module[*].DCWH | acc32 | Wh | Lifetime Energy | |
module[*].Tms | uint32 | Secs | Timestamp | |
module[*].Tmp | int16 | C | Temperature | |
module[*].DCSt | enum16 | Operating State | ||
module[*].DCEvt | bitfield32 | Module Events |
Value names:
Evt(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 2RESERVED_2, bit 3DC_DISCONNECT, bit 4RESERVED_4, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8RESERVED_8, bit 9RESERVED_9, bit 10RESERVED_10, bit 11RESERVED_11, bit 12BLOWN_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 16RESERVED_16, bit 17RESERVED_17, bit 18RESERVED_18, bit 19RESERVED_19, bit 20TEST_FAILED, bit 21INPUT_UNDER_VOLTAGE, bit 22INPUT_OVER_CURRENTmodule[*].DCSt(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBY, 9TEST, 10RESERVED_10module[*].DCEvt(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 2RESERVED_2, bit 3DC_DISCONNECT, bit 4RESERVED_4, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8RESERVED_8, bit 9RESERVED_9, bit 10RESERVED_10, bit 11RESERVED_11, bit 12BLOWN_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 16RESERVED_16, bit 17RESERVED_17, bit 18RESERVED_18, bit 19RESERVED_19, bit 20TEST_FAILED, bit 21INPUT_UNDER_VOLTAGE, bit 22INPUT_OVER_CURRENT
Meters
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 201 | ac_meter_an_or_ab | 62 |
| 202 | ac_meter_abn | 62 |
| 203 | ac_meter_abcn | 62 |
| 204 | ac_meter_abc | 62 |
| 211 | ac_meter_an_or_ab_float | 62 |
| 212 | ac_meter_abn_float | 62 |
| 213 | ac_meter_abcn_float | 62 |
| 214 | ac_meter_abc_float | 62 |
| 220 | ac_meter_secure | 22 |
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
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | int16 | A | Amps | Total AC Current |
AphA | int16 | A | Amps PhaseA | Phase A Current |
AphB | int16 | A | Amps PhaseB | Phase B Current |
AphC | int16 | A | Amps PhaseC | Phase C Current |
PhV | int16 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | int16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | int16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | int16 | V | Phase Voltage CN | Phase Voltage CN |
PPV | int16 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PPVphAB | int16 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | int16 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | int16 | V | Phase Voltage CA | Phase Voltage CA |
Hz | int16 | Hz | Hz | Frequency |
W | int16 | W | Watts | Total Real Power |
WphA | int16 | W | Watts phase A | |
WphB | int16 | W | Watts phase B | |
WphC | int16 | W | Watts phase C | |
VA | int16 | VA | VA | AC Apparent Power |
VAphA | int16 | VA | VA phase A | |
VAphB | int16 | VA | VA phase B | |
VAphC | int16 | VA | VA phase C | |
VAR | int16 | var | VAR | Reactive Power |
VARphA | int16 | var | VAR phase A | |
VARphB | int16 | var | VAR phase B | |
VARphC | int16 | var | VAR phase C | |
PF | int16 | Pct | PF | Power Factor |
PFphA | int16 | Pct | PF phase A | |
PFphB | int16 | Pct | PF phase B | |
PFphC | int16 | Pct | PF phase C | |
TotWhExp | acc32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | acc32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | acc32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | acc32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | acc32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | acc32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | acc32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | acc32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | acc32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | acc32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | acc32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | acc32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | acc32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | acc32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | acc32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | acc32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | acc32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1PhA | acc32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1PhB | acc32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1PhC | acc32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | acc32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2PhA | acc32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2PhB | acc32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2PhC | acc32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | acc32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3PhA | acc32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3PhB | acc32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3PhC | acc32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | acc32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4PhA | acc32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4PhB | acc32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4PhC | acc32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2Power_Failure, bit 3Under_Voltage, bit 4Low_PF, bit 5Over_Current, bit 6Over_Voltage, bit 7Missing_Sensor, bit 16OEM01, bit 17OEM02, bit 18OEM03, bit 19OEM04, bit 20OEM05, bit 21OEM06, bit 22OEM07, bit 23OEM08, bit 24OEM09, bit 25OEM10, bit 26OEM11, bit 27OEM12, bit 28OEM13, bit 29OEM14, bit 30OEM15
Model 202: split single phase (ABN) meter
Name in records: ac_meter_abn.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | int16 | A | Amps | Total AC Current |
AphA | int16 | A | Amps PhaseA | Phase A Current |
AphB | int16 | A | Amps PhaseB | Phase B Current |
AphC | int16 | A | Amps PhaseC | Phase C Current |
PhV | int16 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | int16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | int16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | int16 | V | Phase Voltage CN | Phase Voltage CN |
PPV | int16 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PhVphAB | int16 | V | Phase Voltage AB | Phase Voltage AB |
PhVphBC | int16 | V | Phase Voltage BC | Phase Voltage BC |
PhVphCA | int16 | V | Phase Voltage CA | Phase Voltage CA |
Hz | int16 | Hz | Hz | Frequency |
W | int16 | W | Watts | Total Real Power |
WphA | int16 | W | Watts phase A | |
WphB | int16 | W | Watts phase B | |
WphC | int16 | W | Watts phase C | |
VA | int16 | VA | VA | AC Apparent Power |
VAphA | int16 | VA | VA phase A | |
VAphB | int16 | VA | VA phase B | |
VAphC | int16 | VA | VA phase C | |
VAR | int16 | var | VAR | Reactive Power |
VARphA | int16 | var | VAR phase A | |
VARphB | int16 | var | VAR phase B | |
VARphC | int16 | var | VAR phase C | |
PF | int16 | Pct | PF | Power Factor |
PFphA | int16 | Pct | PF phase A | |
PFphB | int16 | Pct | PF phase B | |
PFphC | int16 | Pct | PF phase C | |
TotWhExp | acc32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | acc32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | acc32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | acc32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | acc32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | acc32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | acc32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | acc32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | acc32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | acc32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | acc32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | acc32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | acc32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | acc32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | acc32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | acc32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | acc32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1PhA | acc32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1PhB | acc32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1PhC | acc32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | acc32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2PhA | acc32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2PhB | acc32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2PhC | acc32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | acc32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3PhA | acc32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3PhB | acc32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3PhC | acc32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | acc32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4PhA | acc32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4PhB | acc32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4PhC | acc32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 203: wye-connect three phase (abcn) meter
Name in records: ac_meter_abcn.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | int16 | A | Amps | Total AC Current |
AphA | int16 | A | Amps PhaseA | Phase A Current |
AphB | int16 | A | Amps PhaseB | Phase B Current |
AphC | int16 | A | Amps PhaseC | Phase C Current |
PhV | int16 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | int16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | int16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | int16 | V | Phase Voltage CN | Phase Voltage CN |
PPV | int16 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PhVphAB | int16 | V | Phase Voltage AB | Phase Voltage AB |
PhVphBC | int16 | V | Phase Voltage BC | Phase Voltage BC |
PhVphCA | int16 | V | Phase Voltage CA | Phase Voltage CA |
Hz | int16 | Hz | Hz | Frequency |
W | int16 | W | Watts | Total Real Power |
WphA | int16 | W | Watts phase A | |
WphB | int16 | W | Watts phase B | |
WphC | int16 | W | Watts phase C | |
VA | int16 | VA | VA | AC Apparent Power |
VAphA | int16 | VA | VA phase A | |
VAphB | int16 | VA | VA phase B | |
VAphC | int16 | VA | VA phase C | |
VAR | int16 | var | VAR | Reactive Power |
VARphA | int16 | var | VAR phase A | |
VARphB | int16 | var | VAR phase B | |
VARphC | int16 | var | VAR phase C | |
PF | int16 | Pct | PF | Power Factor |
PFphA | int16 | Pct | PF phase A | |
PFphB | int16 | Pct | PF phase B | |
PFphC | int16 | Pct | PF phase C | |
TotWhExp | acc32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | acc32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | acc32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | acc32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | acc32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | acc32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | acc32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | acc32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | acc32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | acc32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | acc32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | acc32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | acc32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | acc32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | acc32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | acc32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | acc32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1PhA | acc32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1PhB | acc32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1PhC | acc32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | acc32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2PhA | acc32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2PhB | acc32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2PhC | acc32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | acc32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3PhA | acc32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3PhB | acc32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3PhC | acc32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | acc32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4PhA | acc32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4PhB | acc32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4PhC | acc32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 204: delta-connect three phase (abc) meter
Name in records: ac_meter_abc.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | int16 | A | Amps | Total AC Current |
AphA | int16 | A | Amps PhaseA | Phase A Current |
AphB | int16 | A | Amps PhaseB | Phase B Current |
AphC | int16 | A | Amps PhaseC | Phase C Current |
PhV | int16 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | int16 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | int16 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | int16 | V | Phase Voltage CN | Phase Voltage CN |
PPV | int16 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PhVphAB | int16 | V | Phase Voltage AB | Phase Voltage AB |
PhVphBC | int16 | V | Phase Voltage BC | Phase Voltage BC |
PhVphCA | int16 | V | Phase Voltage CA | Phase Voltage CA |
Hz | int16 | Hz | Hz | Frequency |
W | int16 | W | Watts | Total Real Power |
WphA | int16 | W | Watts phase A | |
WphB | int16 | W | Watts phase B | |
WphC | int16 | W | Watts phase C | |
VA | int16 | VA | VA | AC Apparent Power |
VAphA | int16 | VA | VA phase A | |
VAphB | int16 | VA | VA phase B | |
VAphC | int16 | VA | VA phase C | |
VAR | int16 | var | VAR | Reactive Power |
VARphA | int16 | var | VAR phase A | |
VARphB | int16 | var | VAR phase B | |
VARphC | int16 | var | VAR phase C | |
PF | int16 | Pct | PF | Power Factor |
PFphA | int16 | Pct | PF phase A | |
PFphB | int16 | Pct | PF phase B | |
PFphC | int16 | Pct | PF phase C | |
TotWhExp | acc32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | acc32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | acc32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | acc32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | acc32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | acc32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | acc32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | acc32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | acc32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | acc32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | acc32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | acc32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | acc32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | acc32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | acc32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | acc32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | acc32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1PhA | acc32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1PhB | acc32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1PhC | acc32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | acc32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2PhA | acc32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2PhB | acc32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2PhC | acc32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | acc32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3PhA | acc32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3PhB | acc32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3PhC | acc32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | acc32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4PhA | acc32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4PhB | acc32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4PhC | acc32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 211: single phase (AN or AB) meter
Name in records: ac_meter_an_or_ab_float.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | Total AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PhV | float32 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
PPV | float32 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
Hz | float32 | Hz | Hz | Frequency |
W | float32 | W | Watts | Total Real Power |
WphA | float32 | W | Watts phase A | |
WphB | float32 | W | Watts phase B | |
WphC | float32 | W | Watts phase C | |
VA | float32 | VA | VA | AC Apparent Power |
VAphA | float32 | VA | VA phase A | |
VAphB | float32 | VA | VA phase B | |
VAphC | float32 | VA | VA phase C | |
VAR | float32 | var | VAR | Reactive Power |
VARphA | float32 | var | VAR phase A | |
VARphB | float32 | var | VAR phase B | |
VARphC | float32 | var | VAR phase C | |
PF | float32 | PF | PF | Power Factor |
PFphA | float32 | PF | PF phase A | |
PFphB | float32 | PF | PF phase B | |
PFphC | float32 | PF | PF phase C | |
TotWhExp | float32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | float32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | float32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | float32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | float32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | float32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | float32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | float32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | float32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | float32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | float32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | float32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | float32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | float32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | float32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | float32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | float32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1phA | float32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1phB | float32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1phC | float32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | float32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2phA | float32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2phB | float32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2phC | float32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | float32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3phA | float32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3phB | float32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3phC | float32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | float32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4phA | float32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4phB | float32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4phC | float32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 212: split single phase (ABN) meter
Name in records: ac_meter_abn_float.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | Total AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PhV | float32 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
PPV | float32 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
Hz | float32 | Hz | Hz | Frequency |
W | float32 | W | Watts | Total Real Power |
WphA | float32 | W | Watts phase A | |
WphB | float32 | W | Watts phase B | |
WphC | float32 | W | Watts phase C | |
VA | float32 | VA | VA | AC Apparent Power |
VAphA | float32 | VA | VA phase A | |
VAphB | float32 | VA | VA phase B | |
VAphC | float32 | VA | VA phase C | |
VAR | float32 | var | VAR | Reactive Power |
VARphA | float32 | var | VAR phase A | |
VARphB | float32 | var | VAR phase B | |
VARphC | float32 | var | VAR phase C | |
PF | float32 | PF | PF | Power Factor |
PFphA | float32 | PF | PF phase A | |
PFphB | float32 | PF | PF phase B | |
PFphC | float32 | PF | PF phase C | |
TotWhExp | float32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | float32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | float32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | float32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | float32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | float32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | float32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | float32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | float32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | float32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | float32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | float32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | float32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | float32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | float32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | float32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | float32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1phA | float32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1phB | float32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1phC | float32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | float32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2phA | float32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2phB | float32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2phC | float32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | float32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3phA | float32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3phB | float32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3phC | float32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | float32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4phA | float32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4phB | float32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4phC | float32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 213: wye-connect three phase (abcn) meter
Name in records: ac_meter_abcn_float.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | Total AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PhV | float32 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
PPV | float32 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
Hz | float32 | Hz | Hz | Frequency |
W | float32 | W | Watts | Total Real Power |
WphA | float32 | W | Watts phase A | |
WphB | float32 | W | Watts phase B | |
WphC | float32 | W | Watts phase C | |
VA | float32 | VA | VA | AC Apparent Power |
VAphA | float32 | VA | VA phase A | |
VAphB | float32 | VA | VA phase B | |
VAphC | float32 | VA | VA phase C | |
VAR | float32 | var | VAR | Reactive Power |
VARphA | float32 | var | VAR phase A | |
VARphB | float32 | var | VAR phase B | |
VARphC | float32 | var | VAR phase C | |
PF | float32 | PF | PF | Power Factor |
PFphA | float32 | PF | PF phase A | |
PFphB | float32 | PF | PF phase B | |
PFphC | float32 | PF | PF phase C | |
TotWhExp | float32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | float32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | float32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | float32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | float32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | float32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | float32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | float32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | float32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | float32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | float32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | float32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | float32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | float32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | float32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | float32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | float32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1phA | float32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1phB | float32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1phC | float32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | float32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2phA | float32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2phB | float32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2phC | float32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | float32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3phA | float32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3phB | float32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3phC | float32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | float32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4phA | float32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4phB | float32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4phC | float32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 214: delta-connect three phase (abc) meter
Name in records: ac_meter_abc_float.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | float32 | A | Amps | Total AC Current |
AphA | float32 | A | Amps PhaseA | Phase A Current |
AphB | float32 | A | Amps PhaseB | Phase B Current |
AphC | float32 | A | Amps PhaseC | Phase C Current |
PhV | float32 | V | Voltage LN | Line to Neutral AC Voltage (average of active phases) |
PhVphA | float32 | V | Phase Voltage AN | Phase Voltage AN |
PhVphB | float32 | V | Phase Voltage BN | Phase Voltage BN |
PhVphC | float32 | V | Phase Voltage CN | Phase Voltage CN |
PPV | float32 | V | Voltage LL | Line to Line AC Voltage (average of active phases) |
PPVphAB | float32 | V | Phase Voltage AB | Phase Voltage AB |
PPVphBC | float32 | V | Phase Voltage BC | Phase Voltage BC |
PPVphCA | float32 | V | Phase Voltage CA | Phase Voltage CA |
Hz | float32 | Hz | Hz | Frequency |
W | float32 | W | Watts | Total Real Power |
WphA | float32 | W | Watts phase A | |
WphB | float32 | W | Watts phase B | |
WphC | float32 | W | Watts phase C | |
VA | float32 | VA | VA | AC Apparent Power |
VAphA | float32 | VA | VA phase A | |
VAphB | float32 | VA | VA phase B | |
VAphC | float32 | VA | VA phase C | |
VAR | float32 | var | VAR | Reactive Power |
VARphA | float32 | var | VAR phase A | |
VARphB | float32 | var | VAR phase B | |
VARphC | float32 | var | VAR phase C | |
PF | float32 | PF | PF | Power Factor |
PFphA | float32 | PF | PF phase A | |
PFphB | float32 | PF | PF phase B | |
PFphC | float32 | PF | PF phase C | |
TotWhExp | float32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhExpPhA | float32 | Wh | Total Watt-hours Exported phase A | |
TotWhExpPhB | float32 | Wh | Total Watt-hours Exported phase B | |
TotWhExpPhC | float32 | Wh | Total Watt-hours Exported phase C | |
TotWhImp | float32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotWhImpPhA | float32 | Wh | Total Watt-hours Imported phase A | |
TotWhImpPhB | float32 | Wh | Total Watt-hours Imported phase B | |
TotWhImpPhC | float32 | Wh | Total Watt-hours Imported phase C | |
TotVAhExp | float32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhExpPhA | float32 | VAh | Total VA-hours Exported phase A | |
TotVAhExpPhB | float32 | VAh | Total VA-hours Exported phase B | |
TotVAhExpPhC | float32 | VAh | Total VA-hours Exported phase C | |
TotVAhImp | float32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVAhImpPhA | float32 | VAh | Total VA-hours Imported phase A | |
TotVAhImpPhB | float32 | VAh | Total VA-hours Imported phase B | |
TotVAhImpPhC | float32 | VAh | Total VA-hours Imported phase C | |
TotVArhImpQ1 | float32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ1phA | float32 | varh | Total VAr-hours Imported Q1 phase A | |
TotVArhImpQ1phB | float32 | varh | Total VAr-hours Imported Q1 phase B | |
TotVArhImpQ1phC | float32 | varh | Total VAr-hours Imported Q1 phase C | |
TotVArhImpQ2 | float32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhImpQ2phA | float32 | varh | Total VAr-hours Imported Q2 phase A | |
TotVArhImpQ2phB | float32 | varh | Total VAr-hours Imported Q2 phase B | |
TotVArhImpQ2phC | float32 | varh | Total VAr-hours Imported Q2 phase C | |
TotVArhExpQ3 | float32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ3phA | float32 | varh | Total VAr-hours Exported Q3 phase A | |
TotVArhExpQ3phB | float32 | varh | Total VAr-hours Exported Q3 phase B | |
TotVArhExpQ3phC | float32 | varh | Total VAr-hours Exported Q3 phase C | |
TotVArhExpQ4 | float32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
TotVArhExpQ4phA | float32 | varh | Total VAr-hours Exported Q4 Imported phase A | |
TotVArhExpQ4phB | float32 | varh | Total VAr-hours Exported Q4 Imported phase B | |
TotVArhExpQ4phC | float32 | varh | Total VAr-hours Exported Q4 Imported phase C | |
Evt | bitfield32 | Events | Meter Event Flags |
Value names:
Evt(bitfield32): bit 2M_EVENT_Power_Failure, bit 3M_EVENT_Under_Voltage, bit 4M_EVENT_Low_PF, bit 5M_EVENT_Over_Current, bit 6M_EVENT_Over_Voltage, bit 7M_EVENT_Missing_Sensor, bit 8M_EVENT_Reserved1, bit 9M_EVENT_Reserved2, bit 10M_EVENT_Reserved3, bit 11M_EVENT_Reserved4, bit 12M_EVENT_Reserved5, bit 13M_EVENT_Reserved6, bit 14M_EVENT_Reserved7, bit 15M_EVENT_Reserved8, bit 16M_EVENT_OEM01, bit 17M_EVENT_OEM02, bit 18M_EVENT_OEM03, bit 19M_EVENT_OEM04, bit 20M_EVENT_OEM05, bit 21M_EVENT_OEM06, bit 22M_EVENT_OEM07, bit 23M_EVENT_OEM08, bit 24M_EVENT_OEM09, bit 25M_EVENT_OEM10, bit 26M_EVENT_OEM11, bit 27M_EVENT_OEM12, bit 28M_EVENT_OEM13, bit 29M_EVENT_OEM14, bit 30M_EVENT_OEM15
Model 220: Secure AC Meter Selected Readings
Name in records: ac_meter_secure.
Include this model for secure metering
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
A | int16 | A | Amps | Total AC Current |
PhV | int16 | V | Voltage | Average phase or line voltage |
Hz | int16 | Hz | Hz | Frequency |
W | int16 | W | Watts | Total Real Power |
VA | int16 | VA | VA | AC Apparent Power |
VAR | int16 | var | VAR | Reactive Power |
PF | int16 | Pct | PF | Power Factor |
TotWhExp | acc32 | Wh | Total Watt-hours Exported | Total Real Energy Exported |
TotWhImp | acc32 | Wh | Total Watt-hours Imported | Total Real Energy Imported |
TotVAhExp | acc32 | VAh | Total VA-hours Exported | Total Apparent Energy Exported |
TotVAhImp | acc32 | VAh | Total VA-hours Imported | Total Apparent Energy Imported |
TotVArhImpQ1 | acc32 | varh | Total VAR-hours Imported Q1 | Total Reactive Energy Imported Quadrant 1 |
TotVArhImpQ2 | acc32 | varh | Total VAr-hours Imported Q2 | Total Reactive Power Imported Quadrant 2 |
TotVArhExpQ3 | acc32 | varh | Total VAr-hours Exported Q3 | Total Reactive Power Exported Quadrant 3 |
TotVArhExpQ4 | acc32 | varh | Total VAr-hours Exported Q4 | Total Reactive Power Exported Quadrant 4 |
Evt | bitfield32 | Events | Meter Event Flags | |
Ts | uint32 | Timestamp | Timestamp value is the number of seconds since January 1, 2000 | |
Ms | uint16 | Milliseconds | Millisecond counter 0-999 | |
Seq | uint16 | Sequence | Sequence number of request | |
Alg | enum16 | Algorithm | Algorithm used to compute the digital signature | |
N | uint16 | N | Number of registers comprising the digital signature. | |
repeating[*].DS | uint16 |
Value names:
Evt(bitfield32): bit 2Power_Failure, bit 3Under_Voltage, bit 4Low_PF, bit 5Over_Current, bit 6Over_Voltage, bit 7Missing_Sensor, bit 16OEM01, bit 17OEM02, bit 18OEM03, bit 19OEM04, bit 20OEM05, bit 21OEM06, bit 22OEM07, bit 23OEM08, bit 24OEM09, bit 25OEM10, bit 26OEM11, bit 27OEM12, bit 28OEM13, bit 29OEM14, bit 30OEM15Alg(enum16): 0NONE, 1AES-GMAC-64, 2ECC-256
Environmental sensors
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 302 | irradiance | 5 |
| 303 | bom_temp | 1 |
| 304 | inclinometer | 3 |
| 305 | location | 6 |
| 306 | ref_point | 4 |
| 307 | base_met | 11 |
| 308 | mini_met | 4 |
Model 302: Irradiance Model
Name in records: irradiance.
Include to support various irradiance measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
repeating[*].GHI | uint16 | W/m2 | GHI | Global Horizontal Irradiance |
repeating[*].POAI | uint16 | W/m2 | POAI | Plane-of-Array Irradiance |
repeating[*].DFI | uint16 | W/m2 | DFI | Diffuse Irradiance |
repeating[*].DNI | uint16 | W/m2 | DNI | Direct Normal Irradiance |
repeating[*].OTI | uint16 | W/m2 | OTI | Other Irradiance |
Model 303: Back of Module Temperature Model
Name in records: bom_temp.
Include to support variable number of back of module temperature measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
temp[*].TmpBOM | int16 | C | Temp | Back of module temperature measurement |
Model 304: Inclinometer Model
Name in records: inclinometer.
Include to support orientation measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
incl[*].Inclx | int32 | Degrees | X | X-Axis inclination |
incl[*].Incly | int32 | Degrees | Y | Y-Axis inclination |
incl[*].Inclz | int32 | Degrees | Z | Z-Axis inclination |
Model 305: GPS
Name in records: location.
Include to support location measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Tm | string | hhmmss.sssZ | Tm | UTC 24 hour time stamp to millisecond hhmmss.sssZ format |
Date | string | YYYYMMDD | Date | UTC Date string YYYYMMDD format |
Loc | string | text | Location | Location string (40 chars max) |
Lat | int32 | Degrees | Lat | Latitude with seven degrees of precision |
Long | int32 | Degrees | Long | Longitude with seven degrees of precision |
Alt | int32 | meters | Altitude | Altitude measurement in meters |
Model 306: Reference Point Model
Name in records: ref_point.
Include to support a standard reference point
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
GHI | uint16 | W/m2 | GHI | Global Horizontal Irradiance |
A | uint16 | W/m2 | Amps | Current measurement at reference point |
V | uint16 | W/m2 | Voltage | Voltage measurement at reference point |
Tmp | uint16 | W/m2 | Temperature | Temperature measurement at reference point |
Model 307: Base Met
Name in records: base_met.
Base Meteorological Model
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
TmpAmb | int16 | C | Ambient Temperature | |
RH | int16 | Pct | Relative Humidity | |
Pres | int16 | HPa | Barometric Pressure | |
WndSpd | int16 | mps | Wind Speed | |
WndDir | int16 | deg | Wind Direction | |
Rain | int16 | mm | Rainfall | |
Snw | int16 | mm | Snow Depth | |
PPT | int16 | Precipitation Type | Precipitation Type (WMO 4680 SYNOP code reference) | |
ElecFld | int16 | Vm | Electric Field | |
SurWet | int16 | kO | Surface Wetness | |
SoilWet | int16 | Pct | Soil Wetness |
Model 308: Mini Met Model
Name in records: mini_met.
Include to support a few basic measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
GHI | uint16 | W/m2 | GHI | Global Horizontal Irradiance |
TmpBOM | int16 | C | Temp | Back of module temperature measurement |
TmpAmb | int16 | C | Ambient Temperature | |
WndSpd | uint16 | m/s | Wind Speed |
String combiners
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 401 | string_combiner_current | 13 |
| 402 | string_combiner_advanced | 21 |
| 403 | string_combiner_current_input | 13 |
| 404 | string_combiner_advanced_inputs | 21 |
Model 401: String Combiner (Current)
Name in records: string_combiner_current.
A basic string combiner
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DCAMax | uint16 | A | Rating | Maximum DC Current Rating |
N | count | N | Number of Inputs | |
Evt | bitfield32 | Event | Events | |
EvtVnd | bitfield32 | Vendor Event | Vendor defined events | |
DCA | int16 | A | Amps | Total measured current |
DCAhr | uint32 | Ah | Amp-hours | Total metered Amp-hours |
DCV | uint16 | V | Voltage | Output Voltage |
Tmp | int16 | C | Temp | Internal operating temperature |
string[*].InID | uint16 | ID | Uniquely identifies this input set | |
string[*].InEvt | bitfield32 | Input Event | String Input Event Flags | |
string[*].InEvtVnd | bitfield32 | Input Event Vendor | String Input Vendor Event Flags | |
string[*].InDCA | int16 | A | Amps | String Input Current |
string[*].InDCAhr | uint32 | Ah | Amp-hours | String Input Amp-Hours |
Value names:
Evt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTEDstring[*].InEvt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTED
Model 402: String Combiner (Advanced)
Name in records: string_combiner_advanced.
An advanced string combiner
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DCAMax | uint16 | A | Rating | Maximum DC Current Rating |
N | count | N | Number of Inputs | |
Evt | bitfield32 | Event | Events | |
EvtVnd | bitfield32 | Vendor Event | Vendor defined events | |
DCA | int16 | A | Amps | Total measured current |
DCAhr | uint32 | Ah | Amp-hours | Total metered Amp-hours |
DCV | uint16 | V | Voltage | Output Voltage |
Tmp | int16 | C | Temp | Internal operating temperature |
DCW | int16 | W | Watts | Output power |
DCPR | uint16 | Pct | PR | DC Performance ratio value |
DCWh | uint32 | Wh | Watt-hours | Output energy |
string[*].InID | uint16 | ID | Uniquely identifies this input set | |
string[*].InEvt | bitfield32 | Input Event | String Input Event Flags | |
string[*].EvtVnd | bitfield32 | Vendor Event | Vendor defined events | |
string[*].InDCA | int16 | A | Amps | String Input Current |
string[*].InDCAhr | uint32 | Ah | Amp-hours | String Input Amp-Hours |
string[*].InDCV | uint16 | V | Voltage | String Input Voltage |
string[*].InDCW | int16 | W | Watts | String Input Power |
string[*].InDCWh | uint32 | Wh | Watt-hours | String Input Energy |
string[*].InDCPR | uint16 | Pct | PR | String Performance Ratio |
string[*].InN | uint16 | N | Number of modules in this input string |
Value names:
Evt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTEDstring[*].InEvt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTED
Model 403: String Combiner (Current)
Name in records: string_combiner_current_input.
A basic string combiner model
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DCAMax | uint16 | A | Rating | Maximum DC Current Rating |
N | count | N | Number of Inputs | |
Evt | bitfield32 | Event | Events | |
EvtVnd | bitfield32 | Vendor Event | Vendor defined events | |
DCA | int16 | A | Amps | Total measured current |
DCAhr | acc32 | Ah | Amp-hours | Total metered Amp-hours |
DCV | int16 | V | Voltage | Output Voltage |
Tmp | int16 | C | Temp | Internal operating temperature |
string[*].InID | uint16 | ID | Uniquely identifies this input set | |
string[*].InEvt | bitfield32 | Input Event | String Input Event Flags | |
string[*].InEvtVnd | bitfield32 | Input Event Vendor | String Input Vendor Event Flags | |
string[*].InDCA | int16 | A | Amps | String Input Current |
string[*].InDCAhr | acc32 | Ah | Amp-hours | String Input Amp-Hours |
Value names:
Evt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTEDstring[*].InEvt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTED
Model 404: String Combiner (Advanced)
Name in records: string_combiner_advanced_inputs.
An advanced string combiner including voltage and energy measurements
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DCAMax | uint16 | A | Rating | Maximum DC Current Rating |
N | count | N | Number of Inputs | |
Evt | bitfield32 | Event | Events | |
EvtVnd | bitfield32 | Vendor Event | Vendor defined events | |
DCA | int16 | A | Amps | Total measured current |
DCAhr | acc32 | Ah | Amp-hours | Total metered Amp-hours |
DCV | int16 | V | Voltage | Output Voltage |
Tmp | int16 | C | Temp | Internal operating temperature |
DCW | int16 | W | Watts | Output power |
DCPR | int16 | Pct | PR | DC Performance ratio value |
DCWh | acc32 | Wh | Watt-hours | Output energy |
string[*].InID | uint16 | ID | Uniquely identifies this input set | |
string[*].InEvt | bitfield32 | Input Event | String Input Event Flags | |
string[*].InEvtVnd | bitfield32 | Input Event Vendor | String Input Vendor Event Flags | |
string[*].InDCA | int16 | A | Amps | String Input Current |
string[*].InDCAhr | acc32 | Ah | Amp-hours | String Input Amp-Hours |
string[*].InDCV | int16 | V | Voltage | String Input Voltage |
string[*].InDCW | int16 | W | Watts | String Input Power |
string[*].InDCWh | acc32 | Wh | Watt-hours | String Input Energy |
string[*].InDCPR | uint16 | Pct | PR | String Performance Ratio |
string[*].InN | uint16 | N | Number of modules in this input string |
Value names:
Evt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTEDstring[*].InEvt(bitfield32): bit 0LOW_VOLTAGE, bit 1LOW_POWER, bit 2LOW_EFFICIENCY, bit 3CURRENT, bit 4VOLTAGE, bit 5POWER, bit 6PR, bit 7DISCONNECTED, bit 8FUSE_FAULT, bit 9COMBINER_FUSE_FAULT, bit 10COMBINER_CABINET_OPEN, bit 11TEMP, bit 12GROUNDFAULT, bit 13REVERSED_POLARITY, bit 14INCOMPATIBLE, bit 15COMM_ERROR, bit 16INTERNAL_ERROR, bit 17THEFT, bit 18ARC_DETECTED
Solar modules
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
Model 501: Solar Module
Name in records: solar_module_float.
A solar module model supporting DC-DC converter
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Stat | enum16 | Status | Module Status Code | |
StatVend | enum16 | Vendor Status | Module Vendor Status Code | |
Evt | bitfield32 | Events | Module Event Flags | |
EvtVend | bitfield32 | Vendor Module Event Flags | Vendor specific flags | |
Ctl | enum16 | Control | Module Control | |
CtlVend | enum32 | Vendor Control | Vendor Module Control | |
CtlVal | int32 | Control Value | Module Control Value | |
Tms | uint32 | Secs | Timestamp | Time in seconds since 2000 epoch |
OutA | float32 | A | Output Current | Output Current |
OutV | float32 | V | Output Voltage | Output Voltage |
OutWh | float32 | Wh | Output Energy | Output Energy |
OutW | float32 | W | Output Power | Output Power |
Tmp | float32 | C | Temp | Module Temperature |
InA | float32 | A | Input Current | Input Current |
InV | float32 | V | Input Voltage | Input Voltage |
InWh | float32 | Wh | Input Energy | Input Energy |
InW | float32 | W | Input Power | Input Power |
Value names:
Stat(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBY, 9TEST, 10OTHEREvt(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 2RESERVED_2, bit 3DC_DISCONNECT, bit 4RESERVED_4, bit 5RESERVED_5, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMPERATURE, bit 8RESERVED_8, bit 9RESERVED_9, bit 10RESERVED_10, bit 11RESERVED_11, bit 12BLOWN_FUSE, bit 13UNDER_TEMPERATURE, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 16THEFT_DETECTION, bit 17OUTPUT_OVER_CURRENT, bit 18OUTPUT_OVER_VOLTAGE, bit 19OUTPUT_UNDER_VOLTAGE, bit 20TEST_FAILED
Model 502: Solar Module
Name in records: solar_module.
A solar module model supporting DC-DC converter
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Stat | enum16 | Status | Module Status Code | |
StatVend | enum16 | Vendor Status | Module Vendor Status Code | |
Evt | bitfield32 | Events | Module Event Flags | |
EvtVend | bitfield32 | Vendor Module Event Flags | Vendor specific flags | |
Ctl | enum16 | Control | Module Control | |
CtlVend | enum32 | Vendor Control | Vendor Module Control | |
CtlVal | int32 | Control Value | Module Control Value | |
Tms | uint32 | Secs | Timestamp | Time in seconds since 2000 epoch |
OutA | int16 | A | Output Current | Output Current |
OutV | int16 | V | Output Voltage | Output Voltage |
OutWh | acc32 | Wh | Output Energy | Output Energy |
OutPw | int16 | W | Output Power | Output Power |
Tmp | int16 | C | Temp | Module Temperature |
InA | int16 | A | Input Current | Input Current |
InV | int16 | V | Input Voltage | Input Voltage |
InWh | acc32 | Wh | Input Energy | Input Energy |
InW | int16 | W | Input Power | Input Power |
Value names:
Stat(enum16): 1OFF, 2SLEEPING, 3STARTING, 4MPPT, 5THROTTLED, 6SHUTTING_DOWN, 7FAULT, 8STANDBY, 9TEST, 10OTHEREvt(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 2RESERVED_2, bit 3DC_DISCONNECT, bit 4RESERVED_4, bit 5RESERVED_5, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMPERATURE, bit 8RESERVED_8, bit 9RESERVED_9, bit 10RESERVED_10, bit 11RESERVED_11, bit 12BLOWN_FUSE, bit 13UNDER_TEMPERATURE, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 16THEFT_DETECTION, bit 17OUTPUT_OVER_CURRENT, bit 18OUTPUT_OVER_VOLTAGE, bit 19OUTPUT_UNDER_VOLTAGE, bit 20TEST_FAILED
Trackers
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 601 | tracker_controller | 19 |
Model 601: Tracker Controller DRAFT 2
Name in records: tracker_controller.
Monitors and controls multiple trackers
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Nam | string | Controller | Descriptive name for this control unit | |
Typ | enum16 | Type | Type of tracker | |
DtLoc | string | YYYYMMDD | Date | Local date in YYYYMMDD format |
TmLoc | string | hhmmss | Time | 24 hour local time stamp to second |
Day | uint16 | Day | Number of the day in the year (1-366) | |
GlblElCtl | int32 | Degrees | Manual Elevation | Global manual override target position of elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type |
GlblAzCtl | int32 | Degrees | Manual Azimuth | Global manual override target position of azimuth in degrees from true north towards east. Unimplemented for single axis azimuth tracker type |
GlblCtl | enum16 | Global Mode | Global 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. | |
GlblAlm | bitfield16 | Global Alarm | Global tracker alarm conditions | |
N | uint16 | Trackers | Number of trackers being controlled. Size of repeating block. | |
tracker[*].Id | string | Tracker | Descriptive name for this tracker unit | |
tracker[*].ElTrgt | int32 | Degrees | Target Elevation | Auto target elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type |
tracker[*].AzTrgt | int32 | Degrees | Target Azimuth | Auto target azimuth in degrees from true north towards east. Unimplemented for single axis horizontal tracker type |
tracker[*].ElPos | int32 | Degrees | Elevation | Actual elevation position in degrees from horizontal. Unimplemented for single axis azimuth tracker type |
tracker[*].AzPos | int32 | Degrees | Azimuth | Actual azimuth position in degrees from true north towards east. Unimplemented for single axis horizontal tracker type |
tracker[*].ElCtl | int32 | Degrees | Manual Elevation | Manual override target position of elevation in degrees from horizontal. Unimplemented for single axis azimuth tracker type |
tracker[*].AzCtl | int32 | Degrees | Manual Azimuth | Manual override target position of azimuth in degrees from true north towards east. Unimplemented for single axis azimuth tracker type |
tracker[*].Ctl | enum16 | Mode | Control 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[*].Alm | bitfield16 | Alarm | Tracker alarm conditions |
Value names:
Typ(enum16): 0Unknown, 1Fixed, 2Horizontal, 3Tilted, 4Azimuth, 5Dual, 99OtherGlblCtl(enum16): 0Automatic, 1Manual, 2CalibrateGlblAlm(bitfield16): bit 0SetPoint, bit 1ObsEl, bit 2ObsAztracker[*].Ctl(enum16): 0Automatic, 1Manual, 2Calibratetracker[*].Alm(bitfield16): bit 0SetPoint, bit 1ObsEl, bit 2ObsAz
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.
| Model | Name in records | Points |
|---|---|---|
| 701 | DERMeasureAC | 60 |
| 702 | DERCapacity | 42 |
| 703 | DEREnterService | 9 |
| 704 | DERCtlAC | 45 |
| 705 | DERVoltVar | 19 |
| 706 | DERVoltWatt | 14 |
| 707 | DERTripLV | 15 |
| 708 | DERTripHV | 15 |
| 709 | DERTripLF | 15 |
| 710 | DERTripHF | 15 |
| 711 | DERFreqDroop | 14 |
| 712 | DERWattVar | 14 |
| 713 | DERStorageCapacity | 5 |
| 714 | DERMeasureDC | 17 |
| 715 | DERCtl | 5 |
Model 701: DER AC Measurement
Name in records: DERMeasureAC.
DER AC measurement model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ACType | enum16 | AC Wiring Type | AC wiring type. | |
St | enum16 | Operating State | Operating state of the DER. | |
InvSt | enum16 | Inverter State | Inverter state. | |
ConnSt | enum16 | Grid Connection State | Grid connection state of the DER. | |
Alrm | bitfield32 | Alarm Bitfield | Active alarms for the DER. | |
DERMode | bitfield32 | DER Operational Characteristics | Current operational characteristics of the DER. | |
W | int16 | W | Active Power | Total active power. Active power is positive for DER generation and negative for absorption. |
VA | int16 | VA | Apparent Power | Total apparent power. |
Var | int16 | Var | Reactive Power | Total reactive power. |
PF | int16 | Power Factor | Power factor. The sign of power factor should be the sign of active power. | |
A | int16 | A | Total AC Current | Total AC current. |
LLV | uint16 | V | Voltage LL | Line to line AC voltage as an average of active phases. |
LNV | uint16 | V | Voltage LN | Line to neutral AC voltage as an average of active phases. |
Hz | uint32 | Hz | Frequency | AC frequency. |
TotWhInj | uint64 | Wh | Total Energy Injected | Total active energy injected (Quadrants 1 & 4). |
TotWhAbs | uint64 | Wh | Total Energy Absorbed | Total active energy absorbed (Quadrants 2 & 3). |
TotVarhInj | uint64 | Varh | Total Reactive Energy Inj | Total reactive energy injected (Quadrants 1 & 2). |
TotVarhAbs | uint64 | Varh | Total Reactive Energy Abs | Total reactive energy absorbed (Quadrants 3 & 4). |
TmpAmb | int16 | C | Ambient Temperature | Ambient temperature. |
TmpCab | int16 | C | Cabinet Temperature | Cabinet temperature. |
TmpSnk | int16 | C | Heat Sink Temperature | Heat sink temperature. |
TmpTrns | int16 | C | Transformer Temperature | Transformer temperature. |
TmpSw | int16 | C | IGBT/MOSFET Temperature | IGBT/MOSFET temperature. |
TmpOt | int16 | C | Other Temperature | Other temperature. |
WL1 | int16 | W | Watts L1 | Active power L1. |
VAL1 | int16 | VA | VA L1 | Apparent power L1. |
VarL1 | int16 | Var | Var L1 | Reactive power L1. |
PFL1 | int16 | PF L1 | Power factor phase L1. | |
AL1 | int16 | A | Amps L1 | Current phase L1. |
VL1L2 | uint16 | V | Phase Voltage L1-L2 | Phase voltage L1-L2. |
VL1 | uint16 | V | Phase Voltage L1-N | Phase voltage L1-N. |
TotWhInjL1 | uint64 | Wh | Total Watt-Hours Inj L1 | Total active energy injected L1. |
TotWhAbsL1 | uint64 | Wh | Total Watt-Hours Abs L1 | Total active energy absorbed L1. |
TotVarhInjL1 | uint64 | Varh | Total Var-Hours Inj L1 | Total reactive energy injected L1. |
TotVarhAbsL1 | uint64 | Varh | Total Var-Hours Abs L1 | Total reactive energy absorbed L1. |
WL2 | int16 | W | Watts L2 | Active power L2. |
VAL2 | int16 | VA | VA L2 | Apparent power L2. |
VarL2 | int16 | Var | Var L2 | Reactive power L2. |
PFL2 | int16 | PF L2 | Power factor L2. | |
AL2 | int16 | A | Amps L2 | Current L2. |
VL2L3 | uint16 | V | Phase Voltage L2-L3 | Phase voltage L2-L3. |
VL2 | uint16 | V | Phase Voltage L2-N | Phase voltage L2-N. |
TotWhInjL2 | uint64 | Wh | Total Watt-Hours Inj L2 | Total active energy injected L2. |
TotWhAbsL2 | uint64 | Wh | Total Watt-Hours Abs L2 | Total active energy absorbed L2. |
TotVarhInjL2 | uint64 | Varh | Total Var-Hours Inj L2 | Total reactive energy injected L2. |
TotVarhAbsL2 | uint64 | Varh | Total Var-Hours Abs L2 | Total reactive energy absorbed L2. |
WL3 | int16 | W | Watts L3 | Active power L3. |
VAL3 | int16 | VA | VA L3 | Apparent power L3. |
VarL3 | int16 | Var | Var L3 | Reactive power L3. |
PFL3 | int16 | PF L3 | Power factor L3. | |
AL3 | int16 | A | Amps L3 | Current L3. |
VL3L1 | uint16 | V | Phase Voltage L3-L1 | Phase voltage L3-L1. |
VL3 | uint16 | V | Phase Voltage L3-N | Phase voltage L3-N. |
TotWhInjL3 | uint64 | Wh | Total Watt-Hours Inj L3 | Total active energy injected L3. |
TotWhAbsL3 | uint64 | Wh | Total Watt-Hours Abs L3 | Total active energy absorbed L3. |
TotVarhInjL3 | uint64 | Varh | Total Var-Hours Inj L3 | Total reactive energy injected L3. |
TotVarhAbsL3 | uint64 | Varh | Total Var-Hours Abs L3 | Total reactive energy absorbed L3. |
ThrotPct | uint16 | Pct | Throttling In Pct | Throttling in pct of maximum active power. |
ThrotSrc | bitfield32 | Throttle Source Information | Active throttling source. | |
MnAlrmInfo | string | Manufacturer Alarm Info | Manufacturer alarm information. Valid if MANUFACTURER_ALRM indication is active. |
Value names:
ACType(enum16): 0SINGLE_PHASE, 1SPLIT_PHASE, 2THREE_PHASESt(enum16): 0OFF, 1ONInvSt(enum16): 0OFF, 1SLEEPING, 2STARTING, 3RUNNING, 4THROTTLED, 5SHUTTING_DOWN, 6FAULT, 7STANDBYConnSt(enum16): 0DISCONNECTED, 1CONNECTEDAlrm(bitfield32): bit 0GROUND_FAULT, bit 1DC_OVER_VOLT, bit 2AC_DISCONNECT, bit 3DC_DISCONNECT, bit 4GRID_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 8OVER_FREQUENCY, bit 9UNDER_FREQUENCY, bit 10AC_OVER_VOLT, bit 11AC_UNDER_VOLT, bit 12BLOWN_STRING_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15HW_TEST_FAILURE, bit 16MANUFACTURER_ALRMDERMode(bitfield32): bit 0GRID_FOLLOWING, bit 1GRID_FORMING, bit 2PV_CLIPPEDThrotSrc(bitfield32): bit 0MAX_W, bit 1FIXED_W, bit 2FIXED_VAR, bit 3FIXED_PF, bit 4VOLT_VAR, bit 5FREQ_WATT, bit 6DYN_REACT_CURR, bit 7LVRT, bit 8HVRT, bit 9WATT_VAR, bit 10VOLT_WATT, bit 11SCHEDULED, bit 12LFRT, bit 13HFRT, bit 14DERATED
Model 702: DER Capacity
Name in records: DERCapacity.
DER capacity model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
WMaxRtg | uint16 | W | Active Power Max Rating | Maximum active power rating at unity power factor in watts. |
WOvrExtRtg | uint16 | W | Active Power (Over-Excited) Rating | Active power rating at specified over-excited power factor in watts. |
WOvrExtRtgPF | uint16 | Specified Over-Excited PF | Specified over-excited power factor. | |
WUndExtRtg | uint16 | W | Active Power (Under-Excited) Rating | Active power rating at specified under-excited power factor in watts. |
WUndExtRtgPF | uint16 | Specified Under-Excited PF | Specified under-excited power factor. | |
VAMaxRtg | uint16 | VA | Apparent Power Max Rating | Maximum apparent power rating in voltamperes. |
VarMaxInjRtg | uint16 | Var | Reactive Power Injected Rating | Maximum injected reactive power rating in vars. |
VarMaxAbsRtg | uint16 | Var | Reactive Power Absorbed Rating | Maximum absorbed reactive power rating in vars. |
WChaRteMaxRtg | uint16 | W | Charge Rate Max Rating | Maximum active power charge rate in watts. |
WDisChaRteMaxRtg | uint16 | W | Discharge Rate Max Rating | Maximum active power discharge rate in watts. |
VAChaRteMaxRtg | uint16 | VA | Charge Rate Max VA Rating | Maximum apparent power charge rate in voltamperes. |
VADisChaRteMaxRtg | uint16 | VA | Discharge Rate Max VA Rating | Maximum apparent power discharge rate in voltamperes. |
VNomRtg | uint16 | V | AC Voltage Nominal Rating | AC voltage nominal rating. |
VMaxRtg | uint16 | V | AC Voltage Max Rating | AC voltage maximum rating. |
VMinRtg | uint16 | V | AC Voltage Min Rating | AC voltage minimum rating. |
AMaxRtg | uint16 | A | AC Current Max Rating | AC current maximum rating in amps. |
PFOvrExtRtg | uint16 | PF Over-Excited Rating (Unused) | Unused. Please use WOvrExtRtgPF. | |
PFUndExtRtg | uint16 | PF Under-Excited Rating (Unused) | Unused. Please use WUndExtRtgPF. | |
ReactSusceptRtg | uint16 | S | Reactive Susceptance | Reactive susceptance that remains connected to the Area EPS in the cease to energize and trip state. |
NorOpCatRtg | enum16 | Normal Operating Category | Normal operating performance category as specified in IEEE 1547-2018. | |
AbnOpCatRtg | enum16 | Abnormal Operating Category | Abnormal operating performance category as specified in IEEE 1547-2018. | |
CtrlModes | bitfield32 | Supported Control Modes | Supported control mode functions. | |
IntIslandCatRtg | bitfield16 | Intentional Island Categories | Intentional island categories. | |
WMax | uint16 | W | Active Power Max Setting | Maximum active power setting used to adjust maximum active power setting. |
WMaxOvrExt | uint16 | W | Active Power (Over-Excited) Setting | Active power setting at specified over-excited power factor in watts. |
WOvrExtPF | uint16 | Specified Over-Excited PF | Specified over-excited power factor. | |
WMaxUndExt | uint16 | W | Active Power (Under-Excited) Setting | Active power setting at specified under-excited power factor in watts. |
WUndExtPF | uint16 | Specified Under-Excited PF | Specified under-excited power factor. | |
VAMax | uint16 | VA | Apparent Power Max Setting | Maximum apparent power setting used to adjust maximum apparent power rating. |
VarMaxInj | uint16 | Var | Reactive Power Injected Setting | Maximum injected reactive power setting used to adjust maximum injected reactive power rating. |
VarMaxAbs | uint16 | Var | Reactive Power Absorbed Setting | Maximum absorbed reactive power setting used to adjust maximum absorbed reactive power rating. |
WChaRteMax | uint16 | W | Charge Rate Max Setting | Maximum active power charge rate setting used to adjust maximum active power charge rate rating. |
WDisChaRteMax | uint16 | W | Discharge Rate Max Setting | Maximum active power discharge rate setting used to adjust maximum active power discharge rate rating. |
VAChaRteMax | uint16 | VA | Charge Rate Max VA Setting | Maximum apparent power charge rate setting used to adjust maximum apparent power charge rate rating. |
VADisChaRteMax | uint16 | VA | Discharge Rate Max VA Setting | Maximum apparent power discharge rate setting used to adjust maximum apparent power discharge rate rating. |
VNom | uint16 | V | Nominal AC Voltage Setting | Nominal AC voltage setting. |
VMax | uint16 | V | AC Voltage Max Setting | AC voltage maximum setting used to adjust AC voltage maximum rating. |
VMin | uint16 | V | AC Voltage Min Setting | AC voltage minimum setting used to adjust AC voltage minimum rating. |
AMax | uint16 | A | AC Current Max Setting | Maximum AC current setting used to adjust maximum AC current rating. |
PFOvrExt | uint16 | PF Over-Excited Setting (Unused) | Unused. Please use WOvrExtPF. | |
PFUndExt | uint16 | PF Under-Excited Setting (Unused) | Unused. Please use WUndExtPF. | |
IntIslandCat | bitfield16 | Intentional Island Categories | Intentional island categories. |
Value names:
NorOpCatRtg(enum16): 0CAT_A, 1CAT_BAbnOpCatRtg(enum16): 0CAT_1, 1CAT_2, 2CAT_3CtrlModes(bitfield32): bit 0MAX_W, bit 1FIXED_W, bit 2FIXED_VAR, bit 3FIXED_PF, bit 4VOLT_VAR, bit 5FREQ_WATT, bit 6DYN_REACT_CURR, bit 7LV_TRIP, bit 8HV_TRIP, bit 9WATT_VAR, bit 10VOLT_WATT, bit 11SCHEDULED, bit 12LF_TRIP, bit 13HF_TRIPIntIslandCatRtg(bitfield16): bit 0UNCATEGORIZED, bit 1INT_ISL_CAPABLE, bit 2BLACK_START_CAPABLE, bit 3ISOCH_CAPABLEIntIslandCat(bitfield16): bit 0UNCATEGORIZED, bit 1INT_ISL_CAPABLE, bit 2BLACK_START_CAPABLE, bit 3ISOCH_CAPABLE
Model 703: Enter Service
Name in records: DEREnterService.
Enter service model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ES | enum16 | Permit Enter Service | Permit enter service. | |
ESVHi | uint16 | Pct | Enter Service Voltage High | Enter service voltage high threshold as percent of normal voltage. |
ESVLo | uint16 | Pct | Enter Service Voltage Low | Enter service voltage low threshold as percent of normal voltage. |
ESHzHi | uint32 | Hz | Enter Service Frequency High | Enter service frequency high threshold. |
ESHzLo | uint32 | Hz | Enter Service Frequency Low | Enter service frequency low threshold. |
ESDlyTms | uint32 | Secs | Enter Service Delay Time | Enter service delay time in seconds. |
ESRndTms | uint32 | Secs | Enter Service Random Delay | Enter service random delay in seconds. |
ESRmpTms | uint32 | Secs | Enter Service Ramp Time | Enter service ramp time in seconds. |
ESDlyRemTms | uint32 | Secs | Enter Service Delay Remaining | Enter service delay time remaining in seconds. |
Value names:
ES(enum16): 0DISABLED, 1ENABLED
Model 704: DER AC Controls
Name in records: DERCtlAC.
DER AC controls model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
PFWInjEna | enum16 | Power Factor Enable (W Inj) Enable | Power factor enable when injecting active power. | |
PFWInjEnaRvrt | enum16 | Power Factor Reversion Enable (W Inj) | Power factor reversion timer when injecting active power enable. | |
PFWInjRvrtTms | uint32 | Secs | PF Reversion Time (W Inj) | Power factor reversion timer when injecting active power. |
PFWInjRvrtRem | uint32 | Secs | PF Reversion Time Rem (W Inj) | Power factor reversion time remaining when injecting active power. |
PFWAbsEna | enum16 | Power Factor Enable (W Abs) Enable | Power factor enable when absorbing active power. | |
PFWAbsEnaRvrt | enum16 | Power Factor Reversion Enable (W Abs) | Power factor reversion timer when absorbing active power enable. | |
PFWAbsRvrtTms | uint32 | Secs | PF Reversion Time (W Abs) | Power factor reversion timer when absorbing active power. |
PFWAbsRvrtRem | uint32 | Secs | PF Reversion Time Rem (W Abs) | Power factor reversion time remaining when absorbing active power. |
WMaxLimPctEna | enum16 | Limit Max Power Pct Enable | Limit maximum active power percent enable. | |
WMaxLimPct | uint16 | Pct | Limit Max Power Pct Setpoint | Limit maximum active power percent value. |
WMaxLimPctRvrt | uint16 | Pct | Reversion Limit Max Power Pct | Reversion limit maximum active power percent value. |
WMaxLimPctEnaRvrt | enum16 | Reversion Limit Max Power Pct Enable | Reversion limit maximum active power percent value enable. | |
WMaxLimPctRvrtTms | uint32 | Secs | Limit Max Power Pct Reversion Time | Limit maximum active power percent reversion time. |
WMaxLimPctRvrtRem | uint32 | Secs | Limit Max Power Pct Rev Time Rem | Limit maximum active power percent reversion time remaining. |
WSetEna | enum16 | Set Active Power Enable | Set active power enable. | |
WSetMod | enum16 | Set Active Power Mode | Set active power mode. | |
WSet | int32 | W | Active Power Setpoint (W) | Active power setting value in watts. |
WSetRvrt | int32 | W | Reversion Active Power (W) | Reversion active power setting value in watts. |
WSetPct | int16 | Pct | Active Power Setpoint (Pct) | Active power setting value as percent. |
WSetPctRvrt | int16 | Pct | Reversion Active Power (Pct) | Reversion active power setting value as percent. |
WSetEnaRvrt | enum16 | Reversion Active Power Enable | Reversion active power function enable. | |
WSetRvrtTms | uint32 | Secs | Active Power Reversion Time | Set active power reversion time. |
WSetRvrtRem | uint32 | Secs | Active Power Rev Time Rem | Set active power reversion time remaining. |
VarSetEna | enum16 | Set Reactive Power Enable | Set reactive power enable. | |
VarSetMod | enum16 | Set Reactive Power Mode | Set reactive power mode. | |
VarSetPri | enum16 | Reactive Power Priority | Reactive power priority. | |
VarSet | int32 | Var | Reactive Power Setpoint (Vars) | Reactive power setting value in vars. |
VarSetRvrt | int32 | Var | Reversion Reactive Power (Vars) | Reversion reactive power setting value in vars. |
VarSetPct | int16 | Pct | Reactive Power Setpoint (Pct) | Reactive power setting value as percent. |
VarSetPctRvrt | int16 | Pct | Reversion Reactive Power (Pct) | Reversion reactive power setting value as percent. |
VarSetEnaRvrt | enum16 | Reversion Reactive Power Enable | Reversion reactive power function enable. | |
VarSetRvrtTms | uint32 | Secs | Reactive Power Reversion Time | Set reactive power reversion time. |
VarSetRvrtRem | uint32 | Secs | Reactive Power Rev Time Rem | Set reactive power reversion time remaining. |
WRmp | uint16 | %Max/Sec | Normal Ramp Rate | Ramp rate for increases in active power during normal generation. |
WRmpRef | enum16 | Normal Ramp Rate Reference | Ramp rate reference unit for increases in active power or current during normal generation. | |
VarRmp | uint16 | %Max/Sec | Reactive Power Ramp Rate | Ramp rate based on max reactive power per second. |
AntiIslEna | enum16 | Anti-Islanding Enable | Anti-islanding enable. | |
PFWInj.PF | uint16 | Power Factor (W Inj) | Power factor setpoint when injecting active power. | |
PFWInj.Ext | enum16 | Power Factor Excitation (W Inj) | Power factor excitation setpoint when injecting active power. | |
PFWInjRvrt.PF | uint16 | Reversion Power Factor (W Inj) | Reversion power factor setpoint when injecting active power. | |
PFWInjRvrt.Ext | enum16 | Reversion PF Excitation (W Inj) | Reversion power factor excitation setpoint when injecting active power. | |
PFWAbs.PF | uint16 | Power Factor (W Abs) | Power factor setpoint when absorbing active power. | |
PFWAbs.Ext | enum16 | Power Factor Excitation (W Abs) | Power factor excitation setpoint when absorbing active power. | |
PFWAbsRvrt.PF | uint16 | Reversion Power Factor (W Abs) | Reversion power factor setpoint when absorbing active power. | |
PFWAbsRvrt.Ext | enum16 | Reversion PF Excitation (W Abs) | Reversion power factor excitation setpoint when absorbing active power. |
Value names:
PFWInjEna(enum16): 0DISABLED, 1ENABLEDPFWInjEnaRvrt(enum16): 0DISABLED, 1ENABLEDPFWAbsEna(enum16): 0DISABLED, 1ENABLEDPFWAbsEnaRvrt(enum16): 0DISABLED, 1ENABLEDWMaxLimPctEna(enum16): 0DISABLED, 1ENABLEDWMaxLimPctEnaRvrt(enum16): 0DISABLED, 1ENABLEDWSetEna(enum16): 0DISABLED, 1ENABLEDWSetMod(enum16): 0W_MAX_PCT, 1WATTSWSetEnaRvrt(enum16): 0DISABLED, 1ENABLEDVarSetEna(enum16): 0DISABLED, 1ENABLEDVarSetMod(enum16): 0W_MAX_PCT, 1VAR_MAX_PCT, 2VAR_AVAIL_PCT, 3VA_MAX_PCT, 4VARSVarSetPri(enum16): 0ACTIVE, 1REACTIVE, 2VENDORVarSetEnaRvrt(enum16): 0DISABLED, 1ENABLEDWRmpRef(enum16): 0A_MAX, 1W_MAXAntiIslEna(enum16): 0DISABLED, 1ENABLEDPFWInj.Ext(enum16): 0OVER_EXCITED, 1UNDER_EXCITEDPFWInjRvrt.Ext(enum16): 0OVER_EXCITED, 1UNDER_EXCITEDPFWAbs.Ext(enum16): 0OVER_EXCITED, 1UNDER_EXCITEDPFWAbsRvrt.Ext(enum16): 0OVER_EXCITED, 1UNDER_EXCITED
Model 705: DER Volt-Var
Name in records: DERVoltVar.
DER Volt-Var model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Volt-Var Module Enable | Volt-Var control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrv | uint16 | Stored Curve Count | Number of stored curves supported. | |
RvrtTms | uint32 | Secs | Reversion Timeout | Reversion time in seconds. 0 = No reversion time. |
RvrtRem | uint32 | Secs | Reversion Time Remaining | Reversion time remaining in seconds. |
RvrtCrv | uint16 | Reversion Curve | Default curve after reversion timeout. | |
Crv[*].ActPt | uint16 | Active Points | Number of active points. | |
Crv[*].DeptRef | enum16 | Dependent Reference | Curve dependent reference. | |
Crv[*].Pri | enum16 | Power Priority | Power priority. | |
Crv[*].VRef | uint16 | VNomPct | Vref Adjustment | Vref adjustment as percentage of nominal voltage. |
Crv[*].VRefAuto | uint16 | VNomPct | Current Autonomous Vref | Autonomous vref value as a percentage of nominal voltage. |
Crv[*].VRefAutoEna | enum16 | Autonomous Vref Enable | Enable autonomous vref. | |
Crv[*].VRefAutoTms | uint16 | Secs | Auto Vref Time Constant | Autonomous vref time constant. |
Crv[*].RspTms | uint32 | Secs | Open Loop Response Time | Open loop response time. |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].Pt[*].Var | int16 | DeptRef | Reactive Power Point | Curve reactive power point as set in DeptRef point. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].DeptRef(enum16): 0W_MAX_PCT, 1VAR_MAX_PCT, 2VAR_AVAL_PCT, 3VA_MAX_PCTCrv[*].Pri(enum16): 0ACTIVE, 1REACTIVE, 2VENDORCrv[*].VRefAutoEna(enum16): 0DISABLED, 1ENABLEDCrv[*].ReadOnly(enum16): 0RW, 1R
Model 706: DER Volt-Watt
Name in records: DERVoltWatt.
DER Volt-Watt model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Volt-Watt Module Enable | Volt-Watt control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrv | uint16 | Stored Curve Count | Number of stored curves supported. | |
RvrtTms | uint32 | Secs | Reversion Timeout | Reversion time in seconds. 0 = No reversion time. |
RvrtRem | uint32 | Secs | Reversion Time Remaining | Reversion time remaining in seconds. |
RvrtCrv | uint16 | Reversion Curve | Default curve after reversion timeout. | |
Crv[*].ActPt | uint16 | Active Points | Number of active points. | |
Crv[*].DeptRef | enum16 | Dependent Reference | Curve dependent reference. | |
Crv[*].RspTms | uint32 | Secs | Open Loop Response Time | Open loop response time. |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].Pt[*].W | int16 | DeptRef | Dependent Reference | Active power in percent of rated active power. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].DeptRef(enum16): 0W_MAX_PCT, 1W_AVAL_PCTCrv[*].ReadOnly(enum16): 0RW, 1R
Model 707: DER Trip LV
Name in records: DERTripLV.
DER low voltage trip model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Trip LV Module Enable | DER low voltage trip control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrvSet | uint16 | Stored Curve Count | Number of stored curves supported. | |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].MustTrip.ActPt | uint16 | Number Of Active Points | Number of active points in must trip curve. | |
Crv[*].MustTrip.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MustTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MayTrip.ActPt | uint16 | Number Of Active Points | Number of active points in may trip curve. | |
Crv[*].MayTrip.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MayTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MomCess.ActPt | uint16 | Number Of Active Points | Number of active points in the momentary cessation curve. | |
Crv[*].MomCess.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MomCess.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].ReadOnly(enum16): 0RW, 1R
Model 708: DER Trip HV
Name in records: DERTripHV.
DER high voltage trip model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Trip HV Module Enable | DER high voltage trip control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrvSet | uint16 | Stored Curve Count | Number of stored curves supported. | |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].MustTrip.ActPt | uint16 | Number Of Active Points | Number of active points in must trip curve. | |
Crv[*].MustTrip.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MustTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MayTrip.ActPt | uint16 | Number Of Active Points | Number of active points in may trip curve. | |
Crv[*].MayTrip.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MayTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MomCess.ActPt | uint16 | Number Of Active Points | Number of active points in the momentary cessation curve. | |
Crv[*].MomCess.Pt[*].V | uint16 | VNomPct | Voltage Point | Curve voltage point as percentage. |
Crv[*].MomCess.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].ReadOnly(enum16): 0RW, 1R
Model 709: DER Trip LF
Name in records: DERTripLF.
DER low frequency trip model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Trip LF Module Enable | DER low frequency trip control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrvSet | uint16 | Stored Curve Count | Number of stored curves supported. | |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].MustTrip.ActPt | uint16 | Number Of Active Points | Number of active points in must trip curve. | |
Crv[*].MustTrip.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MustTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MayTrip.ActPt | uint16 | Number Of Active Points | Number of active points in may trip curve. | |
Crv[*].MayTrip.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MayTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MomCess.ActPt | uint16 | Number Of Active Points | Number of active points in the momentary cessation curve. | |
Crv[*].MomCess.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MomCess.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].ReadOnly(enum16): 0RW, 1R
Model 710: DER Trip HF
Name in records: DERTripHF.
DER high frequency trip model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Trip HF Module Enable | DER high frequency trip control enable. | |
AdptCrvReq | uint16 | Adopt Curve Request | Index of curve points to adopt. First curve index is 1. | |
AdptCrvRslt | enum16 | Adopt Curve Result | Result of last adopt curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrvSet | uint16 | Stored Curve Count | Number of stored curves supported. | |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].MustTrip.ActPt | uint16 | Number Of Active Points | Number of active points in must trip curve. | |
Crv[*].MustTrip.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MustTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MayTrip.ActPt | uint16 | Number Of Active Points | Number of active points in may trip curve. | |
Crv[*].MayTrip.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MayTrip.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Crv[*].MomCess.ActPt | uint16 | Number Of Active Points | Number of active points in the momentary cessation curve. | |
Crv[*].MomCess.Pt[*].Hz | uint32 | Hz | Frequency Point | Curve frequency point. |
Crv[*].MomCess.Pt[*].Tms | uint32 | Secs | Time Point | Curve time point in seconds. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].ReadOnly(enum16): 0RW, 1R
Model 711: DER Frequency Droop
Name in records: DERFreqDroop.
DER Frequency Droop model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Frequency Droop Module Enable | DER Frequency-Watt (Frequency-Droop) control enable. | |
AdptCtlReq | uint16 | Set Active Control Request | Set active control. 0 = No active control. | |
AdptCtlRslt | enum16 | Set Active Control Result | Result of last set active control operation. | |
NCtl | uint16 | Stored Control Count | Number of stored controls supported. | |
RvrtTms | uint32 | Secs | Reversion Timeout | Reversion time in seconds. 0 = No reversion time. |
RvrtRem | uint32 | Secs | Reversion Time Left | Reversion time remaining in seconds. |
RvrtCtl | uint16 | Reversion Control | Default control after reversion timeout. | |
Ctl[*].DbOf | uint32 | Hz | Over-Frequency Deadband | The deadband value for over-frequency conditions in Hz. |
Ctl[*].DbUf | uint32 | Hz | Under-Frequency Deadband | The deadband value for under-frequency conditions in Hz. |
Ctl[*].KOf | uint16 | Over-Frequency Change Ratio | Frequency droop per-unit frequency change for over-frequency conditions corresponding to 1 per-unit power output change. | |
Ctl[*].KUf | uint16 | Under-Frequency Change Ratio | Frequency droop per-unit frequency change for under-frequency conditions corresponding to 1 per-unit power output change. | |
Ctl[*].RspTms | uint32 | Secs | Open-Loop Response Time | The open-loop response time in seconds. |
Ctl[*].PMin | int16 | Pct | Minimum Active Power | The 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[*].ReadOnly | enum16 | Control Access | Control read-write access. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCtlRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCtl[*].ReadOnly(enum16): 0RW, 1R
Model 712: DER Watt-Var
Name in records: DERWattVar.
DER Watt-Var model.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Ena | enum16 | DER Watt-Var Module Enable | DER Watt-Var control enable. | |
AdptCrvReq | uint16 | Set Active Curve Request | Set active curve. 0 = No active curve. | |
AdptCrvRslt | enum16 | Set Active Curve Result | Result of last set active curve operation. | |
NPt | uint16 | Number Of Points | Number of curve points supported. | |
NCrv | uint16 | Stored Curve Count | Number of stored curves supported. | |
RvrtTms | uint32 | Secs | Reversion Timeout | Reversion time in seconds. 0 = No reversion time. |
RvrtRem | uint32 | Secs | Reversion Time Left | Reversion time remaining in seconds. |
RvrtCrv | uint16 | Reversion Curve | Default curve after reversion timeout. | |
Crv[*].ActPt | uint16 | Active Points | Number of active points. | |
Crv[*].DeptRef | enum16 | Dependent Reference | Curve dependent reference. | |
Crv[*].Pri | enum16 | Power Priority | Power priority. | |
Crv[*].ReadOnly | enum16 | Curve Access | Curve read-write access. | |
Crv[*].Pt[*].W | int16 | WMaxPct | Active Power Point | Curve active power point as percentage. |
Crv[*].Pt[*].Var | int16 | VarPct | Reactive Power Point | Curve reactive power point as set in DeptRef point. |
Value names:
Ena(enum16): 0DISABLED, 1ENABLEDAdptCrvRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDCrv[*].DeptRef(enum16): 0W_MAX_PCT, 1VAR_MAX_PCT, 2VAR_AVAL_PCT, 3VA_MAX_PCTCrv[*].Pri(enum16): 0ACTIVE, 1REACTIVECrv[*].ReadOnly(enum16): 0RW, 1R
Model 713: DER Storage Capacity
Name in records: DERStorageCapacity.
DER storage capacity.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
WHRtg | uint16 | WH | Energy Rating | Energy rating of the DER storage. |
WHAvail | uint16 | WH | Energy Available | Energy available of the DER storage (WHAvail = WHRtg * SoC * SoH) |
SoC | uint16 | Pct | State of Charge | State of charge of the DER storage. |
SoH | uint16 | Pct | State of Health | State of health of the DER storage. |
Sta | enum16 | Status | Storage status. |
Value names:
Sta(enum16): 0OK, 1WARNING, 2ERROR
Model 714: DER DC Measurement
Name in records: DERMeasureDC.
DER DC measurement.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
PrtAlrms | bitfield32 | Port Alarms | Bitfield of ports with active alarms. Bit is 1 if port has an active alarm. Bit 0 is first port. | |
NPrt | uint16 | Number Of Ports | Number of DC ports. | |
DCA | int16 | A | DC Current | Total DC current for all ports. |
DCW | int16 | W | DC Power | Total DC power for all ports. |
DCWhInj | uint64 | Wh | DC Energy Injected | Total cumulative DC energy injected for all ports. |
DCWhAbs | uint64 | Wh | DC Energy Absorbed | Total cumulative DC energy absorbed for all ports. |
Prt[*].PrtTyp | enum16 | Port Type | Port type. | |
Prt[*].ID | uint16 | Port ID | Port ID. | |
Prt[*].IDStr | string | Port ID String | Port ID string. | |
Prt[*].DCA | int16 | A | DC Current | DC current for the port. |
Prt[*].DCV | uint16 | V | DC Voltage | DC voltage for the port. |
Prt[*].DCW | int16 | W | DC Power | DC power for the port. |
Prt[*].DCWhInj | uint64 | Wh | DC Energy Injected | Total cumulative DC energy injected for the port. |
Prt[*].DCWhAbs | uint64 | Wh | DC Energy Absorbed | Total cumulative DC energy absorbed for the port. |
Prt[*].Tmp | int16 | C | DC Port Temperature | DC port temperature. |
Prt[*].DCSta | enum16 | DC Port Status | DC port status. | |
Prt[*].DCAlrm | bitfield32 | DC Port Alarm | DC port alarm. |
Value names:
Prt[*].PrtTyp(enum16): 0PV, 1ESS, 2EV, 3INJ, 4ABS, 5BIDIR, 6DC_DCPrt[*].DCSta(enum16): 0OFF, 1ON, 2WARNING, 3ERRORPrt[*].DCAlrm(bitfield32): bit 0GROUND_FAULT, bit 1INPUT_OVER_VOLTAGE, bit 3DC_DISCONNECT, bit 5CABINET_OPEN, bit 6MANUAL_SHUTDOWN, bit 7OVER_TEMP, bit 12BLOWN_FUSE, bit 13UNDER_TEMP, bit 14MEMORY_LOSS, bit 15ARC_DETECTION, bit 19RESERVED, bit 20TEST_FAILED, bit 21INPUT_UNDER_VOLTAGE, bit 22INPUT_OVER_CURRENT
Model 715: DERCtl
Name in records: DERCtl.
DER Control
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
LocRemCtl | enum16 | Control Mode | DER control mode. Enumeration. | |
DERHb | uint32 | DER Heartbeat | Value is incremented every second by the DER with periodic resets to zero. | |
ControllerHb | uint32 | Controller Heartbeat | Value is incremented every second by the controller with periodic resets to zero. | |
AlarmReset | uint16 | Alarm Reset | Used to reset any latched alarms. 1 = Reset. | |
OpCtl | enum16 | Set Operation | Commands to PCS. |
Value names:
LocRemCtl(enum16): 0REMOTE, 1LOCALOpCtl(enum16): 0STOP, 1START, 2ENTER_STANDBY, 3EXIT_STANDBY
Storage
Point names are the ones records use; [*] stands for a repeating group's index. See Supported SunSpec models for every group.
| Model | Name in records | Points |
|---|---|---|
| 801 | storage | 1 |
| 802 | battery | 44 |
| 803 | lithium_ion_bank | 44 |
| 804 | lithium_ion_string | 41 |
| 805 | lithium-ion-module | 23 |
| 806 | flow_battery | 2 |
| 807 | flow_battery_string | 44 |
| 808 | flow_battery_module | 2 |
| 809 | flow_battery_stack | 2 |
Model 801: Energy Storage Base Model (DEPRECATED)
Name in records: storage.
This model has been deprecated.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DEPRECATED | enum16 | Deprecated Model | This model has been deprecated. |
Model 802: Battery Base Model
Name in records: battery.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
AHRtg | uint16 | Ah | Nameplate Charge Capacity | Nameplate charge capacity in amp-hours. |
WHRtg | uint16 | Wh | Nameplate Energy Capacity | Nameplate energy capacity in DC watt-hours. |
WChaRteMax | uint16 | W | Nameplate Max Charge Rate | Maximum rate of energy transfer into the storage device in DC watts. |
WDisChaRteMax | uint16 | W | Nameplate Max Discharge Rate | Maximum rate of energy transfer out of the storage device in DC watts. |
DisChaRte | uint16 | %WHRtg | Self Discharge Rate | Self discharge rate. Percentage of capacity (WHRtg) discharged per day. |
SoCMax | uint16 | %WHRtg | Nameplate Max SoC | Manufacturer maximum state of charge, expressed as a percentage. |
SoCMin | uint16 | %WHRtg | Nameplate Min SoC | Manufacturer minimum state of charge, expressed as a percentage. |
SocRsvMax | uint16 | %WHRtg | Max Reserve Percent | Setpoint for maximum reserve for storage as a percentage of the nominal maximum storage. |
SoCRsvMin | uint16 | %WHRtg | Min Reserve Percent | Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage. |
SoC | uint16 | %WHRtg | State of Charge | State of charge, expressed as a percentage. |
DoD | uint16 | % | Depth of Discharge | Depth of discharge, expressed as a percentage. |
SoH | uint16 | % | State of Health | Percentage of battery life remaining. |
NCyc | uint32 | Cycle Count | Number of cycles executed in the battery. | |
ChaSt | enum16 | Charge Status | Charge status of storage device. Enumeration. | |
LocRemCtl | enum16 | Control Mode | Battery control mode. Enumeration. | |
Hb | uint16 | Battery Heartbeat | Value is incremented every second with periodic resets to zero. | |
CtrlHb | uint16 | Controller Heartbeat | Value is incremented every second with periodic resets to zero. | |
AlmRst | uint16 | Alarm Reset | Used to reset any latched alarms. 1 = Reset. | |
Typ | enum16 | Battery Type | Type of battery. Enumeration. | |
State | enum16 | State of the Battery Bank | State of the battery bank. Enumeration. | |
StateVnd | enum16 | Vendor Battery Bank State | Vendor specific battery bank state. Enumeration. | |
WarrDt | uint32 | Warranty Date | Date the device warranty expires. | |
Evt1 | bitfield32 | Battery Event 1 Bitfield | Alarms and warnings. | |
Evt2 | bitfield32 | Battery Event 2 Bitfield | Alarms and warnings. | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events. | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events. | |
V | uint16 | V | External Battery Voltage | DC Bus Voltage. |
VMax | uint16 | V | Max Battery Voltage | Instantaneous maximum battery voltage. |
VMin | uint16 | V | Min Battery Voltage | Instantaneous minimum battery voltage. |
CellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the bank. |
CellVMaxStr | uint16 | Max Cell Voltage String | String containing the cell with maximum voltage. | |
CellVMaxMod | uint16 | Max Cell Voltage Module | Module containing the cell with maximum voltage. | |
CellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the bank. |
CellVMinStr | uint16 | Min Cell Voltage String | String containing the cell with minimum voltage. | |
CellVMinMod | uint16 | Min Cell Voltage Module | Module containing the cell with minimum voltage. | |
CellVAvg | uint16 | V | Average Cell Voltage | Average cell voltage for all cells in the bank. |
A | int16 | A | Total DC Current | Total DC current flowing to/from the battery bank. |
AChaMax | uint16 | A | Max Charge Current | Instantaneous maximum DC charge current. |
ADisChaMax | uint16 | A | Max Discharge Current | Instantaneous maximum DC discharge current. |
W | int16 | W | Total Power | Total power flowing to/from the battery bank. |
ReqInvState | enum16 | Inverter State Request | Request from battery to start or stop the inverter. Enumeration. | |
ReqW | int16 | W | Battery Power Request | AC Power requested by battery. |
SetOp | enum16 | Set Operation | Instruct the battery bank to perform an operation such as connecting. Enumeration. | |
SetInvState | enum16 | Set Inverter State | Set the current state of the inverter. |
Value names:
ChaSt(enum16): 1OFF, 2EMPTY, 3DISCHARGING, 4CHARGING, 5FULL, 6HOLDING, 7TESTINGLocRemCtl(enum16): 0REMOTE, 1LOCALTyp(enum16): 0NOT APPLICABLE_UNKNOWN, 1LEAD_ACID, 2NICKEL_METAL_HYDRATE, 3NICKEL_CADMIUM, 4LITHIUM_ION, 5CARBON_ZINC, 6ZINC_CHLORIDE, 7ALKALINE, 8RECHARGEABLE_ALKALINE, 9SODIUM_SULFUR, 10FLOW, 99OTHERState(enum16): 1DISCONNECTED, 2INITIALIZING, 3CONNECTED, 4STANDBY, 5SOC PROTECTION, 6SUSPENDING, 99FAULTEvt1(bitfield32): bit 0COMMUNICATION_ERROR, bit 1OVER_TEMP_ALARM, bit 2OVER_TEMP_WARNING, bit 3UNDER_TEMP_ALARM, bit 4UNDER_TEMP_WARNING, bit 5OVER_CHARGE_CURRENT_ALARM, bit 6OVER_CHARGE_CURRENT_WARNING, bit 7OVER_DISCHARGE_CURRENT_ALARM, bit 8OVER_DISCHARGE_CURRENT_WARNING, bit 9OVER_VOLT_ALARM, bit 10OVER_VOLT_WARNING, bit 11UNDER_VOLT_ALARM, bit 12UNDER_VOLT_WARNING, bit 13UNDER_SOC_MIN_ALARM, bit 14UNDER_SOC_MIN_WARNING, bit 15OVER_SOC_MAX_ALARM, bit 16OVER_SOC_MAX_WARNING, bit 17VOLTAGE_IMBALANCE_WARNING, bit 18TEMPERATURE_IMBALANCE_ALARM, bit 19TEMPERATURE_IMBALANCE_WARNING, bit 20CONTACTOR_ERROR, bit 21FAN_ERROR, bit 22GROUND_FAULT, bit 23OPEN_DOOR_ERROR, bit 24CURRENT_IMBALANCE_WARNING, bit 25OTHER_ALARM, bit 26OTHER_WARNING, bit 27RESERVED_1, bit 28CONFIGURATION_ALARM, bit 29CONFIGURATION_WARNINGReqInvState(enum16): 0NO REQUEST, 1START, 2STOPSetOp(enum16): 1CONNECT, 2DISCONNECTSetInvState(enum16): 1INVERTER_STOPPED, 2INVERTER_STANDBY, 3INVERTER_STARTED
Model 803: Lithium-Ion Battery Bank Model
Name in records: lithium_ion_bank.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
NStr | uint16 | String Count | Number of strings in the bank. | |
NStrCon | uint16 | Connected String Count | Number of strings with contactor closed. | |
ModTmpMax | int16 | C | Max Module Temperature | Maximum temperature for all modules in the bank. |
ModTmpMaxStr | uint16 | Max Module Temperature String | String containing the module with maximum temperature. | |
ModTmpMaxMod | uint16 | Max Module Temperature Module | Module with maximum temperature. | |
ModTmpMin | int16 | C | Min Module Temperature | Minimum temperature for all modules in the bank. |
ModTmpMinStr | uint16 | Min Module Temperature String | String containing the module with minimum temperature. | |
ModTmpMinMod | uint16 | Min Module Temperature Module | Module with minimum temperature. | |
ModTmpAvg | int16 | C | Average Module Temperature | Average temperature for all modules in the bank. |
StrVMax | uint16 | V | Max String Voltage | Maximum string voltage for all strings in the bank. |
StrVMaxStr | uint16 | Max String Voltage String | String with maximum voltage. | |
StrVMin | uint16 | V | Min String Voltage | Minimum string voltage for all strings in the bank. |
StrVMinStr | uint16 | Min String Voltage String | String with minimum voltage. | |
StrVAvg | uint16 | V | Average String Voltage | Average string voltage for all strings in the bank. |
StrAMax | int16 | A | Max String Current | Maximum current of any string in the bank. |
StrAMaxStr | uint16 | Max String Current String | String with the maximum current. | |
StrAMin | int16 | A | Min String Current | Minimum current of any string in the bank. |
StrAMinStr | uint16 | Min String Current String | String with the minimum current. | |
StrAAvg | int16 | A | Average String Current | Average string current for all strings in the bank. |
NCellBal | uint16 | Battery Cell Balancing Count | Total number of cells that are currently being balanced. | |
string[*].StrNMod | uint16 | Module Count | Count of modules in the string. | |
string[*].StrSt | bitfield32 | String Status | Current status of the string. | |
string[*].StrConFail | enum16 | Connection Failure Reason | ||
string[*].StrSoC | uint16 | % | String State of Charge | Battery string state of charge, expressed as a percentage. |
string[*].StrSoH | uint16 | % | String State of Health | Battery string state of health, expressed as a percentage. |
string[*].StrA | int16 | A | String Current | String current measurement. |
string[*].StrCellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the string. |
string[*].StrCellVMaxMod | uint16 | Max Cell Voltage Module | Module containing the maximum cell voltage. | |
string[*].StrCellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the string. |
string[*].StrCellVMinMod | uint16 | Min Cell Voltage Module | Module containing the minimum cell voltage. | |
string[*].StrCellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in the string. |
string[*].StrModTmpMax | int16 | C | Max Module Temperature | Maximum temperature for all modules in the bank. |
string[*].StrModTmpMaxMod | uint16 | Max Module Temperature Module | Module with the maximum temperature. | |
string[*].StrModTmpMin | int16 | C | Min Module Temperature | Minimum temperature for all modules in the bank. |
string[*].StrModTmpMinMod | uint16 | Min Module Temperature Module | Module with the minimum temperature. | |
string[*].StrModTmpAvg | int16 | C | Average Module Temperature | Average temperature for all modules in the bank. |
string[*].StrDisRsn | enum16 | Disabled Reason | Reason why the string is currently disabled. | |
string[*].StrConSt | bitfield32 | Contactor Status | Status of the contactor(s) for the string. | |
string[*].StrEvt1 | bitfield32 | String Event 1 | Alarms, warnings and status values. | |
string[*].StrEvt2 | bitfield32 | String Event 2 | Alarms, warnings and status values. | |
string[*].StrEvtVnd1 | bitfield32 | Vendor String Event Bitfield 1 | Vendor defined events. | |
string[*].StrEvtVnd2 | bitfield32 | Vendor String Event Bitfield 2 | Vendor defined events. | |
string[*].StrSetEna | enum16 | Enable/Disable String | Enables and disables the string. | |
string[*].StrSetCon | enum16 | Connect/Disconnect String | Connects and disconnects the string. |
Value names:
string[*].StrSt(bitfield32): bit 0STRING_ENABLED, bit 1CONTACTOR_STATUSstring[*].StrConFail(enum16): 0NO_FAILURE, 1BUTTON_PUSHED, 2STR_GROUND_FAULT, 3OUTSIDE_VOLTAGE_RANGE, 4STRING_NOT_ENABLED, 5FUSE_OPEN, 6CONTACTOR_FAILURE, 7PRECHARGE_FAILURE, 8STRING_FAULTstring[*].StrDisRsn(enum16): 0NONE, 1FAULT, 2MAINTENANCE, 3EXTERNAL, 4OTHERstring[*].StrConSt(bitfield32): bit 0CONTACTOR_0, bit 1CONTACTOR_1, bit 2CONTACTOR_2, bit 3CONTACTOR_3, bit 4CONTACTOR_4, bit 5CONTACTOR_5, bit 6CONTACTOR_6, bit 7CONTACTOR_7, bit 8CONTACTOR_8, bit 9CONTACTOR_9, bit 10CONTACTOR_10, bit 11CONTACTOR_11, bit 12CONTACTOR_12, bit 13CONTACTOR_13, bit 14CONTACTOR_14, bit 15CONTACTOR_15, bit 16CONTACTOR_16, bit 17CONTACTOR_17, bit 18CONTACTOR_18, bit 19CONTACTOR_19, bit 20CONTACTOR_20, bit 21CONTACTOR_21, bit 22CONTACTOR_22, bit 23CONTACTOR_23, bit 24CONTACTOR_24, bit 25CONTACTOR_25, bit 26CONTACTOR_26, bit 27CONTACTOR_27, bit 28CONTACTOR_28, bit 29CONTACTOR_29, bit 30CONTACTOR_30string[*].StrEvt1(bitfield32): bit 0COMMUNICATION_ERROR, bit 1OVER_TEMP_ALARM, bit 2OVER_TEMP_WARNING, bit 3UNDER_TEMP_ALARM, bit 4UNDER_TEMP_WARNING, bit 5OVER_CHARGE_CURRENT_ALARM, bit 6OVER_CHARGE_CURRENT_WARNING, bit 7OVER_DISCHARGE_CURRENT_ALARM, bit 8OVER_DISCHARGE_CURRENT_WARNING, bit 9OVER_VOLT_ALARM, bit 10OVER_VOLT_WARNING, bit 11UNDER_VOLT_ALARM, bit 12UNDER_VOLT_WARNING, bit 13UNDER_SOC_MIN_ALARM, bit 14UNDER_SOC_MIN_WARNING, bit 15OVER_SOC_MAX_ALARM, bit 16OVER_SOC_MAX_WARNING, bit 17VOLTAGE_IMBALANCE_WARNING, bit 18TEMPERATURE_IMBALANCE_ALARM, bit 19TEMPERATURE_IMBALANCE_WARNING, bit 20CONTACTOR_ERROR, bit 21FAN_ERROR, bit 22GROUND_FAULT, bit 23OPEN_DOOR_ERROR, bit 24RESERVED_1, bit 25OTHER_ALARM, bit 26OTHER_WARNING, bit 27RESERVED_2, bit 28CONFIGURATION_ALARM, bit 29CONFIGURATION_WARNINGstring[*].StrSetEna(enum16): 1ENABLE_STRING, 2DISABLE_STRINGstring[*].StrSetCon(enum16): 1CONNECT_STRING, 2DISCONNECT_STRING
Model 804: Lithium-Ion String Model
Name in records: lithium_ion_string.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Idx | uint16 | String Index | Index of the string within the bank. | |
NMod | uint16 | Module Count | Count of modules in the string. | |
St | bitfield32 | String Status | Current status of the string. | |
ConFail | enum16 | Connection Failure Reason | ||
NCellBal | uint16 | String Cell Balancing Count | Number of cells currently being balanced in the string. | |
SoC | uint16 | % | String State of Charge | Battery string state of charge, expressed as a percentage. |
DoD | uint16 | % | String Depth of Discharge | Depth of discharge for the string, expressed as a percentage. |
NCyc | uint32 | String Cycle Count | Number of discharge cycles executed upon the string. | |
SoH | uint16 | % | String State of Health | Battery string state of health, expressed as a percentage. |
A | int16 | A | String Current | String current measurement. |
V | uint16 | V | String Voltage | String voltage measurement. |
CellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the string. |
CellVMaxMod | uint16 | Max Cell Voltage Module | Module containing the cell with maximum cell voltage. | |
CellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the string. |
CellVMinMod | uint16 | Min Cell Voltage Module | Module containing the cell with minimum cell voltage. | |
CellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in the string. |
ModTmpMax | int16 | C | Max Module Temperature | Maximum temperature for all modules in the string. |
ModTmpMaxMod | uint16 | Max Module Temperature Module | Module with the maximum temperature. | |
ModTmpMin | int16 | C | Min Module Temperature | Minimum temperature for all modules in the string. |
ModTmpMinMod | uint16 | Min Module Temperature Module | Module with the minimum temperature. | |
ModTmpAvg | int16 | C | Average Module Temperature | Average temperature for all modules in the string. |
ConSt | bitfield32 | Contactor Status | Status of the contactor(s) for the string. | |
Evt1 | bitfield32 | String Event 1 | Alarms, warnings and status values. | |
Evt2 | bitfield32 | String Event 2 | Alarms, warnings and status values. | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events. | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events. | |
SetEna | enum16 | Enable/Disable String | Enables and disables the string. Should reset to 0 upon completion. | |
SetCon | enum16 | Connect/Disconnect String | Connects and disconnects the string. | |
lithium_ion_string_module[*].ModNCell | uint16 | Module Cell Count | Count of all cells in the module. | |
lithium_ion_string_module[*].ModSoC | uint16 | % | Module SoC | Module state of charge, expressed as a percentage. |
lithium_ion_string_module[*].ModSoH | uint16 | % | Module SoH | Module state of health, expressed as a percentage. |
lithium_ion_string_module[*].ModCellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the module. |
lithium_ion_string_module[*].ModCellVMaxCell | uint16 | Max Cell Voltage Cell | Cell with maximum voltage. | |
lithium_ion_string_module[*].ModCellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the module. |
lithium_ion_string_module[*].ModCellVMinCell | uint16 | Min Cell Voltage Cell | Cell with minimum voltage. | |
lithium_ion_string_module[*].ModCellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in the module. |
lithium_ion_string_module[*].ModCellTmpMax | int16 | C | Max Cell Temperature | Maximum temperature for all cells in the module. |
lithium_ion_string_module[*].ModCellTmpMaxCell | uint16 | Max Cell Temperature Cell | Cell with maximum temperature. | |
lithium_ion_string_module[*].ModCellTmpMin | int16 | C | Min Cell Temperature | Minimum temperature for all cells in the module. |
lithium_ion_string_module[*].ModCellTmpMinCell | uint16 | Min Cell Temperature Cell | Cell with minimum temperature. | |
lithium_ion_string_module[*].ModCellTmpAvg | int16 | C | Average Cell Temperature | Average temperature for all cells in the module. |
Value names:
St(bitfield32): bit 0STRING_ENABLED, bit 1CONTACTOR_STATUSConFail(enum16): 0NO_FAILURE, 1BUTTON_PUSHED, 2STR_GROUND_FAULT, 3OUTSIDE_VOLTAGE_RANGE, 4STRING_NOT_ENABLED, 5FUSE_OPEN, 6CONTACTOR_FAILURE, 7PRECHARGE_FAILURE, 8STRING_FAULTConSt(bitfield32): bit 0CONTACTOR_0, bit 1CONTACTOR_1, bit 2CONTACTOR_2, bit 3CONTACTOR_3, bit 4CONTACTOR_4, bit 5CONTACTOR_5, bit 6CONTACTOR_6, bit 7CONTACTOR_7, bit 8CONTACTOR_8, bit 9CONTACTOR_9, bit 10CONTACTOR_10, bit 11CONTACTOR_11, bit 12CONTACTOR_12, bit 13CONTACTOR_13, bit 14CONTACTOR_14, bit 15CONTACTOR_15, bit 16CONTACTOR_16, bit 17CONTACTOR_17, bit 18CONTACTOR_18, bit 19CONTACTOR_19, bit 20CONTACTOR_20, bit 21CONTACTOR_21, bit 22CONTACTOR_22, bit 23CONTACTOR_23, bit 24CONTACTOR_24, bit 25CONTACTOR_25, bit 26CONTACTOR_26, bit 27CONTACTOR_27, bit 28CONTACTOR_28, bit 29CONTACTOR_29, bit 30CONTACTOR_30Evt1(bitfield32): bit 0COMMUNICATION_ERROR, bit 1OVER_TEMP_ALARM, bit 2OVER_TEMP_WARNING, bit 3UNDER_TEMP_ALARM, bit 4UNDER_TEMP_WARNING, bit 5OVER_CHARGE_CURRENT_ALARM, bit 6OVER_CHARGE_CURRENT_WARNING, bit 7OVER_DISCHARGE_CURRENT_ALARM, bit 8OVER_DISCHARGE_CURRENT_WARNING, bit 9OVER_VOLT_ALARM, bit 10OVER_VOLT_WARNING, bit 11UNDER_VOLT_ALARM, bit 12UNDER_VOLT_WARNING, bit 13UNDER_SOC_MIN_ALARM, bit 14UNDER_SOC_MIN_WARNING, bit 15OVER_SOC_MAX_ALARM, bit 16OVER_SOC_MAX_WARNING, bit 17VOLTAGE_IMBALANCE_WARNING, bit 18TEMPERATURE_IMBALANCE_ALARM, bit 19TEMPERATURE_IMBALANCE_WARNING, bit 20CONTACTOR_ERROR, bit 21FAN_ERROR, bit 22GROUND_FAULT, bit 23OPEN_DOOR_ERROR, bit 24RESERVED_1, bit 25OTHER_ALARM, bit 26OTHER_WARNING, bit 27RESERVED_2, bit 28CONFIGURATION_ALARM, bit 29CONFIGURATION_WARNINGSetCon(enum16): 1CONNECT_STRING, 2DISCONNECT_STRING
Model 805: Lithium-Ion Module Model
Name in records: lithium-ion-module.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
StrIdx | uint16 | String Index | Index of the string containing the module. | |
ModIdx | uint16 | Module Index | Index of the module within the string. | |
NCell | uint16 | Module Cell Count | Count of all cells in the module. | |
SoC | uint16 | % | Module SoC | Module state of charge, expressed as a percentage. |
DoD | uint16 | % | Depth of Discharge | Depth of discharge for the module. |
SoH | uint16 | % | Module SoH | Module state of health, expressed as a percentage. |
NCyc | uint32 | Cycle Count | Count of cycles executed. | |
V | uint16 | V | Module Voltage | Voltage of the module. |
CellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the module. |
CellVMaxCell | uint16 | Max Cell Voltage Cell | Cell with the maximum voltage. | |
CellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the module. |
CellVMinCell | uint16 | Min Cell Voltage Cell | Cell with the minimum voltage. | |
CellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in the module. |
CellTmpMax | int16 | C | Max Cell Temperature | Maximum temperature for all cells in the module. |
CellTmpMaxCell | uint16 | Max Cell Temperature Cell | Cell with the maximum cell temperature. | |
CellTmpMin | int16 | C | Min Cell Temperature | Minimum temperature for all cells in the module. |
CellTmpMinCell | uint16 | Min Cell Temperature Cell | Cell with the minimum cell temperature. | |
CellTmpAvg | int16 | C | Average Cell Temperature | Average temperature for all cells in the module. |
NCellBal | uint16 | Balanced Cell Count | Number of cells currently being balanced in the module. | |
SN | string | Serial Number | Serial number for the module. | |
lithium-ion-module-cell[*].CellV | uint16 | V | Cell Voltage | Cell terminal voltage. |
lithium-ion-module-cell[*].CellTmp | int16 | C | Cell Temperature | Cell temperature. |
lithium-ion-module-cell[*].CellSt | bitfield32 | Cell Status | Status of the cell. |
Value names:
lithium-ion-module-cell[*].CellSt(bitfield32): bit 0CELL_IS_BALANCING
Model 806: Flow Battery Model
Name in records: flow_battery.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
BatTBD | uint16 | Battery Points To Be Determined | ||
battery_string[*].BatStTBD | uint16 | Battery String Points To Be Determined |
Model 807: Flow Battery String Model
Name in records: flow_battery_string.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Idx | uint16 | String Index | Index of the string within the bank. | |
NMod | uint16 | Module Count | Number of modules in this string. | |
NModCon | uint16 | Connected Module Count | Number of electrically connected modules in this string. | |
ModVMax | uint16 | V | Max Module Voltage | Maximum voltage for all modules in the string. |
ModVMaxMod | uint16 | Max Module Voltage Module | Module with the maximum voltage. | |
ModVMin | uint16 | V | Min Module Voltage | Minimum voltage for all modules in the string. |
ModVMinMod | uint16 | Min Module Voltage Module | Module with the minimum voltage. | |
ModVAvg | uint16 | V | Average Module Voltage | Average voltage for all modules in the string. |
CellVMax | uint16 | V | Max Cell Voltage | Maximum voltage for all cells in the string. |
CellVMaxMod | uint16 | Max Cell Voltage Module | Module containing the cell with the maximum voltage. | |
CellVMaxStk | uint16 | Max Cell Voltage Stack | Stack containing the cell with the maximum voltage. | |
CellVMin | uint16 | V | Min Cell Voltage | Minimum voltage for all cells in the string. |
CellVMinMod | uint16 | Min Cell Voltage Module | Module containing the cell with the minimum voltage. | |
CellVMinStk | uint16 | Min Cell Voltage Stack | Stack containing the cell with the minimum voltage. | |
CellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in the string. |
TmpMax | int16 | C | Max Temperature | Maximum electrolyte temperature for all modules in the string. |
TmpMaxMod | uint16 | Max Temperature Module | Module with the maximum temperature. | |
TmpMin | int16 | C | Min Temperature | Minimum electrolyte temperature for all modules in the string. |
TmpMinMod | uint16 | Min Temperature Module | Module with the minimum temperature. | |
TmpAvg | int16 | C | Average Temperature | Average electrolyte temperature for all modules in the string. |
Evt1 | bitfield32 | String Event 1 | Alarms, warnings and status values. | |
Evt2 | bitfield32 | String Event 2 | Alarms, warnings and status values. | |
EvtVnd1 | bitfield32 | Vendor Event Bitfield 1 | Vendor defined events. | |
EvtVnd2 | bitfield32 | Vendor Event Bitfield 2 | Vendor defined events. | |
module[*].ModIdx | uint16 | Module Index | Index of the module within the string. | |
module[*].ModNStk | uint16 | Stack Count | Number of stacks in this module. | |
module[*].ModSt | bitfield32 | Module Status | Current status of the module. | |
module[*].ModSoC | uint16 | % | Module State of Charge | State of charge for this module. |
module[*].ModOCV | uint16 | V | Open Circuit Voltage | Open circuit voltage for this module. |
module[*].ModV | uint16 | V | External Voltage | External voltage fo this module. |
module[*].ModCellVMax | uint16 | V | Maximum Cell Voltage | Maximum voltage for all cells in this module. |
module[*].ModCellVMaxCell | uint16 | Max Cell Voltage Cell | Cell with the maximum cell voltage. | |
module[*].ModCellVMin | uint16 | V | Minimum Cell Voltage | Minimum voltage for all cells in this module. |
module[*].ModCellVMinCell | uint16 | Min Cell Voltage Cell | Cell with the minimum cell voltage. | |
module[*].ModCellVAvg | uint16 | V | Average Cell Voltage | Average voltage for all cells in this module. |
module[*].ModAnoTmp | uint16 | C | Anolyte Temperature | |
module[*].ModCatTmp | uint16 | C | Catholyte Temperature | |
module[*].ModConSt | bitfield32 | Contactor Status | ||
module[*].ModEvt1 | bitfield32 | Module Event 1 | Alarms, warnings and status values. | |
module[*].ModEvt2 | bitfield32 | Module Event 2 | Alarms, warnings and status values. | |
module[*].ModConFail | enum16 | Connection Failure Reason | ||
module[*].ModSetEna | enum16 | Enable/Disable Module | Enables and disables the module. | |
module[*].ModSetCon | enum16 | Connect/Disconnect Module | Connects and disconnects the module. | |
module[*].ModDisRsn | enum16 | Disabled Reason | Reason why the module is currently disabled. |
Value names:
Evt1(bitfield32): bit 0COMMUNICATION_ERROR, bit 1OVER_TEMP_ALARM, bit 2OVER_TEMP_WARNING, bit 3UNDER_TEMP_ALARM, bit 4UNDER_TEMP_WARNING, bit 5OVER_CHARGE_CURRENT_ALARM, bit 6OVER_CHARGE_CURRENT_WARNING, bit 7OVER_DISCHARGE_CURRENT_ALARM, bit 8OVER_DISCHARGE_CURRENT_WARNING, bit 9OVER_VOLT_ALARM, bit 10OVER_VOLT_WARNING, bit 11UNDER_VOLT_ALARM, bit 12UNDER_VOLT_WARNING, bit 13UNDER_SOC_MIN_ALARM, bit 14UNDER_SOC_MIN_WARNING, bit 15OVER_SOC_MAX_ALARM, bit 16OVER_SOC_MAX_WARNING, bit 17VOLTAGE_IMBALANCE_WARNING, bit 18RESERVED_1, bit 19RESERVED_2, bit 20CONTACTOR_ERROR, bit 21FAN_ERROR, bit 22GROUND_FAULT, bit 23OPEN_DOOR_ERROR, bit 24RESERVED_3, bit 25OTHER_ALARM, bit 26OTHER_WARNING, bit 27FIRE_ALARM, bit 28CONFIGURATION_ALARM, bit 29CONFIGURATION_WARNINGEvt2(bitfield32): bit 0LEAK_ALARM, bit 1PUMP_ALARM, bit 2HIGH_PRESSURE_ALARM, bit 3HIGH_PRESSURE_WARNING, bit 4LOW_FLOW_ALARM, bit 5LOW_FLOW_WARNINGmodule[*].ModSt(bitfield32): bit 0MODULE_ENABLED, bit 1CONTACTOR_STATUSmodule[*].ModConSt(bitfield32): bit 0CONTACTOR_0, bit 1CONTACTOR_1, bit 2CONTACTOR_2, bit 3CONTACTOR_3, bit 4CONTACTOR_4, bit 5CONTACTOR_5, bit 6CONTACTOR_6, bit 7CONTACTOR_7, bit 8CONTACTOR_8, bit 9CONTACTOR_9, bit 10CONTACTOR_10, bit 11CONTACTOR_11, bit 12CONTACTOR_12, bit 13CONTACTOR_13, bit 14CONTACTOR_14, bit 15CONTACTOR_15, bit 16CONTACTOR_16, bit 17CONTACTOR_17, bit 18CONTACTOR_18, bit 19CONTACTOR_19, bit 20CONTACTOR_20, bit 21CONTACTOR_21, bit 22CONTACTOR_22, bit 23CONTACTOR_23, bit 24CONTACTOR_24, bit 25CONTACTOR_25, bit 26CONTACTOR_26, bit 27CONTACTOR_27, bit 28CONTACTOR_28, bit 29CONTACTOR_29, bit 30CONTACTOR_30module[*].ModEvt1(bitfield32): bit 0COMMUNICATION_ERROR, bit 1OVER_TEMP_ALARM, bit 2OVER_TEMP_WARNING, bit 3UNDER_TEMP_ALARM, bit 4UNDER_TEMP_WARNING, bit 5OVER_CHARGE_CURRENT_ALARM, bit 6OVER_CHARGE_CURRENT_WARNING, bit 7OVER_DISCHARGE_CURRENT_ALARM, bit 8OVER_DISCHARGE_CURRENT_WARNING, bit 9OVER_VOLT_ALARM, bit 10OVER_VOLT_WARNING, bit 11UNDER_VOLT_ALARM, bit 12UNDER_VOLT_WARNING, bit 13UNDER_SOC_MIN_ALARM, bit 14UNDER_SOC_MIN_WARNING, bit 15OVER_SOC_MAX_ALARM, bit 16OVER_SOC_MAX_WARNING, bit 17VOLTAGE_IMBALANCE_WARNING, bit 18RESERVED_1, bit 19RESERVED_2, bit 20CONTACTOR_ERROR, bit 21FAN_ERROR, bit 22GROUND_FAULT, bit 23OPEN_DOOR_ERROR, bit 24RESERVED_3, bit 25RESERVED_4, bit 26RESERVED_5, bit 27FIRE_ALARM, bit 28MODULE_CONFIGURATION_ALARM, bit 29MODULE_CONFIGURATION_WARNINGmodule[*].ModEvt2(bitfield32): bit 0LEAK_ALARM, bit 1PUMP_ALARM, bit 2HIGH_PRESSURE_ALARM, bit 3HIGH_PRESSURE_WARNING, bit 4LOW_FLOW_ALARM, bit 5LOW_FLOW_WARNINGmodule[*].ModConFail(enum16): 0NO_FAILURE, 1BUTTON_PUSHED, 2MODULE_GROUND_FAULT, 3OUTSIDE_VOLTAGE_RANGE, 4MODULE_NOT_ENABLED, 5FUSE_OPEN, 6CONTACTOR_FAILURE, 7PRECHARGE_FAILURE, 8MODULE_FAULTmodule[*].ModSetEna(enum16): 1ENABLE_MODULE, 2DISABLE_MODULEmodule[*].ModSetCon(enum16): 1CONNECT_MODULE, 2DISCONNECT_MODULEmodule[*].ModDisRsn(enum16): 0NONE, 1FAULT, 2MAINTENANCE, 3EXTERNAL, 4OTHER
Model 808: Flow Battery Module Model
Name in records: flow_battery_module.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
ModuleTBD | uint16 | Module Points To Be Determined | ||
stack[*].StackTBD | uint16 | Stack Points To Be Determined |
Model 809: Flow Battery Stack Model
Name in records: flow_battery_stack.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
StackTBD | uint16 | Stack Points To Be Determined | ||
cell[*].CellTBD | uint16 | Cell 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.
| Model | Name in records | Points |
|---|---|---|
| 63001 | model_63001 | 59 |
| 63002 | model_63002 | 2 |
| 64001 | model_64001 | 35 |
| 64020 | model_64020 | 30 |
| 64101 | model_64101 | 7 |
| 64111 | model_64111 | 18 |
| 64112 | model_64112 | 58 |
| 64410 | DCSimInterface | 32 |
| 64411 | ACSimInterface | 51 |
| 64412 | DERExploitation | 43 |
| 64413 | PVSimCurves | 5 |
| 64414 | DERSimControls | 9 |
| 64415 | CSIPControl | 5 |
Model 63001: SunSpec Test Model 1
Name in records: model_63001.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
int16_1 | int16 | |||
int16_2 | int16 | |||
int16_3 | int16 | |||
int16_4 | int16 | |||
int16_5 | int16 | |||
int16_u | int16 | |||
uint16_1 | uint16 | |||
uint16_2 | uint16 | |||
uint16_3 | uint16 | |||
uint16_4 | uint16 | |||
uint16_5 | uint16 | |||
uint16_u | uint16 | |||
acc16 | acc16 | |||
acc16_u | acc16 | |||
enum16 | enum16 | |||
enum16_u | enum16 | |||
bitfield16 | bitfield16 | |||
bitfield16_u | bitfield16 | |||
int32_1 | int32 | |||
int32_2 | int32 | |||
int32_3 | int32 | |||
int32_4 | int32 | |||
int32_5 | int32 | |||
int32_u | int32 | |||
uint32_1 | uint32 | |||
uint32_2 | uint32 | |||
uint32_3 | uint32 | |||
uint32_4 | uint32 | |||
uint32_5 | uint32 | |||
uint32_u | uint32 | |||
acc32 | acc32 | |||
acc32_u | acc32 | |||
enum32 | enum32 | |||
enum32_u | enum32 | |||
bitfield32 | bitfield32 | |||
bitfield32_u | bitfield32 | |||
ipaddr | ipaddr | |||
ipaddr_u | ipaddr | |||
int64 | int64 | |||
int64_u | int64 | |||
acc64 | acc64 | |||
acc64_u | acc64 | |||
ipv6addr | ipv6addr | |||
ipv6addr_u | ipv6addr | |||
float32 | float32 | |||
float32_u | float32 | |||
string | string | |||
string_u | string | |||
repeating[*].int16_11 | int16 | |||
repeating[*].int16_12 | int16 | |||
repeating[*].int16_u | int16 | |||
repeating[*].uint16_11 | uint16 | |||
repeating[*].uint16_12 | uint16 | |||
repeating[*].uint16_13 | uint16 | |||
repeating[*].uint16_u | uint16 | |||
repeating[*].int32 | int32 | |||
repeating[*].int32_u | int32 | |||
repeating[*].uint32 | uint32 | |||
repeating[*].uint32_u | uint32 |
Model 63002: SunSpec Test Model 2
Name in records: model_63002.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
repeating[*].int16_1 | int16 | |||
repeating[*].int16_2 | int16 |
Model 64001: Veris Status and Configuration
Name in records: model_64001.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Cmd | enum16 | Command Code | ||
HWRev | uint16 | Hardware Revision | ||
RSFWRev | uint16 | RS FW Revision | ||
OSFWRev | uint16 | OS FW Revision | ||
ProdRev | string | Product Revision | ||
Boots | uint16 | Boot Count | ||
Switch | bitfield16 | DIP Switches | ||
Sensors | uint16 | Num Detected Sensors | ||
Talking | uint16 | Num Communicating Sensors | ||
Status | bitfield16 | System Status | ||
Config | bitfield16 | System Configuration | ||
LEDblink | uint16 | Pct | LED Blink Threshold | |
LEDon | uint16 | Pct | LED On Threshold | |
Reserved | uint16 | |||
Loc | string | Location String | ||
S1ID | enum16 | Sensor 1 Unit ID | ||
S1Addr | uint16 | Sensor 1 Address | ||
S1OSVer | uint16 | Sensor 1 OS Version | ||
S1Ver | string | Sensor 1 Product Version | ||
S1Serial | string | Sensor 1 Serial Num | ||
S2ID | enum16 | Sensor 2 Unit ID | ||
S2Addr | uint16 | Sensor 2 Address | ||
S2OSVer | uint16 | Sensor 2 OS Version | ||
S2Ver | string | Sensor 2 Product Version | ||
S2Serial | string | Sensor 2 Serial Num | ||
S3ID | enum16 | Sensor 3 Unit ID | ||
S3Addr | uint16 | Sensor 3 Address | ||
S3OSVer | uint16 | Sensor 3 OS Version | ||
S3Ver | string | Sensor 3 Product Version | ||
S3Serial | string | Sensor 3 Serial Num | ||
S4ID | enum16 | Sensor 4 Unit ID | ||
S4Addr | uint16 | Sensor 4 Address | ||
S4OSVer | uint16 | Sensor 4 OS Version | ||
S4Ver | string | Sensor 4 Product Version | ||
S4Serial | string | Sensor 4 Serial Num |
Model 64020: Mersen GreenString
Name in records: model_64020.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Aux0Tmp | int16 | C | Aux 0 temperature | |
Aux1Tmp | int16 | C | Aux 1 temperature | |
Aux2Tmp | int16 | C | Aux 2 temperature | |
Aux3Tmp | int16 | C | Aux 3 temperature | |
Aux4Tmp | int16 | C | Aux 4 temperature | |
ProbeTmp | int16 | C | Probe Temperature | |
MainTmp | int16 | C | Main Temperature | |
Sensor1Voltage | int16 | V | Sensor1 Voltage | scale of 0-10V |
Sensor2Voltage | int16 | V | Sensor2 Voltage | scale of 0-10V |
Sensor3Voltage | int16 | V | Sensor3 Voltage | scale of 0-10V |
Sensor4Voltage | int16 | V | Sensor4 Voltage | scale of 0-10V |
Sensor5Voltage | int16 | V | Sensor5 Voltage | scale of 0-10V |
Sensor6Voltage | int16 | V | Sensor6 Voltage | scale of 0-10V |
Sensor7Voltage | int16 | V | Sensor7 Voltage | scale of 0-10V |
Sensor1Current | int16 | A | Sensor1 Current | scale of 4-20mA |
Sensor2Current | int16 | A | Sensor2 Current | in 4-20mA or 4-20mA |
Sensor3Current | int16 | A | Sensor3 Current | in 4-20mA or 4-20mA |
Sensor4Current | int16 | A | Sensor4 Current | in 4-20mA or 4-20mA |
Sensor5Current | int16 | A | Sensor5 Current | in 4-20mA or 4-20mA |
Sensor6Current | int16 | A | Sensor6 Current | in 4-20mA or 4-20mA |
Sensor7Current | int16 | A | Sensor7 Current | in 4-20mA or 4-20mA |
Sensor8 | uint16 | Hz | Sensor8 frequency | frequency in Hz |
Relay1 | uint16 | Relay 1 state | ||
Relay2 | uint16 | Relay 2 state | ||
Relay3 | uint16 | Relay 3 state | ||
ResetAccumulators | uint16 | Reset the accumulators | always 0 in reading, used the code 0xC0DA during the writing for resetting them | |
Reset | uint16 | Reset the system | always 0 in reading, used the code 0xC0DA during the writing for resetting the system | |
repeating[*].SerialNumber | string | Serial number | strings of 16 characters | |
repeating[*].Firmware | string | Firmware version | string of 11 characters | |
repeating[*].Hardware | uint16 | Hardware version |
Model 64101: Eltek Inverter Extension
Name in records: model_64101.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Eltek_Country_Code | uint16 | |||
Eltek_Feeding_Phase | uint16 | |||
Eltek_APD_Method | uint16 | |||
Eltek_APD_Power_Ref | uint16 | |||
Eltek_RPS_Method | uint16 | |||
Eltek_RPS_Q_Ref | uint16 | |||
Eltek_RPS_CosPhi_Ref | int16 |
Model 64111: Basic Charge Controller
Name in records: model_64111.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Port | uint16 | Port Number | ||
BattV | uint16 | V | Battery Voltage | |
ArrayV | uint16 | V | Array Voltage | |
OutputA | uint16 | A | Output Current | |
InputA | uint16 | A | Array Current | |
ChargerSt | enum16 | Operating State | ||
OutputW | uint16 | W | Output Wattage | |
TodayMinBatV | uint16 | V | Today's Minimum Battery Voltage | |
TodayMaxBatV | uint16 | V | Today's Maximum Battery Voltage | |
VOCV | uint16 | V | VOC | |
TodayMaxVOC | uint16 | V | Today's Maximum VOC | |
TodaykWhOutput | uint16 | kWh | Today's kWh | |
TodayAHOutput | uint16 | AH | Today's AH | |
LifeTimeKWHOut | uint16 | kWh | Lifetime kWh | |
LifeTimeAHOut | uint16 | kAH | Lifetime kAH | |
LifeTimeMaxOut | uint16 | W | Lifetime Maximum Output Wattage | |
LifeTimeMaxBatt | uint16 | V | Lifetime Maximum Battery Voltage | |
LifeTimeMaxVOC | uint16 | V | Lifetime Maximum VOC Voltage |
Value names:
ChargerSt(enum16): 0Off, 1Float, 2Bulk, 3Absorb, 4EQ
Model 64112: OutBack FM Charge Controller
Name in records: model_64112.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Port | uint16 | Port Number | ||
CC_Config_fault | bitfield16 | Faults | ||
CC_Config_absorb_V | uint16 | V | Absorb | |
CC_Config_absorb_Hr | uint16 | Tmh | Absorb Time | |
CC_Config_absorb_End_A | uint16 | A | Absorb End | |
CC_Config_rebulk_V | uint16 | V | Rebulk | |
CC_Config_float_V | uint16 | V | Float | |
CC_Config_max_Chg_A | uint16 | A | Maximum Charge | |
CC_Config_equalize_V | uint16 | V | Equalize | |
CC_Config_equalize_Hr | uint16 | Tmh | Equalize Time | |
CC_Config_auto_equalize | uint16 | Tmd | Auto Equalize Interval | |
CC_Config_MPPT_mode | enum16 | MPPT mode | ||
CC_Config_sweep_width | enum16 | Sweep Width | ||
CC_Config_sweep_max | enum16 | Sweep Maximum | ||
CC_Config_U_Pick_Duty_cyc | uint16 | Pct | U-Pick PWM Duty Cycle | |
CC_Config_grid_tie | enum16 | Grid Tie Mode | ||
CC_Config_temp_comp | enum16 | Temp Comp Mode | ||
CC_Config_temp_comp_llimt | uint16 | V | Temp Comp Lower Limit | |
CC_Config_temp_comp_hlimt | uint16 | V | Temp Comp Upper Limit | |
CC_Config_auto_restart | enum16 | Auto Restart Mode | ||
CC_Config_wakeup_VOC | uint16 | V | Wakeup VOC Change | |
CC_Config_snooze_mode_A | uint16 | A | Snooze Mode | |
CC_Config_wakeup_interval | uint16 | Tms | Wakeup Interval | |
CC_Config_AUX_mode | enum16 | AUX Output Mode | ||
CC_Config_AUX_control | enum16 | AUX Output Control | ||
CC_Config_AUX_state | enum16 | AUX Output State | ||
CC_Config_AUX_polarity | enum16 | AUX Output Polarity | ||
CC_Config_AUX_L_Batt_disc | uint16 | V | AUX Low Battery Disconnect | |
CC_Config_AUX_L_Batt_rcon | uint16 | V | AUX Low Battery Reconnect | |
CC_Config_AUX_L_Batt_dly | uint16 | Tms | AUX Low Battery Disconnect Delay | |
CC_Config_AUX_Vent_fan_V | uint16 | V | AUX Vent Fan | |
CC_Config_AUX_PV_triggerV | uint16 | V | AUX PV Trigger | |
CC_Config_AUX_PV_trg_h_tm | uint16 | Tms | AUX PV Trigger Hold Time | |
CC_Config_AUX_Nlite_ThrsV | uint16 | V | AUX Night Light Threshold | |
CC_Config_AUX_Nlite_On_tm | uint16 | Tmh | AUX Night Light On Time | |
CC_Config_AUX_Nlite_On_hist | uint16 | Tms | AUX Night Light On Hysteresis | |
CC_Config_AUX_Nlite_Off_hist | uint16 | Tms | AUX Night Light Off Hysteresis | |
CC_Config_AUX_Error_batt_V | uint16 | V | AUX Error Output Low Battery | |
CC_Config_AUX_Divert_h_time | uint16 | Tms | AUX Divert Hold Time | |
CC_Config_AUX_Divert_dly_time | uint16 | Tms | AUX Divert Delay Time | |
CC_Config_AUX_Divert_Rel_V | uint16 | V | AUX Divert Relative | |
CC_Config_AUX_Divert_Hyst_V | uint16 | V | AUX Divert Hysteresis | |
CC_Config_MajorFWRev | uint16 | FM CC Major Firmware Number | ||
CC_Config_MidFWRev | uint16 | FM CC Mid Firmware Number | ||
CC_Config_MinorFWRev | uint16 | FM CC Minor Firmware Number | ||
CC_Config_DataLog_Day_offset | uint16 | Tmd | Set Data Log Day Offset | |
CC_Config_DataLog_Cur_Day_off | uint16 | Tmd | Current Data Log Day Offset | |
CC_Config_DataLog_Daily_AH | uint16 | Ah | Data Log Daily (Ah) | |
CC_Config_DataLog_Daily_KWH | uint16 | kWh | Data Log Daily (kWh) | |
CC_Config_DataLog_Max_Out_A | uint16 | A | Data Log Daily Maximum Output (A) | |
CC_Config_DataLog_Max_Out_W | uint16 | W | Data Log Daily Maximum Output (W) | |
CC_Config_DataLog_Absorb_T | uint16 | Tms | Data Log Daily Absorb Time | |
CC_Config_DataLog_Float_T | uint16 | Tms | Data Log Daily Float Time | |
CC_Config_DataLog_Min_Batt_V | uint16 | V | Data Log Daily Minimum Battery | |
CC_Config_DataLog_Max_Batt_V | uint16 | V | Data Log Daily Maximum Battery | |
CC_Config_DataLog_Max_Input_V | uint16 | V | Data Log Daily Maximum Input | |
CC_Config_DataLog_Clear | uint16 | Data Log Clear | ||
CC_Config_DataLog_Clr_Comp | uint16 | Data Log Clear Complement |
Value names:
CC_Config_MPPT_mode(enum16): 0Auto, 1U_Pick, 2WindCC_Config_sweep_width(enum16): 0Half, 1FullCC_Config_sweep_max(enum16): 0Eighty_Percent, 1Eighty_Five_Percent, 2Ninty_Percent, 3Ninty_Nine_PercentCC_Config_grid_tie(enum16): 0Disabled, 1EnabledCC_Config_temp_comp(enum16): 0Wide, 1LimitedCC_Config_auto_restart(enum16): 0Off, 1Every_90_Minutes, 2Every_90_Minutes_if_Absorb_or_FloatCC_Config_AUX_mode(enum16): 0Float, 1Diversion_Relay, 2Diversion_Solid_St, 3Low_Batt_Disconnect, 4Remote, 5Vent_Fan, 6PV_Trigger, 7Error_Output, 8Night_LightCC_Config_AUX_control(enum16): 0Off, 1Auto, 2OnCC_Config_AUX_state(enum16): 0Disabled, 1EnabledCC_Config_AUX_polarity(enum16): 0Low, 1High
Model 64410: DC Simulator Control Interface
Name in records: DCSimInterface.
A generic DC simulator/power supply control interface for DER electrical testing.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
VMaxLim | uint16 | V | Maximum Voltage | Upper Voltage Protection Limit |
PMaxLim | uint16 | W | Maximum Power | Upper Power Protection Limit |
IMaxLim | uint16 | A | Maximum Current | Upper Current Protection Limit |
Mode | enum16 | CV or CC Mode | Constant Voltage (CV) or Constant Current (CC) Mode | |
Ena | enum16 | Power On/Off | Power On/Off | |
Reset | enum16 | Reset Device | Reset Device | |
VSet | uint16 | V | Voltage Setpoint | Voltage Setpoint |
PSet | uint16 | W | Power Setpoint | Power Setpoint |
ISet | uint16 | A | Current Setpoint | Current Setpoint |
EN50530 | enum16 | EN50530 Mode | EN50530 Mode - Enable or disable EN50530 profile mode | |
Vmpp | uint16 | V | EN50530 MPP Voltage | EN50530 MPP Voltage |
Pmpp | uint16 | W | EN50530 MPP Power | EN50530 MPP Power |
GSet | uint16 | W/m^2 | Irradiance Setpoint | Irradiance Setpoint |
VSlewRate | uint16 | V/s | Voltage Slew Rate | Voltage Slew Rate |
PSlewRate | uint16 | W/s | Power Slew Rate | Power Slew Rate |
ISlewRate | uint16 | A/s | Current Slew Rate | Current Slew Rate |
EnaProf | enum16 | Enable Profile | Start/Stop the Profile | |
AdptProfReq | uint16 | Profile Adoption Request | Index of profile points to adopt. First curve index is 1. | |
AdptProfRslt | enum16 | Adopt Profile Result | Result of last adopt profile operation. | |
V | int32 | Measured Voltage | Measured Voltage | |
P | int32 | Measured Power | Measured Power | |
I | int32 | Measured Current | Measured Current | |
Errors | string | Errors | Error States | |
NPt | uint16 | Number Of Points | Number of profile points supported. | |
NProf | uint16 | Stored Profile Count | Number of stored profiles supported. | |
Prof[*].ActPt | uint16 | Active Points | Number of active points. | |
Prof[*].DeptRef | bitfield32 | Dependent References | Profile references. | |
Prof[*].Pt[*].Tms | uint16 | Sec | Profile Time | Profile time. |
Prof[*].Pt[*].V | uint16 | V | Voltage Point | Profile voltage point in Volts. |
Prof[*].Pt[*].P | uint16 | W | Power Point | Profile power point in Watts. |
Prof[*].Pt[*].I | uint16 | A | Current Point | Profile current point in Amps. |
Prof[*].Pt[*].G | uint16 | Pct | Irradiance Point | Profile irradiance point as percentage. |
Value names:
Mode(enum16): 0CV, 1CCEna(enum16): 1ON, 0OFFReset(enum16): 1RESET, 0DO_NOT_RESETEN50530(enum16): 1EN50530, 0DO_NOT_EN50530EnaProf(enum16): 1START, 0STOPAdptProfRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILEDProf[*].DeptRef(bitfield32): bit 0VOLTAGE, bit 1POWER, bit 2CURRENT, bit 3IRRADIANCE
Model 64411: AC Simulator Control Interface
Name in records: ACSimInterface.
A generic AC simulator/power supply control interface for DER electrical testing.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Phases | uint16 | Active Phases | Set the number of active phases for the power supply | |
PhaseAngle | uint16 | Phase Angle | Phase angle (deg) between phases. 0 for signle phase, 120 for two phase, 120 for three phase. | |
VNom | uint16 | V | Nominal Voltage | Nominal L-N Voltage |
VMax | uint16 | V | Maximum Voltage | Maximum Voltage Protection Level |
IMax | uint16 | A | Maximum Current | Maximum Current Protection Level |
Freq | uint16 | Hz | Frequency | Frequency Setpoint |
Output | enum16 | Output State | AC Output State | |
Relay | enum16 | Relay State | AC Relay State | |
Regen | enum16 | Regeneration State | Regeneration State | |
VSet | uint16 | V | Voltage Setpoint | Voltage Setpoint (all phases) |
VSetA | uint16 | V | Voltage Setpoint Phase A | Voltage Setpoint Phase A |
VSetB | uint16 | V | Voltage Setpoint Phase B | Voltage Setpoint Phase B |
VSetC | uint16 | V | Voltage Setpoint Phase C | Voltage Setpoint Phase C |
FreqSlew | uint16 | Hz/s | Frequency Slew Rate | Frequency Slew Rate |
VSlew | uint16 | V/s | Voltage Slew Rate | Voltage Slew Rate |
VA | int32 | V | Measured Voltage Phase A | Measured Voltage Phase A |
VB | int32 | V | Measured Voltage Phase B | Measured Voltage Phase B |
VC | int32 | V | Measured Voltage Phase C | Measured Voltage Phase C |
Hz | int32 | Hz | Measured Frequency | Measured Frequency |
IA | int32 | A | Measured Current Phase A | Measured Current Phase A |
IB | int32 | A | Measured Current Phase B | Measured Current Phase B |
IC | int32 | A | Measured Current Phase C | Measured Current Phase C |
VHarA | string | V | Voltage Harmonics Phase A | Voltage Harmonics Pct, Phase A (comma seperated string for harmonics 1-50) |
VHarB | string | V | Voltage Harmonics Phase B | Voltage Harmonics Pct, Phase B (comma seperated string for harmonics 1-50) |
VHarC | string | V | Voltage Harmonics Phase C | Voltage Harmonics Pct, Phase C (comma seperated string for harmonics 1-50) |
IHarA | string | A | Current Harmonics Phase A | Current Harmonics Pct, Phase A (comma seperated string for harmonics 1-50) |
IHarB | string | A | Current Harmonics Phase B | Current Harmonics Pct, Phase B (comma seperated string for harmonics 1-50) |
IHarC | string | A | Current Harmonics Phase C | Current Harmonics Pct, Phase C (comma seperated string for harmonics 1-50) |
IIntHarA | string | A | Current Interharmonics Phase A | Current Interharmonics Pct, Phase A (comma seperated string for interharmonics 1-50) |
IIntHarB | string | A | Current Interharmonics Phase B | Current Interharmonics Pct, Phase B (comma seperated string for interharmonics 1-50) |
IIntHarC | string | A | Current Interharmonics Phase C | Current Interharmonics Pct, Phase C (comma seperated string for interharmonics 1-50) |
VThdA | uint16 | % | Voltage THD Phase A | Voltage THD Phase A |
VThdB | uint16 | % | Voltage THD Phase B | Voltage THD Phase B |
VThdC | uint16 | % | Voltage THD Phase C | Voltage THD Phase C |
IThdA | uint16 | % | Current THD Phase A | Current THD Phase A |
IThdB | uint16 | % | Current THD Phase B | Current THD Phase B |
IThdC | uint16 | % | Current THD Phase C | Current THD Phase C |
EnaProf | enum16 | Enable Profile | Start/Stop the AC Profile | |
ProfRslt | enum16 | Profile Result | Result of last profile operation. | |
NProf | uint16 | Stored Profile Count | Number of stored profiles supported. | |
NPt | uint16 | Max Profile Point Count | Max profile points in the profiles. | |
Prof[*].Name | string | Profile Name | Profile name. | |
Prof[*].ActPt | uint16 | Active Points | Number of active points. | |
Prof[*].Pt[*].Tms | uint16 | Sec | Profile Time | Profile time. |
Prof[*].Pt[*].VA | uint16 | V | Voltage Point | Profile voltage phase A point in Volts. |
Prof[*].Pt[*].VB | uint16 | V | Voltage Point Phase B | Profile voltage phase B point in Volts. |
Prof[*].Pt[*].VC | uint16 | V | Voltage Point Phase C | Profile voltage phase C point in Volts. |
Prof[*].Pt[*].Hz | uint16 | Hz | Frequency Point | Profile frequency point in Hz. |
Prof[*].Pt[*].PhaseAngleA | uint16 | Degrees | Phase Angle A | Profile phase A angle in degrees. |
Prof[*].Pt[*].PhaseAngleB | uint16 | Degrees | Phase Angle B | Profile phase B angle in degrees. |
Prof[*].Pt[*].PhaseAngleC | uint16 | Degrees | Phase Angle C | Profile phase C angle in degrees. |
Value names:
Output(enum16): 0OFF, 1ONRelay(enum16): 0OPEN, 1CLOSEDRegen(enum16): 0OFF, 1ONEnaProf(enum16): 0STOP, 1START, 2TRIGGERProfRslt(enum16): 0IN_PROGRESS, 1COMPLETED, 2FAILED
Model 64412: DER Cyber Exploitation
Name in records: DERExploitation.
Operations that represent a hacked DER device
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
DAManipulation | enum16 | DA Manipulation | Modify the device ID of the DER | |
FalsifyDeviceIdentity | enum16 | Falsify Device Identity | Change the DER manufacturer and model | |
MeasPAlwaysNameplate | enum16 | Meas P Always Nameplate | Set the DER meas to always be at nameplate power | |
MeasQAlwaysMinimum | enum16 | Meas Q Always Minimum | Set the DER to always be at minimum reactive power | |
MeasQAlwaysMaximum | enum16 | Meas Q Always Maximum | Set the DER to always be at maximum reactive power | |
MeasQAlwaysZero | enum16 | Meas Q Always Zero | Set the DER to always be at zero reactive power | |
MeasZeroP | enum16 | Meas Zero P | Set the DER to always be at zero P, Q, and S | |
MeasInvertQ | enum16 | Meas Invert Q | Set the DER to reverse the Q measurement data | |
MeasLowV | enum16 | Meas Low V | Set the DER to always measure low voltage | |
MeasHighV | enum16 | Meas High V | Set the DER to always measure high voltage | |
MeasLowL1V | enum16 | Meas Low L1 V | Set the DER to always measure low line 1 voltage | |
MeasHighL1V | enum16 | Meas High L1 V | Set the DER to always measure high line 1 voltage | |
MeasLowF | enum16 | Meas Low F | Set the DER to always measure low frequency | |
MeasHighF | enum16 | Meas High F | Set the DER to always measure high frequency | |
MeasLowAmps | enum16 | Meas Low Amps | Set the DER to always measure low current | |
MeasHighAmps | enum16 | Meas High Amps | Set the DER to always measure high current | |
MeasHighS | enum16 | Meas High S | Set the DER to always measure high apparent power | |
MeasLowS | enum16 | Meas Low S | Set the DER to always measure low apparent power | |
MeasHighQ | enum16 | Meas High Q | Set the DER to always measure high reactive power | |
MeasLowQ | enum16 | Meas Low Q | Set the DER to always measure low reactive power | |
MeasLowPF | enum16 | Meas Low PF | Set the DER to always measure low power factor | |
MeasLowReversedPF | enum16 | Meas Low Reversed PF | Set the DER to always measure low reversed power factor | |
NameplateHighP | enum16 | Nameplate High P | Set the DER nameplate power to be high | |
NameplateLowP | enum16 | Nameplate Low P | Set the DER nameplate power to be low | |
NameplateHighS | enum16 | Nameplate High S | Set the DER nameplate apparent power to be high | |
NameplateLowS | enum16 | Nameplate Low S | Set the DER nameplate apparent power to be low | |
NameplateHighQ | enum16 | Nameplate High Q | Set the DER nameplate reactive power to be high | |
NameplateLowQ | enum16 | Nameplate Low Q | Set the DER nameplate reactive power to be low | |
NameplateHighNomV | enum16 | Nameplate High Nom V | Set the DER nameplate voltage to be high | |
NameplateLowNomV | enum16 | Nameplate Low Nom V | Set the DER nameplate voltage to be low | |
NameplateLowAmps | enum16 | Nameplate Low Amps | Set the DER nameplate current to be low | |
NameplateLowVarmaxinj | enum16 | Nameplate Low Varmaxinj | Set the DER nameplate VarMaxInj to be low | |
NameplateLowVarmaxabs | enum16 | Nameplate Low Varmaxabs | Set the DER nameplate VarMaxAbs to be low | |
NameplateLowPF | enum16 | Nameplate Low PF | Set the DER nameplate power factor to be low | |
SettingsHighNomV | enum16 | Settings High Nom V | Set the DER settings voltage to be high | |
SettingsLowAmps | enum16 | Settings Low Amps | Set the DER settings current to be low | |
SettingsHighP | enum16 | Settings High P | Set the DER settings power to be high | |
SettingsLowP | enum16 | Settings Low P | Set the DER settings power to be low | |
SettingsHighVAMax | enum16 | Settings High VAMax | Set the DER settings VAMax to be high | |
SettingsHighVarmaxinj | enum16 | Settings High Varmaxinj | Set the DER settings VarMaxInj to be high | |
SettingsHighVarmaxabs | enum16 | Settings High Varmaxabs | Set the DER settings VarMaxAbs to be high | |
ChangeCommonModelID | enum16 | Change Common Model ID | Change the common model ID | |
ChangeCommonModelLength | enum16 | Change Common Model Length | Change the common model length |
Value names:
DAManipulation(enum16): 0OFF, 1ONFalsifyDeviceIdentity(enum16): 0OFF, 1ONMeasPAlwaysNameplate(enum16): 0OFF, 1ONMeasQAlwaysMinimum(enum16): 0OFF, 1ONMeasQAlwaysMaximum(enum16): 0OFF, 1ONMeasQAlwaysZero(enum16): 0OFF, 1ONMeasZeroP(enum16): 0OFF, 1ONMeasInvertQ(enum16): 0OFF, 1ONMeasLowV(enum16): 0OFF, 1ONMeasHighV(enum16): 0OFF, 1ONMeasLowL1V(enum16): 0OFF, 1ONMeasHighL1V(enum16): 0OFF, 1ONMeasLowF(enum16): 0OFF, 1ONMeasHighF(enum16): 0OFF, 1ONMeasLowAmps(enum16): 0OFF, 1ONMeasHighAmps(enum16): 0OFF, 1ONMeasHighS(enum16): 0OFF, 1ONMeasLowS(enum16): 0OFF, 1ONMeasHighQ(enum16): 0OFF, 1ONMeasLowQ(enum16): 0OFF, 1ONMeasLowPF(enum16): 0OFF, 1ONMeasLowReversedPF(enum16): 0OFF, 1ONNameplateHighP(enum16): 0OFF, 1ONNameplateLowP(enum16): 0OFF, 1ONNameplateHighS(enum16): 0OFF, 1ONNameplateLowS(enum16): 0OFF, 1ONNameplateHighQ(enum16): 0OFF, 1ONNameplateLowQ(enum16): 0OFF, 1ONNameplateHighNomV(enum16): 0OFF, 1ONNameplateLowNomV(enum16): 0OFF, 1ONNameplateLowAmps(enum16): 0OFF, 1ONNameplateLowVarmaxinj(enum16): 0OFF, 1ONNameplateLowVarmaxabs(enum16): 0OFF, 1ONNameplateLowPF(enum16): 0OFF, 1ONSettingsHighNomV(enum16): 0OFF, 1ONSettingsLowAmps(enum16): 0OFF, 1ONSettingsHighP(enum16): 0OFF, 1ONSettingsLowP(enum16): 0OFF, 1ONSettingsHighVAMax(enum16): 0OFF, 1ONSettingsHighVarmaxinj(enum16): 0OFF, 1ONSettingsHighVarmaxabs(enum16): 0OFF, 1ONChangeCommonModelID(enum16): 0OFF, 1ONChangeCommonModelLength(enum16): 0OFF, 1ON
Model 64413: PV Curves
Name in records: PVSimCurves.
Current-Voltage and Power-Voltage Profiles for PV Simulation.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
IVLen | count | IV length | Number of points in the IV curve. | |
Irr | uint16 | W/m^2 | POA Irradiance | Plane of Array Irradiance |
IV[*].P | float32 | W | Power | Power |
IV[*].I | float32 | A | Current | Current |
IV[*].V | float32 | V | Voltage | Voltage |
Model 64414: DER Simulation Controls
Name in records: DERSimControls.
Configuration parameters for the DER device simulator.
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
Time | string | Time offset | Time offset for simulation formatted 'HH:MM:SS' | |
Temperature | float32 | Ambient temperature (degrees Celsius) | ||
GridModelSource | string | The data source for the grid model. 'csv' or 'const' | ||
IrradianceModelSource | string | The data source for the irradiance model. 'csv' or 'const' | ||
Irradiance | float32 | The irradiance on the DER device (W/m^2) for the 'const' irradiance model | ||
GridVoltageA | float32 | Phase A RMS Voltage (pu) for the 'const' grid model | ||
GridVoltageB | float32 | Phase B RMS Voltage (pu) for the 'const' grid model | ||
GridVoltageC | float32 | Phase C RMS Voltage (pu) for the 'const' grid model | ||
GridFrequency | float32 | Grid frequency (Hz) for the 'const' grid model |
Model 64415: CSIP Client Control
Name in records: CSIPControl.
CSIP Client Control for Alarms and Error tests
| Point | Type | Units | Label | Description |
|---|---|---|---|---|
LogEventEna | enum16 | LogEvent Mode Enable | Enable or disable the LogEvent mode | |
HTTPMsg | enum16 | HTTP Message Mode Enable | Enable or disable the HTTP Message mode | |
COMM004Cert | enum16 | COMM-004 Certificate | Select COMM-004 certificate type | |
SubscribedResource | string | Subscribed Resource URL | The URL of the resource to subscribe to | |
SubscriptionEna | enum16 | Subscribtion Enable | Enable or disable the Subscription mode |
Value names:
LogEventEna(enum16): 0DISABLED, 1ENABLEDHTTPMsg(enum16): 0DISABLED, 1ENABLEDCOMM004Cert(enum16): 0Default Certificate, 1COMM_004A, 2COMM_004B, 3COMM_004C, 4COMM_004D, 5COMM_004E, 6COMM_004F, 7COMM_004GSubscriptionEna(enum16): 0DISABLED, 1ENABLED
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:
- finds SunSpec devices (inverters, meters, batteries) on Modbus TCP, either from a static list or by scanning a subnet;
- walks each device's SunSpec model chain and then reads every model on a fixed schedule;
- decodes each model's registers into named points with the official SunSpec model definitions (scale factors applied, "not implemented" values mapped to null);
- buffers the samples in a local SQLite database;
- 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.
| Crate | Responsibility |
|---|---|
types | Shared 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)). |
discovery | IPv4 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-client | Modbus TCP client on tokio-modbus 0.17: splits reads at 125 registers, retries with exponential backoff, reconnects after connection errors, resolves hostnames. |
sunspec-parser | The 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-actor | The 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. |
buffer | BufferStore, a SQLite queue (sqlx 0.8): batched enqueue, dequeue of the oldest rows, delete by ID, row-count and age retention. |
avro-kafka | The 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-app | The binary and a library (collector_app::run, run_with_publisher) with the orchestration modules: config, devices, ingest, uplink, retention, health, metrics_server, systemd. |
sunspec-sim | A 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:
- If
--print-schemaor--print-modelsis given, prints the Avro schema or the model catalog and exits. - Initialises logging (
RUST_LOG,SUNSPEC_LOG_FORMAT). - Installs the SIGINT/SIGTERM handler before loading the configuration, so a stop during startup still takes the graceful path.
- 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. - 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:
- creates the shutdown
watchchannel and the sharedHealthstate; - binds
metrics.portand starts the HTTP server for/metrics,/healthzand/devices; if the port cannot be bound, startup fails with an error containingbind metrics port <port>; - opens the SQLite buffer (failure ends the process);
- creates the bounded sample channel (
runtime.channel_capacity, default 256); - starts the ingest, uplink, retention and device tasks;
- tells systemd it is ready (
READY=1, Linux only) and starts the watchdog task; - 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(orSUNSPEC_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.subnetis scanned. Every host that accepts a TCP connection on the Modbus port yields one device per entry indiscovery.unit_ids. The scan is repeated everydiscovery.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.
- Connect. A
ModbusClientis created for the device withpoller.request_timeout_msas both the connect timeout and the per-request timeout. - 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 theSunSmarker (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 bylength + 2, and stop at model ID0xFFFF. 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. - 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 topoller.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 oneSample. The device'sModelSchedule(frompoller.include_models,exclude_modelsandmodel_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):
| Error | Cause |
|---|---|
Modbus | Connect failure, or a timeout/transport error while discovering the model chain. |
NoSunSpecMap | No SunS marker at any candidate base address. |
MalformedChain | Address or length overflow, more than 256 models, or no models before the end marker. |
NothingToPoll | The include and exclude lists leave none of the discovered models; retried with backoff, rediscovering the chain each time. |
TooManyFailedCycles | 10 consecutive poll cycles in which at least one model read failed. |
Stalled | No successful read for the stall timeout (see below). |
ChannelClosed | The 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 mostmodbus.max_batch_sizeregisters. 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_msoptionally 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_counttimes (default 2) after the first attempt, waitingmodbus.retry_backoff_ms x 2^attempt(default 100 ms, doubling) up tomodbus.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
localhostmay resolve to::1before127.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:
| Models | Description |
|---|---|
| 1 | Common (manufacturer, model, serial number) |
| 2-19 | Aggregator, secure dataset and certificate models, network and communication interfaces |
| 101-103, 111-113 | Inverters, single/split/three phase (integer + scale factor, and float) |
| 120-145 | Nameplate, settings, status, controls, storage, and the IEEE 1547-2003 era control curves |
| 160 | Multiple MPPT (repeating modules) |
| 201-204, 211-214, 220 | AC meters (integer + scale factor, float, secure readings) |
| 302-308, 401-404, 501-502, 601 | Environmental sensors, string combiners, solar modules, tracker controller |
| 701-715 | IEEE 1547-2018 DER: measurements, capacity, controls, curves, trip settings, storage capacity, DC measurement |
| 801-809 | Storage: battery base, lithium-ion and flow battery banks, strings and modules |
| 63001-64415 | SunSpec 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:
count1: a fixed sub-group, namedgroup.Point;countn > 1: n repetitions, namedgroup[i].Point;count0: repeated to fill the rest of the model;countnaming a point: repeated as many times as that point's value. The point may belong to an enclosing group, and0xFFFF(not implemented) means no repetitions.
For example, model 160 publishes
module[0].DCA,module[1].DCAand so on. The IEEE 1547 curve models nest repeating groups sized by top-level points: model 705 (volt-var) hasNCrvcurves ofNPtpoints each, published asCrv[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
IDandLheader points,sunssf(scale factor) points andpadpoints 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 type | Published as | Not implemented |
|---|---|---|
int16 | long (double if scaled) | 0x8000 |
uint16, enum16, bitfield16 | long (double if scaled) | 0xFFFF |
count, acc16 | long (double if scaled) | 0 |
int32 | long (double if scaled) | 0x80000000 |
uint32, enum32, bitfield32 | long (double if scaled) | 0xFFFFFFFF |
acc32, acc64, ipaddr | long (double if scaled) | 0 |
int64 | long (double if scaled) | 0x8000000000000000 |
uint64 | long (double if scaled) | 0xFFFFFFFFFFFFFFFF |
float32, float64 | double | NaN |
string | string, trimmed of NULs and whitespace | empty after trimming |
eui48 | string of hex digits | all registers 0xFFFF |
ipv6addr | string of hex digits | all 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
sunssfpoint. - 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].DCAuses the top-levelDCA_SF). - If the scale factor point itself is not implemented (
0x8000), the point isNull: 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
uplink::run drains the buffer to the publisher:
- Dequeue the oldest
buffer.batch_sizerows (default 100). - 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. - 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.
- Delete only the rows Kafka acknowledged and count them in
sunspec_uplink_records_total. Rows that could never be encoded are deleted and counted ascorrupt. Rows that were not acknowledged stay in the buffer, are counted insunspec_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 againwith 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:
| Where | Counter |
|---|---|
| Channel full for a whole poll interval | sunspec_samples_dropped_total{reason="channel_full"} |
| SQLite write failed | sunspec_buffer_dropped_total{reason="write_error"} |
Row older than buffer.max_age_s | sunspec_buffer_dropped_total{reason="max_age"} |
More than buffer.max_rows rows | sunspec_buffer_dropped_total{reason="max_rows"} |
| Row cannot be parsed or encoded | sunspec_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.
| Area | Metrics |
|---|---|
| Devices | 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_devices_known |
| Buffer | sunspec_samples_dropped_total{reason}, sunspec_buffer_writes_total, sunspec_buffer_dropped_total{reason}, sunspec_buffer_rows, sunspec_buffer_oldest_age_seconds |
| Uplink | sunspec_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:
- The shutdown flag is set on the
watchchannel. The HTTP server, the retention task and the watchdog stop. - 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.
- Ingest writes everything still queued to the buffer and returns.
- 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 ownwatchchannel, only after ingest has finished. - 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:
| Task | Count | Lifetime |
|---|---|---|
| Signal handler | 1 | Until SIGINT or SIGTERM |
Device set (devices::run) | 1 | Until shutdown; runs subnet scans |
Device supervisor (run_device) | 1 per device, in a JoinSet | Until shutdown |
| Ingest | 1 | Until the sample channel closes |
| Uplink | 1 | Until its stop signal, plus the final drain |
| Retention | 1 | Until shutdown |
HTTP server (/metrics, /healthz, /devices) | 1 | Until shutdown |
| systemd watchdog | 0 or 1 (Linux, when enabled) | Until shutdown |
Coordination:
- Shutdown: a
tokio::sync::watch::channel(bool)shared by all tasks; the uplink has a secondwatchchannel so it stops after ingest. - Samples: one bounded
tokio::sync::mpscchannel (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/healthzand 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 bycollector-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_concurrencypermits.
Configuration
Values are resolved in this order, later sources winning:
- compiled defaults;
- the config file given with
--config PATHorSUNSPEC_CONFIG(.jsonis parsed as JSON, anything else as TOML); 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.
| Component | Failure | Recovery |
|---|---|---|
| Modbus request | Timeout, transport or connect error, or the device closed the connection | Retry with backoff on a new connection |
| Modbus request | Exception | Not retried (except Server Device Busy); connection kept |
| Device session | Connect, discovery, 10 failed cycles, stall | Supervisor restarts it with exponential backoff |
| Sample channel | Full for a poll interval | Sample dropped and counted |
| Buffer write | SQLite error | Batch dropped and counted (write_error) |
| Buffer row | Not parsable or not encodable | Row deleted and counted (corrupt) |
| Kafka publish | Not acknowledged | Row kept and retried first; uplink backs off when a whole batch fails, ending early once unreachable brokers answer again |
| Subnet scan | Host unreachable | Host skipped; found on a later rescan |
| Ingest or uplink | Stuck | /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
| Component | Choice |
|---|---|
| Async runtime | Tokio (multi-threaded) |
| Modbus TCP | tokio-modbus 0.17 |
| SunSpec models | Official SunSpec JSON definitions, parsed with serde_json |
| Buffer | SQLite through sqlx 0.8 (bundled SQLite) |
| Serialization | JSON in the buffer, Avro single-object (apache-avro 0.21) on the wire |
| Kafka | rdkafka 0.39 (librdkafka 2.12, built from source with SSL and zstd) |
| HTTP | axum 0.8 |
| Metrics | metrics 0.22 with metrics-exporter-prometheus 0.13 |
| Logging | tracing with tracing-subscriber (text or JSON) |
| Errors | thiserror in libraries, anyhow in the application |
Platform support
| Platform | Status | Notes |
|---|---|---|
| Linux x86_64 | Supported | systemd readiness and watchdog; release archives (glibc 2.18+) and Docker images |
| Linux aarch64 | Supported | Cross-compiled with cross and docker/Dockerfile.cross; release archives (glibc 2.18+) and Docker images (see Building & Packaging) |
| macOS | Development | systemd readiness and watchdog are no-ops |
| Windows | Not tested | Only 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
| Tool | Version | Purpose |
|---|---|---|
| Rust | 1.88+ (MSRV, rust-version in the workspace Cargo.toml) | Compiler and cargo |
| cmake | 3.x | Builds librdkafka from source (rdkafka's cmake-build feature) |
| C/C++ toolchain | any | Compiles librdkafka and the bundled SQLite and zstd |
| pkg-config | any | Locates OpenSSL on Linux |
| OpenSSL headers | 3.x | TLS support in librdkafka (libssl-dev) |
| libcurl headers | any | Needed 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
| Tool | Purpose |
|---|---|
| Docker with Compose v2 | Docker images and the local stack (Kafka, simulator, collector, Prometheus) |
cross | aarch64 cross-compilation |
cargo-audit | The security audit CI runs (cargo install cargo-audit --locked) |
| mdBook 0.4.40 | Building this documentation |
sqlite3 CLI | Inspecting 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
SunSmarker at the base address, each model's header (ID, length) and a zero-filled body, then the end marker0xFFFF 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
| Flag | Default | Meaning |
|---|---|---|
--bind ADDR:PORT | 0.0.0.0:1502 | Listen address |
--units LIST | 1 | Unit IDs to serve, each with the same model chain |
--models LIST | 1,103,120,160:48 | Model 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 ADDR | 40000 | Base address of the SunS marker |
--rtu | off | Speak 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 FILE | none | Replay 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" }).
| Item | Purpose |
|---|---|
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::Zeros | Exception 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):
| Method | Effect |
|---|---|
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
| File | Covers |
|---|---|
crates/sunspec-parser/tests/decode_tests.rs | Enum 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.rs | Reads 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.rs | RTU 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.rs | Discovery 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.rs | Enqueue, 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.rs | Single-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.rs | The 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.rs | Ignored by default; see below |
crates/collector-app/tests/app_tests.rs | The 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.rs | TOML 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.rs | Polling 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.rs | Framing 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.rs | Every 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.rs | Unix only: spawns the built collector-app binary, sends SIGTERM and expects a graceful stop with exit code 0 |
Unit tests inside the crates
| Module | Covers |
|---|---|
poller-actor/src/lib.rs | Base address order; respawn backoff doubles and caps at 60 s; default stall timeout |
collector-app/src/health.rs | Healthy only while both heartbeats are fresh; device counts do not affect health |
collector-app/src/ingest.rs | Everything queued before the channel closes is persisted |
collector-app/src/uplink.rs | No pause while a backlog remains; failure backoff up to 30 s |
collector-app/src/retention.rs | Row 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_kafkapublishes two records toSUNSPEC_KAFKA_BROKERS.published_records_decode_to_samplesconsumes the topic and checks that the records published by a running collector decode, carry thecontent-typeheader, are keyed by their device, and include a decoded model 103 power value. It expects records from devices onSUNSPEC_E2E_DEVICE_HOST, by defaultsimulator, the compose stack's simulator (keyssimulator/1,simulator/2). For a collector polling a simulator on the host, setSUNSPEC_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 = 0so 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
-waland-shmfiles 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_LOCKmutex. - 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:
| Job | What it runs | On a pull request, runs when it changes |
|---|---|---|
| Changes | Classifies the PR's changed files | Always |
| Check & Lint | cargo fmt --all -- --check, cargo clippy --all-targets --workspace, cargo build --workspace | Code (see below) |
| Test | cargo test --workspace | Code |
| Security Audit | cargo audit (prebuilt cargo-audit) | Cargo.lock, a Cargo.toml or ci.yml |
| Docker E2E | The 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-toolchainis pinned to a commit on itsmasterbranch and giventoolchain: 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 todocker/Dockerfile.cross. Dependabot proposes new SHAs and digests weekly. - Each workflow's top-level
permissionsiscontents: read; a job that needs more (publishing images, releases or Pages) asks for it in its ownpermissions.
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:
- Copy
json/model_<id>.jsonintocrates/sunspec-parser/models/, unmodified, and update the commit recorded incrates/sunspec-parser/models/README.md. - Nothing else needs registering:
build.rsembeds everymodel_<id>.jsonin that directory.every_model_file_is_bundledandevery_bundled_model_decodes_its_fixed_points(crates/sunspec-parser/tests/decode_tests.rs) check that the file parses, decodes, and has unique point names. - 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.
- Update the model tables in
crates/sunspec-parser/models/README.mdand 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 inCATEGORIESthere and indocs/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 withthiserror;collector-appusesanyhow::Resultwith.context(). typesstays lightweight:serdeis 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
tracingmacros 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 atinfo.
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
devicelabel withDeviceIdentity::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.mdfollows Keep a Changelog. Every user-visible change gets an entry under## [Unreleased]in the same pull request, inAdded,Changed,Fixed,RemovedorSecurity.- Changes that need action from users (record format, metric names, config keys and validation) start with Breaking:.
- All crates share one version,
versionunder[workspace.package]inCargo.toml. Releases are published by.github/workflows/release.ymlwhen av*tag is pushed; the steps are in Cutting a release. - Dependabot opens weekly pull requests for Cargo dependencies (
depsprefix; patch updates grouped into one PR, minor and major updates one PR each, since a 0.x minor bump is breaking), GitHub Actions (ciprefix; minor and patch releases grouped) and the base image digests of the Dockerfiles and.clusterfuzzlite/Dockerfile(dockerprefix, 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
| Problem | Fix |
|---|---|
cmake not found while building rdkafka-sys | Install cmake (apt install cmake, brew install cmake) |
curl/curl.h: No such file or directory | Install the curl headers (libcurl4-openssl-dev); see Building & Packaging |
| openssl-sys cannot find OpenSSL | Install libssl-dev and pkg-config (Linux) or brew install openssl@3 (macOS) |
Startup fails with load config failed or config validation failed | Configuration is strict; the error names the key or variable |
Startup fails with bind metrics port 9090 | The port is taken (often by another collector); set SUNSPEC_METRICS_PORT to a free port (0 picks one) |
failed to install metrics recorder | The recorder can be installed once per process; keep such tests in their own test file |
SQLite database is locked | Use one collector per buffer file |
| Modbus timeouts | Check nc -zv <ip> 502; raise SUNSPEC_REQUEST_TIMEOUT_MS (SUNSPEC_MODBUS_TIMEOUT_MS is deprecated and ignored) |
no SunSpec register map at base addresses | The 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 (cratecrates/collector-app);sunspec-sim, the SunSpec Modbus TCP simulator (cratecrates/sunspec-sim), for local development and tests.
| Build | Command | Output |
|---|---|---|
| Debug | cargo build --workspace | target/debug/collector-app, target/debug/sunspec-sim |
| Release | cargo build --release --locked -p collector-app -p sunspec-sim | target/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-openssl | target/<target>/release/collector-app, target/<target>/release/sunspec-sim |
| Collector image | docker build -t sunspec-collector . | /app/collector-app in the image |
| Simulator image | docker 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-buildcompiles librdkafka 2.12 (bundled inrdkafka-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.
sslbuilds 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 thevendored-opensslfeature 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.cincludes<curl/curl.h>under#ifdef WITH_OAUTHBEARER_OIDC, and the generatedconfig.halways 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-syscompiles its bundled zstd, and sqlx'ssqlitefeature 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:
| Stage | Base | Contents |
|---|---|---|
builder | rust:1.88-slim-bookworm | Installs 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 |
prebuilt | scratch | Only with --build-arg BINARIES=prebuilt: copies dist/bin/<arch>/collector-app and sunspec-sim to /out instead of compiling (see Release images) |
runtime | debian:bookworm-slim | Installs ca-certificates, libssl3 and curl (for the health check) and creates the system user and group collector |
simulator | runtime | /app/sunspec-sim, runs as collector, exposes 1502, ENTRYPOINT ["/app/sunspec-sim"] with default arguments --bind 0.0.0.0:1502 --units 1 |
collector | runtime | /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 relativebuffer.path(sunspec-buffer.sqlite) is/app/data/sunspec-buffer.sqlite, and abuffer.pathfrom a config file orSUNSPEC_BUFFER_PATHis used as given (relative paths resolve against/app/data). Keep/app/dataon a volume so buffered samples survive container replacement. A new named volume takes its ownership from the image, so it is writable bycollector; 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.sqlitestored the buffer under that name. To keep sending rows already buffered in an existing volume, setSUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite(or move the file and its-waland-shmfiles tosunspec-buffer.sqlitewhile 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 followsSUNSPEC_METRICS_PORT. If you set the port only in a config file ([metrics] port), also setSUNSPEC_METRICS_PORTor override the check with--health-cmd. -
Stopping.
docker stopsends SIGTERM, which starts the graceful shutdown (pollers stop, queued samples are written, the uplink drains for up tobuffer.shutdown_drain_timeout_ms). Docker sends SIGKILL after 10 s by default;docker stop -t 30matches the 30 s the systemd unit allows. -
Schema and version.
docker run --rm sunspec-collector --print-schemaprints the Avro schema of the published records, and--versionthe 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:
| Service | Image | Notes |
|---|---|---|
kafka | apache/kafka:3.9.1 | Single 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. |
simulator | sunspec-simulator:local (built from the simulator target) | Units 1 and 2 on port 1502 |
collector | sunspec-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 |
prometheus | prom/prometheus:v3.5.0 | localhost: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.tomlmaps each target to the image tagsunspec-cross-<target>; build the image under that tag first.docker/Dockerfile.crossstarts fromBASE_IMAGE, the target'sghcr.io/cross-rs/<target>:0.2.5image 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, installscmake,make,perl,pkg-configandlibcurl4-openssl-devfor the target (the curl headers librdkafka needs at compile time), and pointsPKG_CONFIG_LIBDIRandPKG_CONFIG_PATHat the target's multiarch directory.--features collector-app/vendored-opensslis required: the images' OpenSSL is older than the version librdkafka 2.12 needs. OpenSSL is compiled from source (hencemakeandperl).
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:
| Job | What it does |
|---|---|
| Prepare | Lints 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 |
| Build | Cross-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 |
| Assemble | Adds 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 |
| Image | Builds 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 |
| Publish | On 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
-
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).
-
In a pull request, set
versionunder[workspace.package]inCargo.toml, runcargo checksoCargo.lockfollows, and rename## [Unreleased]inCHANGELOG.mdto## [<version>] - <date>with a new empty## [Unreleased]above it. -
After it merges, tag the merge commit on
mainand push the tag:git switch main && git pull git tag -a v0.6.0 -m "v0.6.0" git push origin v0.6.0 -
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:
| Target | Input | Checks |
|---|---|---|
decode_model | A bundled model ID and arbitrary registers | decode_model_labeled does not panic, agrees with decode_model, names each point once, labels only existing points, and returns only finite floats |
model_chain | An arbitrary register map at the configured or a standard base | discover_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 |
config | An arbitrary config file, loaded as TOML and as JSON | CollectorConfig::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.shto get the systemd unit withRestartPreventExitStatus=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 thelibrdkafkatarget, and other client errors are logged asKafka client error. The uplink logsrecords 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 suggestRUST_LOG=info,collector_app=debug,poller_actor=debugover a baredebug, which also turns on the libraries' debug output - The ignored Kafka decode test reads the device host from
SUNSPEC_E2E_DEVICE_HOST(defaultsimulator), 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 asOperation now in progress (os error 36)orResource temporarily unavailable (os error 35) - The deployment guide listed a
modbus reconnectedlog line; the collector logsmodbus connected
[0.5.0] - 2026-10-18
Upgrade notes
- If you run the collector with the shipped systemd unit (
install.shinstalls it), upgrade: in 0.2.0–0.4.0 its watchdog pings never reached systemd, soWatchdogSec=30killed and restarted the service about every 30 seconds (see Fixed). Reruninstall.sh, thensudo systemctl restart sunspec-collector. - Records use Avro schema 2.1, which adds a
labelsmap 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: MPPTandEvt1: 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./devicesshows them too - Model catalog:
collector-app --print-modelsprints, andGET /modelsand/models/{id}serve, every bundled model's points with type, units, label, description and enum/bitfield symbols; point names match records (module[*].DCAfor repeating groups) /devicesand/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"(alsoSUNSPEC_MODBUS_FRAMINGand a per-deviceframing) 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 samerequest_timeout_msandmax_batch_size.--probeworks through converters too - Signed build provenance for releases: the archives,
install.sh,SHA256SUMSand both GHCR images are attested by the release workflow (SLSA provenance signed with Sigstore, verifiable withgh attestation verify), and each release includes the signed bundlesunspec-collector-<version>.intoto.jsonl sunspec-sim --rtuandsunspec_sim::start_rtu_over_tcpsimulate a converter (RTU framing with CRC; absent units stay silent);ModbusClient::newcreates 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_secondsnow 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'sWatchdogSec=30systemd killed and restarted the service about every 30 seconds. sd-notify is updated to 0.5, whosenotifyleaves 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 = 23901madeWHinfinite); points whose value is not a finite number, including infinite float readings, are now null. Found by fuzzing
Security
- The
opensslcrate (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:/metricsis 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,debianand 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-identifiersis givensunspec-sim --fixture dump.jsonreplays a probe dump; the library gainsread_dump,UnitSpec::from_dumpandSimHandle::load_dump, andtypesgainsDeviceDumpfixtures/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
pointsinstead 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.svgand.png) - Polling profiles:
poller.model_intervals_sreads slow-changing models (for example common, nameplate and settings) at most once per interval instead of every cycle, andpoller.include_models/poller.exclude_modelschoose which discovered models are polled (envSUNSPEC_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_modelsandmodel_intervals_s(merged model by model with the global intervals) SimHandle::reads_at(unit, addr)insunspec-sim, counting read requests per start address
Changed
sunspec-parserembeds everymodels/model_<id>.jsonthroughbuild.rsinstead 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_secondsis 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.avscanddocs/ops.md. Rows buffered by an older version are published with emptypoints, 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.sqlitevia the image'sSUNSPEC_BUFFER_PATH). To keep using an existing volume's unsent rows, setSUNSPEC_BUFFER_PATH=/app/data/buffer.sqlite(orbuffer.path). - Config is strict and several metric names changed; see
docs/ops.mdfor the rename table anddocs/configuration.mdfor deprecated keys. - Docker: the collector image's
/app/collector-appis now theENTRYPOINT, 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; aninstall.shthat verifies and installs a release (curl -fsSL …/releases/latest/download/install.sh | sudo sh) and keeps an existing/etc/sunspec-collector.env; multi-archghcr.io/imrans110/sunspec-collectorandsunspec-simulatorimages built from the same binaries; GitHub Release notes from this file. Every push tomainruns it as a dry run, which installs the archive under systemd and smoke-tests both image platforms; av*tag publishes collector-app --versionand--help, andsunspec-sim --version- Kafka connection security: TLS (
ssl), mutual TLS, and SASLPLAIN/SCRAM-SHA-256/SCRAM-SHA-512(sasl_plaintext,sasl_ssl) via newkafka.security_protocol,kafka.ssl_*andkafka.sasl_*settings (andSUNSPEC_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_*withplaintext) 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/modelscommit vendored-opensslcargo 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) anddocs/buffer_maintenance.md(buffer semantics, retention, sizing, inspection); the October 2026 audit is recorded indocs/CODE_REVIEW.md - CI
Docker E2Ejob: 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 simulatorDocker target and compose service (two units behind one gateway), sodocker compose up --buildproduces data without hardware/healthzendpoint: 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 healthySUNSPEC_LOG_FORMAT=jsonfor one JSON object per log line;RUST_LOGfiltering is explicit[metrics] portand[runtime] channel_capacity/respawn_delay_msconfig file sections- Prometheus help text for every metric;
*_secondsmetrics 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-schemaprints the Avro schema of published records (crates/avro-kafka/schemas/sunspec_sample.avsc)sunspec-simwrites 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) andbuffer.max_age_s(default 7 days) bound the buffer during long uplink outages; dropped rows are counted insunspec_buffer_dropped_total{reason}, withsunspec_buffer_rowsandsunspec_buffer_oldest_age_secondsgauges 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-simcrate: a SunSpec Modbus TCP simulator (library andsunspec-simbinary) 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 respectmax_connectionssetting- Per-device respawn backoff (exponential, capped at 60s)
- Device-salted jitter to prevent correlated polling
debug!()log for zero-length model skipSemaphoreClosederror variant in discoveryparse_unit_id_list()anddiscovery_unit_idsconfig 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
NPtsizingCrv[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_upis 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_totalalso 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/metricsand/healthz - The Docker image no longer sets
SUNSPEC_BUFFER_PATH(which overrodebuffer.pathfrom a config file); the collector runs in/app/data, so the default buffer is/app/data/sunspec-buffer.sqlite. TheHEALTHCHECKfollowsSUNSPEC_METRICS_PORT - The example env file had an inline comment that systemd and
docker --env-filepass 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 officialapache/kafka:3.9.1(KRaft) with a host listener onlocalhost: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 = 0is 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
zstdcompression because librdkafka was built without zstd; rdkafka'szstdfeature 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_topicandkafka_enable_idempotenceinDefaultimpl (compile error) - Missing
discovery_unit_idsfield onCollectorConfig(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_countcolumn from buffer schema
Changed
- All crates take their version from
[workspace.package]inCargo.toml, so a release bumps one line - Config validation rejects
kafka.acks0/1whilekafka.enable_idempotenceis true (librdkafka refused to start) andmodbus.max_batch_sizeabove 125 (previously clamped silently) modbus.timeout_ms/SUNSPEC_MODBUS_TIMEOUT_MSare deprecated and ignored with a warning: device sessions always usedpoller.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
HEALTHCHECKuses/healthz; images run as a non-root user; the cross-compilation image (nowdocker/Dockerfile.cross) drops the unusedlibrdkafka-devandlibsqlite3-devpackages - 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 unsupportedkafka.compression/kafka.acksvalues fail at startup instead of silently falling back to defaults - Breaking: metrics renamed to the
sunspec_prefix with base units and_totalcounters:sunspec_buffer_writes_total,sunspec_uplink_records_total,sunspec_uplink_errors_total,sunspec_uplink_publish_seconds; the high-cardinalitybatch_sizelabel 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 byhost/unit_id, withcontent-typeandschema-fingerprintheaders. Previously each message was an Avro container file mixing devices, with the schema embedded and no key. Records carry decodedpointsalongside the rawregisters - Breaking:
model_nameuses the official SunSpec names (e.g.inverter_three_phaseinstead ofthree_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_COUNTare deprecated and ignored (a warning is logged)poller.jitter_msnow offsets each device's fixed polling schedule instead of stretching every interval- Orchestration moved from
main.rsintocollector_app::runand 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; thediagslavetest is replaced by simulator-backed client tests - Docker base images upgraded from
bullseyetobookworm - Tokio features narrowed from
"full"to specific features - Removed
#![allow(dead_code)]from all 7 library crates Cargo.lockis now committed (removed from.gitignoreand.dockerignore)- mdBook version pinned to 0.4.40 in deploy workflow
- MSRV raised to 1.88 in workspace
Cargo.tomland the Docker builder image (required by quick-xml 0.42 and apache-avro 0.21) rustfmt.tomlexpanded withmax_width,reorder_imports,reorder_modules- sqlx 0.7 → 0.8, built with only the
sqliteandruntime-tokiofeatures (no TLS stack or unused MySQL/Postgres drivers) - quick-xml 0.31 → 0.42
Removed
CollectorConfig::discovery_unit_ids(duplicate ofdiscovery.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 auditvulnerabilities: 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.
2.2.3 Uplink layer (Kafka producer)
- Reads normalized data from SQLite and sends to Kafka.
- Handles batching, compression, and backpressure.
2.3 Feature matrix and technology selection
| Component | Selected technology | Justification | Alternatives |
|---|---|---|---|
| Language | Rust (2021) | Memory safety, zero-GC latency, high performance | Python, C++, Go |
| Async runtime | Tokio | Mature ecosystem, robust async patterns | async-std, smol |
| Modbus stack | sunspec + tokio-modbus | Type-safe model discovery and scale factors | raw tokio-modbus |
| Local buffer | SQLite (sqlx) | ACID, file-based, strong tooling | Sled, RocksDB |
| Messaging | Kafka (rust-rdkafka) | High throughput, decoupled producer/consumer | MQTT, HTTP |
| Serialization | Avro | Compact binary with schema evolution | JSON, Protobuf |
| Resilience | sd-notify (systemd) | Native watchdog integration | custom 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=NORMALfor 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 Uplink subsystem: Kafka and Avro
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-avroderive. - 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
criterionfor 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
tokiowith specific features:rt-multi-thread,macros,io-util,net,time,sync,signal.tokio-modbusfor async Modbus TCP.sqlxwithsqlite+runtime-tokio-rustls+macros.rdkafkawithcmake-build+ssl.apache-avrowithderive.
8.2 Error handling
thiserrorfor library errors.anyhowfor 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
PollSampleon 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.
8.5 Uplink configuration
message.timeout.ms=5000.queue.buffering.max.messages=10000.compression.codec=zstd.acks=all.- Pause drains on
QueueFullerrors.
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=notifywithWatchdogSecfor health monitoring.Restart=alwayswithRestartSec=5for 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
startaddress 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_configdiscovery 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}andsunspec_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-simcrate, 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
| # | Finding | Fix |
|---|---|---|
| 1 | With 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 |
| 2 | Gateways: 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 |
| 3 | Discovery 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 |
| 4 | A device that was offline at startup was never polled. | #12: per-device supervision with backoff, plus subnet rescans |
| 5 | SIGTERM (systemd/Docker stop) killed the process with no graceful shutdown. | #11: SIGTERM handled, plus a process-level test |
High: data path and durability
| # | Finding | Fix |
|---|---|---|
| 6 | Upload throughput was capped at batch_size / drain_interval (200 samples/s). | #13: drain back to back while a backlog exists |
| 7 | The 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 |
| 8 | Only 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 |
| 9 | Each 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
| # | Finding | Fix |
|---|---|---|
| 10 | Dropped 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 |
| 11 | Config 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 |
| 12 | About 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 |
| 13 | Metrics 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 |
| 14 | docker-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
| # | Finding | Fix |
|---|---|---|
| 15 | 13 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) |
| 16 | No branch protection, so a broken main went unnoticed for 8 months. | Branch protection requires Check & Lint, Test, Security Audit and Docker E2E |
| 17 | Docs 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.
2.8 Uplink task — corrupt vs failed message confusion ✅
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:
| Item | Priority | Notes |
|---|---|---|
| Kafka TLS/SASL config exposure | Medium | rdkafka supports it; need config fields |
| Schema Registry integration | Medium | Hardcoded Avro schema should evolve |
| SunSpec model name resource file | Low | Only 5 models named currently |
docker-compose.yml for dev | Low | Kafka + collector local setup |
cargo audit / cargo deny CI | Medium | Security scanning workflow |
| Dependabot configuration | Low | Automated dependency updates |
CONTRIBUTING.md, CHANGELOG.md | Low | Community documentation |
| Avro error wrapping (box error) | Low | Preserves full error chain |
| ARM64 Dockerfile version pin | Low | Reproducibility |
6. Security Observations
| Area | Status | Notes |
|---|---|---|
| SQL injection | Safe | Parameterized queries throughout |
| Docker | Fixed | Non-root user, healthcheck added |
| Modbus TCP | No TLS | Standard for Modbus protocol |
| Kafka | No TLS/SASL | Config supports it via rdkafka but not exposed |
| Config credentials | Risk | Environment variables could leak in logs |
| systemd service | Fixed | Added NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp |
7. Performance Observations
| Component | Observation | Impact |
|---|---|---|
ModbusClient | Single Mutex<Context> serializes all reads | Bottleneck under high model count |
BufferStore | Now configurable via BufferConfig | Fixed — was hardcoded to 5 |
uplink_task | JSON deserialize + Avro re-serialize every cycle | CPU overhead on each batch |
| Discovery | Fresh TCP connection per device | Network overhead at startup |
| Tokio | Specific features only | Fixed — was "full" |
8. Test Coverage Assessment
| Crate | Unit Tests | Integration Tests | Gaps |
|---|---|---|---|
types | None | None | Serialization round-trip |
sunspec-parser | Yes | Fixture-based | Edge cases |
modbus-client | None | diagslave | Retry/backoff logic |
discovery | None | None | CIDR parsing edges |
poller-actor | None | None | Error recovery, jitter |
avro-kafka | Serialization | Kafka integration | Mock publisher |
buffer | SQLite ops | None | Concurrent access |
collector-app | Config validation | E2E harness | uplink/buffer tasks |
Files Modified
| File | Changes |
|---|---|
crates/collector-app/src/config.rs | Added discovery_unit_ids field, unit_ids to FileDiscoveryConfig, parse_unit_id_list() fn, removed duplicate Default fields |
crates/collector-app/src/main.rs | Removed duplicate Prometheus install, separated corrupt/valid IDs in uplink, added respawn backoff |
crates/discovery/src/lib.rs | Added SemaphoreClosed error, replaced expect(), removed dead_code |
crates/modbus-client/src/lib.rs | Clamped backoff shift to 63, removed dead_code |
crates/poller-actor/src/lib.rs | Device-salted jitter, debug log for zero-length models, removed dead_code |
crates/buffer/src/lib.rs | Added with_config(), removed unused retry_count column, removed dead_code |
crates/avro-kafka/src/lib.rs | Removed dead_code |
crates/sunspec-parser/src/lib.rs | Removed dead_code |
crates/types/src/lib.rs | Removed dead_code |
Dockerfile | Bookworm base, non-root user, healthcheck, --no-install-recommends |
.gitignore | Removed Cargo.lock, added .DS_Store, .env, *.sqlite*, IDE patterns |
.dockerignore | Removed Cargo.lock, added docs/, .github/, .env |
Cargo.toml | Specific tokio features, rust-version = "1.80" |
rustfmt.toml | Added max_width, reorder_imports, reorder_modules |
.github/workflows/deploy-docs.yml | Pinned mdBook to 0.4.40 |
.github/workflows/ci.yml | New — fmt, clippy, build, test |