Compare commits

...

4 Commits

13 changed files with 217 additions and 303 deletions

View File

@ -1,14 +1,24 @@
pub struct AtemPacket { use core::{fmt::Display, str};
length: u16,
flags: u8, // TODO: we don't need itertools once https://github.com/rust-lang/rust/issues/79524 lands
session_id: u16, use itertools::Itertools;
remote_packet_id: u16,
body: Vec<u8>, /// The "hello" packet to start communication with the ATEM
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
pub struct AtemPacket<T: AsRef<[u8]>> {
buf: T,
} }
#[derive(Debug)]
pub enum AtemPacketErr { pub enum AtemPacketErr {
TooShort(String), /// The packet was too short
LengthDiffers(String), TooShort { got: usize },
/// The packet's stated and actual lengths were different
LengthDiffers { expected: u16, got: usize },
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -32,64 +42,122 @@ impl From<PacketFlag> for u8 {
} }
} }
impl AtemPacket { impl<T: AsRef<[u8]>> AtemPacket<T> {
pub fn new_checked(buf: T) -> Result<Self, AtemPacketErr> {
let len = buf.as_ref().len();
if len < 12 {
return Err(AtemPacketErr::TooShort {
got: buf.as_ref().len(),
});
}
let p = Self { buf };
if p.length() as usize != len {
return Err(AtemPacketErr::LengthDiffers {
expected: p.length(),
got: len,
});
}
Ok(p)
}
pub fn length(&self) -> u16 { pub fn length(&self) -> u16 {
self.length u16::from_be_bytes(self.buf.as_ref()[0..=1].try_into().unwrap()) & 0x07ff
} }
pub fn flags(&self) -> u8 { pub fn flags(&self) -> u8 {
self.flags self.buf.as_ref()[0] >> 3
} }
pub fn session_id(&self) -> u16 { pub fn session_id(&self) -> u16 {
self.session_id u16::from_be_bytes(self.buf.as_ref()[2..=3].try_into().unwrap())
}
pub fn ack_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[4..=5].try_into().unwrap())
}
pub fn remote_sequence_number(&self) -> u16 {
u16::from_be_bytes(self.buf.as_ref()[9..=10].try_into().unwrap())
} }
pub fn remote_packet_id(&self) -> u16 { pub fn remote_packet_id(&self) -> u16 {
self.remote_packet_id u16::from_be_bytes(self.buf.as_ref()[10..=11].try_into().unwrap())
}
pub fn body(&self) -> Vec<u8> {
self.body.clone()
} }
/// Return true if this packet has the given [`PacketFlag`]
pub fn has_flag(&self, flag: PacketFlag) -> bool { pub fn has_flag(&self, flag: PacketFlag) -> bool {
self.flags & u8::from(flag) > 0 self.flags() & u8::from(flag) > 0
}
}
impl TryFrom<&[u8]> for AtemPacket {
type Error = AtemPacketErr;
fn try_from(buffer: &[u8]) -> Result<Self, Self::Error> {
if buffer.len() < 12 {
return Err(AtemPacketErr::TooShort(format!(
"Invalid packet from ATEM {:x?}",
buffer
)));
} }
let length = u16::from_be_bytes(buffer[0..2].try_into().unwrap()) & 0x07ff; /// Get an iterator over the `Field`s in this packet.
if length as usize != buffer.len() { ///
return Err(AtemPacketErr::LengthDiffers(format!( /// Returns None if this is a packet without fields.
"Length of message differs, expected {} got {}", pub fn fields(&self) -> Option<Fields> {
length, // TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)?
buffer.len() if self.has_flag(PacketFlag::NewSessionId) {
))); None
} } else {
Some(Fields {
let flags = buffer[0] >> 3; data: self.body(),
let session_id = u16::from_be_bytes(buffer[2..4].try_into().unwrap()); offset: 0,
let remote_packet_id = u16::from_be_bytes(buffer[10..12].try_into().unwrap());
let body = buffer[12..].to_vec();
Ok(AtemPacket {
length,
flags,
session_id,
remote_packet_id,
body,
}) })
} }
}
pub fn body(&self) -> &[u8] {
&self.buf.as_ref()[12..]
}
}
#[cfg(feature = "std")]
impl<T: AsRef<[u8]>> Display for AtemPacket<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"sid: {}, len: {}, fields: ({})",
self.session_id(),
self.length(),
self.fields()
.and_then(|f| Some(
f.map(|f| str::from_utf8(f.r#type).unwrap())
.intersperse(", ")
.collect::<String>()
))
.unwrap_or("none".into())
)
}
}
/// An ATEM protocol field - a 4-ascii character type plus variable length data
pub struct Field<'a> {
r#type: &'a [u8; 4],
data: &'a [u8],
}
/// Created by [`AtemPacket::fields`]
pub struct Fields<'a> {
data: &'a [u8],
offset: usize,
}
impl<'a> Iterator for Fields<'a> {
type Item = Field<'a>;
fn next(&mut self) -> Option<Self::Item> {
let remain = self.data.len() - self.offset;
if remain == 0 {
return None;
} else if remain < 8 {
// TODO: is 8 indeed the minimum size for something here? (i.e. no field data)
panic!("Oh no");
}
let length =
u16::from_be_bytes(self.data[self.offset..=self.offset + 1].try_into().unwrap());
// TODO: sanity check length
let r#type: &[u8; 4] = self.data[self.offset + 4..=self.offset + 7]
.try_into()
.unwrap();
let data = &self.data[self.offset + 8..self.offset + (length as usize)];
self.offset += (length as usize);
Some(Field { r#type, data })
}
} }

View File

@ -1,3 +1,4 @@
use core::net::SocketAddr;
use std::{io, sync::Arc, thread::yield_now}; use std::{io, sync::Arc, thread::yield_now};
use tokio::{sync::RwLock, task::JoinHandle}; use tokio::{sync::RwLock, task::JoinHandle};
@ -17,8 +18,8 @@ pub enum AtemSocketConnectionError {
} }
impl AtemSocket { impl AtemSocket {
pub fn new() -> Self { pub fn new<A: Into<SocketAddr>>(addr: A) -> Self {
let socket = AtemSocketInner::new(); let socket = AtemSocketInner::new(addr.into());
let socket = Arc::new(RwLock::new(socket)); let socket = Arc::new(RwLock::new(socket));
let socket_clone = Arc::clone(&socket); let socket_clone = Arc::clone(&socket);
@ -37,12 +38,8 @@ impl AtemSocket {
} }
} }
pub async fn connect( pub async fn connect(&mut self) -> Result<(), AtemSocketConnectionError> {
&mut self, self.socket.write().await.connect().await?;
address: String,
port: u16,
) -> Result<(), AtemSocketConnectionError> {
self.socket.write().await.connect(address, port).await?;
Ok(()) Ok(())
} }
@ -60,9 +57,3 @@ impl AtemSocket {
.await; .await;
} }
} }
impl Default for AtemSocket {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,13 +1,13 @@
use core::net::SocketAddr;
use std::{ use std::{
io, io,
net::SocketAddr,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use log::debug; use log::{debug, trace};
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use crate::atem_lib::atem_util; use crate::atem_lib::atem_packet::{AtemPacket, PacketFlag, COMMAND_CONNECT_HELLO};
const IN_FLIGHT_TIMEOUT: u64 = 60; const IN_FLIGHT_TIMEOUT: u64 = 60;
const CONNECTION_TIMEOUT: u64 = 5000; const CONNECTION_TIMEOUT: u64 = 5000;
@ -39,27 +39,6 @@ impl Into<u8> for ConnectionState {
} }
} }
#[derive(PartialEq)]
enum PacketFlag {
AckRequest,
NewSessionId,
IsRetransmit,
RetransmitRequest,
AckReply,
}
impl From<PacketFlag> for u8 {
fn from(flag: PacketFlag) -> Self {
match flag {
PacketFlag::AckRequest => 0x01,
PacketFlag::NewSessionId => 0x02,
PacketFlag::IsRetransmit => 0x04,
PacketFlag::RetransmitRequest => 0x08,
PacketFlag::AckReply => 0x10,
}
}
}
#[derive(Clone)] #[derive(Clone)]
struct InFlightPacket { struct InFlightPacket {
packet_id: u16, packet_id: u16,
@ -89,8 +68,7 @@ pub struct AtemSocketInner {
session_id: u16, session_id: u16,
socket: Option<UdpSocket>, socket: Option<UdpSocket>,
address: String, address: SocketAddr,
port: u16,
last_received_at: SystemTime, last_received_at: SystemTime,
last_received_packed_id: u16, last_received_packed_id: u16,
@ -113,7 +91,7 @@ enum AtemSocketWriteError {
} }
impl AtemSocketInner { impl AtemSocketInner {
pub fn new() -> Self { pub fn new(addr: SocketAddr) -> Self {
AtemSocketInner { AtemSocketInner {
connection_state: ConnectionState::Closed, connection_state: ConnectionState::Closed,
reconnect_timer: None, reconnect_timer: None,
@ -123,8 +101,7 @@ impl AtemSocketInner {
session_id: 0, session_id: 0,
socket: None, socket: None,
address: "0.0.0.0".to_string(), address: addr,
port: 0,
last_received_at: SystemTime::now(), last_received_at: SystemTime::now(),
last_received_packed_id: 0, last_received_packed_id: 0,
@ -134,15 +111,9 @@ impl AtemSocketInner {
} }
} }
pub async fn connect(&mut self, address: String, port: u16) -> Result<(), io::Error> { pub async fn connect(&mut self) -> Result<(), io::Error> {
self.address = address.clone();
self.port = port;
let socket = UdpSocket::bind("0.0.0.0:0").await?; let socket = UdpSocket::bind("0.0.0.0:0").await?;
let remote_addr = format!("{}:{}", address, port) socket.connect(self.address).await?;
.parse::<SocketAddr>()
.unwrap();
socket.connect(remote_addr).await?;
self.socket = Some(socket); self.socket = Some(socket);
self.start_timers(); self.start_timers();
@ -152,7 +123,7 @@ impl AtemSocketInner {
self.in_flight = vec![]; self.in_flight = vec![];
debug!("Reconnect"); debug!("Reconnect");
self.send_packet(&atem_util::COMMAND_CONNECT_HELLO).await; self.send_packet(&COMMAND_CONNECT_HELLO).await;
self.connection_state = ConnectionState::SynSent; self.connection_state = ConnectionState::SynSent;
Ok(()) Ok(())
@ -218,7 +189,7 @@ impl AtemSocketInner {
async fn restart_connection(&mut self) { async fn restart_connection(&mut self) {
self.disconnect(); self.disconnect();
self.connect(self.address.clone(), self.port).await.ok(); self.connect().await.ok();
} }
pub async fn tick(&mut self) { pub async fn tick(&mut self) {
@ -277,30 +248,23 @@ impl AtemSocketInner {
} }
async fn recieved_packet(&mut self, packet: &[u8]) { async fn recieved_packet(&mut self, packet: &[u8]) {
debug!("RECV {:x?}", packet); trace!("RX Raw: {:x?}", packet);
if packet.len() < 12 { let checked = match AtemPacket::new_checked(packet) {
debug!("Invalid packet from ATEM {:x?}", packet); Ok(p) => p,
Err(e) => {
debug!("Invalid packet ({:?}): {:x?}", e, packet);
return; return;
} }
};
debug!("RX: {}", checked);
self.last_received_at = SystemTime::now(); self.last_received_at = SystemTime::now();
let length = u16::from_be_bytes(packet[0..2].try_into().unwrap()) & 0x07ff;
if length as usize != packet.len() { self.session_id = checked.session_id();
debug!( let remote_packet_id = checked.remote_packet_id();
"Length of message differs, expected {} got {}",
length,
packet.len()
);
return;
}
let flags = packet[0] >> 3; if checked.flags() & u8::from(PacketFlag::NewSessionId) > 0 {
self.session_id = u16::from_be_bytes(packet[2..4].try_into().unwrap());
let remote_packet_id = u16::from_be_bytes(packet[10..12].try_into().unwrap());
if flags & u8::from(PacketFlag::NewSessionId) > 0 {
debug!("New session"); debug!("New session");
self.connection_state = ConnectionState::Established; self.connection_state = ConnectionState::Established;
self.last_received_packed_id = remote_packet_id; self.last_received_packed_id = remote_packet_id;
@ -309,20 +273,20 @@ impl AtemSocketInner {
} }
if self.connection_state == ConnectionState::Established { if self.connection_state == ConnectionState::Established {
if flags & u8::from(PacketFlag::RetransmitRequest) > 0 { if checked.has_flag(PacketFlag::RetransmitRequest) {
let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap()); let from_packet_id = u16::from_be_bytes(packet[6..8].try_into().unwrap());
debug!("Retransmit request: {:x?}", from_packet_id); debug!("Retransmit request: {:x?}", from_packet_id);
self.retransmit_from(from_packet_id).await; self.retransmit_from(from_packet_id).await;
} }
if flags & u8::from(PacketFlag::AckRequest) > 0 { if checked.has_flag(PacketFlag::AckRequest) {
if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID { if remote_packet_id == (self.last_received_packed_id + 1) % MAX_PACKET_ID {
self.last_received_packed_id = remote_packet_id; self.last_received_packed_id = remote_packet_id;
self.send_or_queue_ack().await; self.send_or_queue_ack().await;
if length > 12 { if checked.length() > 12 {
self.on_command_received(&packet[12..], remote_packet_id); self.on_command_received(checked.body(), remote_packet_id);
} }
} else if self } else if self
.is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id) .is_packet_covered_by_ack(self.last_received_packed_id, remote_packet_id)
@ -331,12 +295,12 @@ impl AtemSocketInner {
} }
} }
if flags & u8::from(PacketFlag::IsRetransmit) > 0 { if checked.has_flag(PacketFlag::IsRetransmit) {
debug!("ATEM retransmitted packet {:x?}", remote_packet_id); debug!("ATEM retransmitted packet {:x?}", remote_packet_id);
} }
if flags & u8::from(PacketFlag::AckReply) > 0 { if checked.has_flag(PacketFlag::AckReply) {
let ack_packet_id = u16::from_be_bytes(packet[4..6].try_into().unwrap()); let ack_packet_id = checked.ack_number();
let mut acked_commands: Vec<AckedPacket> = vec![]; let mut acked_commands: Vec<AckedPacket> = vec![];
self.in_flight = self self.in_flight = self

View File

@ -1,4 +0,0 @@
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];

View File

@ -1,4 +1,3 @@
mod atem_packet; pub mod atem_packet;
pub mod atem_socket; pub mod atem_socket;
mod atem_socket_inner; mod atem_socket_inner;
pub mod atem_util;

View File

@ -2,5 +2,5 @@
pub struct ColorGeneratorState { pub struct ColorGeneratorState {
pub hue: u64, pub hue: u64,
pub saturation: u64, pub saturation: u64,
pub luma: u64 pub luma: u64,
} }

View File

@ -1,6 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::enums::{ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType, FairlightInputConfiguration, FairlightInputType}; use crate::enums::{
ExternalPortType, FairlightAnalogInputLevel, FairlightAudioMixOption, FairlightAudioSourceType,
FairlightInputConfiguration, FairlightInputType,
};
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioDynamicsState { pub struct FairlightAudioDynamicsState {
@ -8,7 +11,7 @@ pub struct FairlightAudioDynamicsState {
pub limiter: Option<FairlightAudioLimiterState>, pub limiter: Option<FairlightAudioLimiterState>,
pub compressor: Option<FairlightAudioCompressorState>, pub compressor: Option<FairlightAudioCompressorState>,
pub expander: Option<FairlightAudioExpanderState> pub expander: Option<FairlightAudioExpanderState>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -17,7 +20,7 @@ pub struct FairlightAudioLimiterState {
pub threshold: u64, pub threshold: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64 pub release: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -27,7 +30,7 @@ pub struct FairlightAudioCompressorState {
pub ratio: u64, pub ratio: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64 pub release: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -39,7 +42,7 @@ pub struct FairlightAudioExpanderState {
pub ratio: u64, pub ratio: u64,
pub attack: u64, pub attack: u64,
pub hold: u64, pub hold: u64,
pub release: u64 pub release: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -55,14 +58,14 @@ pub struct FairlightAudioEqualizerBandState {
pub frequency: u64, pub frequency: u64,
pub gain: u64, pub gain: u64,
pub q_factor: u64 pub q_factor: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioMasterChannelPropertiesState { pub struct FairlightAudioMasterChannelPropertiesState {
// Gain in decibel, -Infinity to +6dB // Gain in decibel, -Infinity to +6dB
pub fader_gain: u64, pub fader_gain: u64,
pub follow_fade_to_black: bool pub follow_fade_to_black: bool,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -78,21 +81,21 @@ pub struct FairlightAudioMonitorChannel {
pub gain: u64, pub gain: u64,
pub input_master_gain: u64, pub input_master_gain: u64,
pub input_talkback_gain: u64, pub input_talkback_gain: u64,
pub input_sidetone_gain: u64 pub input_sidetone_gain: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioSource { pub struct FairlightAudioSource {
pub properties: Option<FairlightAudioSourcePropertiesState>, pub properties: Option<FairlightAudioSourcePropertiesState>,
pub equalizer: Option<FairlightAudioEqualizerState>, pub equalizer: Option<FairlightAudioEqualizerState>,
pub dynamics: Option<FairlightAudioDynamicsState> pub dynamics: Option<FairlightAudioDynamicsState>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioEqualizerState { pub struct FairlightAudioEqualizerState {
pub enabled: bool, pub enabled: bool,
pub gain: u64, pub gain: u64,
bands: Vec<FairlightAudioEqualizerBandState> bands: Vec<FairlightAudioEqualizerBandState>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -110,13 +113,13 @@ pub struct FairlightAudioSourcePropertiesState {
pub fader_gain: u64, pub fader_gain: u64,
supported_mix_options: Vec<FairlightAudioMixOption>, supported_mix_options: Vec<FairlightAudioMixOption>,
pub mix_option: FairlightAudioMixOption pub mix_option: FairlightAudioMixOption,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct FairlightAudioInput { pub struct FairlightAudioInput {
pub properties: Option<FairlightAudioInputProperties>, pub properties: Option<FairlightAudioInputProperties>,
pub sources: HashMap<String, FairlightAudioSource> pub sources: HashMap<String, FairlightAudioSource>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -128,7 +131,7 @@ pub struct FairlightAudioInputProperties {
pub active_configuration: FairlightInputConfiguration, pub active_configuration: FairlightInputConfiguration,
supported_input_levels: Vec<FairlightAnalogInputLevel>, supported_input_levels: Vec<FairlightAnalogInputLevel>,
pub activeInputLevel: FairlightAnalogInputLevel pub activeInputLevel: FairlightAnalogInputLevel,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -137,5 +140,5 @@ pub struct AtemFairlightAudioState {
pub master: Option<FairlightAudioMasterChannel>, pub master: Option<FairlightAudioMasterChannel>,
pub monitor: Option<FairlightAudioMonitorChannel>, pub monitor: Option<FairlightAudioMonitorChannel>,
pub audio_follow_video_crossfade_transition_enabled: Option<bool> pub audio_follow_video_crossfade_transition_enabled: Option<bool>,
} }

View File

@ -10,5 +10,5 @@ pub struct InputChannel {
pub external_port_type: ExternalPortType, pub external_port_type: ExternalPortType,
internal_port_type: InternalPortType, internal_port_type: InternalPortType,
source_availability: SourceAvailability, source_availability: SourceAvailability,
me_availability: MeAvailability me_availability: MeAvailability,
} }

View File

@ -11,7 +11,7 @@ pub struct RecordingState {
pub duration: Option<Timecode>, pub duration: Option<Timecode>,
pub disks: HashMap<u64, RecordingDiskProperties> pub disks: HashMap<u64, RecordingDiskProperties>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -19,7 +19,7 @@ pub struct RecordingDiskProperties {
pub disk_id: u64, pub disk_id: u64,
pub volume_name: String, pub volume_name: String,
pub recording_time_available: u64, pub recording_time_available: u64,
pub status: RecordingDiskStatus pub status: RecordingDiskStatus,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -27,7 +27,7 @@ pub struct RecordingStateStatus {
pub state: RecordingStatus, pub state: RecordingStatus,
pub error: RecordingError, pub error: RecordingError,
pub recording_time_available: u64 pub recording_time_available: u64,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
@ -37,5 +37,5 @@ pub struct RecordingStateProperties {
pub working_set_1_disk_id: u64, pub working_set_1_disk_id: u64,
pub working_set_2_disk_id: u64, pub working_set_2_disk_id: u64,
pub record_in_all_cameras: bool pub record_in_all_cameras: bool,
} }

View File

@ -8,19 +8,19 @@ pub struct StreamingState {
pub stats: Option<StreamingStateStats>, pub stats: Option<StreamingStateStats>,
pub service: StreamingServiceProperties, pub service: StreamingServiceProperties,
pub duration: Option<Timecode> pub duration: Option<Timecode>,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct StreamingStateStatus { pub struct StreamingStateStatus {
state: StreamingStatus, state: StreamingStatus,
error: StreamingError error: StreamingError,
} }
#[derive(Getters, new)] #[derive(Getters, new)]
pub struct StreamingStateStats { pub struct StreamingStateStats {
cache_used: u64, cache_used: u64,
encoding_bitrate: u64 encoding_bitrate: u64,
} }
pub struct StreamingServiceProperties { pub struct StreamingServiceProperties {
@ -28,5 +28,5 @@ pub struct StreamingServiceProperties {
pub url: String, pub url: String,
pub key: String, pub key: String,
bitrates: (u64, u64) bitrates: (u64, u64),
} }

View File

@ -1,4 +1,4 @@
use std::time::Duration; use std::{env::args, net::Ipv4Addr, time::Duration};
use atem_connection_rs::{ use atem_connection_rs::{
atem_lib::atem_socket::AtemSocket, atem_lib::atem_socket::AtemSocket,
@ -18,8 +18,11 @@ async fn main() {
let switch_to_source_1 = ProgramInput::new(0, 1); let switch_to_source_1 = ProgramInput::new(0, 1);
let switch_to_source_2 = ProgramInput::new(0, 2); let switch_to_source_2 = ProgramInput::new(0, 2);
let mut atem = AtemSocket::new(); let mut atem = AtemSocket::new((
atem.connect("127.0.0.1".to_string(), 9910).await.ok(); args().skip(1).next().unwrap().parse::<Ipv4Addr>().unwrap(),
9910,
));
atem.connect().await.ok();
let mut tracking_id = 0; let mut tracking_id = 0;
loop { loop {

View File

@ -1,70 +1,15 @@
{ {
"nodes": { "nodes": {
"devshell": { "naersk": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1705332421, "lastModified": 1745925850,
"narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", "narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"owner": "numtide",
"repo": "devshell",
"rev": "83cb93d6d063ad290beee669f4badf9914cc16ec",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9", "rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -75,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1704161960, "lastModified": 1749401433,
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", "narHash": "sha256-HXIQzULIG/MEUW2Q/Ss47oE3QrjxvpUX7gUl4Xp6lnc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "63143ac2c9186be6d9da6035fa22620018c85932", "rev": "08fcb0dcb59df0344652b38ea6326a2d8271baff",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -91,12 +36,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1705883077, "lastModified": 1733412085,
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=", "narHash": "sha256-FillH0qdWDt/nlO6ED7h4cmN+G9uXwGjwmCnHs0QVYM=",
"owner": "NixOS", "path": "/nix/store/5wvrlpfrwlmg24ywkybvidyzcvki6bwx-source",
"repo": "nixpkgs", "rev": "4dc2fc4e62dbf62b84132fe526356fbac7b03541",
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9", "type": "path"
"type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
@ -105,25 +49,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1705883077, "lastModified": 1744536153,
"narHash": "sha256-ByzHHX3KxpU1+V0erFy8jpujTufimh6KaS/Iv3AciHk=", "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5f5210aa20e343b7e35f40c033000db0ef80d7b9", "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -135,24 +65,22 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"devshell": "devshell",
"naersk": "naersk", "naersk": "naersk",
"nixpkgs": "nixpkgs_3", "nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay", "rust-overlay": "rust-overlay",
"utils": "utils" "utils": "utils"
} }
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1705976279, "lastModified": 1749436897,
"narHash": "sha256-Zx97bJ3+O8IP70uJPD//rRsr8bcxICISMTZUT/L9eFk=", "narHash": "sha256-OkDtaCGQQVwVFz5HWfbmrMJR99sFIMXHCHEYXzUJEJY=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "f889dc31ef97835834bdc3662394ebdb3c96b974", "rev": "e7876c387e35dc834838aff254d8e74cf5bd4f19",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -176,46 +104,16 @@
"type": "github" "type": "github"
} }
}, },
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems_3" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1705309234, "lastModified": 1731533236,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -3,7 +3,6 @@
inputs = { inputs = {
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
devshell.url = "github:numtide/devshell";
naersk.url = "github:nix-community/naersk"; naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
}; };
@ -13,7 +12,6 @@
nixpkgs, nixpkgs,
utils, utils,
naersk, naersk,
devshell,
rust-overlay, rust-overlay,
}: }:
utils.lib.eachDefaultSystem (system: let utils.lib.eachDefaultSystem (system: let
@ -35,16 +33,10 @@
apps.default = utils.lib.mkApp {drv = packages.default;}; apps.default = utils.lib.mkApp {drv = packages.default;};
# Provide a dev env with rust and rust-analyzer devShells.default = pkgs.mkShell {
devShells.default = let
pkgs = import nixpkgs {
inherit system;
overlays = [devshell.overlays.default];
};
in
pkgs.devshell.mkShell {
packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc]; packages = with pkgs; [(rust.override {extensions = ["rust-src"];}) rust-analyzer gcc];
}; };
formatter = pkgs.alejandra; formatter = pkgs.nixfmt-rfc-style;
}); });
} }