Skip to main content

matc/clusters/codec/
push_av_stream_transport.rs

1//! Matter TLV encoders and decoders for Push AV Stream Transport Cluster
2//! Cluster IDs: 0x0555
3//!
4//! This file is automatically generated from PushAVStreamTransport.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum CMAFInterface {
18    /// CMAF Interface-1 Mode
19    Interface1 = 0,
20    /// CMAF Interface-2 Mode with DASH Support
21    Interface2dash = 1,
22    /// CMAF Interface-2 Mode with HLS Support
23    Interface2hls = 2,
24}
25
26impl CMAFInterface {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(CMAFInterface::Interface1),
31            1 => Some(CMAFInterface::Interface2dash),
32            2 => Some(CMAFInterface::Interface2hls),
33            _ => None,
34        }
35    }
36
37    /// Convert to u8 value
38    pub fn to_u8(self) -> u8 {
39        self as u8
40    }
41}
42
43impl From<CMAFInterface> for u8 {
44    fn from(val: CMAFInterface) -> Self {
45        val as u8
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50#[repr(u8)]
51pub enum ContainerFormat {
52    /// CMAF container format
53    Cmaf = 0,
54}
55
56impl ContainerFormat {
57    /// Convert from u8 value
58    pub fn from_u8(value: u8) -> Option<Self> {
59        match value {
60            0 => Some(ContainerFormat::Cmaf),
61            _ => None,
62        }
63    }
64
65    /// Convert to u8 value
66    pub fn to_u8(self) -> u8 {
67        self as u8
68    }
69}
70
71impl From<ContainerFormat> for u8 {
72    fn from(val: ContainerFormat) -> Self {
73        val as u8
74    }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78#[repr(u8)]
79pub enum IngestMethods {
80    /// CMAF ingestion format
81    Cmafingest = 0,
82}
83
84impl IngestMethods {
85    /// Convert from u8 value
86    pub fn from_u8(value: u8) -> Option<Self> {
87        match value {
88            0 => Some(IngestMethods::Cmafingest),
89            _ => None,
90        }
91    }
92
93    /// Convert to u8 value
94    pub fn to_u8(self) -> u8 {
95        self as u8
96    }
97}
98
99impl From<IngestMethods> for u8 {
100    fn from(val: IngestMethods) -> Self {
101        val as u8
102    }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
106#[repr(u8)]
107pub enum StatusCode {
108    /// The specified TLSEndpointID cannot be found.
109    Invalidtlsendpoint = 2,
110    /// The specified VideoStreamID or AudioStreamID cannot be found.
111    Invalidstream = 3,
112    /// The specified URL is invalid.
113    Invalidurl = 4,
114    /// A specified ZoneID was invalid.
115    Invalidzone = 5,
116    /// The specified combination of Ingestion method and Container format is not supported.
117    Invalidcombination = 6,
118    /// The trigger type is invalid for this command.
119    Invalidtriggertype = 7,
120    /// The Stream Transport Status is invalid for this command.
121    Invalidtransportstatus = 8,
122    /// The requested Container options are not supported with the streams indicated.
123    Invalidoptions = 9,
124    /// The requested StreamUsage is not allowed.
125    Invalidstreamusage = 10,
126    /// Time sync has not occurred yet.
127    Invalidtime = 11,
128    /// The requested pre roll length is not compatible with the streams key frame interval.
129    Invalidprerolllength = 12,
130    /// The requested Streams info contained duplicate entries.
131    Duplicatestreamvalues = 13,
132}
133
134impl StatusCode {
135    /// Convert from u8 value
136    pub fn from_u8(value: u8) -> Option<Self> {
137        match value {
138            2 => Some(StatusCode::Invalidtlsendpoint),
139            3 => Some(StatusCode::Invalidstream),
140            4 => Some(StatusCode::Invalidurl),
141            5 => Some(StatusCode::Invalidzone),
142            6 => Some(StatusCode::Invalidcombination),
143            7 => Some(StatusCode::Invalidtriggertype),
144            8 => Some(StatusCode::Invalidtransportstatus),
145            9 => Some(StatusCode::Invalidoptions),
146            10 => Some(StatusCode::Invalidstreamusage),
147            11 => Some(StatusCode::Invalidtime),
148            12 => Some(StatusCode::Invalidprerolllength),
149            13 => Some(StatusCode::Duplicatestreamvalues),
150            _ => None,
151        }
152    }
153
154    /// Convert to u8 value
155    pub fn to_u8(self) -> u8 {
156        self as u8
157    }
158}
159
160impl From<StatusCode> for u8 {
161    fn from(val: StatusCode) -> Self {
162        val as u8
163    }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
167#[repr(u8)]
168pub enum TransportStatus {
169    /// Push Transport can transport AV Streams
170    Active = 0,
171    /// Push Transport cannot transport AV Streams
172    Inactive = 1,
173}
174
175impl TransportStatus {
176    /// Convert from u8 value
177    pub fn from_u8(value: u8) -> Option<Self> {
178        match value {
179            0 => Some(TransportStatus::Active),
180            1 => Some(TransportStatus::Inactive),
181            _ => None,
182        }
183    }
184
185    /// Convert to u8 value
186    pub fn to_u8(self) -> u8 {
187        self as u8
188    }
189}
190
191impl From<TransportStatus> for u8 {
192    fn from(val: TransportStatus) -> Self {
193        val as u8
194    }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
198#[repr(u8)]
199pub enum TransportTriggerType {
200    /// Triggered only via a command invocation
201    Command = 0,
202    /// Triggered via motion detection or command
203    Motion = 1,
204    /// Triggered always when transport status is Active
205    Continuous = 2,
206}
207
208impl TransportTriggerType {
209    /// Convert from u8 value
210    pub fn from_u8(value: u8) -> Option<Self> {
211        match value {
212            0 => Some(TransportTriggerType::Command),
213            1 => Some(TransportTriggerType::Motion),
214            2 => Some(TransportTriggerType::Continuous),
215            _ => None,
216        }
217    }
218
219    /// Convert to u8 value
220    pub fn to_u8(self) -> u8 {
221        self as u8
222    }
223}
224
225impl From<TransportTriggerType> for u8 {
226    fn from(val: TransportTriggerType) -> Self {
227        val as u8
228    }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
232#[repr(u8)]
233pub enum TriggerActivationReason {
234    /// Trigger has been activated by user action
235    Userinitiated = 0,
236    /// Trigger has been activated by automation
237    Automation = 1,
238    /// Trigger has been activated for emergency reasons
239    Emergency = 2,
240    /// Trigger has been activated by a doorbell press
241    Doorbellpressed = 3,
242}
243
244impl TriggerActivationReason {
245    /// Convert from u8 value
246    pub fn from_u8(value: u8) -> Option<Self> {
247        match value {
248            0 => Some(TriggerActivationReason::Userinitiated),
249            1 => Some(TriggerActivationReason::Automation),
250            2 => Some(TriggerActivationReason::Emergency),
251            3 => Some(TriggerActivationReason::Doorbellpressed),
252            _ => None,
253        }
254    }
255
256    /// Convert to u8 value
257    pub fn to_u8(self) -> u8 {
258        self as u8
259    }
260}
261
262impl From<TriggerActivationReason> for u8 {
263    fn from(val: TriggerActivationReason) -> Self {
264        val as u8
265    }
266}
267
268// Struct definitions
269
270#[derive(Debug, serde::Serialize)]
271pub struct AudioStream {
272    pub audio_stream_name: Option<String>,
273    pub audio_stream_id: Option<u16>,
274}
275
276#[derive(Debug, serde::Serialize)]
277pub struct CMAFContainerOptions {
278    pub cmaf_interface: Option<CMAFInterface>,
279    pub segment_duration: Option<u16>,
280    pub chunk_duration: Option<u16>,
281    pub session_group: Option<u8>,
282    pub track_name: Option<String>,
283    pub metadata_enabled: Option<bool>,
284}
285
286#[derive(Debug, serde::Serialize)]
287pub struct ContainerOptions {
288    pub container_type: Option<ContainerFormat>,
289    pub cmaf_container_options: Option<CMAFContainerOptions>,
290}
291
292#[derive(Debug, serde::Serialize)]
293pub struct SupportedFormat {
294    pub container_format: Option<ContainerFormat>,
295    pub ingest_method: Option<IngestMethods>,
296}
297
298#[derive(Debug, serde::Serialize)]
299pub struct TransportConfiguration {
300    pub connection_id: Option<u16>,
301    pub transport_status: Option<TransportStatus>,
302    pub transport_options: Option<TransportOptions>,
303}
304
305#[derive(Debug, serde::Serialize)]
306pub struct TransportMotionTriggerTimeControl {
307    pub initial_duration: Option<u16>,
308    pub augmentation_duration: Option<u16>,
309    pub max_duration: Option<u32>,
310    pub blind_duration: Option<u16>,
311}
312
313#[derive(Debug, serde::Serialize)]
314pub struct TransportOptions {
315    pub stream_usage: Option<u8>,
316    pub video_stream_id: Option<u16>,
317    pub audio_stream_id: Option<u16>,
318    pub tls_endpoint_id: Option<u16>,
319    pub url: Option<String>,
320    pub trigger_options: Option<TransportTriggerOptions>,
321    pub ingest_method: Option<IngestMethods>,
322    pub container_options: Option<ContainerOptions>,
323    pub expiry_time: Option<u64>,
324    pub video_streams: Option<Vec<VideoStream>>,
325    pub audio_streams: Option<Vec<AudioStream>>,
326}
327
328#[derive(Debug, serde::Serialize)]
329pub struct TransportTriggerOptions {
330    pub trigger_type: Option<TransportTriggerType>,
331    pub motion_zones: Option<Vec<TransportZoneOptions>>,
332    pub motion_sensitivity: Option<u8>,
333    pub motion_time_control: Option<TransportMotionTriggerTimeControl>,
334    pub max_pre_roll_len: Option<u16>,
335}
336
337#[derive(Debug, serde::Serialize)]
338pub struct TransportZoneOptions {
339    pub zone: Option<u16>,
340    pub sensitivity: Option<u8>,
341}
342
343#[derive(Debug, serde::Serialize)]
344pub struct VideoStream {
345    pub video_stream_name: Option<String>,
346    pub video_stream_id: Option<u16>,
347}
348
349// Command encoders
350
351/// Encode AllocatePushTransport command (0x00)
352pub fn encode_allocate_push_transport(transport_options: TransportOptions) -> anyhow::Result<Vec<u8>> {
353            // Encode struct TransportOptionsStruct
354            let mut transport_options_fields = Vec::new();
355            if let Some(x) = transport_options.stream_usage { transport_options_fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
356            if let Some(x) = transport_options.video_stream_id { transport_options_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
357            if let Some(x) = transport_options.audio_stream_id { transport_options_fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
358            if let Some(x) = transport_options.tls_endpoint_id { transport_options_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
359            if let Some(x) = transport_options.url { transport_options_fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
360            if let Some(inner) = transport_options.trigger_options {
361                let mut trigger_options_nested_fields = Vec::new();
362                if let Some(x) = inner.trigger_type { trigger_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
363                if let Some(listv) = inner.motion_zones {
364                    let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
365                        let mut nested_fields = Vec::new();
366                            if let Some(x) = inner.zone { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
367                            if let Some(x) = inner.sensitivity { nested_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
368                        (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
369                    }).collect();
370                    trigger_options_nested_fields.push((1, tlv::TlvItemValueEnc::Array(inner_vec)).into());
371                }
372                if let Some(x) = inner.motion_sensitivity { trigger_options_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
373                if let Some(inner) = inner.motion_time_control {
374                    let mut motion_time_control_nested_fields = Vec::new();
375                    if let Some(x) = inner.initial_duration { motion_time_control_nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
376                    if let Some(x) = inner.augmentation_duration { motion_time_control_nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
377                    if let Some(x) = inner.max_duration { motion_time_control_nested_fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
378                    if let Some(x) = inner.blind_duration { motion_time_control_nested_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
379                    trigger_options_nested_fields.push((3, tlv::TlvItemValueEnc::StructInvisible(motion_time_control_nested_fields)).into());
380                }
381                if let Some(x) = inner.max_pre_roll_len { trigger_options_nested_fields.push((4, tlv::TlvItemValueEnc::UInt16(x)).into()); }
382                transport_options_fields.push((5, tlv::TlvItemValueEnc::StructInvisible(trigger_options_nested_fields)).into());
383            }
384            if let Some(x) = transport_options.ingest_method { transport_options_fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
385            if let Some(inner) = transport_options.container_options {
386                let mut container_options_nested_fields = Vec::new();
387                if let Some(x) = inner.container_type { container_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
388                if let Some(inner) = inner.cmaf_container_options {
389                    let mut cmaf_container_options_nested_fields = Vec::new();
390                    if let Some(x) = inner.cmaf_interface { cmaf_container_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
391                    if let Some(x) = inner.segment_duration { cmaf_container_options_nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
392                    if let Some(x) = inner.chunk_duration { cmaf_container_options_nested_fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
393                    if let Some(x) = inner.session_group { cmaf_container_options_nested_fields.push((3, tlv::TlvItemValueEnc::UInt8(x)).into()); }
394                    if let Some(x) = inner.track_name { cmaf_container_options_nested_fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
395                    if let Some(x) = inner.metadata_enabled { cmaf_container_options_nested_fields.push((7, tlv::TlvItemValueEnc::Bool(x)).into()); }
396                    container_options_nested_fields.push((1, tlv::TlvItemValueEnc::StructInvisible(cmaf_container_options_nested_fields)).into());
397                }
398                transport_options_fields.push((7, tlv::TlvItemValueEnc::StructInvisible(container_options_nested_fields)).into());
399            }
400            if let Some(x) = transport_options.expiry_time { transport_options_fields.push((8, tlv::TlvItemValueEnc::UInt64(x)).into()); }
401            if let Some(listv) = transport_options.video_streams {
402                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
403                    let mut nested_fields = Vec::new();
404                        if let Some(x) = inner.video_stream_name { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
405                        if let Some(x) = inner.video_stream_id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
406                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
407                }).collect();
408                transport_options_fields.push((9, tlv::TlvItemValueEnc::Array(inner_vec)).into());
409            }
410            if let Some(listv) = transport_options.audio_streams {
411                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
412                    let mut nested_fields = Vec::new();
413                        if let Some(x) = inner.audio_stream_name { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
414                        if let Some(x) = inner.audio_stream_id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
415                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
416                }).collect();
417                transport_options_fields.push((10, tlv::TlvItemValueEnc::Array(inner_vec)).into());
418            }
419    let tlv = tlv::TlvItemEnc {
420        tag: 0,
421        value: tlv::TlvItemValueEnc::StructInvisible(vec![
422        (0, tlv::TlvItemValueEnc::StructInvisible(transport_options_fields)).into(),
423        ]),
424    };
425    Ok(tlv.encode()?)
426}
427
428/// Encode DeallocatePushTransport command (0x02)
429pub fn encode_deallocate_push_transport(connection_id: u16) -> anyhow::Result<Vec<u8>> {
430    let tlv = tlv::TlvItemEnc {
431        tag: 0,
432        value: tlv::TlvItemValueEnc::StructInvisible(vec![
433        (0, tlv::TlvItemValueEnc::UInt16(connection_id)).into(),
434        ]),
435    };
436    Ok(tlv.encode()?)
437}
438
439/// Encode ModifyPushTransport command (0x03)
440pub fn encode_modify_push_transport(connection_id: u16, transport_options: TransportOptions) -> anyhow::Result<Vec<u8>> {
441            // Encode struct TransportOptionsStruct
442            let mut transport_options_fields = Vec::new();
443            if let Some(x) = transport_options.stream_usage { transport_options_fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
444            if let Some(x) = transport_options.video_stream_id { transport_options_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
445            if let Some(x) = transport_options.audio_stream_id { transport_options_fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
446            if let Some(x) = transport_options.tls_endpoint_id { transport_options_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
447            if let Some(x) = transport_options.url { transport_options_fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
448            if let Some(inner) = transport_options.trigger_options {
449                let mut trigger_options_nested_fields = Vec::new();
450                if let Some(x) = inner.trigger_type { trigger_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
451                if let Some(listv) = inner.motion_zones {
452                    let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
453                        let mut nested_fields = Vec::new();
454                            if let Some(x) = inner.zone { nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
455                            if let Some(x) = inner.sensitivity { nested_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
456                        (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
457                    }).collect();
458                    trigger_options_nested_fields.push((1, tlv::TlvItemValueEnc::Array(inner_vec)).into());
459                }
460                if let Some(x) = inner.motion_sensitivity { trigger_options_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
461                if let Some(inner) = inner.motion_time_control {
462                    let mut motion_time_control_nested_fields = Vec::new();
463                    if let Some(x) = inner.initial_duration { motion_time_control_nested_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
464                    if let Some(x) = inner.augmentation_duration { motion_time_control_nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
465                    if let Some(x) = inner.max_duration { motion_time_control_nested_fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
466                    if let Some(x) = inner.blind_duration { motion_time_control_nested_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
467                    trigger_options_nested_fields.push((3, tlv::TlvItemValueEnc::StructInvisible(motion_time_control_nested_fields)).into());
468                }
469                if let Some(x) = inner.max_pre_roll_len { trigger_options_nested_fields.push((4, tlv::TlvItemValueEnc::UInt16(x)).into()); }
470                transport_options_fields.push((5, tlv::TlvItemValueEnc::StructInvisible(trigger_options_nested_fields)).into());
471            }
472            if let Some(x) = transport_options.ingest_method { transport_options_fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
473            if let Some(inner) = transport_options.container_options {
474                let mut container_options_nested_fields = Vec::new();
475                if let Some(x) = inner.container_type { container_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
476                if let Some(inner) = inner.cmaf_container_options {
477                    let mut cmaf_container_options_nested_fields = Vec::new();
478                    if let Some(x) = inner.cmaf_interface { cmaf_container_options_nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
479                    if let Some(x) = inner.segment_duration { cmaf_container_options_nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
480                    if let Some(x) = inner.chunk_duration { cmaf_container_options_nested_fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
481                    if let Some(x) = inner.session_group { cmaf_container_options_nested_fields.push((3, tlv::TlvItemValueEnc::UInt8(x)).into()); }
482                    if let Some(x) = inner.track_name { cmaf_container_options_nested_fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
483                    if let Some(x) = inner.metadata_enabled { cmaf_container_options_nested_fields.push((7, tlv::TlvItemValueEnc::Bool(x)).into()); }
484                    container_options_nested_fields.push((1, tlv::TlvItemValueEnc::StructInvisible(cmaf_container_options_nested_fields)).into());
485                }
486                transport_options_fields.push((7, tlv::TlvItemValueEnc::StructInvisible(container_options_nested_fields)).into());
487            }
488            if let Some(x) = transport_options.expiry_time { transport_options_fields.push((8, tlv::TlvItemValueEnc::UInt64(x)).into()); }
489            if let Some(listv) = transport_options.video_streams {
490                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
491                    let mut nested_fields = Vec::new();
492                        if let Some(x) = inner.video_stream_name { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
493                        if let Some(x) = inner.video_stream_id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
494                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
495                }).collect();
496                transport_options_fields.push((9, tlv::TlvItemValueEnc::Array(inner_vec)).into());
497            }
498            if let Some(listv) = transport_options.audio_streams {
499                let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
500                    let mut nested_fields = Vec::new();
501                        if let Some(x) = inner.audio_stream_name { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
502                        if let Some(x) = inner.audio_stream_id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
503                    (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
504                }).collect();
505                transport_options_fields.push((10, tlv::TlvItemValueEnc::Array(inner_vec)).into());
506            }
507    let tlv = tlv::TlvItemEnc {
508        tag: 0,
509        value: tlv::TlvItemValueEnc::StructInvisible(vec![
510        (0, tlv::TlvItemValueEnc::UInt16(connection_id)).into(),
511        (1, tlv::TlvItemValueEnc::StructInvisible(transport_options_fields)).into(),
512        ]),
513    };
514    Ok(tlv.encode()?)
515}
516
517/// Encode SetTransportStatus command (0x04)
518pub fn encode_set_transport_status(connection_id: Option<u16>, transport_status: TransportStatus) -> anyhow::Result<Vec<u8>> {
519    let tlv = tlv::TlvItemEnc {
520        tag: 0,
521        value: tlv::TlvItemValueEnc::StructInvisible(vec![
522        (0, tlv::TlvItemValueEnc::UInt16(connection_id.unwrap_or(0))).into(),
523        (1, tlv::TlvItemValueEnc::UInt8(transport_status.to_u8())).into(),
524        ]),
525    };
526    Ok(tlv.encode()?)
527}
528
529/// Encode ManuallyTriggerTransport command (0x05)
530pub fn encode_manually_trigger_transport(connection_id: u16, activation_reason: TriggerActivationReason, time_control: Option<TransportMotionTriggerTimeControl>, user_defined: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
531    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
532    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt16(connection_id)).into());
533    tlv_fields.push((1, tlv::TlvItemValueEnc::UInt8(activation_reason.to_u8())).into());
534    if let Some(time_control) = time_control {
535        // Encode struct TransportMotionTriggerTimeControlStruct
536        let mut time_control_fields = Vec::new();
537        if let Some(x) = time_control.initial_duration { time_control_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
538        if let Some(x) = time_control.augmentation_duration { time_control_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
539        if let Some(x) = time_control.max_duration { time_control_fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
540        if let Some(x) = time_control.blind_duration { time_control_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
541        tlv_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(time_control_fields)).into());
542    }
543    if let Some(x) = user_defined { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
544    let tlv = tlv::TlvItemEnc {
545        tag: 0,
546        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
547    };
548    Ok(tlv.encode()?)
549}
550
551/// Encode FindTransport command (0x06)
552pub fn encode_find_transport(connection_id: Option<u16>) -> anyhow::Result<Vec<u8>> {
553    let tlv = tlv::TlvItemEnc {
554        tag: 0,
555        value: tlv::TlvItemValueEnc::StructInvisible(vec![
556        (0, tlv::TlvItemValueEnc::UInt16(connection_id.unwrap_or(0))).into(),
557        ]),
558    };
559    Ok(tlv.encode()?)
560}
561
562// Attribute decoders
563
564/// Decode SupportedFormats attribute (0x0000)
565pub fn decode_supported_formats(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<SupportedFormat>> {
566    let mut res = Vec::new();
567    if let tlv::TlvItemValue::List(v) = inp {
568        for item in v {
569            res.push(SupportedFormat {
570                container_format: item.get_int(&[0]).and_then(|v| ContainerFormat::from_u8(v as u8)),
571                ingest_method: item.get_int(&[1]).and_then(|v| IngestMethods::from_u8(v as u8)),
572            });
573        }
574    }
575    Ok(res)
576}
577
578/// Decode CurrentConnections attribute (0x0001)
579pub fn decode_current_connections(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TransportConfiguration>> {
580    let mut res = Vec::new();
581    if let tlv::TlvItemValue::List(v) = inp {
582        for item in v {
583            res.push(TransportConfiguration {
584                connection_id: item.get_int(&[0]).map(|v| v as u16),
585                transport_status: item.get_int(&[1]).and_then(|v| TransportStatus::from_u8(v as u8)),
586                transport_options: {
587                    if let Some(nested_tlv) = item.get(&[2]) {
588                        if let tlv::TlvItemValue::List(_) = nested_tlv {
589                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
590                            Some(TransportOptions {
591                stream_usage: nested_item.get_int(&[0]).map(|v| v as u8),
592                video_stream_id: nested_item.get_int(&[1]).map(|v| v as u16),
593                audio_stream_id: nested_item.get_int(&[2]).map(|v| v as u16),
594                tls_endpoint_id: nested_item.get_int(&[3]).map(|v| v as u16),
595                url: nested_item.get_string_owned(&[4]),
596                trigger_options: {
597                    if let Some(nested_tlv) = nested_item.get(&[5]) {
598                        if let tlv::TlvItemValue::List(_) = nested_tlv {
599                            let nested_item = tlv::TlvItem { tag: 5, value: nested_tlv.clone() };
600                            Some(TransportTriggerOptions {
601                trigger_type: nested_item.get_int(&[0]).and_then(|v| TransportTriggerType::from_u8(v as u8)),
602                motion_zones: {
603                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
604                        let mut items = Vec::new();
605                        for list_item in l {
606                            items.push(TransportZoneOptions {
607                zone: list_item.get_int(&[0]).map(|v| v as u16),
608                sensitivity: list_item.get_int(&[1]).map(|v| v as u8),
609                            });
610                        }
611                        Some(items)
612                    } else {
613                        None
614                    }
615                },
616                motion_sensitivity: nested_item.get_int(&[2]).map(|v| v as u8),
617                motion_time_control: {
618                    if let Some(nested_tlv) = nested_item.get(&[3]) {
619                        if let tlv::TlvItemValue::List(_) = nested_tlv {
620                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
621                            Some(TransportMotionTriggerTimeControl {
622                initial_duration: nested_item.get_int(&[0]).map(|v| v as u16),
623                augmentation_duration: nested_item.get_int(&[1]).map(|v| v as u16),
624                max_duration: nested_item.get_int(&[2]).map(|v| v as u32),
625                blind_duration: nested_item.get_int(&[3]).map(|v| v as u16),
626                            })
627                        } else {
628                            None
629                        }
630                    } else {
631                        None
632                    }
633                },
634                max_pre_roll_len: nested_item.get_int(&[4]).map(|v| v as u16),
635                            })
636                        } else {
637                            None
638                        }
639                    } else {
640                        None
641                    }
642                },
643                ingest_method: nested_item.get_int(&[6]).and_then(|v| IngestMethods::from_u8(v as u8)),
644                container_options: {
645                    if let Some(nested_tlv) = nested_item.get(&[7]) {
646                        if let tlv::TlvItemValue::List(_) = nested_tlv {
647                            let nested_item = tlv::TlvItem { tag: 7, value: nested_tlv.clone() };
648                            Some(ContainerOptions {
649                container_type: nested_item.get_int(&[0]).and_then(|v| ContainerFormat::from_u8(v as u8)),
650                cmaf_container_options: {
651                    if let Some(nested_tlv) = nested_item.get(&[1]) {
652                        if let tlv::TlvItemValue::List(_) = nested_tlv {
653                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
654                            Some(CMAFContainerOptions {
655                cmaf_interface: nested_item.get_int(&[0]).and_then(|v| CMAFInterface::from_u8(v as u8)),
656                segment_duration: nested_item.get_int(&[1]).map(|v| v as u16),
657                chunk_duration: nested_item.get_int(&[2]).map(|v| v as u16),
658                session_group: nested_item.get_int(&[3]).map(|v| v as u8),
659                track_name: nested_item.get_string_owned(&[4]),
660                metadata_enabled: nested_item.get_bool(&[7]),
661                            })
662                        } else {
663                            None
664                        }
665                    } else {
666                        None
667                    }
668                },
669                            })
670                        } else {
671                            None
672                        }
673                    } else {
674                        None
675                    }
676                },
677                expiry_time: nested_item.get_int(&[8]),
678                video_streams: {
679                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[9]) {
680                        let mut items = Vec::new();
681                        for list_item in l {
682                            items.push(VideoStream {
683                video_stream_name: list_item.get_string_owned(&[0]),
684                video_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
685                            });
686                        }
687                        Some(items)
688                    } else {
689                        None
690                    }
691                },
692                audio_streams: {
693                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[10]) {
694                        let mut items = Vec::new();
695                        for list_item in l {
696                            items.push(AudioStream {
697                audio_stream_name: list_item.get_string_owned(&[0]),
698                audio_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
699                            });
700                        }
701                        Some(items)
702                    } else {
703                        None
704                    }
705                },
706                            })
707                        } else {
708                            None
709                        }
710                    } else {
711                        None
712                    }
713                },
714            });
715        }
716    }
717    Ok(res)
718}
719
720
721// JSON dispatcher function
722
723/// Decode attribute value and return as JSON string
724///
725/// # Parameters
726/// * `cluster_id` - The cluster identifier
727/// * `attribute_id` - The attribute identifier
728/// * `tlv_value` - The TLV value to decode
729///
730/// # Returns
731/// JSON string representation of the decoded value or error
732pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
733    // Verify this is the correct cluster
734    if ![0x0555].contains(&cluster_id) {
735        return format!("{{\"error\": \"Invalid cluster ID. Expected [0x0555], got {}\"}}", cluster_id);
736    }
737
738    match attribute_id {
739        0x0000 => {
740            match decode_supported_formats(tlv_value) {
741                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
742                Err(e) => format!("{{\"error\": \"{}\"}}", e),
743            }
744        }
745        0x0001 => {
746            match decode_current_connections(tlv_value) {
747                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
748                Err(e) => format!("{{\"error\": \"{}\"}}", e),
749            }
750        }
751        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
752    }
753}
754
755/// Get list of all attributes supported by this cluster
756///
757/// # Returns
758/// Vector of tuples containing (attribute_id, attribute_name)
759pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
760    vec![
761        (0x0000, "SupportedFormats"),
762        (0x0001, "CurrentConnections"),
763    ]
764}
765
766// Command listing
767
768pub fn get_command_list() -> Vec<(u32, &'static str)> {
769    vec![
770        (0x00, "AllocatePushTransport"),
771        (0x02, "DeallocatePushTransport"),
772        (0x03, "ModifyPushTransport"),
773        (0x04, "SetTransportStatus"),
774        (0x05, "ManuallyTriggerTransport"),
775        (0x06, "FindTransport"),
776    ]
777}
778
779pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
780    match cmd_id {
781        0x00 => Some("AllocatePushTransport"),
782        0x02 => Some("DeallocatePushTransport"),
783        0x03 => Some("ModifyPushTransport"),
784        0x04 => Some("SetTransportStatus"),
785        0x05 => Some("ManuallyTriggerTransport"),
786        0x06 => Some("FindTransport"),
787        _ => None,
788    }
789}
790
791pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
792    match cmd_id {
793        0x00 => Some(vec![
794            crate::clusters::codec::CommandField { tag: 0, name: "transport_options", kind: crate::clusters::codec::FieldKind::Struct { name: "TransportOptionsStruct" }, optional: false, nullable: false },
795        ]),
796        0x02 => Some(vec![
797            crate::clusters::codec::CommandField { tag: 0, name: "connection_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
798        ]),
799        0x03 => Some(vec![
800            crate::clusters::codec::CommandField { tag: 0, name: "connection_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
801            crate::clusters::codec::CommandField { tag: 1, name: "transport_options", kind: crate::clusters::codec::FieldKind::Struct { name: "TransportOptionsStruct" }, optional: false, nullable: false },
802        ]),
803        0x04 => Some(vec![
804            crate::clusters::codec::CommandField { tag: 0, name: "connection_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: true },
805            crate::clusters::codec::CommandField { tag: 1, name: "transport_status", kind: crate::clusters::codec::FieldKind::Enum { name: "TransportStatus", variants: &[(0, "Active"), (1, "Inactive")] }, optional: false, nullable: false },
806        ]),
807        0x05 => Some(vec![
808            crate::clusters::codec::CommandField { tag: 0, name: "connection_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
809            crate::clusters::codec::CommandField { tag: 1, name: "activation_reason", kind: crate::clusters::codec::FieldKind::Enum { name: "TriggerActivationReason", variants: &[(0, "Userinitiated"), (1, "Automation"), (2, "Emergency"), (3, "Doorbellpressed")] }, optional: false, nullable: false },
810            crate::clusters::codec::CommandField { tag: 2, name: "time_control", kind: crate::clusters::codec::FieldKind::Struct { name: "TransportMotionTriggerTimeControlStruct" }, optional: true, nullable: false },
811            crate::clusters::codec::CommandField { tag: 3, name: "user_defined", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
812        ]),
813        0x06 => Some(vec![
814            crate::clusters::codec::CommandField { tag: 0, name: "connection_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: true },
815        ]),
816        _ => None,
817    }
818}
819
820pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
821    match cmd_id {
822        0x00 => Err(anyhow::anyhow!("command \"AllocatePushTransport\" has complex args: use raw mode")),
823        0x02 => {
824        let connection_id = crate::clusters::codec::json_util::get_u16(args, "connection_id")?;
825        encode_deallocate_push_transport(connection_id)
826        }
827        0x03 => Err(anyhow::anyhow!("command \"ModifyPushTransport\" has complex args: use raw mode")),
828        0x04 => {
829        let connection_id = crate::clusters::codec::json_util::get_opt_u16(args, "connection_id")?;
830        let transport_status = {
831            let n = crate::clusters::codec::json_util::get_u64(args, "transport_status")?;
832            TransportStatus::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid TransportStatus: {}", n))?
833        };
834        encode_set_transport_status(connection_id, transport_status)
835        }
836        0x05 => Err(anyhow::anyhow!("command \"ManuallyTriggerTransport\" has complex args: use raw mode")),
837        0x06 => {
838        let connection_id = crate::clusters::codec::json_util::get_opt_u16(args, "connection_id")?;
839        encode_find_transport(connection_id)
840        }
841        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
842    }
843}
844
845#[derive(Debug, serde::Serialize)]
846pub struct AllocatePushTransportResponse {
847    pub transport_configuration: Option<TransportConfiguration>,
848}
849
850#[derive(Debug, serde::Serialize)]
851pub struct FindTransportResponse {
852    pub transport_configurations: Option<Vec<TransportConfiguration>>,
853}
854
855// Command response decoders
856
857/// Decode AllocatePushTransportResponse command response (01)
858pub fn decode_allocate_push_transport_response(inp: &tlv::TlvItemValue) -> anyhow::Result<AllocatePushTransportResponse> {
859    if let tlv::TlvItemValue::List(_fields) = inp {
860        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
861        Ok(AllocatePushTransportResponse {
862                transport_configuration: {
863                    if let Some(nested_tlv) = item.get(&[0]) {
864                        if let tlv::TlvItemValue::List(_) = nested_tlv {
865                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
866                            Some(TransportConfiguration {
867                connection_id: nested_item.get_int(&[0]).map(|v| v as u16),
868                transport_status: nested_item.get_int(&[1]).and_then(|v| TransportStatus::from_u8(v as u8)),
869                transport_options: {
870                    if let Some(nested_tlv) = nested_item.get(&[2]) {
871                        if let tlv::TlvItemValue::List(_) = nested_tlv {
872                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
873                            Some(TransportOptions {
874                stream_usage: nested_item.get_int(&[0]).map(|v| v as u8),
875                video_stream_id: nested_item.get_int(&[1]).map(|v| v as u16),
876                audio_stream_id: nested_item.get_int(&[2]).map(|v| v as u16),
877                tls_endpoint_id: nested_item.get_int(&[3]).map(|v| v as u16),
878                url: nested_item.get_string_owned(&[4]),
879                trigger_options: {
880                    if let Some(nested_tlv) = nested_item.get(&[5]) {
881                        if let tlv::TlvItemValue::List(_) = nested_tlv {
882                            let nested_item = tlv::TlvItem { tag: 5, value: nested_tlv.clone() };
883                            Some(TransportTriggerOptions {
884                trigger_type: nested_item.get_int(&[0]).and_then(|v| TransportTriggerType::from_u8(v as u8)),
885                motion_zones: {
886                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
887                        let mut items = Vec::new();
888                        for list_item in l {
889                            items.push(TransportZoneOptions {
890                zone: list_item.get_int(&[0]).map(|v| v as u16),
891                sensitivity: list_item.get_int(&[1]).map(|v| v as u8),
892                            });
893                        }
894                        Some(items)
895                    } else {
896                        None
897                    }
898                },
899                motion_sensitivity: nested_item.get_int(&[2]).map(|v| v as u8),
900                motion_time_control: {
901                    if let Some(nested_tlv) = nested_item.get(&[3]) {
902                        if let tlv::TlvItemValue::List(_) = nested_tlv {
903                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
904                            Some(TransportMotionTriggerTimeControl {
905                initial_duration: nested_item.get_int(&[0]).map(|v| v as u16),
906                augmentation_duration: nested_item.get_int(&[1]).map(|v| v as u16),
907                max_duration: nested_item.get_int(&[2]).map(|v| v as u32),
908                blind_duration: nested_item.get_int(&[3]).map(|v| v as u16),
909                            })
910                        } else {
911                            None
912                        }
913                    } else {
914                        None
915                    }
916                },
917                max_pre_roll_len: nested_item.get_int(&[4]).map(|v| v as u16),
918                            })
919                        } else {
920                            None
921                        }
922                    } else {
923                        None
924                    }
925                },
926                ingest_method: nested_item.get_int(&[6]).and_then(|v| IngestMethods::from_u8(v as u8)),
927                container_options: {
928                    if let Some(nested_tlv) = nested_item.get(&[7]) {
929                        if let tlv::TlvItemValue::List(_) = nested_tlv {
930                            let nested_item = tlv::TlvItem { tag: 7, value: nested_tlv.clone() };
931                            Some(ContainerOptions {
932                container_type: nested_item.get_int(&[0]).and_then(|v| ContainerFormat::from_u8(v as u8)),
933                cmaf_container_options: {
934                    if let Some(nested_tlv) = nested_item.get(&[1]) {
935                        if let tlv::TlvItemValue::List(_) = nested_tlv {
936                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
937                            Some(CMAFContainerOptions {
938                cmaf_interface: nested_item.get_int(&[0]).and_then(|v| CMAFInterface::from_u8(v as u8)),
939                segment_duration: nested_item.get_int(&[1]).map(|v| v as u16),
940                chunk_duration: nested_item.get_int(&[2]).map(|v| v as u16),
941                session_group: nested_item.get_int(&[3]).map(|v| v as u8),
942                track_name: nested_item.get_string_owned(&[4]),
943                metadata_enabled: nested_item.get_bool(&[7]),
944                            })
945                        } else {
946                            None
947                        }
948                    } else {
949                        None
950                    }
951                },
952                            })
953                        } else {
954                            None
955                        }
956                    } else {
957                        None
958                    }
959                },
960                expiry_time: nested_item.get_int(&[8]),
961                video_streams: {
962                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[9]) {
963                        let mut items = Vec::new();
964                        for list_item in l {
965                            items.push(VideoStream {
966                video_stream_name: list_item.get_string_owned(&[0]),
967                video_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
968                            });
969                        }
970                        Some(items)
971                    } else {
972                        None
973                    }
974                },
975                audio_streams: {
976                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[10]) {
977                        let mut items = Vec::new();
978                        for list_item in l {
979                            items.push(AudioStream {
980                audio_stream_name: list_item.get_string_owned(&[0]),
981                audio_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
982                            });
983                        }
984                        Some(items)
985                    } else {
986                        None
987                    }
988                },
989                            })
990                        } else {
991                            None
992                        }
993                    } else {
994                        None
995                    }
996                },
997                            })
998                        } else {
999                            None
1000                        }
1001                    } else {
1002                        None
1003                    }
1004                },
1005        })
1006    } else {
1007        Err(anyhow::anyhow!("Expected struct fields"))
1008    }
1009}
1010
1011/// Decode FindTransportResponse command response (07)
1012pub fn decode_find_transport_response(inp: &tlv::TlvItemValue) -> anyhow::Result<FindTransportResponse> {
1013    if let tlv::TlvItemValue::List(_fields) = inp {
1014        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1015        Ok(FindTransportResponse {
1016                transport_configurations: {
1017                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
1018                        let mut items = Vec::new();
1019                        for list_item in l {
1020                            items.push(TransportConfiguration {
1021                connection_id: list_item.get_int(&[0]).map(|v| v as u16),
1022                transport_status: list_item.get_int(&[1]).and_then(|v| TransportStatus::from_u8(v as u8)),
1023                transport_options: {
1024                    if let Some(nested_tlv) = list_item.get(&[2]) {
1025                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1026                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
1027                            Some(TransportOptions {
1028                stream_usage: nested_item.get_int(&[0]).map(|v| v as u8),
1029                video_stream_id: nested_item.get_int(&[1]).map(|v| v as u16),
1030                audio_stream_id: nested_item.get_int(&[2]).map(|v| v as u16),
1031                tls_endpoint_id: nested_item.get_int(&[3]).map(|v| v as u16),
1032                url: nested_item.get_string_owned(&[4]),
1033                trigger_options: {
1034                    if let Some(nested_tlv) = nested_item.get(&[5]) {
1035                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1036                            let nested_item = tlv::TlvItem { tag: 5, value: nested_tlv.clone() };
1037                            Some(TransportTriggerOptions {
1038                trigger_type: nested_item.get_int(&[0]).and_then(|v| TransportTriggerType::from_u8(v as u8)),
1039                motion_zones: {
1040                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
1041                        let mut items = Vec::new();
1042                        for list_item in l {
1043                            items.push(TransportZoneOptions {
1044                zone: list_item.get_int(&[0]).map(|v| v as u16),
1045                sensitivity: list_item.get_int(&[1]).map(|v| v as u8),
1046                            });
1047                        }
1048                        Some(items)
1049                    } else {
1050                        None
1051                    }
1052                },
1053                motion_sensitivity: nested_item.get_int(&[2]).map(|v| v as u8),
1054                motion_time_control: {
1055                    if let Some(nested_tlv) = nested_item.get(&[3]) {
1056                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1057                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
1058                            Some(TransportMotionTriggerTimeControl {
1059                initial_duration: nested_item.get_int(&[0]).map(|v| v as u16),
1060                augmentation_duration: nested_item.get_int(&[1]).map(|v| v as u16),
1061                max_duration: nested_item.get_int(&[2]).map(|v| v as u32),
1062                blind_duration: nested_item.get_int(&[3]).map(|v| v as u16),
1063                            })
1064                        } else {
1065                            None
1066                        }
1067                    } else {
1068                        None
1069                    }
1070                },
1071                max_pre_roll_len: nested_item.get_int(&[4]).map(|v| v as u16),
1072                            })
1073                        } else {
1074                            None
1075                        }
1076                    } else {
1077                        None
1078                    }
1079                },
1080                ingest_method: nested_item.get_int(&[6]).and_then(|v| IngestMethods::from_u8(v as u8)),
1081                container_options: {
1082                    if let Some(nested_tlv) = nested_item.get(&[7]) {
1083                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1084                            let nested_item = tlv::TlvItem { tag: 7, value: nested_tlv.clone() };
1085                            Some(ContainerOptions {
1086                container_type: nested_item.get_int(&[0]).and_then(|v| ContainerFormat::from_u8(v as u8)),
1087                cmaf_container_options: {
1088                    if let Some(nested_tlv) = nested_item.get(&[1]) {
1089                        if let tlv::TlvItemValue::List(_) = nested_tlv {
1090                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
1091                            Some(CMAFContainerOptions {
1092                cmaf_interface: nested_item.get_int(&[0]).and_then(|v| CMAFInterface::from_u8(v as u8)),
1093                segment_duration: nested_item.get_int(&[1]).map(|v| v as u16),
1094                chunk_duration: nested_item.get_int(&[2]).map(|v| v as u16),
1095                session_group: nested_item.get_int(&[3]).map(|v| v as u8),
1096                track_name: nested_item.get_string_owned(&[4]),
1097                metadata_enabled: nested_item.get_bool(&[7]),
1098                            })
1099                        } else {
1100                            None
1101                        }
1102                    } else {
1103                        None
1104                    }
1105                },
1106                            })
1107                        } else {
1108                            None
1109                        }
1110                    } else {
1111                        None
1112                    }
1113                },
1114                expiry_time: nested_item.get_int(&[8]),
1115                video_streams: {
1116                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[9]) {
1117                        let mut items = Vec::new();
1118                        for list_item in l {
1119                            items.push(VideoStream {
1120                video_stream_name: list_item.get_string_owned(&[0]),
1121                video_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
1122                            });
1123                        }
1124                        Some(items)
1125                    } else {
1126                        None
1127                    }
1128                },
1129                audio_streams: {
1130                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[10]) {
1131                        let mut items = Vec::new();
1132                        for list_item in l {
1133                            items.push(AudioStream {
1134                audio_stream_name: list_item.get_string_owned(&[0]),
1135                audio_stream_id: list_item.get_int(&[1]).map(|v| v as u16),
1136                            });
1137                        }
1138                        Some(items)
1139                    } else {
1140                        None
1141                    }
1142                },
1143                            })
1144                        } else {
1145                            None
1146                        }
1147                    } else {
1148                        None
1149                    }
1150                },
1151                            });
1152                        }
1153                        Some(items)
1154                    } else {
1155                        None
1156                    }
1157                },
1158        })
1159    } else {
1160        Err(anyhow::anyhow!("Expected struct fields"))
1161    }
1162}
1163
1164// Typed facade (invokes + reads)
1165
1166/// Invoke `AllocatePushTransport` command on cluster `Push AV Stream Transport`.
1167pub async fn allocate_push_transport(conn: &crate::controller::Connection, endpoint: u16, transport_options: TransportOptions) -> anyhow::Result<AllocatePushTransportResponse> {
1168    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_ALLOCATEPUSHTRANSPORT, &encode_allocate_push_transport(transport_options)?).await?;
1169    decode_allocate_push_transport_response(&tlv)
1170}
1171
1172/// Invoke `DeallocatePushTransport` command on cluster `Push AV Stream Transport`.
1173pub async fn deallocate_push_transport(conn: &crate::controller::Connection, endpoint: u16, connection_id: u16) -> anyhow::Result<()> {
1174    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_DEALLOCATEPUSHTRANSPORT, &encode_deallocate_push_transport(connection_id)?).await?;
1175    Ok(())
1176}
1177
1178/// Invoke `ModifyPushTransport` command on cluster `Push AV Stream Transport`.
1179pub async fn modify_push_transport(conn: &crate::controller::Connection, endpoint: u16, connection_id: u16, transport_options: TransportOptions) -> anyhow::Result<()> {
1180    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_MODIFYPUSHTRANSPORT, &encode_modify_push_transport(connection_id, transport_options)?).await?;
1181    Ok(())
1182}
1183
1184/// Invoke `SetTransportStatus` command on cluster `Push AV Stream Transport`.
1185pub async fn set_transport_status(conn: &crate::controller::Connection, endpoint: u16, connection_id: Option<u16>, transport_status: TransportStatus) -> anyhow::Result<()> {
1186    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_SETTRANSPORTSTATUS, &encode_set_transport_status(connection_id, transport_status)?).await?;
1187    Ok(())
1188}
1189
1190/// Invoke `ManuallyTriggerTransport` command on cluster `Push AV Stream Transport`.
1191pub async fn manually_trigger_transport(conn: &crate::controller::Connection, endpoint: u16, connection_id: u16, activation_reason: TriggerActivationReason, time_control: Option<TransportMotionTriggerTimeControl>, user_defined: Option<Vec<u8>>) -> anyhow::Result<()> {
1192    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_MANUALLYTRIGGERTRANSPORT, &encode_manually_trigger_transport(connection_id, activation_reason, time_control, user_defined)?).await?;
1193    Ok(())
1194}
1195
1196/// Invoke `FindTransport` command on cluster `Push AV Stream Transport`.
1197pub async fn find_transport(conn: &crate::controller::Connection, endpoint: u16, connection_id: Option<u16>) -> anyhow::Result<FindTransportResponse> {
1198    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_CMD_ID_FINDTRANSPORT, &encode_find_transport(connection_id)?).await?;
1199    decode_find_transport_response(&tlv)
1200}
1201
1202/// Read `SupportedFormats` attribute from cluster `Push AV Stream Transport`.
1203pub async fn read_supported_formats(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<SupportedFormat>> {
1204    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_ATTR_ID_SUPPORTEDFORMATS).await?;
1205    decode_supported_formats(&tlv)
1206}
1207
1208/// Read `CurrentConnections` attribute from cluster `Push AV Stream Transport`.
1209pub async fn read_current_connections(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<TransportConfiguration>> {
1210    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_PUSH_AV_STREAM_TRANSPORT, crate::clusters::defs::CLUSTER_PUSH_AV_STREAM_TRANSPORT_ATTR_ID_CURRENTCONNECTIONS).await?;
1211    decode_current_connections(&tlv)
1212}
1213
1214#[derive(Debug, serde::Serialize)]
1215pub struct PushTransportBeginEvent {
1216    pub connection_id: Option<u16>,
1217    pub trigger_type: Option<TransportTriggerType>,
1218    pub activation_reason: Option<TriggerActivationReason>,
1219    pub container_type: Option<ContainerFormat>,
1220    pub cmaf_session_number: Option<u64>,
1221}
1222
1223#[derive(Debug, serde::Serialize)]
1224pub struct PushTransportEndEvent {
1225    pub connection_id: Option<u16>,
1226    pub container_type: Option<ContainerFormat>,
1227    pub cmaf_session_number: Option<u64>,
1228}
1229
1230// Event decoders
1231
1232/// Decode PushTransportBegin event (0x00, priority: info)
1233pub fn decode_push_transport_begin_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PushTransportBeginEvent> {
1234    if let tlv::TlvItemValue::List(_fields) = inp {
1235        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1236        Ok(PushTransportBeginEvent {
1237                                connection_id: item.get_int(&[0]).map(|v| v as u16),
1238                                trigger_type: item.get_int(&[1]).and_then(|v| TransportTriggerType::from_u8(v as u8)),
1239                                activation_reason: item.get_int(&[2]).and_then(|v| TriggerActivationReason::from_u8(v as u8)),
1240                                container_type: item.get_int(&[3]).and_then(|v| ContainerFormat::from_u8(v as u8)),
1241                                cmaf_session_number: item.get_int(&[4]),
1242        })
1243    } else {
1244        Err(anyhow::anyhow!("Expected struct fields"))
1245    }
1246}
1247
1248/// Decode PushTransportEnd event (0x01, priority: info)
1249pub fn decode_push_transport_end_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PushTransportEndEvent> {
1250    if let tlv::TlvItemValue::List(_fields) = inp {
1251        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
1252        Ok(PushTransportEndEvent {
1253                                connection_id: item.get_int(&[0]).map(|v| v as u16),
1254                                container_type: item.get_int(&[1]).and_then(|v| ContainerFormat::from_u8(v as u8)),
1255                                cmaf_session_number: item.get_int(&[2]),
1256        })
1257    } else {
1258        Err(anyhow::anyhow!("Expected struct fields"))
1259    }
1260}
1261
1262
1263// Event JSON dispatcher
1264
1265/// Decode event value and return as JSON string
1266pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
1267    if ![0x0555].contains(&cluster_id) {
1268        return format!("{{\"error\": \"Invalid cluster ID. Expected [0x0555], got {}\"}}", cluster_id);
1269    }
1270
1271    match event_id {
1272        0x00 => {
1273            match decode_push_transport_begin_event(tlv_value) {
1274                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1275                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1276            }
1277        }
1278        0x01 => {
1279            match decode_push_transport_end_event(tlv_value) {
1280                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
1281                Err(e) => format!("{{\"error\": \"{}\"}}", e),
1282            }
1283        }
1284        _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
1285    }
1286}
1287
1288/// Get list of all events supported by this cluster
1289///
1290/// # Returns
1291/// Vector of tuples containing (event_id, event_name)
1292pub fn get_event_list() -> Vec<(u32, &'static str)> {
1293    vec![
1294        (0x00, "PushTransportBegin"),
1295        (0x01, "PushTransportEnd"),
1296    ]
1297}
1298