fennec_modbus/contrib/mq2200/
schedule.rs1use core::fmt::{Display, Formatter};
2
3use bytes::{Buf, BufMut};
4
5use crate::{
6 Error,
7 contrib::{Percentage, Watts},
8 protocol::{
9 Address,
10 address,
11 codec::{BitSize, Decode, Encode},
12 },
13};
14
15pub type BlockStride = address::Stride<48010, Block>;
19
20pub const N_ENTRIES_PER_BLOCK: usize = 12;
24
25pub const N_BLOCKS: usize = 8;
27
28pub type Full = [Entry; Entry::N_TOTAL];
33
34pub type Block = [Entry; N_ENTRIES_PER_BLOCK];
36
37#[must_use]
41#[derive(Copy, Clone)]
42pub struct BlockIndex(pub u16);
43
44impl BlockIndex {
45 #[expect(clippy::cast_possible_truncation)]
46 pub const MAX: u16 = (N_BLOCKS - 1) as u16;
47}
48
49impl Address for BlockIndex {}
50
51impl Encode for BlockIndex {
52 fn encode(&self, to: &mut impl BufMut) {
53 BlockStride::from(self.0).encode(to);
54 }
55}
56
57#[derive(Copy, Clone, Debug, Eq, PartialEq)]
58#[repr(u16)]
59#[must_use]
60pub enum WorkingMode {
61 SelfUse = 1_u16,
62 FeedInPriority = 2_u16,
63 BackUp = 3_u16,
64 PeakShaving = 4_u16,
65 ForceCharge = 6_u16,
66 ForceDischarge = 7_u16,
67 Unknown(u16),
68}
69
70impl Encode for WorkingMode {
71 fn encode(&self, to: &mut impl BufMut) {
72 to.put_u16(match self {
73 Self::SelfUse => 1,
74 Self::FeedInPriority => 2,
75 Self::BackUp => 3,
76 Self::PeakShaving => 4,
77 Self::ForceCharge => 6,
78 Self::ForceDischarge => 7,
79 Self::Unknown(working_mode) => *working_mode,
80 });
81 }
82}
83
84impl Decode for WorkingMode {
85 fn decode(from: &mut impl Buf) -> Result<Self, Error> {
86 Ok(match from.try_get_u16()? {
87 1 => Self::SelfUse,
88 2 => Self::FeedInPriority,
89 3 => Self::BackUp,
90 4 => Self::PeakShaving,
91 6 => Self::ForceCharge,
92 7 => Self::ForceDischarge,
93 working_mode => Self::Unknown(working_mode),
94 })
95 }
96}
97
98#[derive(Copy, Clone, Debug, Eq, PartialEq)]
100#[must_use]
101pub struct NaiveTime {
102 pub hour: u8,
103 pub minute: u8,
104}
105
106impl Display for NaiveTime {
107 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
108 write!(f, "{:02}:{:02}", self.hour, self.minute)
109 }
110}
111
112impl NaiveTime {
113 pub const MIN: Self = Self { hour: 0, minute: 0 };
115
116 pub const MAX: Self = Self { hour: 23, minute: 59 };
120}
121
122impl Encode for NaiveTime {
123 fn encode(&self, to: &mut impl BufMut) {
124 to.put_u8(self.hour);
125 to.put_u8(self.minute);
126 }
127}
128
129impl Decode for NaiveTime {
130 fn decode(from: &mut impl Buf) -> Result<Self, Error> {
131 Ok(Self { hour: from.try_get_u8()?, minute: from.try_get_u8()? })
132 }
133}
134
135#[derive(Copy, Clone, Debug, Eq, PartialEq)]
137#[must_use]
138pub struct Entry {
139 pub is_enabled: bool,
140
141 pub start_time: NaiveTime,
143
144 pub end_time: NaiveTime,
149
150 pub working_mode: WorkingMode,
151 pub maximum_state_of_charge: Percentage<u8>,
152 pub minimum_state_of_charge: Percentage<u8>,
153
154 #[allow(clippy::doc_markdown)]
157 pub target_state_of_charge: Percentage<u16>,
158
159 pub power: Watts<u16>,
160
161 pub reserved_1: u16,
163
164 pub reserved_2: u16,
166
167 pub reserved_3: u16,
169}
170
171impl Entry {
172 pub const N_TOTAL: usize = N_BLOCKS * N_ENTRIES_PER_BLOCK;
174}
175
176impl BitSize for Entry {
177 const N_BITS: u16 = 20 * 8;
178}
179
180impl Encode for Entry {
181 fn encode(&self, to: &mut impl BufMut) {
182 to.put_u16(u16::from(self.is_enabled));
183 self.start_time.encode(to);
184 self.end_time.encode(to);
185 self.working_mode.encode(to);
186 to.put_u8(self.maximum_state_of_charge.0);
187 to.put_u8(self.minimum_state_of_charge.0);
188 self.target_state_of_charge.encode(to);
189 self.power.encode(to);
190 self.reserved_1.encode(to);
191 self.reserved_2.encode(to);
192 self.reserved_3.encode(to);
193 }
194}
195
196impl Decode for Entry {
197 fn decode(from: &mut impl Buf) -> Result<Self, Error> {
198 Ok(Self {
199 is_enabled: from.try_get_u16()? != 0,
200 start_time: NaiveTime::decode(from)?,
201 end_time: NaiveTime::decode(from)?,
202 working_mode: WorkingMode::decode(from)?,
203 maximum_state_of_charge: Percentage(from.try_get_u8()?),
204 minimum_state_of_charge: Percentage(from.try_get_u8()?),
205 target_state_of_charge: Percentage::decode(from)?,
206 power: Watts::decode(from)?,
207 reserved_1: u16::decode(from)?,
208 reserved_2: u16::decode(from)?,
209 reserved_3: u16::decode(from)?,
210 })
211 }
212}