first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:29:38 +03:00
commit 427856cd3a
176 changed files with 27613 additions and 0 deletions

211
examples/ez-parse.rs Normal file
View File

@@ -0,0 +1,211 @@
#![allow(clippy::cast_possible_truncation, clippy::match_same_arms)]
use std::fmt::Debug;
use std::io::{Cursor, stdin};
use clap::Parser;
use serde::Deserialize;
use bifrost::error::ApiResult;
use zcl::cluster;
use zcl::error::ZclResult;
use zcl::frame::{ZclFrame, ZclFrameDirection, ZclFrameType};
#[macro_use]
extern crate log;
pub fn f64_str<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::str::FromStr;
let s = String::deserialize(deserializer)?;
f64::from_str(&s).map_err(serde::de::Error::custom)
}
pub fn u16_hex<'de, D>(deserializer: D) -> Result<u16, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(u16::from_be(
u16::from_str_radix(&s, 16).map_err(serde::de::Error::custom)?,
))
}
pub fn u16_hex_opt<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
if let Some(s) = opt {
Ok(Some(u16::from_be(
u16::from_str_radix(&s, 16).map_err(serde::de::Error::custom)?,
)))
} else {
Ok(None)
}
}
pub fn vec_hex_opt<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
if let Some(s) = opt {
Ok(hex::decode(s).map_err(serde::de::Error::custom)?)
} else {
Ok(vec![])
}
}
#[derive(Debug, Deserialize)]
pub struct Record {
/* pub src_mac: String, */
/* pub cmd: Option<String>, */
#[serde(deserialize_with = "f64_str")]
pub time: f64,
pub index: u64,
#[serde(deserialize_with = "u16_hex")]
pub src: u16,
#[serde(deserialize_with = "u16_hex")]
pub dst: u16,
#[serde(deserialize_with = "u16_hex")]
pub cluster: u16,
#[serde(deserialize_with = "vec_hex_opt")]
pub data: Vec<u8>,
}
fn parse(rec: &Record, no_index: bool) -> ZclResult<()> {
if rec.data.is_empty() {
return Ok(());
}
let mut cur = Cursor::new(&rec.data);
let frame = ZclFrame::parse(&mut cur)?;
let data = &rec.data[cur.position() as usize..];
let src = hex::encode(rec.src.to_be_bytes());
let dst = hex::encode(rec.dst.to_be_bytes());
let flags = frame.flags;
let cmd = frame.cmd;
let cls = rec.cluster;
let index = if no_index { 0 } else { rec.index };
let describe = |cat: &str, desc: ZclResult<Option<String>>| {
let dir = if flags.direction == ZclFrameDirection::ClientToServer {
" :>"
} else {
"<: "
};
match desc {
Ok(Some(desc)) => {
if desc.is_empty() {
return;
}
info!(
"[{index:6}] [{src} -> {dst}] {flags:?} [{cls:04x}] {cmd:02x} {dir} {cat}{desc} {}",
hex::encode(data)
);
}
Ok(None) => {
warn!(
"[{index:6}] [{src} -> {dst}] {flags:?} [{cls:04x}] {cmd:02x} {dir} {cat}Unknown {}",
hex::encode(data)
);
}
Err(err) => {
error!(
"[{index:6}] [{src} -> {dst}] {flags:?} [{cls:04x}] {cmd:02x} {dir} FAILED {}: {err}",
hex::encode(data)
);
}
}
};
if frame.flags.frame_type == ZclFrameType::ProfileWide {
describe("", cluster::standard::describe(&frame, data));
return Ok(());
}
match rec.cluster {
0x0003 => describe("Effect:", Ok(cluster::effects::describe(&frame, data))),
0x0004 => describe("Group:", Ok(cluster::groups::describe(&frame, data))),
0x0005 => describe("Scene:", Ok(cluster::scenes::describe(&frame, data))),
0x0006 => describe("OnOff:", Ok(cluster::onoff::describe(&frame, data))),
0x0008 => describe("LevelCtrl:", Ok(cluster::levelctrl::describe(&frame, data))),
0x0019 => {
// suppress OTA messages
}
0x0021 => {
// suppress ZGP (Zigbee Green Power) messages
}
0x0300 => describe("ColorCtrl:", Ok(cluster::colorctrl::describe(&frame, data))),
0x0406 => {
// suppress occypancy sensing
}
0x1000 => describe(
"Commissioning:",
cluster::commissioning::describe(&frame, data),
),
0xFC01 => describe("HueEnt:", cluster::hue_fc01::describe(&frame, data)),
0xFC03 => describe("HueCmp:", cluster::hue_fc03::describe(&frame, data)),
_ => describe("UNKNOWN:", Ok(None)),
}
Ok(())
}
#[derive(Parser, Debug)]
#[command(version, long_about = None)]
#[command(about("Parses hue zigbee frames (as hex-encoded lines on stdin)"))]
struct Args {
/// Ignore packet number (easier diffing, since all packets are numbered 0)
#[arg(short, name = "no-index", default_value_t = false)]
no_index: bool,
}
fn main() -> ApiResult<()> {
pretty_env_logger::formatted_builder()
.filter_level(log::LevelFilter::Debug)
.parse_default_env()
.init();
let args = Args::parse();
for line in stdin().lines() {
let line = line?;
match serde_json::from_str::<Record>(line.trim()) {
Ok(data) => {
if let Err(err) = parse(&data, args.no_index) {
error!("Failed parse: {err}");
eprintln!(" {line:<40}");
eprintln!(" {data:?}");
}
}
Err(err) => {
error!("Failed to parse json: {err}");
eprintln!(" {line:<40}");
}
}
}
Ok(())
}