mirror of
https://codeberg.org/icewind/taspromto.git
synced 2026-06-03 16:44:11 +02:00
add rflink32 support
This commit is contained in:
parent
f472cc54ef
commit
a5bb4b1d80
5 changed files with 140 additions and 1 deletions
|
|
@ -16,6 +16,12 @@ in {
|
|||
description = "Names for mitemp sensors";
|
||||
};
|
||||
|
||||
rfChannelNames = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = "Names for 433mhz temperature sensors";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 3030;
|
||||
|
|
@ -40,6 +46,7 @@ in {
|
|||
environment = {
|
||||
PORT = toString cfg.port;
|
||||
MITEMP_NAMES = concatStringsSep "," (map (k: k + "=" + cfg.mitempNames."${k}") (attrNames cfg.mitempNames));
|
||||
RF_TEMP_NAMES = concatStringsSep "," (map (k: k + "=" + cfg.rfChannelNames."${k}") (attrNames cfg.rfChannelNames));
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub struct Config {
|
|||
pub mqtt_port: u16,
|
||||
pub host_port: u16,
|
||||
pub mi_temp_names: BTreeMap<BDAddr, String>,
|
||||
pub rf_temp_names: BTreeMap<u8, String>,
|
||||
pub mqtt_credentials: Option<Credentials>,
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +49,20 @@ impl Config {
|
|||
})
|
||||
.collect::<Result<BTreeMap<BDAddr, String>, Report>>()?;
|
||||
|
||||
let rf_temp_names = dotenvy::var("RF_TEMP_NAMES").unwrap_or_default();
|
||||
let rf_temp_names = rf_temp_names
|
||||
.split(',')
|
||||
.map(|pair| {
|
||||
let mut parts = pair.split('=');
|
||||
if let (Some(mac), Some(name)) = (parts.next().map(u8::from_str), parts.next()) {
|
||||
let channel = mac.wrap_err("Invalid RF_TEMP_NAMES")?;
|
||||
Ok((channel, name.to_string()))
|
||||
} else {
|
||||
Err(Report::msg("Invalid RF_TEMP_NAMES"))
|
||||
}
|
||||
})
|
||||
.collect::<Result<BTreeMap<u8, String>, Report>>()?;
|
||||
|
||||
let mqtt_credentials = match dotenvy::var("MQTT_USERNAME") {
|
||||
Ok(username) => {
|
||||
let password = dotenvy::var("MQTT_PASSWORD")
|
||||
|
|
@ -62,6 +77,7 @@ impl Config {
|
|||
mqtt_port,
|
||||
host_port,
|
||||
mi_temp_names,
|
||||
rf_temp_names,
|
||||
mqtt_credentials,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use tokio::task::spawn;
|
|||
pub struct DeviceStates {
|
||||
pub devices: HashMap<Device, DeviceState>,
|
||||
pub mi_temp_devices: BTreeMap<BDAddr, MiTempState>,
|
||||
pub rf_temp_devices: BTreeMap<u8, TempState>,
|
||||
}
|
||||
|
||||
impl DeviceStates {
|
||||
|
|
@ -36,11 +37,26 @@ impl DeviceStates {
|
|||
|
||||
device.update(json);
|
||||
}
|
||||
pub fn update_rf(&mut self, payload: &str) {
|
||||
if let Some(data) = parse_rf_payload(payload) {
|
||||
let state = self.rf_temp_devices.entry(data.channel).or_default();
|
||||
state.humidity = data.humidity;
|
||||
state.temperature = data.temperature;
|
||||
} else {
|
||||
eprintln!("invalid rf payload: {payload}")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mi_temp(&self) -> impl Iterator<Item = (&BDAddr, &MiTempState)> {
|
||||
self.mi_temp_devices.iter()
|
||||
}
|
||||
|
||||
pub fn rf_temp(&self) -> impl Iterator<Item = (u8, &TempState)> {
|
||||
self.rf_temp_devices
|
||||
.iter()
|
||||
.map(|(channel, state)| (*channel, state))
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, cleanup_time: Instant, ping_time: Instant, client: &AsyncClient) {
|
||||
self.devices.retain(|device, state| {
|
||||
if state.last_seen < cleanup_time {
|
||||
|
|
@ -406,6 +422,42 @@ pub fn format_mi_temp_state<W: Write>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TempState {
|
||||
temperature: f32,
|
||||
humidity: u8,
|
||||
}
|
||||
|
||||
pub fn format_rf_temp_state<W: Write>(
|
||||
mut writer: W,
|
||||
channel: u8,
|
||||
names: &BTreeMap<u8, String>,
|
||||
state: &TempState,
|
||||
) -> std::fmt::Result {
|
||||
let name = if let Some(name) = names.get(&channel) {
|
||||
name
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if state.temperature > 0.0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"sensor_temperature{{channel=\"{}\", name=\"{}\"}} {}",
|
||||
channel, name, state.temperature
|
||||
)?;
|
||||
}
|
||||
|
||||
if state.humidity > 0 {
|
||||
writeln!(
|
||||
writer,
|
||||
"sensor_humidity{{channel=\"{}\", name=\"{}\"}} {}",
|
||||
channel, name, state.humidity
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores the 6 byte address used to identify Bluetooth devices.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Default, Ord, PartialOrd)]
|
||||
|
|
@ -617,3 +669,48 @@ pub fn format_pms_state<W: Write>(
|
|||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct RfPayload<'a> {
|
||||
name: &'a str,
|
||||
id: u16,
|
||||
channel: u8,
|
||||
battery: bool,
|
||||
temperature: f32,
|
||||
humidity: u8,
|
||||
}
|
||||
|
||||
fn parse_rf_payload(payload: &str) -> Option<RfPayload> {
|
||||
let mut parts = payload.split(";").skip(2);
|
||||
let name = parts.next()?;
|
||||
let id = parts.next()?.strip_prefix("ID=")?.parse().ok()?;
|
||||
let channel = parts.next()?.strip_prefix("CHN=")?.parse().ok()?;
|
||||
let battery = parts.next()?.strip_prefix("BAT=")? == "OK";
|
||||
let temperature = parts.next()?.strip_prefix("TEMP=")?;
|
||||
let temperature = u32::from_str_radix(temperature, 16).ok()?;
|
||||
let humidity = parts.next()?.strip_prefix("HUM=")?.parse().ok()?;
|
||||
|
||||
Some(RfPayload {
|
||||
name,
|
||||
id,
|
||||
channel,
|
||||
battery,
|
||||
temperature: temperature as f32 / 10.0,
|
||||
humidity,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rf_payload() {
|
||||
assert_eq!(
|
||||
RfPayload {
|
||||
name: "Bresser-3CH",
|
||||
id: 49,
|
||||
channel: 1,
|
||||
battery: true,
|
||||
temperature: 16.1,
|
||||
humidity: 58
|
||||
},
|
||||
parse_rf_payload("20;1E;Bresser-3CH;ID=49;CHN=0001;BAT=OK;TEMP=00a1;HUM=58;").unwrap()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
13
src/main.rs
13
src/main.rs
|
|
@ -4,7 +4,9 @@ mod mqtt;
|
|||
mod topic;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::device::{format_device_state, format_mi_temp_state, Device, DeviceStates};
|
||||
use crate::device::{
|
||||
format_device_state, format_mi_temp_state, format_rf_temp_state, Device, DeviceStates,
|
||||
};
|
||||
use crate::mqtt::mqtt_stream;
|
||||
use crate::topic::Topic;
|
||||
use color_eyre::{eyre::WrapErr, Result};
|
||||
|
|
@ -56,6 +58,7 @@ async fn main() -> Result<()> {
|
|||
async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
||||
let host_port = config.host_port;
|
||||
let mi_temp_names = config.mi_temp_names.clone();
|
||||
let rf_temp_names = config.rf_temp_names.clone();
|
||||
|
||||
let state = warp::any().map(move || device_states.clone());
|
||||
|
||||
|
|
@ -70,6 +73,9 @@ async fn serve(device_states: Arc<Mutex<DeviceStates>>, config: Config) {
|
|||
for (addr, state) in state.mi_temp() {
|
||||
format_mi_temp_state(&mut response, *addr, &mi_temp_names, state).unwrap()
|
||||
}
|
||||
for (channel, state) in state.rf_temp() {
|
||||
format_rf_temp_state(&mut response, channel, &rf_temp_names, state).unwrap()
|
||||
}
|
||||
response
|
||||
});
|
||||
|
||||
|
|
@ -126,6 +132,11 @@ async fn mqtt_client<S: Stream<Item = Result<Publish>>>(
|
|||
device_states.update(device, json);
|
||||
}
|
||||
}
|
||||
Topic::Msg(_device) => {
|
||||
let payload = std::str::from_utf8(message.payload.as_ref()).unwrap_or_default();
|
||||
let mut device_states = device_states.lock().unwrap();
|
||||
device_states.update_rf(payload);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,18 @@ pub enum Topic {
|
|||
Result(Device),
|
||||
Other(String),
|
||||
Status(Device),
|
||||
Msg(Device),
|
||||
}
|
||||
|
||||
impl From<&str> for Topic {
|
||||
fn from(raw: &str) -> Self {
|
||||
if let Some(rf_name) = raw.strip_suffix("/msg") {
|
||||
let device = Device {
|
||||
hostname: rf_name.to_string(),
|
||||
};
|
||||
return Topic::Msg(device);
|
||||
}
|
||||
|
||||
let mut parts = raw.split('/');
|
||||
if let (Some(prefix), Some(hostname), Some(cmd)) =
|
||||
(parts.next(), parts.next(), parts.next())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue