1use crate::address::{SlmpAddress, parse_named_address};
2use crate::client::SlmpClient;
3use crate::error::SlmpError;
4use crate::model::{SlmpDeviceAddress, SlmpDeviceCode, SlmpLongTimerResult};
5use async_stream::try_stream;
6use futures_core::stream::Stream;
7use std::collections::{BTreeMap, HashMap};
8use std::time::Duration;
9
10#[derive(Debug, Clone, PartialEq, serde::Serialize)]
11#[serde(untagged)]
12pub enum SlmpValue {
13 Bool(bool),
14 U16(u16),
15 I16(i16),
16 U32(u32),
17 I32(i32),
18 F32(f32),
19}
20
21impl SlmpValue {
22 pub fn as_bool(&self) -> Result<bool, SlmpError> {
23 match self {
24 Self::Bool(value) => Ok(*value),
25 _ => Err(SlmpError::new("Expected bool value.")),
26 }
27 }
28}
29
30pub type NamedAddress = BTreeMap<String, SlmpValue>;
31
32#[derive(Debug, Clone)]
33struct LongTimerReadSpec {
34 base_code: SlmpDeviceCode,
35 kind: LongTimerReadKind,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39enum LongTimerReadKind {
40 Current,
41 Contact,
42 Coil,
43}
44
45#[derive(Debug, Clone)]
46struct NamedReadEntry {
47 address: String,
48 device: SlmpDeviceAddress,
49 dtype: String,
50 bit_index: Option<u8>,
51 long_timer_read: Option<LongTimerReadSpec>,
52}
53
54#[derive(Debug, Clone)]
55struct NamedReadPlan {
56 entries: Vec<NamedReadEntry>,
57 word_devices: Vec<SlmpDeviceAddress>,
58 dword_devices: Vec<SlmpDeviceAddress>,
59}
60
61pub async fn read_typed(
62 client: &SlmpClient,
63 device: SlmpDeviceAddress,
64 dtype: &str,
65) -> Result<SlmpValue, SlmpError> {
66 let normalized_dtype = dtype.to_uppercase();
67 if let Some(spec) = long_timer_read_spec(device.code) {
68 let timer = read_long_like_point(client, spec.base_code, device.number).await?;
69 return decode_long_like_value(&normalized_dtype, &spec, &timer);
70 }
71
72 match normalized_dtype.as_str() {
73 "BIT" => Ok(SlmpValue::Bool(client.read_bits(device, 1).await?[0])),
74 "F" => Ok(SlmpValue::F32(f32::from_bits(
75 client.read_dwords_raw(device, 1).await?[0],
76 ))),
77 "D" => Ok(SlmpValue::U32(client.read_dwords_raw(device, 1).await?[0])),
78 "L" => Ok(SlmpValue::I32(
79 client.read_dwords_raw(device, 1).await?[0] as i32,
80 )),
81 "S" => Ok(SlmpValue::I16(
82 client.read_words_raw(device, 1).await?[0] as i16,
83 )),
84 _ => Ok(SlmpValue::U16(client.read_words_raw(device, 1).await?[0])),
85 }
86}
87
88pub async fn write_typed(
89 client: &SlmpClient,
90 device: SlmpDeviceAddress,
91 dtype: &str,
92 value: &SlmpValue,
93) -> Result<(), SlmpError> {
94 match resolve_write_route(device, dtype) {
95 NamedWriteRoute::RandomBits => {
96 client
97 .write_random_bits(&[(device, scalar_to_bool(value)?)])
98 .await
99 }
100 NamedWriteRoute::ContiguousBits => {
101 client.write_bits(device, &[scalar_to_bool(value)?]).await
102 }
103 NamedWriteRoute::RandomDWords | NamedWriteRoute::ContiguousDWords => {
104 let raw = match dtype.to_uppercase().as_str() {
105 "F" => scalar_to_f32(value)?.to_bits(),
106 "L" => scalar_to_i32(value)? as u32,
107 _ => scalar_to_u32(value)?,
108 };
109 if matches!(
110 resolve_write_route(device, dtype),
111 NamedWriteRoute::RandomDWords
112 ) {
113 client.write_random_words(&[], &[(device, raw)]).await
114 } else {
115 client.write_dwords(device, &[raw]).await
116 }
117 }
118 NamedWriteRoute::ContiguousWords => {
119 client.write_words(device, &[scalar_to_u16(value)?]).await
120 }
121 }
122}
123
124pub async fn write_bit_in_word(
125 client: &SlmpClient,
126 device: SlmpDeviceAddress,
127 bit_index: u8,
128 value: bool,
129) -> Result<(), SlmpError> {
130 if bit_index > 15 {
131 return Err(SlmpError::new("bit_index must be 0-15."));
132 }
133 let mut current = client.read_words_raw(device, 1).await?[0];
134 if value {
135 current |= 1 << bit_index;
136 } else {
137 current &= !(1 << bit_index);
138 }
139 client.write_words(device, &[current]).await
140}
141
142pub async fn read_words_single_request(
143 client: &SlmpClient,
144 start: SlmpDeviceAddress,
145 count: usize,
146) -> Result<Vec<u16>, SlmpError> {
147 validate_single_request_count(count, 960)?;
148 client.read_words_raw(start, count as u16).await
149}
150
151pub async fn read_dwords_single_request(
152 client: &SlmpClient,
153 start: SlmpDeviceAddress,
154 count: usize,
155) -> Result<Vec<u32>, SlmpError> {
156 validate_single_request_count(count, 480)?;
157 client.read_dwords_raw(start, count as u16).await
158}
159
160pub async fn write_words_single_request(
161 client: &SlmpClient,
162 start: SlmpDeviceAddress,
163 values: &[u16],
164) -> Result<(), SlmpError> {
165 validate_single_request_values(values.len(), 960)?;
166 client.write_words(start, values).await
167}
168
169pub async fn write_dwords_single_request(
170 client: &SlmpClient,
171 start: SlmpDeviceAddress,
172 values: &[u32],
173) -> Result<(), SlmpError> {
174 validate_single_request_values(values.len(), 480)?;
175 client.write_dwords(start, values).await
176}
177
178pub async fn read_words_chunked(
179 client: &SlmpClient,
180 start: SlmpDeviceAddress,
181 count: usize,
182 max_words_per_request: usize,
183) -> Result<Vec<u16>, SlmpError> {
184 let chunk = (max_words_per_request / 2) * 2;
185 if chunk == 0 {
186 return Err(SlmpError::new("max_words_per_request must be at least 2."));
187 }
188 let mut remaining = count;
189 let mut offset = 0u32;
190 let mut result = Vec::with_capacity(count);
191 while remaining > 0 {
192 let next = remaining.min(chunk);
193 result.extend(
194 client
195 .read_words_raw(
196 SlmpDeviceAddress::new(start.code, start.number + offset),
197 next as u16,
198 )
199 .await?,
200 );
201 remaining -= next;
202 offset += next as u32;
203 }
204 Ok(result)
205}
206
207pub async fn read_dwords_chunked(
208 client: &SlmpClient,
209 start: SlmpDeviceAddress,
210 count: usize,
211 max_dwords_per_request: usize,
212) -> Result<Vec<u32>, SlmpError> {
213 let mut remaining = count;
214 let mut offset = 0u32;
215 let mut result = Vec::with_capacity(count);
216 while remaining > 0 {
217 let next = remaining.min(max_dwords_per_request);
218 result.extend(
219 client
220 .read_dwords_raw(
221 SlmpDeviceAddress::new(start.code, start.number + offset * 2),
222 next as u16,
223 )
224 .await?,
225 );
226 remaining -= next;
227 offset += next as u32;
228 }
229 Ok(result)
230}
231
232pub async fn write_words_chunked(
233 client: &SlmpClient,
234 start: SlmpDeviceAddress,
235 values: &[u16],
236 max_words_per_request: usize,
237) -> Result<(), SlmpError> {
238 if max_words_per_request == 0 {
239 return Err(SlmpError::new("chunk size must be positive."));
240 }
241 let mut offset = 0usize;
242 while offset < values.len() {
243 let end = (offset + max_words_per_request).min(values.len());
244 client
245 .write_words(
246 SlmpDeviceAddress::new(start.code, start.number + offset as u32),
247 &values[offset..end],
248 )
249 .await?;
250 offset = end;
251 }
252 Ok(())
253}
254
255pub async fn write_dwords_chunked(
256 client: &SlmpClient,
257 start: SlmpDeviceAddress,
258 values: &[u32],
259 max_dwords_per_request: usize,
260) -> Result<(), SlmpError> {
261 if max_dwords_per_request == 0 {
262 return Err(SlmpError::new("chunk size must be positive."));
263 }
264 let mut offset = 0usize;
265 while offset < values.len() {
266 let end = (offset + max_dwords_per_request).min(values.len());
267 client
268 .write_dwords(
269 SlmpDeviceAddress::new(start.code, start.number + (offset * 2) as u32),
270 &values[offset..end],
271 )
272 .await?;
273 offset = end;
274 }
275 Ok(())
276}
277
278pub async fn read_named(
279 client: &SlmpClient,
280 addresses: &[String],
281) -> Result<NamedAddress, SlmpError> {
282 let plan = compile_read_plan(addresses)?;
283 read_named_compiled(client, &plan).await
284}
285
286pub async fn write_named(client: &SlmpClient, updates: &NamedAddress) -> Result<(), SlmpError> {
287 for (address, value) in updates {
288 let parts = parse_named_address(address)?;
289 let device = SlmpAddress::parse(&parts.base)?;
290 let resolved_dtype =
291 resolve_dtype_for_address(address, device, &parts.dtype, parts.bit_index);
292 validate_long_timer_entry(address, device, &resolved_dtype)?;
293 if parts.dtype == "BIT_IN_WORD" {
294 validate_bit_in_word_target(address, device)?;
295 write_bit_in_word(
296 client,
297 device,
298 parts.bit_index.unwrap_or(0),
299 scalar_to_bool(value)?,
300 )
301 .await?;
302 continue;
303 }
304 write_typed(client, device, &resolved_dtype, value).await?;
305 }
306 Ok(())
307}
308
309pub fn poll_named<'a>(
310 client: &'a SlmpClient,
311 addresses: &'a [String],
312 interval: Duration,
313) -> impl Stream<Item = Result<NamedAddress, SlmpError>> + 'a {
314 try_stream! {
315 loop {
316 yield read_named(client, addresses).await?;
317 tokio::time::sleep(interval).await;
318 }
319 }
320}
321
322fn validate_single_request_count(count: usize, max: usize) -> Result<(), SlmpError> {
323 if count == 0 || count > max {
324 return Err(SlmpError::new(format!(
325 "count must be in the range 1-{max}."
326 )));
327 }
328 Ok(())
329}
330
331fn validate_single_request_values(count: usize, max: usize) -> Result<(), SlmpError> {
332 if count == 0 || count > max {
333 return Err(SlmpError::new(format!(
334 "values.len() must be in the range 1-{max}."
335 )));
336 }
337 Ok(())
338}
339
340fn compile_read_plan(addresses: &[String]) -> Result<NamedReadPlan, SlmpError> {
341 let mut entries = Vec::new();
342 let mut word_devices = Vec::new();
343 let mut dword_devices = Vec::new();
344 for address in addresses {
345 let parts = parse_named_address(address)?;
346 let device = SlmpAddress::parse(&parts.base)?;
347 let dtype = resolve_dtype_for_address(address, device, &parts.dtype, parts.bit_index);
348 validate_long_timer_entry(address, device, &dtype)?;
349
350 if parts.dtype == "BIT_IN_WORD" {
351 validate_bit_in_word_target(address, device)?;
352 if device.code.is_word_batchable() && !word_devices.contains(&device) {
353 word_devices.push(device);
354 }
355 } else if matches!(dtype.as_str(), "U" | "S") && device.code.is_word_batchable() {
356 if !word_devices.contains(&device) {
357 word_devices.push(device);
358 }
359 } else if matches!(dtype.as_str(), "D" | "L" | "F") && device.code.is_word_batchable() {
360 if !dword_devices.contains(&device) {
361 dword_devices.push(device);
362 }
363 }
364
365 entries.push(NamedReadEntry {
366 address: address.clone(),
367 device,
368 dtype,
369 bit_index: parts.bit_index,
370 long_timer_read: long_timer_read_spec(device.code),
371 });
372 }
373
374 Ok(NamedReadPlan {
375 entries,
376 word_devices,
377 dword_devices,
378 })
379}
380
381async fn read_named_compiled(
382 client: &SlmpClient,
383 plan: &NamedReadPlan,
384) -> Result<NamedAddress, SlmpError> {
385 let mut result = NamedAddress::new();
386 let (word_values, dword_values) =
387 read_random_maps(client, &plan.word_devices, &plan.dword_devices).await?;
388 let mut long_timer_cache: HashMap<(SlmpDeviceCode, u32), SlmpLongTimerResult> = HashMap::new();
389
390 for entry in &plan.entries {
391 let value = if let Some(spec) = &entry.long_timer_read {
392 let key = (spec.base_code, entry.device.number);
393 if !long_timer_cache.contains_key(&key) {
394 let timer =
395 read_long_like_point(client, spec.base_code, entry.device.number).await?;
396 long_timer_cache.insert(key, timer);
397 }
398 decode_long_like_value(&entry.dtype, spec, long_timer_cache.get(&key).unwrap())?
399 } else if entry.dtype == "BIT_IN_WORD" {
400 let word = if let Some(word) = word_values.get(&entry.device) {
401 *word
402 } else {
403 client.read_words_raw(entry.device, 1).await?[0]
404 };
405 SlmpValue::Bool(((word >> entry.bit_index.unwrap_or(0)) & 1) != 0)
406 } else if entry.dtype == "S" {
407 if let Some(value) = word_values.get(&entry.device) {
408 SlmpValue::I16(*value as i16)
409 } else {
410 read_typed(client, entry.device, &entry.dtype).await?
411 }
412 } else if entry.dtype == "U" {
413 if let Some(value) = word_values.get(&entry.device) {
414 SlmpValue::U16(*value)
415 } else {
416 read_typed(client, entry.device, &entry.dtype).await?
417 }
418 } else if entry.dtype == "F" {
419 if let Some(value) = dword_values.get(&entry.device) {
420 SlmpValue::F32(f32::from_bits(*value))
421 } else {
422 read_typed(client, entry.device, &entry.dtype).await?
423 }
424 } else if entry.dtype == "L" {
425 if let Some(value) = dword_values.get(&entry.device) {
426 SlmpValue::I32(*value as i32)
427 } else {
428 read_typed(client, entry.device, &entry.dtype).await?
429 }
430 } else if entry.dtype == "D" {
431 if let Some(value) = dword_values.get(&entry.device) {
432 SlmpValue::U32(*value)
433 } else {
434 read_typed(client, entry.device, &entry.dtype).await?
435 }
436 } else {
437 read_typed(client, entry.device, &entry.dtype).await?
438 };
439 result.insert(entry.address.clone(), value);
440 }
441
442 Ok(result)
443}
444
445async fn read_random_maps(
446 client: &SlmpClient,
447 word_devices: &[SlmpDeviceAddress],
448 dword_devices: &[SlmpDeviceAddress],
449) -> Result<
450 (
451 HashMap<SlmpDeviceAddress, u16>,
452 HashMap<SlmpDeviceAddress, u32>,
453 ),
454 SlmpError,
455> {
456 let mut words = HashMap::new();
457 let mut dwords = HashMap::new();
458 let mut word_index = 0usize;
459 let mut dword_index = 0usize;
460
461 while word_index < word_devices.len() || dword_index < dword_devices.len() {
462 let word_end = (word_index + 0xFF).min(word_devices.len());
463 let dword_end = (dword_index + 0xFF).min(dword_devices.len());
464 let random = client
465 .read_random(
466 &word_devices[word_index..word_end],
467 &dword_devices[dword_index..dword_end],
468 )
469 .await?;
470 for (device, value) in word_devices[word_index..word_end]
471 .iter()
472 .copied()
473 .zip(random.word_values.into_iter())
474 {
475 words.insert(device, value);
476 }
477 for (device, value) in dword_devices[dword_index..dword_end]
478 .iter()
479 .copied()
480 .zip(random.dword_values.into_iter())
481 {
482 dwords.insert(device, value);
483 }
484 word_index = word_end;
485 dword_index = dword_end;
486 }
487
488 Ok((words, dwords))
489}
490
491async fn read_long_like_point(
492 client: &SlmpClient,
493 base_code: SlmpDeviceCode,
494 number: u32,
495) -> Result<SlmpLongTimerResult, SlmpError> {
496 match base_code {
497 SlmpDeviceCode::LTN => Ok(client.read_long_timer(number, 1).await?.remove(0)),
498 SlmpDeviceCode::LSTN => Ok(client.read_long_retentive_timer(number, 1).await?.remove(0)),
499 SlmpDeviceCode::LCN => {
500 let raw_words = client
501 .read_words_raw(SlmpDeviceAddress::new(SlmpDeviceCode::LCN, number), 4)
502 .await?;
503 Ok(SlmpLongTimerResult {
504 index: number,
505 device: format!("LCN{number}"),
506 current_value: raw_words[0] as u32 | ((raw_words[1] as u32) << 16),
507 contact: (raw_words[2] & 0x0002) != 0,
508 coil: (raw_words[2] & 0x0001) != 0,
509 status_word: raw_words[2],
510 raw_words,
511 })
512 }
513 _ => Err(SlmpError::new("Unsupported long-family base code.")),
514 }
515}
516
517fn decode_long_like_value(
518 dtype: &str,
519 spec: &LongTimerReadSpec,
520 timer: &SlmpLongTimerResult,
521) -> Result<SlmpValue, SlmpError> {
522 Ok(match spec.kind {
523 LongTimerReadKind::Current => {
524 if dtype.eq_ignore_ascii_case("L") {
525 SlmpValue::I32(timer.current_value as i32)
526 } else {
527 SlmpValue::U32(timer.current_value)
528 }
529 }
530 LongTimerReadKind::Contact => SlmpValue::Bool(timer.contact),
531 LongTimerReadKind::Coil => SlmpValue::Bool(timer.coil),
532 })
533}
534
535fn validate_bit_in_word_target(address: &str, device: SlmpDeviceAddress) -> Result<(), SlmpError> {
536 if !device.code.is_word_device() {
537 return Err(SlmpError::new(format!(
538 "Address '{address}' uses '.bit' notation, which is only valid for word devices."
539 )));
540 }
541 Ok(())
542}
543
544fn resolve_dtype_for_address(
545 address: &str,
546 device: SlmpDeviceAddress,
547 dtype: &str,
548 bit_index: Option<u8>,
549) -> String {
550 let normalized = if dtype == "U" && device.code.is_bit_device() {
551 "BIT".to_string()
552 } else {
553 dtype.to_uppercase()
554 };
555 if !address.contains(':')
556 && bit_index.is_none()
557 && matches!(
558 device.code,
559 SlmpDeviceCode::LTN | SlmpDeviceCode::LSTN | SlmpDeviceCode::LCN | SlmpDeviceCode::LZ
560 )
561 {
562 "D".to_string()
563 } else {
564 normalized
565 }
566}
567
568fn resolve_write_route(device: SlmpDeviceAddress, dtype: &str) -> NamedWriteRoute {
569 let normalized = if dtype.eq_ignore_ascii_case("U") && device.code.is_bit_device() {
570 "BIT".to_string()
571 } else {
572 dtype.to_uppercase()
573 };
574 match normalized.as_str() {
575 "BIT"
576 if matches!(
577 device.code,
578 SlmpDeviceCode::LTS
579 | SlmpDeviceCode::LTC
580 | SlmpDeviceCode::LSTS
581 | SlmpDeviceCode::LSTC
582 ) =>
583 {
584 NamedWriteRoute::RandomBits
585 }
586 "BIT" => NamedWriteRoute::ContiguousBits,
587 "D" | "L"
588 if matches!(
589 device.code,
590 SlmpDeviceCode::LTN | SlmpDeviceCode::LSTN | SlmpDeviceCode::LZ
591 ) =>
592 {
593 NamedWriteRoute::RandomDWords
594 }
595 "D" | "L" | "F" => NamedWriteRoute::ContiguousDWords,
596 _ => NamedWriteRoute::ContiguousWords,
597 }
598}
599
600fn long_timer_read_spec(code: SlmpDeviceCode) -> Option<LongTimerReadSpec> {
601 let (base_code, kind) = match code {
602 SlmpDeviceCode::LTN => (SlmpDeviceCode::LTN, LongTimerReadKind::Current),
603 SlmpDeviceCode::LTS => (SlmpDeviceCode::LTN, LongTimerReadKind::Contact),
604 SlmpDeviceCode::LTC => (SlmpDeviceCode::LTN, LongTimerReadKind::Coil),
605 SlmpDeviceCode::LSTN => (SlmpDeviceCode::LSTN, LongTimerReadKind::Current),
606 SlmpDeviceCode::LSTS => (SlmpDeviceCode::LSTN, LongTimerReadKind::Contact),
607 SlmpDeviceCode::LSTC => (SlmpDeviceCode::LSTN, LongTimerReadKind::Coil),
608 SlmpDeviceCode::LCN => (SlmpDeviceCode::LCN, LongTimerReadKind::Current),
609 SlmpDeviceCode::LCS => (SlmpDeviceCode::LCN, LongTimerReadKind::Contact),
610 SlmpDeviceCode::LCC => (SlmpDeviceCode::LCN, LongTimerReadKind::Coil),
611 _ => return None,
612 };
613 Some(LongTimerReadSpec { base_code, kind })
614}
615
616fn validate_long_timer_entry(
617 address: &str,
618 device: SlmpDeviceAddress,
619 dtype: &str,
620) -> Result<(), SlmpError> {
621 let Some(spec) = long_timer_read_spec(device.code) else {
622 return Ok(());
623 };
624 if matches!(spec.kind, LongTimerReadKind::Current) {
625 if dtype != "D" && dtype != "L" {
626 return Err(SlmpError::new(format!(
627 "Address '{address}' uses a 32-bit long current value. Use the plain form or ':D' / ':L'."
628 )));
629 }
630 return Ok(());
631 }
632 if !dtype.eq_ignore_ascii_case("BIT") {
633 return Err(SlmpError::new(format!(
634 "Address '{address}' is a long timer state device. Use the plain device form without a dtype override."
635 )));
636 }
637 Ok(())
638}
639
640fn scalar_to_bool(value: &SlmpValue) -> Result<bool, SlmpError> {
641 match value {
642 SlmpValue::Bool(v) => Ok(*v),
643 SlmpValue::U16(v) => Ok(*v != 0),
644 SlmpValue::I16(v) => Ok(*v != 0),
645 SlmpValue::U32(v) => Ok(*v != 0),
646 SlmpValue::I32(v) => Ok(*v != 0),
647 SlmpValue::F32(v) => Ok(*v != 0.0),
648 }
649}
650
651fn scalar_to_u16(value: &SlmpValue) -> Result<u16, SlmpError> {
652 match value {
653 SlmpValue::U16(v) => Ok(*v),
654 SlmpValue::I16(v) => Ok(*v as u16),
655 SlmpValue::Bool(v) => Ok(u16::from(*v)),
656 SlmpValue::U32(v) => Ok(*v as u16),
657 SlmpValue::I32(v) => Ok(*v as u16),
658 SlmpValue::F32(v) => Ok(*v as u16),
659 }
660}
661
662fn scalar_to_u32(value: &SlmpValue) -> Result<u32, SlmpError> {
663 match value {
664 SlmpValue::U32(v) => Ok(*v),
665 SlmpValue::I32(v) => Ok(*v as u32),
666 SlmpValue::U16(v) => Ok(*v as u32),
667 SlmpValue::I16(v) => Ok(*v as u32),
668 SlmpValue::Bool(v) => Ok(u32::from(*v)),
669 SlmpValue::F32(v) => Ok(*v as u32),
670 }
671}
672
673fn scalar_to_i32(value: &SlmpValue) -> Result<i32, SlmpError> {
674 match value {
675 SlmpValue::I32(v) => Ok(*v),
676 SlmpValue::U32(v) => Ok(*v as i32),
677 SlmpValue::U16(v) => Ok(*v as i32),
678 SlmpValue::I16(v) => Ok(*v as i32),
679 SlmpValue::Bool(v) => Ok(i32::from(*v)),
680 SlmpValue::F32(v) => Ok(*v as i32),
681 }
682}
683
684fn scalar_to_f32(value: &SlmpValue) -> Result<f32, SlmpError> {
685 match value {
686 SlmpValue::F32(v) => Ok(*v),
687 SlmpValue::U32(v) => Ok(*v as f32),
688 SlmpValue::I32(v) => Ok(*v as f32),
689 SlmpValue::U16(v) => Ok(*v as f32),
690 SlmpValue::I16(v) => Ok(*v as f32),
691 SlmpValue::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
692 }
693}
694
695#[derive(Debug, Clone, Copy, PartialEq, Eq)]
696enum NamedWriteRoute {
697 ContiguousBits,
698 ContiguousWords,
699 ContiguousDWords,
700 RandomBits,
701 RandomDWords,
702}
703
704pub fn parse_scalar_for_named(address: &str, value: &str) -> Result<SlmpValue, SlmpError> {
705 let parts = parse_named_address(address)?;
706 let device = SlmpAddress::parse(&parts.base)?;
707 if parts.bit_index.is_some() || device.code.is_bit_device() {
708 return Ok(SlmpValue::Bool(matches!(
709 value,
710 "1" | "true" | "TRUE" | "True"
711 )));
712 }
713 if parts.dtype.eq_ignore_ascii_case("F") {
714 return value
715 .parse::<f32>()
716 .map(SlmpValue::F32)
717 .map_err(|_| SlmpError::new("Invalid float value."));
718 }
719 let parsed = if let Some(hex) = value
720 .strip_prefix("0x")
721 .or_else(|| value.strip_prefix("0X"))
722 {
723 i64::from_str_radix(hex, 16).map_err(|_| SlmpError::new("Invalid integer value."))?
724 } else {
725 value
726 .parse::<i64>()
727 .map_err(|_| SlmpError::new("Invalid integer value."))?
728 };
729 Ok(
730 match resolve_dtype_for_address(address, device, &parts.dtype, parts.bit_index).as_str() {
731 "L" => SlmpValue::I32(parsed as i32),
732 "D" => SlmpValue::U32(parsed as u32),
733 "S" => SlmpValue::I16(parsed as i16),
734 _ => SlmpValue::U16(parsed as u16),
735 },
736 )
737}