12 releases (7 breaking)

new 0.8.0 Dec 10, 2024
0.7.1 Nov 6, 2024
0.4.0 Mar 20, 2024
0.3.1 Dec 21, 2023
0.2.0 Nov 21, 2023

#87 in Hardware support

Download history 352/week @ 2024-09-06 29/week @ 2024-09-13 7/week @ 2024-09-20 20/week @ 2024-09-27 3/week @ 2024-10-04 78/week @ 2024-10-11 210/week @ 2024-10-18 125/week @ 2024-10-25 115/week @ 2024-11-01 16/week @ 2024-11-08 4/week @ 2024-11-15 1/week @ 2024-11-22 233/week @ 2024-12-06

238 downloads per month

MIT/Apache

1MB
21K SLoC

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

Feature Description Extra dependencies Default
tokio Enable tokio_modbus support tokio-modbus, tokio/time yes
serde Enable serde support serde, bitflags/serde yes

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.

Dependencies

~2.8–9MB
~78K SLoC