1use alloc::collections::BTreeMap;
16use alloc::string::String;
17use alloc::vec;
18use alloc::vec::Vec;
19
20use crate::codec::decode::{decode, decode_bytes, decode_raw, decode_str, Ctx};
21use crate::codec::encode::{encode, encode_str, validate_write};
22use crate::codec::mask_for;
23use crate::convert::{desc_bufs, point_desc, point_words};
24use crate::desc::{DateTimeEncoding, ValueKind};
25use crate::error::{DecodeError, Error};
26use crate::measurand::{measurand_matches, MeasurandQuery};
27use crate::schema;
28use crate::transport::Transport;
29use crate::value::{field_value, flag_names, DecodedValue, Value};
30
31const SUNS_MARKER: [u16; 2] = [0x5375, 0x6e53];
33
34pub struct Device<'d, T: Transport> {
37 profile: &'d schema::DeviceProfile,
38 transport: T,
39 model_base: BTreeMap<&'d str, u16>,
41}
42
43impl<'d, T: Transport> Device<'d, T> {
44 pub fn new(
46 doc: &'d schema::ModDefDocument,
47 device_id: Option<&str>,
48 transport: T,
49 ) -> Result<Self, Error<T::Error>> {
50 let profile = doc
51 .devices
52 .iter()
53 .find(|d| device_id.is_none_or(|id| d.device_id == id))
54 .ok_or(Error::DeviceNotFound)?;
55 Ok(Device::from_profile(profile, transport))
56 }
57
58 pub fn from_profile(profile: &'d schema::DeviceProfile, transport: T) -> Self {
59 Device {
60 profile,
61 transport,
62 model_base: BTreeMap::new(),
63 }
64 }
65
66 pub fn profile(&self) -> &'d schema::DeviceProfile {
67 self.profile
68 }
69
70 pub fn transport_mut(&mut self) -> &mut T {
71 &mut self.transport
72 }
73
74 pub fn into_transport(self) -> T {
75 self.transport
76 }
77
78 pub fn points(&self) -> impl Iterator<Item = &'d schema::Point> {
80 self.profile.blocks.iter().flat_map(|b| b.points.iter())
81 }
82
83 pub fn point(
85 &self,
86 id: &str,
87 ) -> Result<(&'d schema::Point, &'d schema::RegisterBlock), Error<T::Error>> {
88 for b in &self.profile.blocks {
89 if let Some(p) = b.points.iter().find(|p| p.point_id == id) {
90 return Ok((p, b));
91 }
92 }
93 Err(Error::PointNotFound)
94 }
95
96 pub async fn read_point(&mut self, id: &str) -> Result<DecodedValue, Error<T::Error>> {
98 let (p, b) = self.point(id)?;
99 let regs = self.read_registers(p, b).await?;
100 let refs = self.ref_context(p).await?;
101 decode_owned(p, b.space(), ®s, &Ctx { refs: &refs }).map_err(Error::Decode)
102 }
103
104 pub async fn read_measurand(
106 &mut self,
107 q: &MeasurandQuery<'_>,
108 ) -> Result<DecodedValue, Error<T::Error>> {
109 let mut found: Option<&'d schema::Point> = None;
110 for p in self.points() {
111 if measurand_matches(p.measurand.as_ref(), q) {
112 if found.is_some() {
113 return Err(Error::AmbiguousMeasurand);
114 }
115 found = Some(p);
116 }
117 }
118 let p = found.ok_or(Error::MeasurandNotSupported)?;
119 self.read_point(&p.point_id).await
120 }
121
122 pub async fn write_point(&mut self, id: &str, v: Value) -> Result<(), Error<T::Error>> {
124 let (p, b) = self.point(id)?;
125 let bufs = desc_bufs(p);
126 let d = point_desc(p, b.space(), &bufs);
127 if !d.writable() {
128 return Err(Error::WriteAccess);
129 }
130 validate_write(&d, &v).map_err(Error::WriteConstraint)?;
131
132 let space = effective_space(p, b);
133 let off = self.offset_of(p, b).await?;
134
135 if space == schema::AddressSpace::Coil {
136 let on = v.as_f64().map(|f| f != 0.0).unwrap_or(false);
137 return self
138 .transport
139 .write_coil(off, on)
140 .await
141 .map_err(Error::Transport);
142 }
143 if space != schema::AddressSpace::HoldingRegister {
144 return Err(Error::UnsupportedMapping("cannot write this address space"));
145 }
146 let refs = self.ref_context(p).await?;
147 let mut regs = vec![0u16; d.words()];
148 encode(&d, &v, &Ctx { refs: &refs }, &mut regs)?;
149 self.transport
150 .write_holding(off, ®s)
151 .await
152 .map_err(Error::Transport)
153 }
154
155 pub async fn write_point_str(&mut self, id: &str, s: &str) -> Result<(), Error<T::Error>> {
157 let (p, b) = self.point(id)?;
158 let bufs = desc_bufs(p);
159 let d = point_desc(p, b.space(), &bufs);
160 if !d.writable() {
161 return Err(Error::WriteAccess);
162 }
163 if effective_space(p, b) != schema::AddressSpace::HoldingRegister {
164 return Err(Error::UnsupportedMapping("cannot write this address space"));
165 }
166 let off = self.offset_of(p, b).await?;
167 let mut regs = vec![0u16; d.words()];
168 encode_str(&d, s, &mut regs)?;
169 self.transport
170 .write_holding(off, ®s)
171 .await
172 .map_err(Error::Transport)
173 }
174
175 async fn ref_context(
180 &mut self,
181 p: &'d schema::Point,
182 ) -> Result<Vec<(&'d str, i64)>, Error<T::Error>> {
183 let mut ids: Vec<&'d str> = Vec::new();
184 if let Some(sr) = p.transform.as_ref().and_then(|t| t.scale_ref.as_ref()) {
185 ids.push(&sr.point_id);
186 }
187 if let Some(sel) = p.selector_ref.as_ref() {
188 ids.push(&sel.point_id);
189 }
190 let mut refs = Vec::with_capacity(ids.len());
191 for id in ids {
192 let (rp, rb) = self.point(id)?;
193 let regs = self.read_registers(rp, rb).await?;
194 let bufs = desc_bufs(rp);
195 let d = point_desc(rp, rb.space(), &bufs);
196 let v = decode(&d, ®s, &Ctx::EMPTY)?;
197 if let Some(iv) = v.as_i64() {
198 refs.push((id, iv));
199 }
200 }
201 Ok(refs)
202 }
203
204 async fn read_registers(
205 &mut self,
206 p: &'d schema::Point,
207 b: &'d schema::RegisterBlock,
208 ) -> Result<Vec<u16>, Error<T::Error>> {
209 if p.storage_type() == schema::StorageType::Composed {
210 return Err(Error::UnsupportedMapping(
211 "composed points are not read via the facade",
212 ));
213 }
214 let space = effective_space(p, b);
215 let n = point_words(p);
216 let off = self.offset_of(p, b).await?;
217 self.read_space(space, off, n).await
218 }
219
220 async fn read_space(
221 &mut self,
222 space: schema::AddressSpace,
223 off: u16,
224 n: usize,
225 ) -> Result<Vec<u16>, Error<T::Error>> {
226 match space {
227 schema::AddressSpace::HoldingRegister => {
228 let mut regs = vec![0u16; n];
229 self.transport
230 .read_holding(off, &mut regs)
231 .await
232 .map_err(Error::Transport)?;
233 Ok(regs)
234 }
235 schema::AddressSpace::InputRegister => {
236 let mut regs = vec![0u16; n];
237 self.transport
238 .read_input(off, &mut regs)
239 .await
240 .map_err(Error::Transport)?;
241 Ok(regs)
242 }
243 schema::AddressSpace::Coil => {
244 let mut bits = [false];
245 self.transport
246 .read_coils(off, &mut bits)
247 .await
248 .map_err(Error::Transport)?;
249 Ok(vec![bits[0] as u16])
250 }
251 schema::AddressSpace::DiscreteInput => {
252 let mut bits = [false];
253 self.transport
254 .read_discrete(off, &mut bits)
255 .await
256 .map_err(Error::Transport)?;
257 Ok(vec![bits[0] as u16])
258 }
259 schema::AddressSpace::Unspecified => {
260 Err(Error::UnsupportedMapping("unspecified address space"))
261 }
262 }
263 }
264
265 async fn offset_of(
266 &mut self,
267 p: &schema::Point,
268 b: &'d schema::RegisterBlock,
269 ) -> Result<u16, Error<T::Error>> {
270 let m = p
271 .mapping
272 .as_ref()
273 .ok_or(Error::UnsupportedMapping("point has no mapping"))?;
274 if b.discovery.is_some() {
275 let base = self.resolve_model_base(b).await?;
276 Ok(base + m.model_relative_offset as u16)
277 } else {
278 Ok(m.offset as u16)
279 }
280 }
281
282 async fn resolve_model_base(
285 &mut self,
286 b: &'d schema::RegisterBlock,
287 ) -> Result<u16, Error<T::Error>> {
288 if let Some(base) = self.model_base.get(b.block_id.as_str()) {
289 return Ok(*base);
290 }
291 let disc = b
292 .discovery
293 .as_ref()
294 .ok_or(Error::UnsupportedMapping("block has no discovery"))?;
295 if disc.kind() != schema::DiscoveryKind::Sunspec {
296 return Err(Error::UnsupportedMapping("unsupported discovery kind"));
297 }
298 let space = b.space();
299 let defaults = [40000u32, 50000, 0];
300 let candidates: &[u32] = if disc.anchor_candidates.is_empty() {
301 &defaults
302 } else {
303 &disc.anchor_candidates
304 };
305
306 let mut anchor = None;
307 for &c in candidates {
308 if let Ok(hdr) = self.read_space(space, c as u16, 2).await {
310 if hdr[..2] == SUNS_MARKER {
311 anchor = Some(c as u16);
312 break;
313 }
314 }
315 }
316 let Some(anchor) = anchor else {
317 return Err(Error::UnsupportedMapping("SunS marker not found"));
318 };
319
320 let mut off = anchor + 2;
322 for _ in 0..256 {
323 let hdr = self.read_space(space, off, 2).await?;
324 let (id, length) = (hdr[0], hdr[1]);
325 if id == 0xffff {
326 break;
327 }
328 if id as u32 == disc.model_id {
329 self.model_base.insert(&b.block_id, off);
331 return Ok(off);
332 }
333 off = off
334 .checked_add(2 + length)
335 .ok_or(Error::UnsupportedMapping("SunSpec model chain overflows"))?;
336 }
337 Err(Error::UnsupportedMapping("SunSpec model not found"))
338 }
339}
340
341fn effective_space(p: &schema::Point, b: &schema::RegisterBlock) -> schema::AddressSpace {
343 match p.mapping.as_ref().map(|m| m.space()) {
344 Some(s) if s != schema::AddressSpace::Unspecified => s,
345 _ => b.space(),
346 }
347}
348
349fn decode_owned(
352 p: &schema::Point,
353 block_space: schema::AddressSpace,
354 regs: &[u16],
355 ctx: &Ctx<'_>,
356) -> Result<DecodedValue, DecodeError> {
357 let bufs = desc_bufs(p);
358 let d = point_desc(p, block_space, &bufs);
359
360 match d.value {
361 ValueKind::Str { .. } => {
362 let mut buf = vec![0u8; regs.len() * 2];
363 let s = decode_str(&d, regs, &mut buf)?;
364 Ok(DecodedValue::Str(String::from(s)))
365 }
366 ValueKind::Bytes => {
367 let mut buf = vec![0u8; regs.len() * 2];
368 let bytes = decode_bytes(&d, regs, &mut buf)?;
369 Ok(DecodedValue::Bytes(bytes.to_vec()))
370 }
371 _ => Ok(match decode(&d, regs, ctx)? {
372 Value::Bool(v) => DecodedValue::Bool(v),
373 Value::U64(v) => DecodedValue::U64(v),
374 Value::I64(v) => DecodedValue::I64(v),
375 Value::F64(v) => DecodedValue::F64(v),
376 Value::Flags(mask) => {
377 DecodedValue::Flags(flag_names(&d, mask).map(String::from).collect())
378 }
379 Value::Fields(window) => {
380 let fields = match d.value {
381 ValueKind::Fields(fs) => fs,
382 _ => &[],
383 };
384 DecodedValue::Fields(
385 fields
386 .iter()
387 .map(|f| (String::from(f.id), field_value(f, window)))
388 .collect(),
389 )
390 }
391 Value::DateTime(t) => DecodedValue::DateTime(match d.value {
392 ValueKind::DateTime(DateTimeEncoding::EpochMillis) => t,
393 _ => t.saturating_mul(1000),
394 }),
395 Value::Unavailable => {
396 let (raw, bits) = decode_raw(&d, regs)?;
398 let meaning =
399 d.na.iter()
400 .find(|na| (na.raw as u64) & mask_for(bits) == raw)
401 .map(|na| na.meaning)
402 .unwrap_or("");
403 DecodedValue::Unavailable(String::from(meaning))
404 }
405 }),
406 }
407}