use crate::device::{
Action, ActionKind, Device, DeviceKind, Error, Interface, Property, PropertyKind, Result,
Value, private, utils,
};
use alloc::{
boxed::Box,
string::{String, ToString},
};
use bitflags_derive::{FlagsDebug, FlagsDisplay};
use core::str;
use embedded_io_async::{Read, Write};
use strum::{Display, FromRepr};
macro_rules! compatible_software_ids {
() => {
605
};
}
pub(super) use compatible_software_ids;
const PROP_BOARD_NUMBER: Property = Property {
kind: PropertyKind::General,
id: "board_number",
name: "Board Number",
unit: None,
};
const PROP_FAULTS: Property = Property {
kind: PropertyKind::Failure,
id: "faults",
name: "Faults",
unit: None,
};
const PROP_PROGRAM_SELECTOR: Property = Property {
kind: PropertyKind::Operation,
id: "program_selector",
name: "Program Selector",
unit: None,
};
const PROP_PROGRAM_TYPE: Property = Property {
kind: PropertyKind::Operation,
id: "program_type",
name: "Program Type",
unit: None,
};
const PROP_TOP_SOLO_ENABLED: Property = Property {
kind: PropertyKind::Operation,
id: "top_solo_enabled",
name: "Top Solo Enabled",
unit: None,
};
const PROP_PROGRAM_PHASE: Property = Property {
kind: PropertyKind::Operation,
id: "program_phase",
name: "Program Phase",
unit: None,
};
const PROP_PROGRAM_STEP: Property = Property {
kind: PropertyKind::Operation,
id: "program_step",
name: "Program Step",
unit: None,
};
const PROP_ACTIVE_ACTUATORS: Property = Property {
kind: PropertyKind::Io,
id: "active_actuators",
name: "Active Actuators",
unit: None,
};
const PROP_CLOSED_SWITCHES: Property = Property {
kind: PropertyKind::Io,
id: "closed_switches",
name: "Closed Switches",
unit: None,
};
const PROP_NTC_RESISTANCE: Property = Property {
kind: PropertyKind::Io,
id: "ntc_resistance",
name: "NTC Resistance",
unit: Some("Ω"),
};
const PROP_FLOW_METER_PULSES: Property = Property {
kind: PropertyKind::Io,
id: "flow_meter_pulses",
name: "Flow Meter Pulses",
unit: None,
};
const PROP_TARGET_WATER_AMOUNT: Property = Property {
kind: PropertyKind::Io,
id: "target_water_amount",
name: "Target Water Amount",
unit: Some("ml"),
};
const ACTION_START_PROGRAM: Action = Action {
kind: ActionKind::Operation,
id: "start_program",
name: "Start Program",
params: None,
};
bitflags::bitflags! {
#[derive(FlagsDisplay, FlagsDebug, PartialEq, Eq, Copy, Clone)]
pub struct Fault: u16 {
const NtcThermistorOpen = 0x0001;
const NtcThermistorShort = 0x0002;
const ProgramSelector = 0x0004;
const Heater = 0x0008;
const Drainage = 0x0010;
const InletStart = 0x0020;
const InletEnd = 0x0040;
const PressureSwitchInlet = 0x0080;
const PressureSwitchHeating = 0x0100;
}
}
#[derive(FromRepr, Display, PartialEq, Eq, Copy, Clone, Debug)]
#[repr(u8)]
pub enum ProgramType {
None = 0x00,
UniversalPlus = 0x03,
EnergySave = 0x04,
Gentle = 0x05,
Universal = 0x06,
Economy = 0x07,
PreWash = 0x08,
Intensive = 0x0a,
Normal = 0x0b,
Test = 0x0c,
}
#[derive(FromRepr, Display, PartialEq, Eq, Copy, Clone, Debug)]
#[repr(u8)]
pub enum ProgramPhase {
Idle,
Reactivation,
PreWash1,
PreWash2,
MainWash,
InterimRinse1,
InterimRinse2,
FinalRinse,
Drying,
Finish,
}
bitflags::bitflags! {
#[derive(FlagsDisplay, FlagsDebug, PartialEq, Eq, Copy, Clone)]
pub struct Actuator: u16 {
const ReleaseElement = 0x0001;
const TopSoloCirculation = 0x0002;
const DetergentDosing = 0x0004;
const RinseAidDosing = 0x0008;
const Reactivation = 0x0010;
const Inlet = 0x0020;
const Heater = 0x0040;
const WaterHardness = 0x0080;
const DryingFan = 0x2000;
const DrainPump = 0x4000;
const CirculationPump = 0x8000;
}
}
bitflags::bitflags! {
#[derive(FlagsDisplay, FlagsDebug, PartialEq, Eq, Copy, Clone)]
pub struct Switch: u8 {
const HeaterPressure = 0x01;
const SaltReservoirEmpty = 0x02;
const RinseAidReservoirEmpty = 0x04;
}
}
#[derive(Debug)]
pub struct Dishwasher<P> {
intf: Interface<P>,
software_id: u16,
}
impl<P: Read + Write> Dishwasher<P> {
pub(crate) async fn initialize(
mut intf: Interface<P>,
software_id: u16,
) -> Result<Self, P::Error> {
intf.unlock_read_access(0x1234).await?;
intf.unlock_full_access(0x5678).await?;
intf.write_memory(0x00f4, 0x02u8).await?;
Ok(Self { intf, software_id })
}
pub async fn query_board_number(&mut self) -> Result<String, P::Error> {
let data: [u8; 8] = self.intf.read_eeprom(0x00ec).await?;
let board = str::from_utf8(&data).map_err(|_| Error::UnexpectedMemoryValue)?;
Ok(board.to_string())
}
pub async fn query_faults(&mut self) -> Result<Fault, P::Error> {
Fault::from_bits(self.intf.read_memory(0x0082).await?).ok_or(Error::UnexpectedMemoryValue)
}
pub async fn query_program_selector(&mut self) -> Result<u8, P::Error> {
Ok(self.intf.read_memory(0x00af).await?)
}
pub async fn query_program_type(&mut self) -> Result<ProgramType, P::Error> {
ProgramType::from_repr(self.intf.read_memory(0x0065).await?)
.ok_or(Error::UnexpectedMemoryValue)
}
pub async fn query_top_solo_enabled(&mut self) -> Result<bool, P::Error> {
let enabled: u8 = self.intf.read_memory(0x008e).await?;
Ok((enabled & 0x01) != 0x00)
}
pub async fn query_program_phase(&mut self) -> Result<ProgramPhase, P::Error> {
ProgramPhase::from_repr(self.intf.read_memory(0x006a).await?)
.ok_or(Error::UnexpectedMemoryValue)
}
pub async fn query_program_step(&mut self) -> Result<u8, P::Error> {
Ok(self.intf.read_memory(0x020d).await?)
}
pub async fn query_active_actuators(&mut self) -> Result<Actuator, P::Error> {
let actuators: u16 = self.intf.read_memory(0x022a).await?;
Actuator::from_bits(actuators & 0xe0ff).ok_or(Error::UnexpectedMemoryValue)
}
pub async fn query_closed_switches(&mut self) -> Result<Switch, P::Error> {
Switch::from_bits(self.intf.read_memory(0x006f).await?).ok_or(Error::UnexpectedMemoryValue)
}
pub async fn query_ntc_resistance(&mut self) -> Result<(u32, u32), P::Error> {
let current: u8 = self.intf.read_memory(0x0061).await?;
let target: u8 = match self.intf.read_memory(0x006c).await? {
0xff => 0x00, t => t,
};
Ok((
utils::ntc_resistance_from_adc(current),
utils::ntc_resistance_from_adc(target),
))
}
pub async fn query_flow_meter_pulses(&mut self) -> Result<(u16, u16), P::Error> {
let current: u16 = self.intf.read_memory(0x0088).await?;
let target: u16 = self.intf.read_memory(0x00c5).await?;
Ok((current, target))
}
pub async fn query_target_water_amount(&mut self) -> Result<u16, P::Error> {
let amount: u16 = self.intf.read_memory(0x00d6).await?;
Ok(amount * 10)
}
pub async fn start_program(&mut self) -> Result<(), P::Error> {
let state: u8 = self.intf.read_memory(0x0084).await?;
if state == 0x02 {
Ok(self.intf.write_memory(0x0084, 0x05u8).await?)
} else {
Err(Error::InvalidState)
}
}
}
#[async_trait::async_trait(?Send)]
impl<P: Read + Write> Device<P> for Dishwasher<P> {
async fn connect(port: P) -> Result<Self, P::Error> {
let mut intf = Interface::new(port);
let id = intf.query_software_id().await?;
match id {
compatible_software_ids!() => Self::initialize(intf, id).await,
_ => Err(Error::UnknownSoftwareId(id)),
}
}
fn interface(&mut self) -> &mut Interface<P> {
&mut self.intf
}
fn software_id(&self) -> u16 {
self.software_id
}
fn kind(&self) -> DeviceKind {
DeviceKind::Dishwasher
}
fn properties(&self) -> &'static [Property] {
&[
PROP_BOARD_NUMBER,
PROP_FAULTS,
PROP_PROGRAM_SELECTOR,
PROP_PROGRAM_TYPE,
PROP_TOP_SOLO_ENABLED,
PROP_PROGRAM_PHASE,
PROP_PROGRAM_STEP,
PROP_ACTIVE_ACTUATORS,
PROP_CLOSED_SWITCHES,
PROP_NTC_RESISTANCE,
PROP_FLOW_METER_PULSES,
PROP_TARGET_WATER_AMOUNT,
]
}
fn actions(&self) -> &'static [Action] {
&[ACTION_START_PROGRAM]
}
async fn query_property(&mut self, prop: &Property) -> Result<Value, P::Error> {
match *prop {
PROP_BOARD_NUMBER => Ok(self.query_board_number().await?.into()),
PROP_FAULTS => Ok(self.query_faults().await?.to_string().into()),
PROP_PROGRAM_SELECTOR => Ok(self.query_program_selector().await?.into()),
PROP_PROGRAM_TYPE => Ok(self.query_program_type().await?.to_string().into()),
PROP_TOP_SOLO_ENABLED => Ok(self.query_top_solo_enabled().await?.into()),
PROP_PROGRAM_PHASE => Ok(self.query_program_phase().await?.to_string().into()),
PROP_PROGRAM_STEP => Ok(self.query_program_step().await?.into()),
PROP_ACTIVE_ACTUATORS => Ok(self.query_active_actuators().await?.to_string().into()),
PROP_CLOSED_SWITCHES => Ok(self.query_closed_switches().await?.to_string().into()),
PROP_NTC_RESISTANCE => Ok(self.query_ntc_resistance().await?.into()),
PROP_FLOW_METER_PULSES => Ok(self.query_flow_meter_pulses().await?.into()),
PROP_TARGET_WATER_AMOUNT => Ok(self.query_target_water_amount().await?.into()),
_ => Err(Error::UnknownProperty),
}
}
async fn trigger_action(
&mut self,
action: &Action,
param: Option<Value>,
) -> Result<(), P::Error> {
match *action {
ACTION_START_PROGRAM => match param {
None => self.start_program().await,
_ => Err(Error::InvalidArgument),
},
_ => Err(Error::UnknownAction),
}
}
}
impl<P> private::Sealed for Dishwasher<P> {}