Expand description
§SunSpec Rust Implementation
This Rust crate contains code for accessing SunSpec compliant devices in a safe and convenient way.
§Highlights
- Pure Rust library
- No unsafe code
- Panic free
- All communication is abstracted via traits making it runtime agnostic
- Supports Modbus TCP and RTU (via tokio-modbus).
-
tokio-modbusis optional. Custom transports can implement the client trait directly. - Implements “Device Information Model Discovery” as defined in the SunSpec specification.
- Compile-time model selection via Cargo features
- Fully typed models generated from the JSON files contained in the SunSpec models repository
- Fully typed enums
- Fully typed bitfields
- Fully documented. Even the generated models.
- Reading of complete models in a single request.
- Supports nested and repeating groups.
- Unknown or unsupported models are reported during discovery.
§Features
| Feature | Description | Extra dependencies | Default |
|---|---|---|---|
tokio | Enable tokio-based timeouts | tokio, tokio/time | yes |
tokio-modbus | Enable tokio-modbus support | tokio-modbus, tokio | yes |
serde | Enable serde support | serde, bitflags/serde | yes |
all-models | Enable all generated models | model1, model2, … | yes |
model<X> | Enable generated model X | none | yes |
If you only need a small subset of models, disable default features and opt in to the features and specific models you need:
[dependencies]
sunspec = { version = "...", default-features = false, features = ["tokio-modbus", "model1", "model103"] }§Examples
The examples directory in the code repository contains the unabridged code.
examples/readme: minimal end-to-end example used in this READMEexamples/model103: reading a common inverter model from a deviceexamples/model712: reading a model with nested and repeating groups
§Example code for accessing data from a three phase inverter using the model 103
use std::{error::Error, net::SocketAddr, time::Duration};
use clap::Parser;
use itertools::Itertools;
use sunspec::{
client::{AsyncClient, Config},
models::{model1::Model1, model103::Model103},
};
use tokio::time::sleep;
use tokio_modbus::client::tcp::connect;
#[derive(Parser)]
struct Args {
addr: SocketAddr,
device_id: u8,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
let client = AsyncClient::new(connect(args.addr).await?, Config::default());
let device = client.device(args.device_id).await?;
let m1: Model1 = device.read_model().await?;
println!("Manufacturer: {}", m1.mn);
println!("Model: {}", m1.md);
println!("Version: {}", m1.vr.as_deref().unwrap_or("(unspecified)"));
println!("Serial Number: {}", m1.sn);
println!(
"Supported models: {}",
device
.models
.supported_model_ids()
.iter()
.map(|id| id.to_string())
.join(", ")
);
loop {
let m103: Model103 = device.read_model().await?;
let w = m103.w as f32 * 10f32.powf(m103.w_sf.into());
let wh = m103.wh as f32 * 10f32.powf(m103.wh_sf.into());
println!("{:12.3} kWh {:9.3} kW", wh / 1000.0, w / 1000.0,);
sleep(Duration::from_secs(1)).await;
}
}§FAQ
How does this crate differ from crates like tokio-sunspec, sunspec-models, sunspec_rs?
-
This crate generates all code using Rust code via the official SunSpec models repository with a code generator that was written in Rust, too.
-
All generated models are plain Rust structs. A single Modbus call can return the complete data for a model rather than having to fetch points individually.
-
All public types are documented. Even the generated models.
-
Full support for nested and repeating groups.
How do I reduce compile times or binary size?
- Disable default features and enable only the features you need.
- This is is useful if you interact only with a small number of models.
Do I have to use tokio-modbus?
- No.
tokio-modbusis just the bundled transport adapter. - You can provide your own transport by implementing the async client trait and
constructing an
AsyncClientwith it. - The separate
tokiofeature only controls tokio-based timeout handling.
What happens if a device exposes models this crate does not know?
- Discovery still succeeds.
- Known models are stored in
device.models. - Unknown model ids, addresses, and lengths are returned in
device.unknown_models.
Can I scan for all slave IDs on a bus?
- Yes.
AsyncClient::devices()probes slave ids0..=255and returns every device that responds with a valid SunSpec header. - If you already know the slave id, use
AsyncClient::device(slave_id)instead.
How do discovery addresses and timeouts work?
- The default discovery addresses are
[40000, 0, 50000]. - That order avoids unnecessary timeouts on devices that do not behave well at
address
0. - You can override discovery addresses and read/write timeouts via
Config.
How are large models handled?
- Models larger than the Modbus single-request limit are read in chunks and then decoded as one typed model value.
- Nested groups and repeating groups are handled by the generated model code.
§License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Re-exports§
pub use models::Models;
Modules§
- client
- This module contains all client specific code.
- models
- This module contains all the genererated SunSpec models.
Structs§
- Invalid
Point Data - Model data that decoded successfully but failed semantic validation.
- Model
Addr - This structure is used to store the address of models after a successful model discovery.
- Point
- Definition of a point
Enums§
- Decode
Error - This error is returned if there was an error decoding the value of a given point.
- Parse
Error - Error returned while parsing a model from registers.
Constants§
- DEFAULT_
DISCOVERY_ ADDRESSES - Default addresses for “SunS” discovery.
- SUNS_
IDENTIFIER - “SunS” identifier used when performing the model discovery.
Traits§
- Enum
Value - Shared conversion logic for enumerated point values.
- Fixed
Size - This trait marks points with a fixed size. All non-string values are actually fixed size.
- Group
- Every group and model implements this trait.
- Model
- Every model implements this trait which contains methods for accessing the address and parsing the model.
- Value
- This trait contains all the conversion methods needed for working with points of the SunSpec models.