Crate sunspec

Source
Expand description

§SunSpec Rust Implementation

Latest Version CI Unsafe forbidden Rust 1.75+

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).
  • Implements “Device Information Model Discovery” as defined in the SunSpec specification.
  • 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.
⚠️ Nested and repeating groups are not supported, yet.

§Features

FeatureDescriptionExtra dependenciesDefault
tokioEnable tokio_modbus supporttokio-modbus, tokio/timeyes
serdeEnable serde supportserde, bitflags/serdeyes

§Examples

The examples directory in the code repository contains the unabridged code.

§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.

§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§

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.

Constants§

DEFAULT_DISCOVERY_ADDRESSES
Default addresses for “SunS” discovery.
SUNS_IDENTIFIER
“SunS” identifier used when performing the model discovery.

Traits§

FixedSize
This trait marks points with a fixed size. All non-string values are actually fixed size.
Model
Every model implements this trait which contains methods for accessing
Value
This trait contains all the conversion methods needed for working with points of the SunSpec models.