use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ActionError {
Unknown = 0,
Interrupted = 1,
}
impl ActionError {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ActionError::Unknown),
1 => Some(ActionError::Interrupted),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ActionError> for u8 {
fn from(val: ActionError) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ActionState {
Inactive = 0,
Active = 1,
Paused = 2,
Disabled = 3,
}
impl ActionState {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ActionState::Inactive),
1 => Some(ActionState::Active),
2 => Some(ActionState::Paused),
3 => Some(ActionState::Disabled),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ActionState> for u8 {
fn from(val: ActionState) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ActionType {
Other = 0,
Scene = 1,
Sequence = 2,
Automation = 3,
Exception = 4,
Notification = 5,
Alarm = 6,
}
impl ActionType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ActionType::Other),
1 => Some(ActionType::Scene),
2 => Some(ActionType::Sequence),
3 => Some(ActionType::Automation),
4 => Some(ActionType::Exception),
5 => Some(ActionType::Notification),
6 => Some(ActionType::Alarm),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ActionType> for u8 {
fn from(val: ActionType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum EndpointListType {
Other = 0,
Room = 1,
Zone = 2,
}
impl EndpointListType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(EndpointListType::Other),
1 => Some(EndpointListType::Room),
2 => Some(EndpointListType::Zone),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<EndpointListType> for u8 {
fn from(val: EndpointListType) -> Self {
val as u8
}
}
pub type CommandBits = u16;
pub mod commandbits {
pub const INSTANT_ACTION: u16 = 0x01;
pub const INSTANT_ACTION_WITH_TRANSITION: u16 = 0x02;
pub const START_ACTION: u16 = 0x04;
pub const START_ACTION_WITH_DURATION: u16 = 0x08;
pub const STOP_ACTION: u16 = 0x10;
pub const PAUSE_ACTION: u16 = 0x20;
pub const PAUSE_ACTION_WITH_DURATION: u16 = 0x40;
pub const RESUME_ACTION: u16 = 0x80;
pub const ENABLE_ACTION: u16 = 0x100;
pub const ENABLE_ACTION_WITH_DURATION: u16 = 0x200;
pub const DISABLE_ACTION: u16 = 0x400;
pub const DISABLE_ACTION_WITH_DURATION: u16 = 0x800;
}
#[derive(Debug, serde::Serialize)]
pub struct Action {
pub action_id: Option<u16>,
pub name: Option<String>,
pub type_: Option<ActionType>,
pub endpoint_list_id: Option<u16>,
pub supported_commands: Option<u8>,
pub state: Option<ActionState>,
}
#[derive(Debug, serde::Serialize)]
pub struct EndpointList {
pub endpoint_list_id: Option<u16>,
pub name: Option<String>,
pub type_: Option<EndpointListType>,
pub endpoints: Option<Vec<u16>>,
}
pub fn encode_instant_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_instant_action_with_transition(action_id: u16, invoke_id: u32, transition_time: u16) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
(2, tlv::TlvItemValueEnc::UInt16(transition_time)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_start_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_start_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
(2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_stop_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_pause_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_pause_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
(2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_resume_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_enable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_enable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
(2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_disable_action(action_id: u16, invoke_id: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_disable_action_with_duration(action_id: u16, invoke_id: u32, duration: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(action_id)).into(),
(1, tlv::TlvItemValueEnc::UInt32(invoke_id)).into(),
(2, tlv::TlvItemValueEnc::UInt32(duration)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_action_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Action>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(Action {
action_id: item.get_int(&[0]).map(|v| v as u16),
name: item.get_string_owned(&[1]),
type_: item.get_int(&[2]).and_then(|v| ActionType::from_u8(v as u8)),
endpoint_list_id: item.get_int(&[3]).map(|v| v as u16),
supported_commands: item.get_int(&[4]).map(|v| v as u8),
state: item.get_int(&[5]).and_then(|v| ActionState::from_u8(v as u8)),
});
}
}
Ok(res)
}
pub fn decode_endpoint_lists(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<EndpointList>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(EndpointList {
endpoint_list_id: item.get_int(&[0]).map(|v| v as u16),
name: item.get_string_owned(&[1]),
type_: item.get_int(&[2]).and_then(|v| EndpointListType::from_u8(v as u8)),
endpoints: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
let items: Vec<u16> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u16) } else { None } }).collect();
Some(items)
} else {
None
}
},
});
}
}
Ok(res)
}
pub fn decode_setup_url(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
if let tlv::TlvItemValue::String(v) = inp {
Ok(v.clone())
} else {
Err(anyhow::anyhow!("Expected String"))
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0025 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0025, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_action_list(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_endpoint_lists(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_setup_url(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
_ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
}
}
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
vec![
(0x0000, "ActionList"),
(0x0001, "EndpointLists"),
(0x0002, "SetupURL"),
]
}
#[derive(Debug, serde::Serialize)]
pub struct StateChangedEvent {
pub action_id: Option<u16>,
pub invoke_id: Option<u32>,
pub new_state: Option<ActionState>,
}
#[derive(Debug, serde::Serialize)]
pub struct ActionFailedEvent {
pub action_id: Option<u16>,
pub invoke_id: Option<u32>,
pub new_state: Option<ActionState>,
pub error: Option<ActionError>,
}
pub fn decode_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateChangedEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(StateChangedEvent {
action_id: item.get_int(&[0]).map(|v| v as u16),
invoke_id: item.get_int(&[1]).map(|v| v as u32),
new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_action_failed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ActionFailedEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ActionFailedEvent {
action_id: item.get_int(&[0]).map(|v| v as u16),
invoke_id: item.get_int(&[1]).map(|v| v as u32),
new_state: item.get_int(&[2]).and_then(|v| ActionState::from_u8(v as u8)),
error: item.get_int(&[3]).and_then(|v| ActionError::from_u8(v as u8)),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}