use core::{
fmt::{Display, Formatter},
ops::RangeInclusive,
};
use bytes::{Buf, BufMut};
use crate::{
Error,
contrib::types::{Percentage, Watts},
protocol::{
Address,
address,
codec::{BitSize, Decode, Encode},
function::{ReadHoldingRegisters, ReadWriteRegisters, WriteMultipleRegisters},
},
};
pub const N_SLOTS_PER_BLOCK: u16 = 12;
pub const N_BLOCKS: u16 = 8;
pub type Full = [Slot; Slot::N_TOTAL as usize];
pub type Block = [Slot; N_SLOTS_PER_BLOCK as usize];
pub const START_ADDRESS: u16 = 48010;
pub type BlockStride = address::Stride<START_ADDRESS, N_BLOCKS, Block>;
#[must_use]
#[derive(Copy, Clone)]
pub struct BlockIndex(pub u16);
impl BlockIndex {
pub const LAST: u16 = (N_BLOCKS - 1);
}
impl Address for BlockIndex {}
impl Encode for BlockIndex {
fn encode_to(&self, buf: &mut impl BufMut) {
BlockStride::new(self.0).encode_to(buf);
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u16)]
#[must_use]
pub enum WorkingMode {
SelfUse = 1_u16,
FeedInPriority = 2_u16,
BackUp = 3_u16,
PeakShaving = 4_u16,
ForceCharge = 6_u16,
ForceDischarge = 7_u16,
Unknown(u16),
}
impl BitSize for WorkingMode {
const N_BITS: u16 = u16::N_BITS;
const N_BYTES: u8 = u16::N_BYTES;
const N_WORDS: u16 = u16::N_WORDS;
}
impl Encode for WorkingMode {
fn encode_to(&self, buf: &mut impl BufMut) {
buf.put_u16(match self {
Self::SelfUse => 1,
Self::FeedInPriority => 2,
Self::BackUp => 3,
Self::PeakShaving => 4,
Self::ForceCharge => 6,
Self::ForceDischarge => 7,
Self::Unknown(working_mode) => *working_mode,
});
}
}
impl Decode for WorkingMode {
fn decode_from(buf: &mut impl Buf) -> Result<Self, Error> {
Ok(match buf.try_get_u16()? {
1 => Self::SelfUse,
2 => Self::FeedInPriority,
3 => Self::BackUp,
4 => Self::PeakShaving,
6 => Self::ForceCharge,
7 => Self::ForceDischarge,
working_mode => Self::Unknown(working_mode),
})
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct NaiveTime {
pub hour: u8,
pub minute: u8,
}
impl Display for NaiveTime {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
impl NaiveTime {
pub const MIN: Self = Self { hour: 0, minute: 0 };
pub const MAX: Self = Self { hour: 23, minute: 59 };
}
impl BitSize for NaiveTime {
const N_BITS: u16 = u8::N_BITS * 2;
const N_BYTES: u8 = u8::N_BYTES * 2;
const N_WORDS: u16 = 1;
}
impl Encode for NaiveTime {
fn encode_to(&self, buf: &mut impl BufMut) {
buf.put_u8(self.hour);
buf.put_u8(self.minute);
}
}
impl Decode for NaiveTime {
fn decode_from(buf: &mut impl Buf) -> Result<Self, Error> {
Ok(Self { hour: buf.try_get_u8()?, minute: buf.try_get_u8()? })
}
}
#[must_use]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct StateOfChargeRange {
pub min: Percentage<u8>,
pub max: Percentage<u8>,
}
impl From<RangeInclusive<Percentage<u8>>> for StateOfChargeRange {
fn from(value: RangeInclusive<Percentage<u8>>) -> Self {
Self { min: *value.start(), max: *value.end() }
}
}
impl BitSize for StateOfChargeRange {
const N_BITS: u16 = u8::N_BITS * 2;
const N_BYTES: u8 = u8::N_BYTES * 2;
const N_WORDS: u16 = 1;
}
impl Encode for StateOfChargeRange {
fn encode_to(&self, buf: &mut impl BufMut) {
buf.put_u8(self.max.0);
buf.put_u8(self.min.0);
}
}
impl Decode for StateOfChargeRange {
fn decode_from(buf: &mut impl Buf) -> Result<Self, Error> {
Ok(Self { max: Percentage(buf.try_get_u8()?), min: Percentage(buf.try_get_u8()?) })
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct Slot {
pub is_enabled: bool,
pub start_time: NaiveTime,
pub end_time: NaiveTime,
pub working_mode: WorkingMode,
pub state_of_charge_range: StateOfChargeRange,
#[allow(clippy::doc_markdown)]
pub target_state_of_charge: Percentage<u16>,
pub power: Watts<u16>,
pub reserved_1: u16,
pub reserved_2: u16,
pub reserved_3: u16,
}
impl Slot {
pub const N_TOTAL: u16 = N_BLOCKS * N_SLOTS_PER_BLOCK;
}
impl BitSize for Slot {
const N_BITS: u16 = 20 * 8;
}
impl Encode for Slot {
fn encode_to(&self, buf: &mut impl BufMut) {
buf.put_u16(u16::from(self.is_enabled));
self.start_time.encode_to(buf);
self.end_time.encode_to(buf);
self.working_mode.encode_to(buf);
self.state_of_charge_range.encode_to(buf);
self.target_state_of_charge.encode_to(buf);
self.power.encode_to(buf);
self.reserved_1.encode_to(buf);
self.reserved_2.encode_to(buf);
self.reserved_3.encode_to(buf);
}
}
impl Decode for Slot {
fn decode_from(buf: &mut impl Buf) -> Result<Self, Error> {
Ok(Self {
is_enabled: buf.try_get_u16()? != 0,
start_time: NaiveTime::decode_from(buf)?,
end_time: NaiveTime::decode_from(buf)?,
working_mode: WorkingMode::decode_from(buf)?,
state_of_charge_range: StateOfChargeRange::decode_from(buf)?,
target_state_of_charge: Percentage::decode_from(buf)?,
power: Watts::decode_from(buf)?,
reserved_1: u16::decode_from(buf)?,
reserved_2: u16::decode_from(buf)?,
reserved_3: u16::decode_from(buf)?,
})
}
}
pub type ReadSlot =
ReadHoldingRegisters<address::Stride<START_ADDRESS, { Slot::N_TOTAL }, Slot>, Slot>;
pub type ReadBlock = ReadHoldingRegisters<BlockIndex, Block>;
pub type WriteSlot =
WriteMultipleRegisters<address::Stride<START_ADDRESS, { Slot::N_TOTAL }, Slot>, Slot>;
pub type WriteBlock = WriteMultipleRegisters<BlockIndex, Block>;
pub type ReadWriteBlock = ReadWriteRegisters<BlockIndex, Block, BlockIndex, Block>;