Skip to content

Commit

Permalink
Merge pull request #43 from chrivers/chrivers/z2m-fixes
Browse files Browse the repository at this point in the history
Chrivers/z2m fixes
  • Loading branch information
chrivers authored Sep 16, 2024
2 parents 52a930d + 360bf65 commit 20fa95d
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 64 deletions.
118 changes: 86 additions & 32 deletions examples/wsparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,103 @@

use std::io::stdin;

use bifrost::{error::ApiResult, z2m::api::Message};
use bifrost::{
error::ApiResult,
z2m::{
api::{Availability, Message, RawMessage},
update::DeviceUpdate,
},
};
use log::LevelFilter;

#[tokio::main]
#[rustfmt::skip]
async fn main() -> ApiResult<()> {
pretty_env_logger::init();
pretty_env_logger::formatted_builder().filter_level(LevelFilter::Debug).init();

for line in stdin().lines() {
let data = serde_json::from_str(&line?);
let line = line?;

let Ok(msg) = data else {
log::error!("INVALID LINE: {:#?}", data);
let raw_data = serde_json::from_str::<RawMessage>(&line);

let Ok(raw_msg) = raw_data else {
log::error!("INVALID LINE: {:#?}", raw_data);
continue;
};

match msg {
Message::BridgeInfo(ref obj) => {
println!("{:#?}", obj.config_schema);
},
Message::BridgeLogging(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeExtensions(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeDevices(ref devices) => {
for dev in devices {
println!("{dev:#?}");
}
},
Message::BridgeGroups(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeDefinitions(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeState(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeEvent(ref obj) => {
println!("{obj:#?}");
},
/* bridge messages are those on bridge/+ topics */

if raw_msg.topic.starts_with("bridge/") {
let data = serde_json::from_str(&line);

let Ok(msg) = data else {
log::error!("INVALID LINE [bridge]: {:#?}", data);
continue;
};

match msg {
Message::BridgeInfo(ref obj) => {
println!("{:#?}", obj.config_schema);
},
Message::BridgeLogging(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeExtensions(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeDevices(ref devices) => {
for dev in devices {
println!("{dev:#?}");
}
},
Message::BridgeGroups(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeDefinitions(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeState(ref obj) => {
println!("{obj:#?}");
},
Message::BridgeEvent(ref obj) => {
println!("{obj:#?}");
},
}

continue;
}

/* everything that ends in /availability are online/offline updates */

if raw_msg.topic.ends_with("/availability") {
let data = serde_json::from_value::<Availability>(raw_msg.payload);

let Ok(msg) = data else {
log::error!("INVALID LINE [availability]: {}", data.unwrap_err());
eprintln!("{line}");
eprintln!();
continue;
};

continue;
}

/* everything else: device updates */

let data = serde_json::from_value::<DeviceUpdate>(raw_msg.payload);

let Ok(msg) = data else {
log::error!("INVALID LINE [device]: {}", data.unwrap_err());
eprintln!("{line}");
eprintln!();
continue;
};

/* having unknown fields is not an error. they are simply not mapped */
/* if !msg.__.is_empty() { */
/* log::warn!("Unknown fields found: {:?}", msg.__.keys()); */
/* } */
println!("{msg:#?}");
}

Ok(())
Expand Down
17 changes: 15 additions & 2 deletions src/z2m/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ pub enum Message {
BridgeExtensions(Value),
}

#[derive(Serialize, Deserialize, Clone, Hash, Debug, Copy)]
#[serde(rename_all = "snake_case")]
pub enum Availability {
Online,
Offline,
}

#[derive(Serialize, Deserialize, Clone, Hash)]
#[serde(transparent)]
pub struct IeeeAddress(#[serde(deserialize_with = "ieee_address")] u64);
Expand Down Expand Up @@ -89,6 +96,7 @@ pub struct BridgeEvent {
pub struct BridgeLogging {
pub level: String,
pub message: String,
pub topic: Option<String>,
}

type BridgeGroups = Vec<Group>;
Expand Down Expand Up @@ -209,7 +217,7 @@ pub struct ConfigAdvanced {
pub channel: i64,
pub elapsed: bool,
pub ext_pan_id: Vec<i64>,
pub homeassistant_legacy_entity_attributes: bool,
pub homeassistant_legacy_entity_attributes: Option<bool>,
pub last_seen: String,
pub legacy_api: bool,
pub legacy_availability_payload: bool,
Expand Down Expand Up @@ -294,8 +302,8 @@ pub enum PowerSource {

pub type BridgeDevices = Vec<Device>;

#[allow(clippy::pub_underscore_fields)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Device {
pub description: Option<String>,
pub date_code: Option<String>,
Expand All @@ -315,6 +323,11 @@ pub struct Device {
pub supported: Option<bool>,
#[serde(rename = "type")]
pub device_type: String,

/* all other fields */
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default, flatten)]
pub __: HashMap<String, Value>,
}

impl Device {
Expand Down
8 changes: 4 additions & 4 deletions src/z2m/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::model::state::AuxData;
use crate::resource::Resources;
use crate::z2m::api::{ExposeLight, Message, RawMessage};
use crate::z2m::request::{ClientRequest, Z2mRequest};
use crate::z2m::update::DeviceUpdate;
use crate::z2m::update::{DeviceColor, DeviceUpdate};

#[derive(Debug)]
struct LearnScene {
Expand Down Expand Up @@ -321,7 +321,7 @@ impl Client {
.with_on(devupd.state.map(Into::into))
.with_brightness(devupd.brightness.map(|b| b / 254.0 * 100.0))
.with_color_temperature(devupd.color_temp)
.with_color_xy(devupd.color.map(|col| col.xy));
.with_color_xy(devupd.color.and_then(|col| col.xy));

*light += upd;
})?;
Expand All @@ -333,8 +333,8 @@ impl Client {
let light = res.get::<Light>(&rlink)?;
let mut color_temperature = None;
let mut color = None;
if let Some(col) = upd.color {
color = Some(ColorUpdate { xy: col.xy });
if let Some(DeviceColor { xy: Some(xy), .. }) = upd.color {
color = Some(ColorUpdate { xy });
} else if let Some(mirek) = upd.color_temp {
color_temperature = Some(ColorTemperatureUpdate { mirek });
}
Expand Down
48 changes: 22 additions & 26 deletions src/z2m/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use serde_json::Value;
use crate::hue::api::On;
use crate::model::types::XY;

#[allow(clippy::pub_underscore_fields)]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(deny_unknown_fields)]
pub struct DeviceUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<DeviceState>,
Expand Down Expand Up @@ -42,6 +42,11 @@ pub struct DeviceUpdate {
pub battery: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transition: Option<f64>,

/* all other fields */
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default, flatten)]
pub __: HashMap<String, Value>,
}

impl DeviceUpdate {
Expand All @@ -53,7 +58,13 @@ impl DeviceUpdate {
#[must_use]
pub fn with_state(self, state: Option<bool>) -> Self {
Self {
state: state.map(DeviceState::from),
state: state.map(|on| {
if on {
DeviceState::On
} else {
DeviceState::Off
}
}),
..self
}
}
Expand Down Expand Up @@ -97,7 +108,7 @@ pub struct DeviceColor {
pub saturation: Option<f64>,

#[serde(flatten)]
pub xy: XY,
pub xy: Option<XY>,
}

impl DeviceColor {
Expand All @@ -108,7 +119,7 @@ impl DeviceColor {
s: None,
hue: None,
saturation: None,
xy,
xy: Some(xy),
}
}

Expand All @@ -119,7 +130,7 @@ impl DeviceColor {
s: None,
hue: Some(h),
saturation: Some(s),
xy: XY::new(0.0, 0.0),
xy: None,
}
}
}
Expand Down Expand Up @@ -181,34 +192,19 @@ pub enum DeviceColorMode {
Xy,
}

#[derive(Copy, Debug, Serialize, Deserialize, Clone)]
#[derive(Copy, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum DeviceState {
On,
Off,
}

impl From<bool> for DeviceState {
fn from(value: bool) -> Self {
if value {
Self::On
} else {
Self::Off
}
}
}

impl From<DeviceState> for bool {
fn from(value: DeviceState) -> Self {
match value {
DeviceState::On => true,
DeviceState::Off => false,
}
}
Lock,
Unlock,
}

impl From<DeviceState> for On {
fn from(value: DeviceState) -> Self {
Self { on: value.into() }
Self {
on: value == DeviceState::On,
}
}
}

0 comments on commit 20fa95d

Please sign in to comment.