From c631c6114aeeb6d64c796ad8a8007d7899eacb81 Mon Sep 17 00:00:00 2001 From: DKolter <danielkolter157@gmail.com> Date: Thu, 14 Nov 2024 19:50:42 +0100 Subject: [PATCH 1/4] Core adoption start, configuration changes, deploy script --- .cargo/config.toml | 6 ++++++ core/.gitignore | 1 - core/config.json | 18 ++++++++++++++++++ deploy.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 .cargo/config.toml delete mode 100644 core/.gitignore create mode 100644 core/config.json create mode 100755 deploy.sh diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d0e0404 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "armv7-unknown-linux-musleabihf" + +[target.armv7-unknown-linux-musleabihf] +linker = "/opt/armv7l-linux-musleabihf-cross/bin/armv7l-linux-musleabihf-gcc" + diff --git a/core/.gitignore b/core/.gitignore deleted file mode 100644 index d344ba6..0000000 --- a/core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.json diff --git a/core/config.json b/core/config.json new file mode 100644 index 0000000..f00516e --- /dev/null +++ b/core/config.json @@ -0,0 +1,18 @@ +{ + "mqtt": { + "host": "test.mosquitto.org", + "port": 1883, + "username": "", + "password": "" + }, + "drivers": [ + { + "protocol": "vebus", + "index": 0, + "args": { + "port": "/dev/ttySTM5", + "sleep": 1 + } + } + ] +} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..f847793 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " -d DOMAIN SSH domain" + echo " -p PORT SSH port" + echo " -t TARGET_PATH Target path on the server" + echo " --release Build the project in release mode" + echo " -h, --help Display this help and exit" + echo "" + echo "Example: $0 -d user@mydomain.net -p 22 -t /home/user/core --release" + exit 1 +} + +RELEASE_MODE=false + +while [[ "$#" -gt 0 ]]; do + case "$1" in + -d) DOMAIN="$2"; shift 2;; + -p) PORT="$2"; shift 2;; + -t) TARGET_PATH="$2"; shift 2;; + --release) RELEASE_MODE=true; shift;; + -h|--help) usage;; + *) echo "Unknown option: $1"; usage;; + esac +done + +if [[ -z "$DOMAIN" || -z "$PORT" || -z "$TARGET_PATH" ]]; then + echo "Error: Missing required arguments." + usage +fi + +echo "Building the Rust project" +if $RELEASE_MODE; then + cargo build --release + BUILD_PATH="target/armv7-unknown-linux-musleabihf/release/core" +else + cargo build + BUILD_PATH="target/armv7-unknown-linux-musleabihf/debug/core" +fi + +echo "Copying the binary to the server" +scp -P "$PORT" "$BUILD_PATH" "$DOMAIN:$TARGET_PATH" -- GitLab From 44d6423deca984be5a9ef389418615c12225461a Mon Sep 17 00:00:00 2001 From: DKolter <danielkolter157@gmail.com> Date: Mon, 25 Nov 2024 18:10:29 +0100 Subject: [PATCH 2/4] Load configuration at runtime, add copy configuration option for deploy script --- core/src/config.rs | 5 +++-- core/src/main.rs | 2 -- deploy.sh | 9 +++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/config.rs b/core/src/config.rs index 3732134..9047540 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -39,8 +39,9 @@ pub enum DriverProtocol { impl Config { pub fn load() -> Result<Self> { log::info!("Loading configuration file"); - let config = include_str!("../config.json"); - let config: Self = serde_json::from_str(config).context("Parsing configuration file")?; + let config = + std::fs::read_to_string("config.json").context("Reading configuration file")?; + let config: Self = serde_json::from_str(&config).context("Parsing configuration file")?; log::info!("Configured drivers: {}", config.drivers.len()); log::info!("MQTT broker host: {}", config.mqtt.host); log::info!("MQTT broker port: {}", config.mqtt.port); diff --git a/core/src/main.rs b/core/src/main.rs index 32fa1d9..771122b 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -1,5 +1,3 @@ -#![no_std] - use adc::AdcDriver; use config::{Config, DriverConfig, DriverProtocol}; use gpio_output::GpioOutputDriver; diff --git a/deploy.sh b/deploy.sh index f847793..cef110d 100755 --- a/deploy.sh +++ b/deploy.sh @@ -6,6 +6,7 @@ usage() { echo " -d DOMAIN SSH domain" echo " -p PORT SSH port" echo " -t TARGET_PATH Target path on the server" + echo " --cc Copy the configuration file to the server" echo " --release Build the project in release mode" echo " -h, --help Display this help and exit" echo "" @@ -14,12 +15,14 @@ usage() { } RELEASE_MODE=false +COPY_CONFIGURATION=false while [[ "$#" -gt 0 ]]; do case "$1" in -d) DOMAIN="$2"; shift 2;; -p) PORT="$2"; shift 2;; -t) TARGET_PATH="$2"; shift 2;; + --cc) COPY_CONFIGURATION=true; shift;; --release) RELEASE_MODE=true; shift;; -h|--help) usage;; *) echo "Unknown option: $1"; usage;; @@ -40,5 +43,7 @@ else BUILD_PATH="target/armv7-unknown-linux-musleabihf/debug/core" fi -echo "Copying the binary to the server" -scp -P "$PORT" "$BUILD_PATH" "$DOMAIN:$TARGET_PATH" +if $COPY_CONFIGURATION; then + echo "Copying the configuration file to the server" + scp -P "$PORT" "core/config.json" "$DOMAIN:$TARGET_PATH" +fi -- GitLab From 55197fccc9f71dcd881889ca574614f0c0fbdfb3 Mon Sep 17 00:00:00 2001 From: DKolter <danielkolter157@gmail.com> Date: Tue, 10 Dec 2024 11:31:43 +0100 Subject: [PATCH 3/4] Review changes, ADC driver untested, due to missing files on core --- .cargo/config.toml | 6 -- Cargo.lock | 13 ---- core/.gitignore | 1 + core/Cargo.toml | 2 - core/config.json | 18 ----- core/src/adc.rs | 136 ++++++++------------------------ core/src/config.rs | 10 +-- core/src/gpio_output.rs | 20 +++-- core/src/main.rs | 25 +++--- core/src/mqtt.rs | 35 +++------ core/src/pwm.rs | 16 ++-- core/src/temperature.rs | 11 +-- core/src/topics.rs | 164 ++++++++++++++++++--------------------- core/src/utils.rs | 16 +--- core/src/vebus.rs | 45 +++++------ core/src/vedirect.rs | 51 ++++++------ deploy.sh | 14 +++- drivers/lin/src/truma.rs | 4 +- 18 files changed, 220 insertions(+), 367 deletions(-) delete mode 100644 .cargo/config.toml create mode 100644 core/.gitignore delete mode 100644 core/config.json diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index d0e0404..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,6 +0,0 @@ -[build] -target = "armv7-unknown-linux-musleabihf" - -[target.armv7-unknown-linux-musleabihf] -linker = "/opt/armv7l-linux-musleabihf-cross/bin/armv7l-linux-musleabihf-gcc" - diff --git a/Cargo.lock b/Cargo.lock index 96023fe..ca78ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,16 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ads1x1x" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b44310e48193933db0cd788961f450a86f35fdad022e4373883fa8e4640488e" -dependencies = [ - "embedded-hal 0.2.7", - "nb 1.1.0", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -189,11 +179,9 @@ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" name = "core" version = "0.1.0" dependencies = [ - "ads1x1x", "anyhow", "embedded-io-adapters", "env_logger 0.11.5", - "heapless", "itertools", "log", "rppal", @@ -449,7 +437,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", - "serde", "stable_deref_trait", ] diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..d344ba6 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +config.json diff --git a/core/Cargo.toml b/core/Cargo.toml index 05c0bab..b65c075 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,11 +5,9 @@ version = "0.1.0" edition = "2021" [dependencies] -ads1x1x = "0.2" anyhow = "1.0" embedded-io-adapters = { version = "0.6", features = ["tokio-1"] } env_logger = "0.11" -heapless = { version = "0.8", features = ["serde"] } log = "0.4" itertools = "0.13" serde = { version = "1.0", features = ["derive"] } diff --git a/core/config.json b/core/config.json deleted file mode 100644 index f00516e..0000000 --- a/core/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "mqtt": { - "host": "test.mosquitto.org", - "port": 1883, - "username": "", - "password": "" - }, - "drivers": [ - { - "protocol": "vebus", - "index": 0, - "args": { - "port": "/dev/ttySTM5", - "sleep": 1 - } - } - ] -} diff --git a/core/src/adc.rs b/core/src/adc.rs index 1abfcaa..8c5295d 100644 --- a/core/src/adc.rs +++ b/core/src/adc.rs @@ -1,94 +1,67 @@ use crate::{ mqtt::{MqttDriver, PublishMessage}, - topics::LevelTopics, - utils::to_mqtt_payload, - MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, -}; -use ads1x1x::{ - ic::{Ads1115, Resolution16Bit}, - interface::I2cInterface, - mode::OneShot, - Ads1x1x, ChannelSelection, DynamicOneShot, FullScaleRange, SlaveAddr, + topics::SensorTopics, }; +use anyhow::{Context, Result}; use core::time::Duration; -use heapless::{String, Vec}; -use itertools::izip; -use rppal::i2c::I2c; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; use serde_json::Value; -const DELAY: u64 = 20; - pub struct AdcDriver { - adc: Ads1x1x<I2cInterface<I2c>, Ads1115, Resolution16Bit, OneShot>, args: AdcDriverArgs, - topics: [LevelTopics; 4], + topics: [SensorTopics; 8], } #[derive(Deserialize)] struct AdcDriverArgs { - bus: u8, - fsr: Fsr, - bounds: Vec<VoltageBounds, 4>, + path: String, sleep: u64, } impl AdcDriver { - pub async fn new(_index: usize, args: Value) -> Self { + pub async fn new(_index: usize, args: Value) -> Result<Self> { let args: AdcDriverArgs = - serde_json::from_value(args).expect("Failed to parse adc driver args"); - let i2c = I2c::with_bus(args.bus).unwrap(); - let addr = SlaveAddr::default(); - let mut adc = Ads1x1x::new_ads1115(i2c, addr); - adc.set_full_scale_range(args.fsr.into()) - .expect("Failed to set adc driver full scale range"); + serde_json::from_value(args).context("Parsing adc driver args")?; let topics = [ - LevelTopics::new(0), - LevelTopics::new(1), - LevelTopics::new(2), - LevelTopics::new(3), + SensorTopics::new("adc", "0"), + SensorTopics::new("adc", "1"), + SensorTopics::new("adc", "2"), + SensorTopics::new("adc", "3"), + SensorTopics::new("adc", "4"), + SensorTopics::new("adc", "5"), + SensorTopics::new("adc", "6"), + SensorTopics::new("adc", "7"), ]; - Self { adc, args, topics } + Ok(Self { args, topics }) } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { + pub fn subscriptions(&self) -> Vec<String> { Vec::new() } pub async fn run(&mut self, mqtt: MqttDriver) { - loop { - let channels = [ - ChannelSelection::SingleA0, - ChannelSelection::SingleA1, - ChannelSelection::SingleA2, - ChannelSelection::SingleA3, - ]; + // Publish the unit of measurement (V) for all topics + for topic in self.topics.iter() { + mqtt.publish(PublishMessage { + topic: topic.unit_of_measurement.clone(), + payload: b"V".to_vec(), + qos: QoS::AtLeastOnce, + retain: true, + }) + .await; + } - for (i, channel, bounds, topic) in izip!( - 0..4, - channels.iter(), - self.args.bounds.iter(), - self.topics.iter() - ) { - let reading = loop { - match self.adc.read(*channel) { - Err(_) => tokio::time::sleep(Duration::from_millis(DELAY)).await, - Ok(reading) => break reading, - } - }; + loop { + for (i, topic) in self.topics.iter().enumerate() { + let value = 0.0; // Read the value from the ADC - log::debug!("ADC reading on channel {i}: {reading}"); - let voltage = self.args.fsr.convert_adc_reading(reading); - log::debug!("Voltage on channel {i}: {voltage}"); - let percentage = - ((voltage - bounds.min) / (bounds.max - bounds.min)).clamp(0.0, 1.0); - log::debug!("Percentage on channel {i}: {percentage}"); + // Publish the value mqtt.publish(PublishMessage { - topic: topic.level.clone(), - payload: to_mqtt_payload(percentage), + topic: topic.state_t.clone(), + payload: value.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) @@ -99,48 +72,3 @@ impl AdcDriver { } } } - -#[derive(Deserialize)] -pub struct VoltageBounds { - min: f32, - max: f32, -} - -#[derive(Deserialize, Clone, Copy)] -#[serde(rename_all = "snake_case")] -pub enum Fsr { - V6_144, - V4_096, - V2_048, - V1_024, - V0_512, - V0_256, -} - -impl From<Fsr> for FullScaleRange { - fn from(range: Fsr) -> Self { - match range { - Fsr::V6_144 => FullScaleRange::Within6_144V, - Fsr::V4_096 => FullScaleRange::Within4_096V, - Fsr::V2_048 => FullScaleRange::Within2_048V, - Fsr::V1_024 => FullScaleRange::Within1_024V, - Fsr::V0_512 => FullScaleRange::Within0_512V, - Fsr::V0_256 => FullScaleRange::Within0_256V, - } - } -} - -impl Fsr { - fn convert_adc_reading(&self, reading: i16) -> f32 { - let max = match self { - Fsr::V6_144 => 6.144, - Fsr::V4_096 => 4.096, - Fsr::V2_048 => 2.048, - Fsr::V1_024 => 1.024, - Fsr::V0_512 => 0.512, - Fsr::V0_256 => 0.256, - }; - let max_reading = i16::MAX as f32; - reading as f32 * max / max_reading - } -} diff --git a/core/src/config.rs b/core/src/config.rs index 9047540..06604da 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -1,21 +1,19 @@ -use crate::{MAX_DRIVERS, MAX_MQTT_MESSAGE_SIZE}; use anyhow::{Context, Result}; -use heapless::{String, Vec}; use serde::Deserialize; use serde_json::Value; #[derive(Deserialize, Debug)] pub struct Config { pub mqtt: MqttConfig, - pub drivers: Vec<DriverConfig, MAX_DRIVERS>, + pub drivers: Vec<DriverConfig>, } #[derive(Deserialize, Debug)] pub struct MqttConfig { - pub host: String<MAX_MQTT_MESSAGE_SIZE>, + pub host: String, pub port: u16, - pub username: Option<String<MAX_MQTT_MESSAGE_SIZE>>, - pub password: Option<String<MAX_MQTT_MESSAGE_SIZE>>, + pub username: Option<String>, + pub password: Option<String>, } #[derive(Deserialize, Debug)] diff --git a/core/src/gpio_output.rs b/core/src/gpio_output.rs index 2d881cd..c932ca1 100644 --- a/core/src/gpio_output.rs +++ b/core/src/gpio_output.rs @@ -1,11 +1,9 @@ use crate::{ mqtt::MqttDriver, topics::ToggleTopics, - utils::{parse_payload_to_bool, parse_string_mqtt_boolean, to_mqtt_payload}, - MAX_MQTT_MESSAGE_SIZE, MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, + utils::{parse_payload_to_bool, parse_string_mqtt_boolean}, }; use core::{str::FromStr, time::Duration}; -use heapless::{String, Vec}; use rppal::gpio::{Gpio, OutputPin}; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; @@ -50,15 +48,15 @@ impl GpioOutputDriver { } } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { - Vec::from_slice(&[self.topics.set_value.clone(), self.topics.set_timed.clone()]).unwrap() + pub fn subscriptions(&self) -> Vec<String> { + vec![self.topics.set_value.clone(), self.topics.set_timed.clone()] } pub async fn run(&mut self, mut mqtt: MqttDriver) { // Publish the initial value of the gpio pin to the topic mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(self.current), + payload: self.current.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) @@ -81,7 +79,7 @@ impl GpioOutputDriver { } } - async fn set_value(&mut self, mqtt: &MqttDriver, payload: Vec<u8, MAX_MQTT_MESSAGE_SIZE>) { + async fn set_value(&mut self, mqtt: &MqttDriver, payload: Vec<u8>) { // Parse the payload to a boolean value let value = parse_payload_to_bool(payload); @@ -101,7 +99,7 @@ impl GpioOutputDriver { // Publish the new value to the topic mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(self.current), + payload: self.current.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) @@ -128,7 +126,7 @@ impl GpioOutputDriver { }; // Parse the value as a boolean - let value = String::<MAX_MQTT_MESSAGE_SIZE>::from_str(value).unwrap(); + let value = String::from_str(value).unwrap(); let value = parse_string_mqtt_boolean(value); // Ignore the command if the value is the same as the current value @@ -158,7 +156,7 @@ impl GpioOutputDriver { // Publish the new value to the topic mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(value), + payload: value.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) @@ -181,7 +179,7 @@ impl GpioOutputDriver { // Publish the restored value to the topic mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(self.current), + payload: self.current.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) diff --git a/core/src/main.rs b/core/src/main.rs index 771122b..dac0f88 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -1,7 +1,7 @@ use adc::AdcDriver; +use anyhow::Result; use config::{Config, DriverConfig, DriverProtocol}; use gpio_output::GpioOutputDriver; -use heapless::{String, Vec}; use mqtt::MqttDriver; use pwm::PwmDriver; use serde_json::Value; @@ -21,12 +21,8 @@ mod utils; mod vebus; mod vedirect; -const MAX_MQTT_SUBSCRIBED_TOPICS: usize = 256; const MAX_MQTT_BROADCASTED_MESSAGES: usize = 128; const MAX_MQTT_PARALLEL_MESSAGES: usize = 64; -const MAX_MQTT_MESSAGE_SIZE: usize = 1024; -const MAX_MQTT_TOPIC_SIZE: usize = 64; -const MAX_DRIVERS: usize = 16; /// The driver is an enumeration of all known drivers. Each driver /// has its own protocol and is responsible for reading data from @@ -46,25 +42,25 @@ impl Driver { /// The index is determined by the order of the drivers; /// in the configuration file at the moment, but will be /// determined by a MQTT handshake with the core in the future. - async fn new(protocol: DriverProtocol, index: usize, args: Value) -> Self { - match protocol { + async fn new(protocol: DriverProtocol, index: usize, args: Value) -> Result<Self> { + Ok(match protocol { DriverProtocol::Vebus => Self::Vebus(VebusDriver::new(index, args).await), DriverProtocol::Vedirect => Self::Vedirect(VedirectDriver::new(index, args).await), DriverProtocol::OutputGpio => { Self::OutputGpio(GpioOutputDriver::new(index, args).await) } - DriverProtocol::Adc => Self::Adc(AdcDriver::new(index, args).await), + DriverProtocol::Adc => Self::Adc(AdcDriver::new(index, args).await?), DriverProtocol::Temperature => { Self::Temperature(TemperatureDriver::new(index, args).await) } DriverProtocol::Pwm => Self::Pwm(PwmDriver::new(index, args).await), - } + }) } /// Returns a list of MQTT topics that the driver subscribes to. /// The index from the `Driver::new` is usually a part of the topic /// name to allow multiple instances of the same driver to run. - fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { + fn subscriptions(&self) -> Vec<String> { match self { Self::Vebus(driver) => driver.subscriptions(), Self::Vedirect(driver) => driver.subscriptions(), @@ -94,15 +90,18 @@ impl Driver { /// Create a list of drivers from the given configurations. /// When a new driver is added, it should be added to the match statement. -async fn create_drivers(configs: Vec<DriverConfig, MAX_DRIVERS>) -> Vec<Driver, MAX_DRIVERS> { - let mut drivers: Vec<Driver, MAX_DRIVERS> = Vec::new(); +async fn create_drivers(configs: Vec<DriverConfig>) -> Vec<Driver> { + let mut drivers: Vec<Driver> = Vec::new(); for config in configs { log::info!( "Creating driver with index {} and protocol: {:?}", config.index, config.protocol ); - let _ = drivers.push(Driver::new(config.protocol, config.index, config.args).await); + match Driver::new(config.protocol, config.index, config.args).await { + Ok(driver) => drivers.push(driver), + Err(e) => log::error!("Failed to create driver: {e}"), + } } drivers diff --git a/core/src/mqtt.rs b/core/src/mqtt.rs index 908ae94..bdc073a 100644 --- a/core/src/mqtt.rs +++ b/core/src/mqtt.rs @@ -1,10 +1,7 @@ extern crate alloc; use crate::{ - config::MqttConfig, Driver, MAX_MQTT_BROADCASTED_MESSAGES, MAX_MQTT_MESSAGE_SIZE, - MAX_MQTT_PARALLEL_MESSAGES, MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, + config::MqttConfig, Driver, MAX_MQTT_BROADCASTED_MESSAGES, MAX_MQTT_PARALLEL_MESSAGES, }; -use core::str::FromStr; -use heapless::{String, Vec}; use rumqttc::v5::{ mqttbytes::{ v5::{Filter, Packet}, @@ -24,16 +21,16 @@ pub struct MqttDriver { } pub struct PublishMessage { - pub topic: String<MAX_MQTT_TOPIC_SIZE>, - pub payload: Vec<u8, MAX_MQTT_MESSAGE_SIZE>, + pub topic: String, + pub payload: Vec<u8>, pub qos: QoS, pub retain: bool, } #[derive(Debug, Clone)] pub struct SubscribedMessage { - pub topic: String<MAX_MQTT_TOPIC_SIZE>, - pub payload: Vec<u8, MAX_MQTT_MESSAGE_SIZE>, + pub topic: String, + pub payload: Vec<u8>, } impl MqttDriver { @@ -47,7 +44,7 @@ impl MqttDriver { let topics = drivers .iter() .flat_map(|driver| driver.subscriptions()) - .collect::<Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS>>(); + .collect::<Vec<String>>(); // Start the MQTT client tokio::spawn(Self::run( @@ -82,7 +79,7 @@ impl MqttDriver { async fn run( config: MqttConfig, - topics: Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS>, + topics: Vec<String>, mut publish_receiver: MpscReceiver<PublishMessage>, subscribed_sender: BroadcastSender<SubscribedMessage>, ) { @@ -99,7 +96,7 @@ impl MqttDriver { let topics = topics .iter() .map(|topic| Filter::new(topic.as_str(), QoS::AtLeastOnce)) - .collect::<Vec<_, MAX_MQTT_SUBSCRIBED_TOPICS>>(); + .collect::<Vec<_>>(); log::info!("Subscribing to {} MQTT topics", topics.len()); if !topics.is_empty() { @@ -116,14 +113,8 @@ impl MqttDriver { notification = connection.poll() => match notification { Ok(Event::Incoming(Packet::Publish(p))) => { // Extract the topic from the received MQTT message - let topic = match core::str::from_utf8(&p.topic) { - Ok(topic) => match String::<MAX_MQTT_TOPIC_SIZE>::from_str(topic) { - Ok(topic) => topic, - Err(_) => { - log::error!("Failed to parse MQTT topic: {:?}", p.topic); - continue; - } - } + let topic = match String::from_utf8(p.topic.to_vec()) { + Ok(topic) => topic, Err(_) => { log::error!("Failed to parse MQTT topic: {:?}", p.topic); continue; @@ -132,11 +123,7 @@ impl MqttDriver { // Extract the payload from the received MQTT message - let mut payload = Vec::<u8, MAX_MQTT_MESSAGE_SIZE>::new(); - if payload.extend_from_slice(&p.payload).is_err() { - log::error!("MQTT message is too long: {}", p.payload.len()); - continue; - } + let payload = p.payload.to_vec(); let message = SubscribedMessage { topic, payload }; if let Err(e) = subscribed_sender.send(message){ diff --git a/core/src/pwm.rs b/core/src/pwm.rs index 799fe78..a56ebf8 100644 --- a/core/src/pwm.rs +++ b/core/src/pwm.rs @@ -1,11 +1,5 @@ -use crate::{ - mqtt::MqttDriver, - topics::DimmableLightTopics, - utils::{parse_payload_to_f64, to_mqtt_payload}, - MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, -}; +use crate::{mqtt::MqttDriver, topics::DimmableLightTopics, utils::parse_payload_to_f64}; use core::{cmp::Ordering, time::Duration}; -use heapless::{String, Vec}; use rppal::pwm::{Channel, Polarity, Pwm}; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; @@ -57,15 +51,15 @@ impl PwmDriver { Self { pwm, topics, args } } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { - Vec::from_slice(&[self.topics.set_value.clone()]).unwrap() + pub fn subscriptions(&self) -> Vec<String> { + vec![self.topics.set_value.clone()] } pub async fn run(&mut self, mut mqtt: MqttDriver) { // Publish the initial value of the PWM channel mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(self.args.initial), + payload: self.args.initial.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) @@ -83,7 +77,7 @@ impl PwmDriver { Some(value) => { mqtt.publish(crate::mqtt::PublishMessage { topic: self.topics.value.clone(), - payload: to_mqtt_payload(value), + payload: value.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) diff --git a/core/src/temperature.rs b/core/src/temperature.rs index 47907a4..1698b0c 100644 --- a/core/src/temperature.rs +++ b/core/src/temperature.rs @@ -1,11 +1,8 @@ use crate::{ mqtt::{MqttDriver, PublishMessage}, topics::TemperatureTopics, - utils::to_mqtt_payload, - MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, }; use core::time::Duration; -use heapless::{String, Vec}; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; use serde_json::Value; @@ -17,7 +14,7 @@ pub struct TemperatureDriver { #[derive(Deserialize)] struct TemperatureDriverArgs { - path: String<MAX_MQTT_TOPIC_SIZE>, + path: String, sleep: u64, } @@ -32,8 +29,8 @@ impl TemperatureDriver { Self { args, topics } } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { - Vec::new() + pub fn subscriptions(&self) -> Vec<String> { + vec![] } pub async fn run(&mut self, mqtt: MqttDriver) { @@ -66,7 +63,7 @@ impl TemperatureDriver { // Publish the temperature mqtt.publish(PublishMessage { topic: self.topics.temperature.clone(), - payload: to_mqtt_payload(temperature), + payload: temperature.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, }) diff --git a/core/src/topics.rs b/core/src/topics.rs index 957fa81..84f29b1 100644 --- a/core/src/topics.rs +++ b/core/src/topics.rs @@ -1,160 +1,150 @@ -use crate::MAX_MQTT_TOPIC_SIZE; -use core::fmt::Write; -use heapless::String; - -macro_rules! format_topic { - ($($arg:tt)*) => {{ - let mut s: String<MAX_MQTT_TOPIC_SIZE> = String::new(); - let _ = write!(s, $($arg)*); - s - }}; -} - #[derive(Debug)] pub struct ToggleTopics { - pub value: String<MAX_MQTT_TOPIC_SIZE>, - pub set_value: String<MAX_MQTT_TOPIC_SIZE>, - pub set_timed: String<MAX_MQTT_TOPIC_SIZE>, + pub value: String, + pub set_value: String, + pub set_timed: String, } impl ToggleTopics { pub fn new(index: usize) -> Self { Self { - value: format_topic!("state/toggle/{index}/value"), - set_value: format_topic!("state/toggle/{index}/set/value"), - set_timed: format_topic!("state/toggle/{index}/set/timed"), + value: format!("state/toggle/{index}/value"), + set_value: format!("state/toggle/{index}/set/value"), + set_timed: format!("state/toggle/{index}/set/timed"), } } } #[derive(Debug)] pub struct TemperatureTopics { - pub temperature: String<MAX_MQTT_TOPIC_SIZE>, + pub temperature: String, } impl TemperatureTopics { pub fn new(index: usize) -> Self { Self { - temperature: format_topic!("state/temperature/{index}/temperatureC"), + temperature: format!("state/temperature/{index}/temperatureC"), } } } #[derive(Debug)] -pub struct LevelTopics { - pub level: String<MAX_MQTT_TOPIC_SIZE>, +pub struct SensorTopics { + pub state_t: String, + pub unit_of_measurement: String, } -impl LevelTopics { - pub fn new(index: usize) -> Self { +impl SensorTopics { + pub fn new(node_id: &str, object_id: &str) -> Self { Self { - level: format_topic!("state/level/{index}/level"), + state_t: format!("s/sensor/{node_id}/{object_id}/state_t"), + unit_of_measurement: format!("s/sensor/{node_id}/{object_id}/unit_of_measurement"), } } } #[derive(Debug)] pub struct ChargerTopics { - pub enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub output_voltage: String<MAX_MQTT_TOPIC_SIZE>, - pub output_current: String<MAX_MQTT_TOPIC_SIZE>, - pub output_power_watts: String<MAX_MQTT_TOPIC_SIZE>, - pub input_voltage: String<MAX_MQTT_TOPIC_SIZE>, - pub input_current: String<MAX_MQTT_TOPIC_SIZE>, - pub input_power_watts: String<MAX_MQTT_TOPIC_SIZE>, - pub set_enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub set_max_output_power: String<MAX_MQTT_TOPIC_SIZE>, - pub set_max_input_power: String<MAX_MQTT_TOPIC_SIZE>, - pub set_max_output_amps: String<MAX_MQTT_TOPIC_SIZE>, - pub set_max_input_amps: String<MAX_MQTT_TOPIC_SIZE>, + pub enabled: String, + pub output_voltage: String, + pub output_current: String, + pub output_power_watts: String, + pub input_voltage: String, + pub input_current: String, + pub input_power_watts: String, + pub set_enabled: String, + pub set_max_output_power: String, + pub set_max_input_power: String, + pub set_max_output_amps: String, + pub set_max_input_amps: String, } impl ChargerTopics { pub fn new(index: usize) -> Self { Self { - enabled: format_topic!("state/charger/{index}/enabled"), - output_voltage: format_topic!("state/charger/{index}/outputVoltage"), - output_current: format_topic!("state/charger/{index}/outputCurrent"), - output_power_watts: format_topic!("state/charger/{index}/outputPowerWatts"), - input_voltage: format_topic!("state/charger/{index}/inputVoltage"), - input_current: format_topic!("state/charger/{index}/inputCurrent"), - input_power_watts: format_topic!("state/charger/{index}/inputPowerWatts"), - set_enabled: format_topic!("state/charger/{index}/set/enabled"), - set_max_output_power: format_topic!("state/charger/{index}/set/maxOutputPower"), - set_max_input_power: format_topic!("state/charger/{index}/set/maxInputPower"), - set_max_output_amps: format_topic!("state/charger/{index}/set/maxOutputAmps"), - set_max_input_amps: format_topic!("state/charger/{index}/set/maxInputAmps"), + enabled: format!("state/charger/{index}/enabled"), + output_voltage: format!("state/charger/{index}/outputVoltage"), + output_current: format!("state/charger/{index}/outputCurrent"), + output_power_watts: format!("state/charger/{index}/outputPowerWatts"), + input_voltage: format!("state/charger/{index}/inputVoltage"), + input_current: format!("state/charger/{index}/inputCurrent"), + input_power_watts: format!("state/charger/{index}/inputPowerWatts"), + set_enabled: format!("state/charger/{index}/set/enabled"), + set_max_output_power: format!("state/charger/{index}/set/maxOutputPower"), + set_max_input_power: format!("state/charger/{index}/set/maxInputPower"), + set_max_output_amps: format!("state/charger/{index}/set/maxOutputAmps"), + set_max_input_amps: format!("state/charger/{index}/set/maxInputAmps"), } } } #[derive(Debug)] pub struct BatteryTopics { - pub soc: String<MAX_MQTT_TOPIC_SIZE>, - pub voltage: String<MAX_MQTT_TOPIC_SIZE>, - pub current: String<MAX_MQTT_TOPIC_SIZE>, - pub _discharge_enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub _charge_enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub set_discharge_enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub set_charge_enabled: String<MAX_MQTT_TOPIC_SIZE>, + pub soc: String, + pub voltage: String, + pub current: String, + pub _discharge_enabled: String, + pub _charge_enabled: String, + pub set_discharge_enabled: String, + pub set_charge_enabled: String, } impl BatteryTopics { pub fn new(index: usize) -> Self { Self { - soc: format_topic!("state/battery/{index}/soc"), - voltage: format_topic!("state/battery/{index}/voltage"), - current: format_topic!("state/battery/{index}/current"), - _discharge_enabled: format_topic!("state/battery/{index}/dischargeEnabled"), - _charge_enabled: format_topic!("state/battery/{index}/chargeEnabled"), - set_discharge_enabled: format_topic!("state/battery/{index}/set/dischargeEnabled"), - set_charge_enabled: format_topic!("state/battery/{index}/set/chargeEnabled"), + soc: format!("state/battery/{index}/soc"), + voltage: format!("state/battery/{index}/voltage"), + current: format!("state/battery/{index}/current"), + _discharge_enabled: format!("state/battery/{index}/dischargeEnabled"), + _charge_enabled: format!("state/battery/{index}/chargeEnabled"), + set_discharge_enabled: format!("state/battery/{index}/set/dischargeEnabled"), + set_charge_enabled: format!("state/battery/{index}/set/chargeEnabled"), } } } #[derive(Debug)] pub struct InverterTopics { - pub enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub output_voltage: String<MAX_MQTT_TOPIC_SIZE>, - pub output_current: String<MAX_MQTT_TOPIC_SIZE>, - pub output_power_watts: String<MAX_MQTT_TOPIC_SIZE>, - pub input_voltage: String<MAX_MQTT_TOPIC_SIZE>, - pub input_current: String<MAX_MQTT_TOPIC_SIZE>, - pub input_power_watts: String<MAX_MQTT_TOPIC_SIZE>, - pub _max_shore_amps: String<MAX_MQTT_TOPIC_SIZE>, - pub set_enabled: String<MAX_MQTT_TOPIC_SIZE>, - pub set_max_shore_amps: String<MAX_MQTT_TOPIC_SIZE>, + pub enabled: String, + pub output_voltage: String, + pub output_current: String, + pub output_power_watts: String, + pub input_voltage: String, + pub input_current: String, + pub input_power_watts: String, + pub _max_shore_amps: String, + pub set_enabled: String, + pub set_max_shore_amps: String, } impl InverterTopics { pub fn new(index: usize) -> Self { Self { - enabled: format_topic!("state/inverter/{index}/enabled"), - output_voltage: format_topic!("state/inverter/{index}/outputVoltage"), - output_current: format_topic!("state/inverter/{index}/outputCurrent"), - output_power_watts: format_topic!("state/inverter/{index}/outputPowerWatts"), - input_voltage: format_topic!("state/inverter/{index}/inputVoltage"), - input_current: format_topic!("state/inverter/{index}/inputCurrent"), - input_power_watts: format_topic!("state/inverter/{index}/inputPowerWatts"), - _max_shore_amps: format_topic!("state/inverter/{index}/maxShoreAmps"), - set_enabled: format_topic!("state/inverter/{index}/set/enabled"), - set_max_shore_amps: format_topic!("state/inverter/{index}/set/maxShoreAmps"), + enabled: format!("state/inverter/{index}/enabled"), + output_voltage: format!("state/inverter/{index}/outputVoltage"), + output_current: format!("state/inverter/{index}/outputCurrent"), + output_power_watts: format!("state/inverter/{index}/outputPowerWatts"), + input_voltage: format!("state/inverter/{index}/inputVoltage"), + input_current: format!("state/inverter/{index}/inputCurrent"), + input_power_watts: format!("state/inverter/{index}/inputPowerWatts"), + _max_shore_amps: format!("state/inverter/{index}/maxShoreAmps"), + set_enabled: format!("state/inverter/{index}/set/enabled"), + set_max_shore_amps: format!("state/inverter/{index}/set/maxShoreAmps"), } } } #[derive(Debug)] pub struct DimmableLightTopics { - pub value: String<MAX_MQTT_TOPIC_SIZE>, - pub set_value: String<MAX_MQTT_TOPIC_SIZE>, + pub value: String, + pub set_value: String, } impl DimmableLightTopics { pub fn new(index: usize) -> Self { Self { - value: format_topic!("state/dimmableLight/{index}/value"), - set_value: format_topic!("state/dimmableLight/{index}/set/value"), + value: format!("state/dimmableLight/{index}/value"), + set_value: format!("state/dimmableLight/{index}/set/value"), } } } diff --git a/core/src/utils.rs b/core/src/utils.rs index 5adb393..06eee3e 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -1,24 +1,14 @@ -use crate::MAX_MQTT_MESSAGE_SIZE; -use core::fmt::{Display, Write}; -use heapless::{String, Vec}; - -pub fn to_mqtt_payload<T: Display>(value: T) -> Vec<u8, MAX_MQTT_MESSAGE_SIZE> { - let mut bytes = Vec::new(); - let _ = write!(bytes, "{value}"); - bytes -} - -pub fn parse_payload_to_bool(mut payload: Vec<u8, MAX_MQTT_MESSAGE_SIZE>) -> bool { +pub fn parse_payload_to_bool(mut payload: Vec<u8>) -> bool { payload.make_ascii_lowercase(); !matches!(payload.as_slice(), b"false" | b"0" | b"" | b"off") } -pub fn parse_string_mqtt_boolean(mut payload: String<MAX_MQTT_MESSAGE_SIZE>) -> bool { +pub fn parse_string_mqtt_boolean(mut payload: String) -> bool { payload.make_ascii_lowercase(); !matches!(payload.as_str(), "false" | "0" | "" | "off") } -pub fn parse_payload_to_f64(payload: Vec<u8, MAX_MQTT_MESSAGE_SIZE>) -> Option<f64> { +pub fn parse_payload_to_f64(payload: Vec<u8>) -> Option<f64> { let payload = core::str::from_utf8(payload.as_slice()).ok()?; payload.parse().ok() } diff --git a/core/src/vebus.rs b/core/src/vebus.rs index 88be697..31d968b 100644 --- a/core/src/vebus.rs +++ b/core/src/vebus.rs @@ -1,9 +1,7 @@ use crate::mqtt::{MqttDriver, PublishMessage}; use crate::topics::{ChargerTopics, InverterTopics}; -use crate::utils::{parse_payload_to_bool, to_mqtt_payload}; -use crate::{MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE}; +use crate::utils::parse_payload_to_bool; use ::vebus::{Command, RamVariables, VebusError, VebusReader, VebusResponse}; -use heapless::{String, Vec}; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; use serde_json::Value; @@ -36,7 +34,7 @@ pub struct VebusDriver { #[derive(Debug, Deserialize)] struct VebusDriverArgs { - port: String<MAX_MQTT_TOPIC_SIZE>, + port: String, sleep: u64, } @@ -78,8 +76,8 @@ impl VebusDriver { } } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { - Vec::from_slice(&[ + pub fn subscriptions(&self) -> Vec<String> { + vec![ self.topics.charger.set_enabled.clone(), self.topics.charger.set_max_output_power.clone(), self.topics.charger.set_max_input_power.clone(), @@ -87,8 +85,7 @@ impl VebusDriver { self.topics.charger.set_max_input_amps.clone(), self.topics.inverter.set_enabled.clone(), self.topics.inverter.set_max_shore_amps.clone(), - ]) - .unwrap() + ] } pub async fn run(&mut self, mut mqtt: MqttDriver) { @@ -194,8 +191,8 @@ impl VebusDriver { ) { self.cache.charger = frame.switch_register.switch_charger; self.cache.inverter = frame.switch_register.switch_inverter; - let charger_enabled = to_mqtt_payload(frame.switch_register.switch_charger); - let inverter_enabled = to_mqtt_payload(frame.switch_register.switch_inverter); + let charger_enabled = frame.switch_register.switch_charger.to_string(); + let inverter_enabled = frame.switch_register.switch_inverter.to_string(); let topics = [ (self.topics.charger.enabled.clone(), charger_enabled), (self.topics.inverter.enabled.clone(), inverter_enabled), @@ -204,7 +201,7 @@ impl VebusDriver { for (topic, payload) in topics { mqtt.publish(PublishMessage { topic, - payload, + payload: payload.into(), qos: QoS::AtLeastOnce, retain: true, }) @@ -213,9 +210,9 @@ impl VebusDriver { } async fn publish_dc_frame(&self, mqtt: &MqttDriver, frame: DcFrame) { - let v = to_mqtt_payload(frame.dc_voltage); - let i = to_mqtt_payload(frame.dc_current_used); - let p = to_mqtt_payload(frame.dc_voltage * frame.dc_current_used); + let v = frame.dc_voltage.to_string(); + let i = frame.dc_current_used.to_string(); + let p = (frame.dc_voltage * frame.dc_current_used).to_string(); let topics = [ (self.topics.charger.output_voltage.clone(), v.clone()), (self.topics.charger.output_current.clone(), i.clone()), @@ -228,7 +225,7 @@ impl VebusDriver { for (topic, payload) in topics { mqtt.publish(PublishMessage { topic, - payload, + payload: payload.into(), qos: QoS::AtLeastOnce, retain: true, }) @@ -238,12 +235,12 @@ impl VebusDriver { async fn publish_ac_frame(&mut self, mqtt: &MqttDriver, frame: AcFrame) { log::debug!("Vebus state: {:?}", frame.state); - let cv = to_mqtt_payload(frame.mains_voltage); - let ci = to_mqtt_payload(frame.mains_current); - let cp = to_mqtt_payload(frame.mains_voltage * frame.mains_current); - let iv = to_mqtt_payload(frame.inverter_voltage); - let ii = to_mqtt_payload(frame.inverter_current); - let ip = to_mqtt_payload(frame.inverter_voltage * frame.inverter_current); + let cv = frame.mains_voltage; + let ci = frame.mains_current; + let cp = frame.mains_voltage * frame.mains_current; + let iv = frame.inverter_voltage; + let ii = frame.inverter_current; + let ip = frame.inverter_voltage * frame.inverter_current; let topics = [ (self.topics.charger.input_voltage.clone(), cv), (self.topics.charger.input_current.clone(), ci), @@ -256,7 +253,7 @@ impl VebusDriver { for (topic, payload) in topics { mqtt.publish(PublishMessage { topic, - payload, + payload: payload.to_string().into(), qos: QoS::AtLeastOnce, retain: true, }) @@ -301,11 +298,11 @@ impl VebusDriver { } async fn read_ram_variables(&mut self) -> Result<RamVariables, VebusCommunicationError> { - let mut scale_offsets = Vec::<ScaleOffsetResponse, 8>::new(); + let mut scale_offsets = Vec::<ScaleOffsetResponse>::new(); for var in RamVariables::ALL_VARIABLES { let response = self.send_command(Command::GetScaleOffset(var)).await?; match response { - VebusResponse::ScaleOffset(so) => scale_offsets.push(so).unwrap(), + VebusResponse::ScaleOffset(so) => scale_offsets.push(so), _ => { return Err(VebusCommunicationError::VebusError( VebusError::UnknownResponseFormat, diff --git a/core/src/vedirect.rs b/core/src/vedirect.rs index 7c81605..f2561c5 100644 --- a/core/src/vedirect.rs +++ b/core/src/vedirect.rs @@ -1,10 +1,8 @@ use crate::{ mqtt::{MqttDriver, PublishMessage}, topics::{BatteryTopics, ChargerTopics, InverterTopics}, - utils::{parse_payload_to_bool, to_mqtt_payload}, - MAX_MQTT_SUBSCRIBED_TOPICS, MAX_MQTT_TOPIC_SIZE, + utils::parse_payload_to_bool, }; -use heapless::{String, Vec}; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; use serde_json::Value; @@ -24,7 +22,7 @@ pub struct VedirectDriver { #[derive(Deserialize)] struct VedirectDriverArgs { - port: String<MAX_MQTT_TOPIC_SIZE>, + port: String, } impl VedirectDriver { @@ -49,8 +47,8 @@ impl VedirectDriver { } } - pub fn subscriptions(&self) -> Vec<String<MAX_MQTT_TOPIC_SIZE>, MAX_MQTT_SUBSCRIBED_TOPICS> { - Vec::from_slice(&[ + pub fn subscriptions(&self) -> Vec<String> { + vec![ self.topics.charger.set_enabled.clone(), self.topics.charger.set_max_output_power.clone(), self.topics.charger.set_max_input_power.clone(), @@ -60,8 +58,7 @@ impl VedirectDriver { self.topics.battery.set_charge_enabled.clone(), self.topics.inverter.set_enabled.clone(), self.topics.inverter.set_max_shore_amps.clone(), - ]) - .unwrap() + ] } pub async fn run(&mut self, mut mqtt: MqttDriver) { @@ -112,12 +109,9 @@ impl VedirectDriver { } async fn publish_battery_record(&self, mqtt: &MqttDriver, record: TextRecord) { - let soc = record - .soc - .map(|soc| soc as f32 / 1000.0) - .map(to_mqtt_payload); - let voltage = record.v.map(|v| v as f32 / 1000.0).map(to_mqtt_payload); - let current = record.i.map(|i| i as f32 / 1000.0).map(to_mqtt_payload); + let soc = record.soc.map(|soc| soc as f32 / 1000.0); + let voltage = record.v.map(|v| v as f32 / 1000.0); + let current = record.i.map(|i| i as f32 / 1000.0); let topics = [ (self.topics.battery.soc.clone(), soc), @@ -129,7 +123,7 @@ impl VedirectDriver { if let Some(value) = value { mqtt.publish(PublishMessage { topic, - payload: value, + payload: value.to_string().into(), qos: QoS::AtLeastOnce, retain: true, }) @@ -146,26 +140,39 @@ impl VedirectDriver { let enabled = record .cs .map(|cs| cs != ChargerStatus::Off) - .map(to_mqtt_payload); + .map(|cs| cs.to_string().into_bytes()); + + let output_voltage = record + .v + .map(|v| v as f32 / 1000.0) + .map(|v| v.to_string().into_bytes()); + + let output_current = record + .i + .map(|i| i as f32 / 1000.0) + .map(|i| i.to_string().into_bytes()); - let output_voltage = record.v.map(|v| v as f32 / 1000.0).map(to_mqtt_payload); - let output_current = record.i.map(|i| i as f32 / 1000.0).map(to_mqtt_payload); let output_power = match (record.v, record.i) { (Some(v), Some(i)) => Some(v as f32 / 1000.0 * i as f32 / 1000.0), _ => None, } - .map(to_mqtt_payload); + .map(|p| p.to_string().into_bytes()); let input_voltage = record .vpv .map(|vpv| vpv as f32 / 1000.0) - .map(to_mqtt_payload); - let input_power = record.ppv.map(to_mqtt_payload); + .map(|vpv| vpv.to_string().into_bytes()); + + let input_power = record + .ppv + .map(|ppv| ppv as f32 / 1000.0) + .map(|ppv| ppv.to_string().into_bytes()); + let input_current = match (record.vpv, record.ppv) { (Some(v), Some(p)) => Some(p as f32 / (v as f32 / 1000.0)), _ => None, } - .map(to_mqtt_payload); + .map(|i| i.to_string().into_bytes()); let topics = [ (self.topics.charger.enabled.clone(), enabled), diff --git a/deploy.sh b/deploy.sh index cef110d..5223ec2 100755 --- a/deploy.sh +++ b/deploy.sh @@ -4,16 +4,21 @@ usage() { echo "Usage: $0 [options]" echo "Options:" echo " -d DOMAIN SSH domain" - echo " -p PORT SSH port" + echo " -p PORT SSH port, defaults to 22" echo " -t TARGET_PATH Target path on the server" echo " --cc Copy the configuration file to the server" echo " --release Build the project in release mode" echo " -h, --help Display this help and exit" echo "" + echo "Requirements:" + echo " - Rust cargo cross installed" + echo " - Docker or podman installed" + echo "" echo "Example: $0 -d user@mydomain.net -p 22 -t /home/user/core --release" exit 1 } +PORT=22 RELEASE_MODE=false COPY_CONFIGURATION=false @@ -36,13 +41,16 @@ fi echo "Building the Rust project" if $RELEASE_MODE; then - cargo build --release + cross build --target armv7-unknown-linux-musleabihf --release BUILD_PATH="target/armv7-unknown-linux-musleabihf/release/core" else - cargo build + cross build --target armv7-unknown-linux-musleabihf BUILD_PATH="target/armv7-unknown-linux-musleabihf/debug/core" fi +echo "Copying the binary to the server" +scp -P "$PORT" "$BUILD_PATH" "$DOMAIN:$TARGET_PATH" + if $COPY_CONFIGURATION; then echo "Copying the configuration file to the server" scp -P "$PORT" "core/config.json" "$DOMAIN:$TARGET_PATH" diff --git a/drivers/lin/src/truma.rs b/drivers/lin/src/truma.rs index 92158cc..21dfa22 100644 --- a/drivers/lin/src/truma.rs +++ b/drivers/lin/src/truma.rs @@ -1,6 +1,4 @@ -use crate::protocol::{ - CombinedFrame, Frame, FrameData, FrameId, Rsid, ServiceRequestFrame, ServiceResponseFrame, Sid, -}; +use crate::protocol::{CombinedFrame, FrameId, Rsid, Sid}; /// Truma frame/command #[derive(Debug, PartialEq, Eq, Clone)] -- GitLab From 6b8631548958e3bbd33346beb874d27fa6d8536e Mon Sep 17 00:00:00 2001 From: DKolter <danielkolter157@gmail.com> Date: Fri, 13 Dec 2024 16:56:06 +0100 Subject: [PATCH 4/4] Adc driver adoption --- core/src/adc.rs | 71 ++++++++++++++++++++++++++++------------------ core/src/config.rs | 1 + core/src/main.rs | 15 ++++++---- core/src/topics.rs | 8 ++---- 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/core/src/adc.rs b/core/src/adc.rs index 8c5295d..8843573 100644 --- a/core/src/adc.rs +++ b/core/src/adc.rs @@ -7,35 +7,40 @@ use core::time::Duration; use rumqttc::v5::mqttbytes::QoS; use serde::Deserialize; use serde_json::Value; +use std::path::{Path, PathBuf}; + +const REFERENCE_VOLTAGE: f64 = 2.048; +const ADC_MAXIMUM: u16 = 4096; pub struct AdcDriver { args: AdcDriverArgs, - topics: [SensorTopics; 8], + pins: Vec<AdcPin>, +} + +struct AdcPin { + path: PathBuf, + topics: SensorTopics, } #[derive(Deserialize)] struct AdcDriverArgs { - path: String, + path: PathBuf, sleep: u64, } impl AdcDriver { - pub async fn new(_index: usize, args: Value) -> Result<Self> { + pub async fn new(client_id: &str, _index: usize, args: Value) -> Result<Self> { let args: AdcDriverArgs = serde_json::from_value(args).context("Parsing adc driver args")?; - let topics = [ - SensorTopics::new("adc", "0"), - SensorTopics::new("adc", "1"), - SensorTopics::new("adc", "2"), - SensorTopics::new("adc", "3"), - SensorTopics::new("adc", "4"), - SensorTopics::new("adc", "5"), - SensorTopics::new("adc", "6"), - SensorTopics::new("adc", "7"), - ]; + let pins = (0..8) + .map(|i| AdcPin { + path: args.path.join(format!("in_voltage{i}_raw")), + topics: SensorTopics::new(&client_id, "adc", &format!("{i}")), + }) + .collect(); - Ok(Self { args, topics }) + Ok(Self { args, pins }) } pub fn subscriptions(&self) -> Vec<String> { @@ -43,24 +48,20 @@ impl AdcDriver { } pub async fn run(&mut self, mqtt: MqttDriver) { - // Publish the unit of measurement (V) for all topics - for topic in self.topics.iter() { - mqtt.publish(PublishMessage { - topic: topic.unit_of_measurement.clone(), - payload: b"V".to_vec(), - qos: QoS::AtLeastOnce, - retain: true, - }) - .await; - } - loop { - for (i, topic) in self.topics.iter().enumerate() { - let value = 0.0; // Read the value from the ADC + for pin in &self.pins { + // Read the raw value from the pin path + let value = match Self::read_raw_value(&pin.path).await { + Ok(value) => value, + Err(err) => { + log::error!("Reading raw value: {:?}", err); + continue; + } + }; // Publish the value mqtt.publish(PublishMessage { - topic: topic.state_t.clone(), + topic: pin.topics.state.clone(), payload: value.to_string().into_bytes(), qos: QoS::AtLeastOnce, retain: true, @@ -71,4 +72,18 @@ impl AdcDriver { tokio::time::sleep(Duration::from_secs(self.args.sleep)).await; } } + + async fn read_raw_value(path: &Path) -> Result<f64> { + let raw = tokio::fs::read_to_string(path) + .await + .context("Reading raw value")?; + + let raw = raw + .trim() + .parse::<u16>() + .context("Parsing raw value")? + .min(ADC_MAXIMUM); + + Ok(raw as f64 / ADC_MAXIMUM as f64 * REFERENCE_VOLTAGE) + } } diff --git a/core/src/config.rs b/core/src/config.rs index 06604da..7d17eaf 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -4,6 +4,7 @@ use serde_json::Value; #[derive(Deserialize, Debug)] pub struct Config { + pub client_id: String, pub mqtt: MqttConfig, pub drivers: Vec<DriverConfig>, } diff --git a/core/src/main.rs b/core/src/main.rs index dac0f88..9047449 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -42,14 +42,19 @@ impl Driver { /// The index is determined by the order of the drivers; /// in the configuration file at the moment, but will be /// determined by a MQTT handshake with the core in the future. - async fn new(protocol: DriverProtocol, index: usize, args: Value) -> Result<Self> { + async fn new( + client_id: &str, + protocol: DriverProtocol, + index: usize, + args: Value, + ) -> Result<Self> { Ok(match protocol { DriverProtocol::Vebus => Self::Vebus(VebusDriver::new(index, args).await), DriverProtocol::Vedirect => Self::Vedirect(VedirectDriver::new(index, args).await), DriverProtocol::OutputGpio => { Self::OutputGpio(GpioOutputDriver::new(index, args).await) } - DriverProtocol::Adc => Self::Adc(AdcDriver::new(index, args).await?), + DriverProtocol::Adc => Self::Adc(AdcDriver::new(client_id, index, args).await?), DriverProtocol::Temperature => { Self::Temperature(TemperatureDriver::new(index, args).await) } @@ -90,7 +95,7 @@ impl Driver { /// Create a list of drivers from the given configurations. /// When a new driver is added, it should be added to the match statement. -async fn create_drivers(configs: Vec<DriverConfig>) -> Vec<Driver> { +async fn create_drivers(client_id: String, configs: Vec<DriverConfig>) -> Vec<Driver> { let mut drivers: Vec<Driver> = Vec::new(); for config in configs { log::info!( @@ -98,7 +103,7 @@ async fn create_drivers(configs: Vec<DriverConfig>) -> Vec<Driver> { config.index, config.protocol ); - match Driver::new(config.protocol, config.index, config.args).await { + match Driver::new(&client_id, config.protocol, config.index, config.args).await { Ok(driver) => drivers.push(driver), Err(e) => log::error!("Failed to create driver: {e}"), } @@ -114,7 +119,7 @@ async fn main() { // Load the configuration file and create respective drivers let config = Config::load().expect("Failed to load configuration file"); - let drivers = create_drivers(config.drivers).await; + let drivers = create_drivers(config.client_id, config.drivers).await; // Create the MQTT driver let mqtt = MqttDriver::new(config.mqtt, &drivers).await; diff --git a/core/src/topics.rs b/core/src/topics.rs index 84f29b1..cb7f636 100644 --- a/core/src/topics.rs +++ b/core/src/topics.rs @@ -30,15 +30,13 @@ impl TemperatureTopics { #[derive(Debug)] pub struct SensorTopics { - pub state_t: String, - pub unit_of_measurement: String, + pub state: String, } impl SensorTopics { - pub fn new(node_id: &str, object_id: &str) -> Self { + pub fn new(client_id: &str, node_id: &str, object_id: &str) -> Self { Self { - state_t: format!("s/sensor/{node_id}/{object_id}/state_t"), - unit_of_measurement: format!("s/sensor/{node_id}/{object_id}/unit_of_measurement"), + state: format!("s/{client_id}/{node_id}/{object_id}/state"), } } } -- GitLab