use std::fmt;
use std::net::Ipv4Addr;
use crate::event::Event;
use crate::state::{Bay, Device, State};
use crate::types::{
AmpZoneSettings, HiddenStatus, PowerStatus, V2ipAudioFormat, V2ipStreamSources,
VolumeMuteStatus,
};
use crate::wire::{
audio_cmd_header, audio_param, audio_sub, build_amp_zone_settings, build_audio_select_input,
build_bay_hide, build_edid_profile, build_rc_action, build_set_bay_name, build_set_volume,
build_stats_request, build_target_only, build_v2ip_manual_source_switch,
build_v2ip_source_switch, mv_cmd_payload, mv_sub, op, Addressee, BayUid, DeviceUid,
EdidProfile, MultiviewerAspectRatio, MultiviewerEdidTemplate, MultiviewerHdcpMode,
MultiviewerItcMode, MultiviewerOutputMode, MultiviewerPipPosition, MultiviewerPipSize,
MultiviewerSource, MultiviewerViewMode, Opcode, RcAction, SendError, StreamAddr, V2ipStreams,
DEVICE_NAME_LEN, V2IP_PORT_AUDIO,
};
use super::{Remote, Shared};
#[derive(Debug)]
#[non_exhaustive]
pub enum ControlError {
UnknownDevice(DeviceUid),
UnknownBay(BayUid),
UnknownSource(String),
Unsupported(&'static str),
NotReported(&'static str),
Send(SendError),
}
impl fmt::Display for ControlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownDevice(uid) => write!(f, "no device {uid}"),
Self::UnknownBay(uid) => write!(f, "no bay {uid}"),
Self::UnknownSource(name) => write!(f, "no source named {name:?}"),
Self::Unsupported(what) => f.write_str(what),
Self::NotReported(what) => write!(f, "{what} has not been reported"),
Self::Send(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for ControlError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Send(e) => Some(e),
_ => None,
}
}
}
impl From<SendError> for ControlError {
fn from(e: SendError) -> Self {
Self::Send(e)
}
}
type WriteBack = Box<dyn FnOnce(&mut State, &mut Vec<Event>) + Send>;
struct Command {
to: Addressee,
opcode: Opcode,
payload: Vec<u8>,
write_back: Option<WriteBack>,
}
impl Command {
fn new(to: Addressee, opcode: Opcode, payload: Vec<u8>) -> Self {
Self {
to,
opcode,
payload,
write_back: None,
}
}
fn then(mut self, f: impl FnOnce(&mut State, &mut Vec<Event>) + Send + 'static) -> Self {
self.write_back = Some(Box::new(f));
self
}
}
impl Shared {
fn command(
&self,
prepare: impl FnOnce(&State) -> Result<Command, ControlError>,
) -> Result<(), ControlError> {
let command = self.read(prepare)?;
self.send(&command.to, command.opcode, &command.payload)?;
if let Some(write_back) = command.write_back {
self.mutate(|state, ev| write_back(state, ev));
}
Ok(())
}
}
fn device_of(state: &State, uid: DeviceUid) -> Result<&Device, ControlError> {
state.device(uid).ok_or(ControlError::UnknownDevice(uid))
}
fn bay_of(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
let device = device_of(state, uid.device)?;
let bay = device.bay(uid.port).ok_or(ControlError::UnknownBay(uid))?;
Ok((device, bay))
}
fn source_streams(device: &Device, port: u16) -> Result<&V2ipStreamSources, ControlError> {
let source = device
.bay(port)
.ok_or(ControlError::UnknownBay(BayUid::new(device.uid, port)))?;
device
.v2ip_source_for(source)
.ok_or(ControlError::NotReported("the source's stream addresses"))
}
fn v2ip_sink(state: &State, uid: BayUid) -> Result<(&Device, &Bay), ControlError> {
let (device, bay) = bay_of(state, uid)?;
if !bay.is_v2ip_sink() {
return Err(ControlError::Unsupported("routing needs a V2IP sink"));
}
Ok((device, bay))
}
fn stored_name(name: &str) -> String {
let bytes = name.as_bytes();
String::from_utf8_lossy(bytes.get(..DEVICE_NAME_LEN).unwrap_or(bytes)).into_owned()
}
impl Remote {
pub fn select_video_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
self.shared.command(|state| {
let (device, bay) = v2ip_sink(state, sink)?;
if !bay.is_output() {
return Err(ControlError::Unsupported("not an output bay"));
}
let streams = source_streams(device, source_port)?;
Ok(Command::new(
Addressee::device(device),
op::V2IP_SOURCE_SWITCH,
build_v2ip_source_switch(device.uid, streams.video.ip, Ipv4Addr::UNSPECIFIED),
))
})
}
pub fn select_audio_source(&self, sink: BayUid, source_port: u16) -> Result<(), ControlError> {
self.shared.command(|state| {
let (device, _) = v2ip_sink(state, sink)?;
let streams = source_streams(device, source_port)?;
Ok(Command::new(
Addressee::device(device),
op::V2IP_SOURCE_SWITCH,
build_v2ip_source_switch(device.uid, Ipv4Addr::UNSPECIFIED, streams.audio.ip),
))
})
}
pub fn select_video_source_by_name(
&self,
sink: BayUid,
name: &str,
) -> Result<(), ControlError> {
self.select_video_source(sink, self.source_port(sink, name)?)
}
pub fn select_audio_source_addr(
&self,
sink: BayUid,
audio_ip: Ipv4Addr,
audio_port: Option<u16>,
format: Option<V2ipAudioFormat>,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = v2ip_sink(state, sink)?;
let streams = V2ipStreams {
audio: StreamAddr {
ip: audio_ip,
port: audio_port.unwrap_or(V2IP_PORT_AUDIO),
},
..V2ipStreams::default()
};
Ok(Command::new(
Addressee::device(device),
op::V2IP_MANUAL_SRC_SWITCH,
build_v2ip_manual_source_switch(device.uid, streams, format),
))
})
}
pub fn select_audio_source_by_name(
&self,
sink: BayUid,
name: &str,
format: Option<V2ipAudioFormat>,
) -> Result<(), ControlError> {
let port = self.source_port(sink, name)?;
let Some(format) = format else {
return self.select_audio_source(sink, port);
};
self.shared.command(move |state| {
let (device, _) = v2ip_sink(state, sink)?;
let audio = source_streams(device, port)?.audio;
let streams = V2ipStreams {
audio: StreamAddr {
ip: audio.ip,
port: audio.port,
},
..V2ipStreams::default()
};
Ok(Command::new(
Addressee::device(device),
op::V2IP_MANUAL_SRC_SWITCH,
build_v2ip_manual_source_switch(device.uid, streams, Some(format)),
))
})
}
fn source_port(&self, sink: BayUid, name: &str) -> Result<u16, ControlError> {
self.shared.read(|state| {
let (device, _) = bay_of(state, sink)?;
device
.bay_by_user_name(name)
.map(|b| b.port)
.ok_or_else(|| ControlError::UnknownSource(name.to_owned()))
})
}
pub fn set_bay_name(&self, bay: BayUid, name: &str) -> Result<(), ControlError> {
let name = stored_name(name);
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
let payload = build_set_bay_name(device.uid, bay.port, &name);
Ok(
Command::new(Addressee::device(device), op::CHANGE_BAY_NAME, payload).then(
move |state, ev| {
if let Some(b) = state.bay_mut(bay) {
b.set_user_name(name, ev);
}
},
),
)
})
}
pub fn set_bay_hidden(&self, bay: BayUid, hidden: bool) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
Ok(Command::new(
Addressee::device(device),
op::BAY_HIDE,
build_bay_hide(device.uid, bay.port, hidden),
)
.then(move |state, ev| {
if let Some(b) = state.bay_mut(bay) {
let status = if hidden {
HiddenStatus::Hidden
} else {
HiddenStatus::Visible
};
b.apply_hidden(status, ev);
}
}))
})
}
pub fn select_edid_profile(
&self,
bay: BayUid,
profile: EdidProfile,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
Ok(Command::new(
Addressee::device(device),
op::BAY_EDID_PROFILE,
build_edid_profile(device.uid, profile),
)
.then(move |state, ev| {
if let Some(b) = state.bay_mut(bay) {
b.set_edid_profile(profile, ev);
}
}))
})
}
pub fn send_action(&self, bay: BayUid, action: RcAction) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
Ok(Command::new(
Addressee::device(device),
op::RC_TX_ACTION,
build_rc_action(device.uid, bay.port, action),
))
})
}
pub fn power_on(&self, bay: BayUid) -> Result<(), ControlError> {
self.set_power(bay, RcAction::POWER_ON, PowerStatus::On)
}
pub fn power_off(&self, bay: BayUid) -> Result<(), ControlError> {
self.set_power(bay, RcAction::POWER_OFF, PowerStatus::Off)
}
fn set_power(
&self,
bay: BayUid,
action: RcAction,
power: PowerStatus,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
Ok(Command::new(
Addressee::device(device),
op::RC_TX_ACTION,
build_rc_action(device.uid, bay.port, action),
)
.then(move |state, ev| {
if let Some(b) = state.bay_mut(bay) {
b.set_power_status(power, ev);
}
}))
})
}
pub fn set_volume(
&self,
bay: BayUid,
volume: u8,
muted: Option<bool>,
) -> Result<(), ControlError> {
let volume = volume.min(100);
let wanted = VolumeMuteStatus {
volume_left: Some(volume),
volume_right: Some(volume),
muted_left: muted,
muted_right: muted,
};
self.shared.command(move |state| {
let target = state.volume_bay(bay);
let (device, b) = bay_of(state, target)?;
if !b.has_volume_control() {
return Err(ControlError::Unsupported("the bay has no volume control"));
}
Ok(Command::new(
Addressee::device(device),
op::AUDIO_SET_VOLUME,
build_set_volume(device.uid, target.port, wanted),
)
.then(move |state, ev| {
if let Some(device) = state.device_mut(target.device) {
device.apply_bay_volume(target.port, wanted, ev);
}
}))
})
}
pub fn volume_up(&self, bay: BayUid) -> Result<(), ControlError> {
self.set_volume(bay, self.current_volume(bay)?.saturating_add(1), None)
}
pub fn volume_down(&self, bay: BayUid) -> Result<(), ControlError> {
self.set_volume(bay, self.current_volume(bay)?.saturating_sub(1), None)
}
pub fn set_muted(&self, bay: BayUid, muted: bool) -> Result<(), ControlError> {
self.set_volume(bay, self.current_volume(bay)?, Some(muted))
}
fn current_volume(&self, bay: BayUid) -> Result<u8, ControlError> {
self.shared.read(|state| {
let (_, b) = bay_of(state, state.volume_bay(bay))?;
b.audio_volume
.map(|v| v.volume())
.ok_or(ControlError::NotReported("the bay's volume"))
})
}
pub fn set_amp_zone_settings(
&self,
bay: BayUid,
settings: AmpZoneSettings,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let (device, _) = bay_of(state, bay)?;
Ok(Command::new(
Addressee::device(device),
op::AMP_ZONE_SETTINGS,
build_amp_zone_settings(device.uid, bay.port, &settings),
)
.then(move |state, ev| {
if let Some(b) = state.bay_mut(bay) {
b.set_amp_settings(settings, ev);
}
}))
})
}
pub fn set_audio_endpoint_muted(
&self,
device: DeviceUid,
endpoint: u16,
muted: bool,
) -> Result<(), ControlError> {
self.audio_endpoint(device, audio_sub::MUTE, endpoint, u32::from(muted))
}
pub fn set_audio_endpoint_trigger(
&self,
device: DeviceUid,
endpoint: u16,
active: bool,
) -> Result<(), ControlError> {
self.audio_endpoint(device, audio_sub::TRIGGER, endpoint, u32::from(active))
}
pub fn set_audio_endpoint_volume(
&self,
device: DeviceUid,
endpoint: u16,
volume: u32,
) -> Result<(), ControlError> {
self.audio_endpoint(device, audio_sub::VOLUME, endpoint, volume)
}
fn audio_endpoint(
&self,
device: DeviceUid,
sub: u16,
endpoint: u16,
value: u32,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let device = device_of(state, device)?;
let mut payload = audio_cmd_header(sub, device.uid);
payload.extend_from_slice(&audio_param(endpoint, value));
Ok(Command::new(
Addressee::device(device),
op::V2IP_AUDIO,
payload,
))
})
}
pub fn select_audio_endpoint_input(
&self,
sink: DeviceUid,
sink_endpoint: u16,
source: DeviceUid,
source_endpoint: u16,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let device = device_of(state, sink)?;
Ok(Command::new(
Addressee::device(device),
op::V2IP_AUDIO,
build_audio_select_input(sink, sink_endpoint, source, source_endpoint),
))
})
}
pub fn subscribe_v2ip_stats(
&self,
device: DeviceUid,
subscribe: bool,
) -> Result<(), ControlError> {
self.shared.command(move |state| {
let device = device_of(state, device)?;
Ok(Command::new(
Addressee::device(device),
op::V2IP_STATS,
build_stats_request(device.uid, subscribe),
))
})
}
pub fn reboot(&self, device: DeviceUid) -> Result<(), ControlError> {
self.shared.command(move |state| {
let d = device_of(state, device)?;
Ok(Command::new(
Addressee::device(d),
op::SYS_REBOOT,
build_target_only(d.uid),
)
.then(move |state, _| {
if let Some(d) = state.device_mut(device) {
d.rebooting = true;
}
}))
})
}
pub fn send_monitoring_pulse(&self) -> Result<(), ControlError> {
self.shared
.send(&Addressee::Broadcast, op::SYS_MONITORING_PULSE, &[])?;
Ok(())
}
pub fn set_multiviewer_view_mode(
&self,
device: DeviceUid,
mode: MultiviewerViewMode,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::VIEW_MODE, &[mode.to_wire()])
}
pub fn set_multiviewer_video_source(
&self,
device: DeviceUid,
screen: u8,
source: MultiviewerSource,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::VIDEO_SOURCE, &[screen, source.to_wire()])
}
pub fn set_multiviewer_audio_source(
&self,
device: DeviceUid,
source: MultiviewerSource,
) -> Result<(), ControlError> {
self.multiviewer(
device,
mv_sub::AUDIO_SOURCE,
&[source.to_wire().saturating_sub(1)],
)
}
pub fn set_multiviewer_audio_volume(
&self,
device: DeviceUid,
volume: u8,
muted: bool,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::AUDIO_VOLUME, &[volume, u8::from(muted)])
}
pub fn set_multiviewer_edid_template(
&self,
device: DeviceUid,
template: MultiviewerEdidTemplate,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::EDID_TEMPLATE, &[template.to_wire()])
}
pub fn set_multiviewer_remote_control(
&self,
device: DeviceUid,
source: MultiviewerSource,
) -> Result<(), ControlError> {
self.multiviewer(
device,
mv_sub::ROUTE_RC,
&[source.to_wire().saturating_sub(1)],
)
}
pub fn set_multiviewer_pip_size(
&self,
device: DeviceUid,
size: MultiviewerPipSize,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::PIP_SIZE, &[size.to_wire()])
}
pub fn set_multiviewer_pip_position(
&self,
device: DeviceUid,
position: MultiviewerPipPosition,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::PIP_POSITION, &[position.to_wire()])
}
pub fn set_multiviewer_aspect_ratio(
&self,
device: DeviceUid,
aspect: MultiviewerAspectRatio,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::ASPECT, &[aspect.to_wire()])
}
pub fn set_multiviewer_auto_switch(
&self,
device: DeviceUid,
enable: bool,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::AUTO_SWITCH, &[u8::from(enable)])
}
pub fn set_multiviewer_output_mode(
&self,
device: DeviceUid,
mode: MultiviewerOutputMode,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::OUTPUT_MODE, &[mode.to_wire()])
}
pub fn set_multiviewer_output_itc(
&self,
device: DeviceUid,
mode: MultiviewerItcMode,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::OUTPUT_ITC_MODE, &[mode.to_wire()])
}
pub fn set_multiviewer_hdcp_mode(
&self,
device: DeviceUid,
mode: MultiviewerHdcpMode,
) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::HDCP_MODE, &[mode.to_wire()])
}
pub fn set_multiviewer_input_source(
&self,
device: DeviceUid,
input: u8,
source: DeviceUid,
) -> Result<(), ControlError> {
let mut args = Vec::with_capacity(24);
args.extend_from_slice(source.as_bytes());
args.push(input);
args.extend_from_slice(&[0; 7]);
self.multiviewer(device, mv_sub::CONFIG_SOURCE, &args)
}
pub fn multiviewer_auto_route(&self, device: DeviceUid) -> Result<(), ControlError> {
self.multiviewer(device, mv_sub::AUTO_ROUTE, &[])
}
fn multiviewer(&self, device: DeviceUid, sub: u8, args: &[u8]) -> Result<(), ControlError> {
self.shared.command(|state| {
let device = device_of(state, device)?;
if !device.is_multiviewer() {
return Err(ControlError::Unsupported("the device is not a multiviewer"));
}
Ok(Command::new(
Addressee::device(device),
op::V2IP_MULTIVIEWER,
mv_cmd_payload(device.uid, sub, args),
))
})
}
}