use std::net::Ipv4Addr;
use crate::event::Event;
use crate::state::State;
use crate::types::{
ActionTransmitRequest, AudioClip, BayNameChange, EdidProfileChange, EdidRecord, EdidRequest,
FactoryResetRequest, IrCapture, IrMeta, IrTransmitRequest, KeyTransmitRequest, MuteStatus,
PduState, RcSettings, RebootRequest, SetRouteRequest, V2ipBlacklistChange,
V2ipPowerSaveRequest, V2ipTilingConfig, VideoWallCommand, VideoWallOp, VolumeMuteStatus,
};
use crate::wire::{cstr, BayUid, DeviceUid, EdidProfile, RcAction, RcKey, DEVICE_NAME_LEN};
use super::handlers::{byte, ipv4_at, u16_at, u32_at, uid_at};
use super::Rx;
fn from_known_device(state: &State, rx: &Rx<'_>) -> Option<DeviceUid> {
state.device(rx.sender()).map(|d| d.uid)
}
pub(super) fn discover_request(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if let Some(device) = from_known_device(state, rx) {
ev.push(Event::DiscoverRequest { device });
}
}
const EDID_SIZE: usize = 256;
pub(super) fn edid(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
match p.len() {
17 => ev.push(Event::EdidRequested {
device,
request: EdidRequest {
target: uid_at(p, 0),
output: byte(p, 16) != 0,
},
}),
len if len == EDID_SIZE + 1 || len == 2 * (EDID_SIZE + 1) => {
for record in p.chunks_exact(EDID_SIZE + 1) {
ev.push(Event::EdidReceived {
device,
edid: EdidRecord {
output: record[0] != 0,
data: record[1..].to_vec(),
},
});
}
}
_ => {}
}
}
fn routing_request(p: &[u8], audio_only: bool) -> Option<SetRouteRequest> {
let need = if audio_only { 20 } else { 21 };
if p.len() < need {
return None;
}
Some(SetRouteRequest {
serial: cstr(&p[0..16]),
sink_bay: u16_at(p, 16),
source_bay: u16_at(p, 18),
no_power_on: !audio_only && byte(p, 20) != 0,
audio_only,
})
}
pub(super) fn set_route(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
route_request(state, rx, ev, false);
}
pub(super) fn audio_set_route(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
route_request(state, rx, ev, true);
}
fn route_request(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>, audio_only: bool) {
let Some(device) = from_known_device(state, rx) else {
return;
};
if let Some(request) = routing_request(rx.frame.payload(), audio_only) {
ev.push(Event::SetRouteRequested { device, request });
}
}
const IR_DATA_SIZE: usize = 24;
const IR_CAPTURE_PROTOCOL: u16 = 0x19;
pub(super) fn ir_capture(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if from_known_device(state, rx).is_none() {
return;
}
let p = rx.frame.payload();
if p.len() < IR_DATA_SIZE || rx.frame.protocol() < IR_CAPTURE_PROTOCOL {
return;
}
let capture = IrCapture {
port: u16_at(p, 0),
timestamp: u32_at(p, 4),
last_change: u32_at(p, 8),
meta: ir_meta(&p[12..21]),
timings: p[IR_DATA_SIZE..].to_vec(),
};
let bay = BayUid::new(rx.sender(), capture.port);
if state.bay(bay).is_some() {
ev.push(Event::IrCaptured { bay, capture });
}
}
fn ir_meta(p: &[u8]) -> IrMeta {
IrMeta {
timer_resolution: u16_at(p, 0),
frequency: u16_at(p, 2),
nb_timings: u16_at(p, 4),
repeat_offset: u16_at(p, 6),
status: byte(p, 8),
}
}
const TX_IR_HEADER: usize = 36;
pub(super) fn ir_transmit(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < TX_IR_HEADER {
return;
}
ev.push(Event::IrTransmitRequested {
device,
request: IrTransmitRequest {
target: uid_at(p, 0),
local_mode: byte(p, 16),
local_bay: byte(p, 17),
timestamp: u32_at(p, 20),
meta: ir_meta(&p[24..33]),
timings: p[TX_IR_HEADER..].to_vec(),
},
});
}
pub(super) fn volume_step(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>, up: bool) {
if from_known_device(state, rx).is_none() {
return;
}
let Some(port) = rx.frame.u8(0) else {
return;
};
let bay = BayUid::new(rx.sender(), u16::from(port));
if state.bay(bay).is_some() {
ev.push(Event::VolumeStep { bay, up });
}
}
pub(super) fn volume_mute(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if from_known_device(state, rx).is_none() {
return;
}
let p = rx.frame.payload();
if p.len() < 4 {
return;
}
let mute = MuteStatus::from_wire(p[3]);
let volume = VolumeMuteStatus {
volume_left: Some(p[1]).filter(|v| *v <= 100),
volume_right: Some(p[2]).filter(|v| *v <= 100),
muted_left: Some(mute.left()),
muted_right: Some(mute.right()),
};
if let Some(device) = state.device_mut(rx.sender()) {
device.apply_bay_volume(u16::from(p[0]), volume, ev);
}
}
pub(super) fn audio_clip(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if from_known_device(state, rx).is_none() {
return;
}
let p = rx.frame.payload();
if p.len() < 2 {
return;
}
let bay = BayUid::new(rx.sender(), u16::from(p[0]));
if state.bay(bay).is_some() {
ev.push(Event::AudioClipped {
bay,
clip: AudioClip {
port: u16::from(p[0]),
clip: p[1],
},
});
}
}
pub(super) fn pdu_state(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
if p.len() < 32 {
return;
}
let float = |off: usize| {
let value = f64::from(f32::from_bits(u32_at(p, off)));
(value * 100.0).round() / 100.0
};
let mut outlets = [0u8; 8];
outlets.copy_from_slice(&p[24..32]);
let st = PduState {
current: float(0),
voltage: float(4),
power: float(8),
dissipation: float(12),
frequency: float(20),
outlets,
};
if let Some(device) = state.device_mut(rx.sender()) {
device.set_pdu_state(st, ev);
}
}
pub(super) fn v2ip_link_remote(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
if let Some(target) = rx.frame.uid(0) {
ev.push(Event::V2ipLinkChanged { device, target });
}
}
pub(super) fn detect_bays(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if let Some(device) = from_known_device(state, rx) {
ev.push(Event::DetectBaysRequested { device });
}
}
pub(super) fn change_bay_name(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 18 + DEVICE_NAME_LEN {
return;
}
ev.push(Event::BayNameChangeRequested {
device,
change: BayNameChange {
target: uid_at(p, 0),
port: u16_at(p, 16),
name: cstr(&p[18..18 + DEVICE_NAME_LEN]),
},
});
}
pub(super) fn reboot(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
if let Some(target) = rx.frame.uid(0) {
ev.push(Event::RebootRequested {
device,
request: RebootRequest { target },
});
}
}
pub(super) fn monitoring_pulse(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if let Some(device) = from_known_device(state, rx) {
ev.push(Event::MonitoringPulse { device });
}
}
pub(super) fn upgrade_fpga(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if let Some(device) = from_known_device(state, rx) {
ev.push(Event::UpgradeFpgaRequested { device });
}
}
pub(super) fn edid_profile(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 18 {
return;
}
ev.push(Event::EdidProfileChangeRequested {
device,
change: EdidProfileChange {
target: uid_at(p, 0),
profile: EdidProfile::from_wire(u16_at(p, 16)),
},
});
}
pub(super) fn setup_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(value) = rx.frame.u8(0) else {
return;
};
if let Some(device) = state.device_mut(rx.sender()) {
device.set_setup_completed(value == 1, ev);
}
}
pub(super) fn set_installer(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(id) = rx.frame.u16(0) else {
return;
};
if let Some(device) = state.device_mut(rx.sender()) {
device.set_installer_id(id, ev);
}
}
pub(super) fn filter_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
if p.len() < 16 || p.len() % 16 != 0 {
return;
}
let filtered: Vec<DeviceUid> = p[16..].chunks_exact(16).map(|c| uid_at(c, 0)).collect();
let Some(port) = state
.device(rx.sender())
.and_then(|d| d.first_output_port())
else {
return;
};
if let Some(bay) = state.bay_mut(BayUid::new(rx.sender(), port)) {
bay.set_filtered(filtered, ev);
}
}
const FACTORY_RESET_ALL: u8 = 0xFF;
pub(super) fn factory_reset(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
let request = match p.len() {
1 if p[0] == FACTORY_RESET_ALL => FactoryResetRequest {
all: true,
target: None,
},
16 => FactoryResetRequest {
all: false,
target: Some(uid_at(p, 0)),
},
_ => FactoryResetRequest::default(),
};
ev.push(Event::FactoryResetRequested { device, request });
}
pub(super) fn v2ip_tiling(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 24 {
return;
}
let tiling = V2ipTilingConfig {
target: uid_at(p, 0),
pos_x: u16_at(p, 16),
pos_y: u16_at(p, 18),
width: u16_at(p, 20),
height: u16_at(p, 22),
};
if let Some(target) = state.device_mut(tiling.target) {
target.set_tiling(tiling, ev);
return;
}
ev.push(Event::TilingChanged { device, tiling });
}
pub(super) fn v2ip_power_save(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
let request = match p.len() {
1 => V2ipPowerSaveRequest {
target: None,
enabled: p[0] == 1,
},
17 => V2ipPowerSaveRequest {
target: Some(uid_at(p, 0)),
enabled: p[16] == 1,
},
_ => return,
};
ev.push(Event::PowerSaveRequested { device, request });
}
pub(super) fn rc_settings(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 28 {
return;
}
let flags = p[24];
let ip = ipv4_at(p, 20);
let settings = RcSettings {
target: uid_at(p, 0),
rc_target: p[16],
ip: (ip != Ipv4Addr::UNSPECIFIED).then_some(ip),
cec_enabled: flags & (1 << 0) != 0,
cec_auto_on: flags & (1 << 1) != 0,
forward_rc: flags & (1 << 2) != 0,
forward_ir: flags & (1 << 3) != 0,
rc_status: (flags >> 4) & 0xF,
status_name: p.get(28..44).map(cstr).unwrap_or_default(),
};
if let Some(target) = state.device_mut(settings.target) {
target.set_rc_settings(settings, ev);
return;
}
ev.push(Event::RcSettingsChanged { device, settings });
}
pub(super) fn tx_key(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 20 {
return;
}
ev.push(Event::KeyTransmitRequested {
device,
request: KeyTransmitRequest {
target: uid_at(p, 0),
local_bay: u16_at(p, 16),
key: RcKey::from_wire(u16_at(p, 18)),
},
});
}
pub(super) fn tx_action(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 20 {
return;
}
ev.push(Event::ActionTransmitRequested {
device,
request: ActionTransmitRequest {
target: uid_at(p, 0),
local_bay: u16_at(p, 16),
action: RcAction::from_wire(u16_at(p, 18)),
},
});
}
pub(super) fn blacklist(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>, registered: bool) {
let Some(device) = from_known_device(state, rx) else {
return;
};
if let Some(target) = rx.frame.uid(0) {
ev.push(Event::BlacklistChanged {
device,
change: V2ipBlacklistChange { target, registered },
});
}
}
pub(super) fn video_wall(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = from_known_device(state, rx) else {
return;
};
let p = rx.frame.payload();
if p.len() < 32 {
return;
}
ev.push(Event::VideoWallCommand {
device,
command: VideoWallCommand {
target: uid_at(p, 0),
pos_x: u16_at(p, 16),
pos_y: u16_at(p, 18),
width: u16_at(p, 20),
height: u16_at(p, 22),
raster_w: u16_at(p, 24),
raster_h: u16_at(p, 26),
op: VideoWallOp::from_wire(p[28]),
},
});
}