diff --git a/atem-connection-rs/src/atem_lib/atem_socket.rs b/atem-connection-rs/src/atem_lib/atem_socket.rs index 5476b90..4873c58 100644 --- a/atem-connection-rs/src/atem_lib/atem_socket.rs +++ b/atem-connection-rs/src/atem_lib/atem_socket.rs @@ -103,6 +103,8 @@ pub struct AtemSocket { socket: Option, address: SocketAddr, + protocol_version: ProtocolVersion, + last_received_at: SystemTime, last_received_packed_id: u16, in_flight: Vec, @@ -159,6 +161,8 @@ impl AtemSocket { socket: None, address: "0.0.0.0:0".parse().unwrap(), + protocol_version: ProtocolVersion::V7_2, + last_received_at: SystemTime::now(), last_received_packed_id: 0, in_flight: vec![], @@ -532,7 +536,7 @@ impl AtemSocket { } fn on_commands_received(&mut self, payload: &[u8]) { - let commands = deserialize_commands(payload); + let commands = deserialize_commands(payload, &self.protocol_version); let _ = self .atem_event_tx diff --git a/atem-connection-rs/src/commands/command_base.rs b/atem-connection-rs/src/commands/command_base.rs index 6296c95..921d7f9 100644 --- a/atem-connection-rs/src/commands/command_base.rs +++ b/atem-connection-rs/src/commands/command_base.rs @@ -8,7 +8,8 @@ pub trait DeserializedCommand: Send + Sync + Debug { } pub trait CommandDeserializer: Send + Sync { - fn deserialize(&self, buffer: &[u8]) -> Arc; + fn deserialize(&self, buffer: &[u8], version: &ProtocolVersion) + -> Arc; } pub trait SerializableCommand: Send + Sync { diff --git a/atem-connection-rs/src/commands/device_profile.rs b/atem-connection-rs/src/commands/device_profile.rs index bc5afef..901c7c4 100644 --- a/atem-connection-rs/src/commands/device_profile.rs +++ b/atem-connection-rs/src/commands/device_profile.rs @@ -1,2 +1,3 @@ pub mod product_identifier; +pub mod topology; pub mod version; diff --git a/atem-connection-rs/src/commands/device_profile/product_identifier.rs b/atem-connection-rs/src/commands/device_profile/product_identifier.rs index 17f1de6..8a59c89 100644 --- a/atem-connection-rs/src/commands/device_profile/product_identifier.rs +++ b/atem-connection-rs/src/commands/device_profile/product_identifier.rs @@ -2,7 +2,7 @@ use std::{ffi::CString, sync::Arc}; use crate::{ commands::command_base::{CommandDeserializer, DeserializedCommand}, - enums::Model, + enums::{Model, ProtocolVersion}, }; pub const DESERIALIZE_PRODUCT_IDENTIFIER_RAW_NAME: &str = "_pin"; @@ -43,7 +43,11 @@ impl DeserializedCommand for ProductIdentifierCommand { pub struct ProductIdentifierCommandDeserializer {} impl CommandDeserializer for ProductIdentifierCommandDeserializer { - fn deserialize(&self, buffer: &[u8]) -> std::sync::Arc { + fn deserialize( + &self, + buffer: &[u8], + version: &ProtocolVersion, + ) -> std::sync::Arc { let null_byte_index = buffer .iter() .position(|byte| *byte == b'\0') diff --git a/atem-connection-rs/src/commands/device_profile/topology.rs b/atem-connection-rs/src/commands/device_profile/topology.rs new file mode 100644 index 0000000..d3b7fbe --- /dev/null +++ b/atem-connection-rs/src/commands/device_profile/topology.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use crate::{ + commands::command_base::{CommandDeserializer, DeserializedCommand}, + enums::ProtocolVersion, +}; + +pub const DESERIALIZE_TOPOLOGY_RAW_NAME: &str = "_top"; + +#[derive(Debug)] +pub struct TopologyCommand { + mix_effects: u8, + sources: u8, + auxilliaries: u8, + mix_minus_outputs: u8, + media_players: u8, + multiviewers: Option, + serial_ports: u8, + max_hyperdecks: u8, + dves: u8, + stingers: u8, + super_sources: u8, + talkback_channels: u8, + downstream_keyers: u8, + camera_control: bool, + advanced_chroma_keyers: bool, + only_configurable_outputs: bool, +} + +impl DeserializedCommand for TopologyCommand { + fn raw_name(&self) -> &'static str { + todo!() + } + + fn apply_to_state(&self, state: &mut crate::state::AtemState) { + todo!() + } +} + +pub struct TopologyCommandDeserializer {} + +impl CommandDeserializer for TopologyCommandDeserializer { + fn deserialize( + &self, + buffer: &[u8], + version: &ProtocolVersion, + ) -> std::sync::Arc { + let v230offset = if *version > ProtocolVersion::V8_0_1 { + 1 + } else { + 0 + }; + + let multiviewers = if v230offset > 0 { + Some(buffer[6]) + } else { + None + }; + + let advanced_chroma_keyers = if buffer.len() > 20 { + buffer[21 + v230offset] == 1 + } else { + false + }; + + let only_configurable_outputs = if buffer.len() > 20 { + buffer[22 + v230offset] == 1 + } else { + false + }; + + Arc::new(TopologyCommand { + mix_effects: buffer[0], + sources: buffer[1], + downstream_keyers: buffer[2], + auxilliaries: buffer[3], + mix_minus_outputs: buffer[4], + media_players: buffer[5], + multiviewers, + serial_ports: buffer[6 + v230offset], + max_hyperdecks: buffer[7 + v230offset], + dves: buffer[8 + v230offset], + stingers: buffer[9 + v230offset], + super_sources: buffer[10 + v230offset], + talkback_channels: buffer[12 + v230offset], + camera_control: buffer[17 + v230offset] == 1, + advanced_chroma_keyers, + only_configurable_outputs, + }) + } +} diff --git a/atem-connection-rs/src/commands/device_profile/version.rs b/atem-connection-rs/src/commands/device_profile/version.rs index d6dfbaa..8154b9d 100644 --- a/atem-connection-rs/src/commands/device_profile/version.rs +++ b/atem-connection-rs/src/commands/device_profile/version.rs @@ -26,7 +26,11 @@ impl DeserializedCommand for VersionCommand { pub struct VersionCommandDeserializer {} impl CommandDeserializer for VersionCommandDeserializer { - fn deserialize(&self, buffer: &[u8]) -> std::sync::Arc { + fn deserialize( + &self, + buffer: &[u8], + version: &ProtocolVersion, + ) -> std::sync::Arc { let version = u32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); let version: ProtocolVersion = version.try_into().expect("Invalid protocol version"); diff --git a/atem-connection-rs/src/commands/init_complete.rs b/atem-connection-rs/src/commands/init_complete.rs index e8380db..bef42ee 100644 --- a/atem-connection-rs/src/commands/init_complete.rs +++ b/atem-connection-rs/src/commands/init_complete.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use crate::enums::ProtocolVersion; + use super::command_base::{CommandDeserializer, DeserializedCommand}; pub const DESERIALIZE_INIT_COMPLETE_RAW_NAME: &str = "InCm"; @@ -19,7 +21,11 @@ impl DeserializedCommand for InitComplete { pub struct InitCompleteDeserializer {} impl CommandDeserializer for InitCompleteDeserializer { - fn deserialize(&self, _buffer: &[u8]) -> std::sync::Arc { + fn deserialize( + &self, + _buffer: &[u8], + version: &ProtocolVersion, + ) -> std::sync::Arc { Arc::new(InitComplete {}) } } diff --git a/atem-connection-rs/src/commands/mix_effects/program_input.rs b/atem-connection-rs/src/commands/mix_effects/program_input.rs index 53c30c5..47a6738 100644 --- a/atem-connection-rs/src/commands/mix_effects/program_input.rs +++ b/atem-connection-rs/src/commands/mix_effects/program_input.rs @@ -4,6 +4,7 @@ use crate::{ commands::command_base::{ BasicWritableCommand, CommandDeserializer, DeserializedCommand, SerializableCommand, }, + enums::ProtocolVersion, state::util::get_mix_effect, }; @@ -58,7 +59,11 @@ impl DeserializedCommand for ProgramInput { pub struct ProgramInputDeserializer {} impl CommandDeserializer for ProgramInputDeserializer { - fn deserialize(&self, buffer: &[u8]) -> Arc { + fn deserialize( + &self, + buffer: &[u8], + version: &ProtocolVersion, + ) -> Arc { let mix_effect = buffer[0]; let source = u16::from_be_bytes([buffer[2], buffer[3]]); diff --git a/atem-connection-rs/src/commands/parse_commands.rs b/atem-connection-rs/src/commands/parse_commands.rs index f09ea3f..0cf7ee8 100644 --- a/atem-connection-rs/src/commands/parse_commands.rs +++ b/atem-connection-rs/src/commands/parse_commands.rs @@ -1,5 +1,7 @@ use std::{collections::VecDeque, sync::Arc}; +use crate::enums::ProtocolVersion; + use super::{ command_base::{CommandDeserializer, DeserializedCommand}, device_profile::{ @@ -14,7 +16,10 @@ use super::{ time::{TimeDeserializer, DESERIALIZE_TIME_RAW_NAME}, }; -pub fn deserialize_commands(payload: &[u8]) -> VecDeque> { +pub fn deserialize_commands( + payload: &[u8], + version: &ProtocolVersion, +) -> VecDeque> { let mut parsed_commands = VecDeque::new(); let mut head = 0; @@ -31,13 +36,14 @@ pub fn deserialize_commands(payload: &[u8]) -> VecDeque Arc { + fn deserialize( + &self, + buffer: &[u8], + version: &ProtocolVersion, + ) -> Arc { let source_count = u16::from_be_bytes([buffer[0], buffer[1]]) as usize; log::debug!("{:?}", buffer); diff --git a/atem-connection-rs/src/commands/time.rs b/atem-connection-rs/src/commands/time.rs index f7604e7..ae34049 100644 --- a/atem-connection-rs/src/commands/time.rs +++ b/atem-connection-rs/src/commands/time.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use crate::enums::ProtocolVersion; + use super::command_base::{CommandDeserializer, DeserializedCommand}; pub const DESERIALIZE_TIME_RAW_NAME: &str = "Time"; @@ -33,6 +35,7 @@ impl CommandDeserializer for TimeDeserializer { fn deserialize( &self, buffer: &[u8], + version: &ProtocolVersion, ) -> std::sync::Arc { let info = TimeInfo { hour: buffer[0], diff --git a/atem-connection-rs/src/enums/mod.rs b/atem-connection-rs/src/enums/mod.rs index 9fc3290..21fcf2f 100644 --- a/atem-connection-rs/src/enums/mod.rs +++ b/atem-connection-rs/src/enums/mod.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + #[derive(Debug, Clone, Default, PartialEq)] pub enum Model { #[default] @@ -71,7 +73,7 @@ impl From for Model { } } -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub enum ProtocolVersion { #[default] Unknown = 0, @@ -98,6 +100,19 @@ impl TryFrom for ProtocolVersion { } } +impl Display for ProtocolVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProtocolVersion::Unknown => write!(f, "Unknown"), + ProtocolVersion::V7_2 => write!(f, "v7.2"), + ProtocolVersion::V7_5_2 => write!(f, "v7.5.2"), + ProtocolVersion::V8_0 => write!(f, "v8.0"), + ProtocolVersion::V8_0_1 => write!(f, "v8.0.1"), + ProtocolVersion::V8_1_1 => write!(f, "v8.1.1"), + } + } +} + #[derive(Clone, PartialEq)] pub enum TransitionStyle { MIX = 0x00,