use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum CommissioningError {
Ok = 0,
Valueoutsiderange = 1,
Invalidauthentication = 2,
Nofailsafe = 3,
Busywithotheradmin = 4,
Requiredtcnotaccepted = 5,
Tcacknowledgementsnotreceived = 6,
Tcminversionnotmet = 7,
}
impl CommissioningError {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(CommissioningError::Ok),
1 => Some(CommissioningError::Valueoutsiderange),
2 => Some(CommissioningError::Invalidauthentication),
3 => Some(CommissioningError::Nofailsafe),
4 => Some(CommissioningError::Busywithotheradmin),
5 => Some(CommissioningError::Requiredtcnotaccepted),
6 => Some(CommissioningError::Tcacknowledgementsnotreceived),
7 => Some(CommissioningError::Tcminversionnotmet),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<CommissioningError> for u8 {
fn from(val: CommissioningError) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum NetworkRecoveryReason {
Unspecified = 0,
Auth = 1,
Visibility = 2,
}
impl NetworkRecoveryReason {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(NetworkRecoveryReason::Unspecified),
1 => Some(NetworkRecoveryReason::Auth),
2 => Some(NetworkRecoveryReason::Visibility),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<NetworkRecoveryReason> for u8 {
fn from(val: NetworkRecoveryReason) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum RegulatoryLocationType {
Indoor = 0,
Outdoor = 1,
Indooroutdoor = 2,
}
impl RegulatoryLocationType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(RegulatoryLocationType::Indoor),
1 => Some(RegulatoryLocationType::Outdoor),
2 => Some(RegulatoryLocationType::Indooroutdoor),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<RegulatoryLocationType> for u8 {
fn from(val: RegulatoryLocationType) -> Self {
val as u8
}
}
#[derive(Debug, serde::Serialize)]
pub struct BasicCommissioningInfo {
pub fail_safe_expiry_length_seconds: Option<u16>,
pub max_cumulative_failsafe_seconds: Option<u16>,
}
pub fn encode_arm_fail_safe(expiry_length_seconds: u16, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(expiry_length_seconds)).into(),
(1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_regulatory_config(new_regulatory_config: RegulatoryLocationType, country_code: String, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt8(new_regulatory_config.to_u8())).into(),
(1, tlv::TlvItemValueEnc::String(country_code)).into(),
(2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_tc_acknowledgements(tc_version: u16, tc_user_response: u8) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(tc_version)).into(),
(1, tlv::TlvItemValueEnc::UInt8(tc_user_response)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_breadcrumb(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected UInt64"))
}
}
pub fn decode_basic_commissioning_info(inp: &tlv::TlvItemValue) -> anyhow::Result<BasicCommissioningInfo> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(BasicCommissioningInfo {
fail_safe_expiry_length_seconds: item.get_int(&[0]).map(|v| v as u16),
max_cumulative_failsafe_seconds: item.get_int(&[1]).map(|v| v as u16),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_regulatory_config(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
if let tlv::TlvItemValue::Int(v) = inp {
RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_location_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
if let tlv::TlvItemValue::Int(v) = inp {
RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_supports_concurrent_connection(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_tc_accepted_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u16)
} else {
Err(anyhow::anyhow!("Expected UInt16"))
}
}
pub fn decode_tc_min_required_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u16)
} else {
Err(anyhow::anyhow!("Expected UInt16"))
}
}
pub fn decode_tc_acknowledgements(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u8)
} else {
Err(anyhow::anyhow!("Expected UInt8"))
}
}
pub fn decode_tc_acknowledgements_required(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_tc_update_deadline(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(Some(*v as u32))
} else {
Ok(None)
}
}
pub fn decode_recovery_identifier(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
if let tlv::TlvItemValue::OctetString(v) = inp {
Ok(v.clone())
} else {
Err(anyhow::anyhow!("Expected OctetString"))
}
}
pub fn decode_network_recovery_reason(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkRecoveryReason>> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(NetworkRecoveryReason::from_u8(*v as u8))
} else {
Ok(None)
}
}
pub fn decode_is_commissioning_without_power(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0030 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0030, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_breadcrumb(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_basic_commissioning_info(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_regulatory_config(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0003 => {
match decode_location_capability(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0004 => {
match decode_supports_concurrent_connection(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0005 => {
match decode_tc_accepted_version(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0006 => {
match decode_tc_min_required_version(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0007 => {
match decode_tc_acknowledgements(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0008 => {
match decode_tc_acknowledgements_required(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0009 => {
match decode_tc_update_deadline(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x000A => {
match decode_recovery_identifier(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x000B => {
match decode_network_recovery_reason(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x000C => {
match decode_is_commissioning_without_power(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, "Breadcrumb"),
(0x0001, "BasicCommissioningInfo"),
(0x0002, "RegulatoryConfig"),
(0x0003, "LocationCapability"),
(0x0004, "SupportsConcurrentConnection"),
(0x0005, "TCAcceptedVersion"),
(0x0006, "TCMinRequiredVersion"),
(0x0007, "TCAcknowledgements"),
(0x0008, "TCAcknowledgementsRequired"),
(0x0009, "TCUpdateDeadline"),
(0x000A, "RecoveryIdentifier"),
(0x000B, "NetworkRecoveryReason"),
(0x000C, "IsCommissioningWithoutPower"),
]
}
#[derive(Debug, serde::Serialize)]
pub struct ArmFailSafeResponse {
pub error_code: Option<CommissioningError>,
pub debug_text: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct SetRegulatoryConfigResponse {
pub error_code: Option<CommissioningError>,
pub debug_text: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct CommissioningCompleteResponse {
pub error_code: Option<CommissioningError>,
pub debug_text: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct SetTCAcknowledgementsResponse {
pub error_code: Option<CommissioningError>,
}
pub fn decode_arm_fail_safe_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ArmFailSafeResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ArmFailSafeResponse {
error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_set_regulatory_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetRegulatoryConfigResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(SetRegulatoryConfigResponse {
error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_commissioning_complete_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CommissioningCompleteResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(CommissioningCompleteResponse {
error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_set_tc_acknowledgements_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTCAcknowledgementsResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(SetTCAcknowledgementsResponse {
error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}