Skip to main content

Crate sunspec

Crate sunspec 

Source
Expand description

§SunSpec Rust Implementation

Latest Version CI Unsafe forbidden Rust 1.76+

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-modbus is 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

FeatureDescriptionExtra dependenciesDefault
tokioEnable tokio-based timeoutstokio, tokio/timeyes
tokio-modbusEnable tokio-modbus supporttokio-modbus, tokioyes
serdeEnable serde supportserde, bitflags/serdeyes
all-modelsEnable all generated modelsmodel1, model2, …yes
model<X>Enable generated model Xnoneyes

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 README
  • examples/model103: reading a common inverter model from a device
  • examples/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-modbus is just the bundled transport adapter.
  • You can provide your own transport by implementing the async client trait and constructing an AsyncClient with it.
  • The separate tokio feature 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 ids 0..=255 and 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

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§

InvalidPointData
Model data that decoded successfully but failed semantic validation.
ModelAddr
This structure is used to store the address of models after a successful model discovery.
Point
Definition of a point

Enums§

DecodeError
This error is returned if there was an error decoding the value of a given point.
ParseError
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§

EnumValue
Shared conversion logic for enumerated point values.
FixedSize
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.