#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Provenance {
HardwareVerified,
FirmwareSource,
Unverified,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct FirmwareProfile {
pub name: &'static str,
pub provenance: Provenance,
pub notes: &'static str,
pub mac_address: [u8; 6],
pub hw_revision: u16,
pub sw_revision: u16,
pub buffer_capacity: u16,
pub max_point_rate: u32,
pub version_string: Option<&'static str>,
pub always_set_playback_flags: u16,
pub ring_points: u16,
pub naks_full: bool,
pub begin_while_idle_acked: bool,
pub stop_while_idle_naks: bool,
pub stale_fullness_until_prepare: bool,
pub estop_clear_immediate: bool,
pub unknown_command_resets_tcp: bool,
pub data_partial_write_then_nak_invalid: bool,
pub data_while_idle_nak_invalid: bool,
pub enforce_single_client: bool,
pub rate_nak_consumes_one_byte: bool,
pub zero_rate_hangs: bool,
pub close_stops_playback: bool,
pub light_engine_flags_mirror_state: bool,
}
impl FirmwareProfile {
pub fn ed1_j4cdac() -> Self {
Self {
name: "ed1-j4cdac",
provenance: Provenance::FirmwareSource,
notes: "From j4cDAC source. DAC_BUFFER_POINTS=1800, broadcast advertises 1799. \
The 'v' build string is set at compile time, so the simulator reports a \
synthetic one. hw_revision is the board revision and assumed 0.",
mac_address: [0x02, 0xed, 0x00, 0x00, 0x00, 0x01],
hw_revision: 0,
sw_revision: 2,
buffer_capacity: 1799,
max_point_rate: 100_000,
version_string: Some("sim-ed1-j4cdac"),
always_set_playback_flags: 0,
ring_points: 1799,
naks_full: false,
begin_while_idle_acked: true,
stop_while_idle_naks: true,
stale_fullness_until_prepare: true,
estop_clear_immediate: true,
unknown_command_resets_tcp: true,
data_partial_write_then_nak_invalid: true,
data_while_idle_nak_invalid: true,
enforce_single_client: false,
rate_nak_consumes_one_byte: true,
zero_rate_hangs: true,
close_stops_playback: false,
light_engine_flags_mirror_state: true,
}
}
pub fn ed2_r331() -> Self {
Self {
name: "ed2-r331",
provenance: Provenance::HardwareVerified,
notes: "Probed over TCP, with the UDP broadcast recorded once (hw_revision 10, \
sw_revision 2, capacity 3899, max rate 100000). The ring holds exactly \
the advertised 3899 points: a full ring answers a further write with \
NAK-Invalid and fullness unchanged, never NAK-Full. The 'v' reply is 32 \
raw bytes, the NUL-padded build string with no response or command \
prefix. A 'b', 'u' or 'q' with a rate above max is NAKed after consuming \
only the opcode, so its argument bytes are parsed as further commands \
(so a low-water mark of 0 would become two e-stops). A rate of 0 hung \
the firmware until power-cycled. The upper byte of the playback flags \
is not constant: 0x5f before one power cycle and 0x77 after it (0x7731 \
idle). All TCP clients share one playback state. Probed at \
169.254.102.149; after the power cycle the DAC was at 192.168.254.66.",
mac_address: [0x02, 0xed, 0x00, 0x00, 0x00, 0x02],
hw_revision: 10,
sw_revision: 2,
buffer_capacity: 3899,
max_point_rate: 100_000,
version_string: Some("r331-ed4bef5"),
always_set_playback_flags: 0x5f31,
ring_points: 3899,
naks_full: false,
begin_while_idle_acked: true,
stop_while_idle_naks: true,
stale_fullness_until_prepare: true,
estop_clear_immediate: true,
unknown_command_resets_tcp: true,
data_partial_write_then_nak_invalid: true,
data_while_idle_nak_invalid: true,
enforce_single_client: false,
rate_nak_consumes_one_byte: true,
zero_rate_hangs: true,
close_stops_playback: true,
light_engine_flags_mirror_state: true,
}
}
pub fn ed3() -> Self {
Self {
name: "ed3",
provenance: Provenance::Unverified,
notes: "Copy of ED2 r331 with a guessed 4095-point buffer. No hardware probed.",
mac_address: [0x02, 0xed, 0x00, 0x00, 0x00, 0x03],
buffer_capacity: 4095,
ring_points: 4095,
version_string: Some("sim-ed3"),
..Self::ed2_r331()
}
}
pub fn ed4() -> Self {
Self {
name: "ed4",
provenance: Provenance::Unverified,
notes: "Copy of ED2 r331 with a guessed 8191-point buffer. No hardware probed.",
mac_address: [0x02, 0xed, 0x00, 0x00, 0x00, 0x04],
buffer_capacity: 8191,
ring_points: 8191,
version_string: Some("sim-ed4"),
..Self::ed2_r331()
}
}
pub fn all() -> Vec<Self> {
vec![
Self::ed1_j4cdac(),
Self::ed2_r331(),
Self::ed3(),
Self::ed4(),
]
}
pub fn identify(version: &str) -> Option<Self> {
Self::all()
.into_iter()
.find(|p| p.version_string == Some(version))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presets_have_unique_names_macs_and_versions() {
let all = FirmwareProfile::all();
for (i, a) in all.iter().enumerate() {
for b in &all[i + 1..] {
assert_ne!(a.name, b.name);
assert_ne!(a.mac_address, b.mac_address);
assert_ne!(a.version_string, b.version_string);
}
}
}
#[test]
fn ring_holds_at_least_advertised_capacity() {
for p in FirmwareProfile::all() {
assert!(p.ring_points >= p.buffer_capacity, "{}", p.name);
}
}
#[test]
fn identify_matches_exact_build_string() {
assert_eq!(
FirmwareProfile::identify("r331-ed4bef5").map(|p| p.name),
Some("ed2-r331")
);
assert!(FirmwareProfile::identify("r331").is_none());
assert!(FirmwareProfile::identify("").is_none());
}
#[test]
fn unverified_presets_are_marked() {
assert_eq!(FirmwareProfile::ed3().provenance, Provenance::Unverified);
assert_eq!(FirmwareProfile::ed4().provenance, Provenance::Unverified);
assert_eq!(
FirmwareProfile::ed2_r331().provenance,
Provenance::HardwareVerified
);
}
}