use std::sync::Mutex;
use crate::switch::Switch;
use crate::types::{AlpacaError, AlpacaResult, DeviceType};
struct SwitchChannel {
name: &'static str,
description: &'static str,
min: f64,
max: f64,
step: f64,
can_write: bool,
}
const CHANNELS: &[SwitchChannel] = &[
SwitchChannel {
name: "Power",
description: "Boolean on/off switch",
min: 0.0,
max: 1.0,
step: 1.0,
can_write: true,
},
SwitchChannel {
name: "Selector",
description: "Multi-state rotary selector (4 positions)",
min: 0.0,
max: 3.0,
step: 1.0,
can_write: true,
},
SwitchChannel {
name: "Dimmer",
description: "Analog dimmer (0-100%, 0.5 step)",
min: 0.0,
max: 100.0,
step: 0.5,
can_write: true,
},
];
pub struct MockSwitch {
connected: Mutex<bool>,
values: Mutex<Vec<f64>>,
names: Mutex<Vec<String>>,
}
impl Default for MockSwitch {
fn default() -> Self {
Self::new()
}
}
impl MockSwitch {
pub fn new() -> Self {
let values = CHANNELS.iter().map(|ch| ch.min).collect();
let names = CHANNELS.iter().map(|ch| ch.name.to_string()).collect();
Self {
connected: Mutex::new(false),
values: Mutex::new(values),
names: Mutex::new(names),
}
}
fn validate_id(id: u32) -> AlpacaResult<&'static SwitchChannel> {
CHANNELS.get(id as usize).ok_or_else(|| {
AlpacaError::InvalidValue(format!(
"Switch ID {id} out of range 0-{}",
CHANNELS.len() - 1
))
})
}
fn clamp_to_step(ch: &SwitchChannel, value: f64) -> f64 {
let clamped = value.clamp(ch.min, ch.max);
if ch.step > 0.0 {
let steps = ((clamped - ch.min) / ch.step).round();
(ch.min + steps * ch.step).min(ch.max)
} else {
clamped
}
}
}
impl_mock_device!(MockSwitch,
name: "Mock Switch",
unique_id: "mock-sw-001",
device_type: DeviceType::Switch,
interface_version: 3,
device_state: |self_: &MockSwitch| {
use crate::device::common::DeviceStateBuilder;
let values = self_.values.lock().unwrap();
let mut b = DeviceStateBuilder::new();
for (i, ch) in CHANNELS.iter().enumerate() {
let val = values[i];
let bool_val = val >= (ch.min + ch.max) / 2.0;
b = b
.add(&format!("GetSwitch{i}"), bool_val)
.add(&format!("GetSwitchValue{i}"), val)
.add(&format!("StateChangeComplete{i}"), true);
}
Ok(b.build())
}
);
impl Switch for MockSwitch {
fn max_switch(&self) -> AlpacaResult<i32> {
Ok(CHANNELS.len() as i32)
}
fn can_write(&self, id: u32) -> AlpacaResult<bool> {
let ch = Self::validate_id(id)?;
Ok(ch.can_write)
}
fn get_switch(&self, id: u32) -> AlpacaResult<bool> {
Self::validate_id(id)?;
let values = self.values.lock().unwrap();
let val = values[id as usize];
let ch = &CHANNELS[id as usize];
Ok(val >= (ch.min + ch.max) / 2.0)
}
fn set_switch(&self, id: u32, state: bool) -> AlpacaResult<()> {
let ch = Self::validate_id(id)?;
if !ch.can_write {
return Err(AlpacaError::NotImplemented("Switch is read-only".into()));
}
let mut values = self.values.lock().unwrap();
values[id as usize] = if state { ch.max } else { ch.min };
Ok(())
}
fn get_switch_value(&self, id: u32) -> AlpacaResult<f64> {
Self::validate_id(id)?;
let values = self.values.lock().unwrap();
Ok(values[id as usize])
}
fn set_switch_value(&self, id: u32, value: f64) -> AlpacaResult<()> {
let ch = Self::validate_id(id)?;
if !ch.can_write {
return Err(AlpacaError::NotImplemented("Switch is read-only".into()));
}
if value < ch.min || value > ch.max {
return Err(AlpacaError::InvalidValue(format!(
"Value {value} out of range {}-{}",
ch.min, ch.max
)));
}
let mut values = self.values.lock().unwrap();
values[id as usize] = Self::clamp_to_step(ch, value);
Ok(())
}
fn get_switch_name(&self, id: u32) -> AlpacaResult<String> {
Self::validate_id(id)?;
let names = self.names.lock().unwrap();
Ok(names[id as usize].clone())
}
fn set_switch_name(&self, id: u32, name: &str) -> AlpacaResult<()> {
Self::validate_id(id)?;
let mut names = self.names.lock().unwrap();
names[id as usize] = name.to_string();
Ok(())
}
fn get_switch_description(&self, id: u32) -> AlpacaResult<String> {
let ch = Self::validate_id(id)?;
Ok(ch.description.to_string())
}
fn min_switch_value(&self, id: u32) -> AlpacaResult<f64> {
let ch = Self::validate_id(id)?;
Ok(ch.min)
}
fn max_switch_value(&self, id: u32) -> AlpacaResult<f64> {
let ch = Self::validate_id(id)?;
Ok(ch.max)
}
fn switch_step(&self, id: u32) -> AlpacaResult<f64> {
let ch = Self::validate_id(id)?;
Ok(ch.step)
}
fn can_async(&self, id: u32) -> AlpacaResult<bool> {
Self::validate_id(id)?;
Ok(false)
}
fn set_async(&self, id: u32, _state: bool) -> AlpacaResult<()> {
Self::validate_id(id)?;
Err(AlpacaError::NotImplemented("Async not supported".into()))
}
fn set_async_value(&self, id: u32, _value: f64) -> AlpacaResult<()> {
Self::validate_id(id)?;
Err(AlpacaError::NotImplemented("Async not supported".into()))
}
fn cancel_async(&self, id: u32) -> AlpacaResult<()> {
Self::validate_id(id)?;
Err(AlpacaError::NotImplemented("Async not supported".into()))
}
fn state_change_complete(&self, id: u32) -> AlpacaResult<bool> {
Self::validate_id(id)?;
Err(AlpacaError::NotImplemented("Async not supported".into()))
}
}