1use crate::address::{
2 KvDeviceAddress, bit_bank_logical_number, is_direct_bit_device_type,
3 is_optimizable_read_named_device_type, offset_device, parse_device, parse_logical_address,
4 parse_named_address_parts, resolve_effective_format, uses_bit_bank_address,
5 validate_device_count, validate_device_span,
6};
7use crate::client::{HostLinkClient, HostLinkPayloadValue};
8use crate::error::HostLinkError;
9use futures_core::Stream;
10use indexmap::IndexMap;
11use std::str::FromStr;
12use std::time::Duration;
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum HostLinkValue {
16 U16(u16),
17 I16(i16),
18 U32(u32),
19 I32(i32),
20 F32(f32),
21 Bool(bool),
22 Text(String),
23}
24
25pub type NamedSnapshot = IndexMap<String, HostLinkValue>;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct TimerCounterValue {
29 pub status: u32,
30 pub current: u32,
31 pub preset: u32,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35enum ReadPlanValueKind {
36 Unsigned16,
37 Signed16,
38 Unsigned32,
39 Signed32,
40 Float32,
41 BitInWord,
42 DirectBit,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum ReadPlanSegmentMode {
47 Words,
48 DirectBits,
49}
50
51#[derive(Debug, Clone)]
52struct ReadPlanRequest {
53 index: usize,
54 address: String,
55 base_address: KvDeviceAddress,
56 kind: ReadPlanValueKind,
57 bit_index: u8,
58}
59
60#[derive(Debug, Clone)]
61struct ReadPlanSegment {
62 start_address: KvDeviceAddress,
63 start_number: u32,
64 count: usize,
65 mode: ReadPlanSegmentMode,
66 requests: Vec<ReadPlanRequest>,
67}
68
69#[derive(Debug, Clone)]
70pub(crate) struct CompiledReadNamedPlan {
71 requests_in_input_order: Vec<ReadPlanRequest>,
72 segments: Vec<ReadPlanSegment>,
73}
74
75impl From<HostLinkValue> for u16 {
76 fn from(value: HostLinkValue) -> Self {
77 match value {
78 HostLinkValue::U16(value) => value,
79 _ => 0,
80 }
81 }
82}
83
84impl HostLinkPayloadValue for HostLinkValue {
85 fn format_for_suffix(&self, data_format: &str) -> String {
86 let mut value = String::new();
87 self.append_to_payload(data_format, &mut value);
88 value
89 }
90
91 fn append_to_payload(&self, data_format: &str, output: &mut String) {
92 match self {
93 HostLinkValue::U16(value) => value.append_to_payload(data_format, output),
94 HostLinkValue::I16(value) => value.append_to_payload(data_format, output),
95 HostLinkValue::U32(value) => value.append_to_payload(data_format, output),
96 HostLinkValue::I32(value) => value.append_to_payload(data_format, output),
97 HostLinkValue::F32(value) => value.append_to_payload(data_format, output),
98 HostLinkValue::Bool(value) => value.append_to_payload(data_format, output),
99 HostLinkValue::Text(value) => value.append_to_payload(data_format, output),
100 }
101 }
102}
103
104pub async fn read_comments(
105 client: &HostLinkClient,
106 device: &str,
107 strip_padding: bool,
108) -> Result<String, HostLinkError> {
109 client.read_comments(device, strip_padding).await
110}
111
112pub async fn read_typed(
113 client: &HostLinkClient,
114 device: &str,
115 dtype: &str,
116) -> Result<HostLinkValue, HostLinkError> {
117 let (device, dtype) = if dtype.trim().is_empty() {
118 let logical = parse_logical_address(device)?;
119 (logical.base_address.to_text()?, logical.data_type)
120 } else {
121 (
122 device.trim().to_ascii_uppercase(),
123 dtype.trim_start_matches('.').to_ascii_uppercase(),
124 )
125 };
126
127 match dtype.as_str() {
128 "F" => {
129 let words = read_words(client, &device, 2).await?;
130 let bits = (words[0] as u32) | ((words[1] as u32) << 16);
131 Ok(HostLinkValue::F32(f32::from_bits(bits)))
132 }
133 "S" => Ok(HostLinkValue::I16(
134 read_single_parsed(client, &device, Some("S"), "Invalid signed 16-bit response")
135 .await?,
136 )),
137 "D" => {
138 if is_timer_counter_composite_device(&device)? {
139 let response = read_single_response(client, &device, Some("D")).await?;
140 Ok(HostLinkValue::U32(parse_last_token(
141 &response,
142 "Invalid unsigned 32-bit response",
143 )?))
144 } else {
145 Ok(HostLinkValue::U32(
146 read_single_parsed::<u32>(
147 client,
148 &device,
149 Some("D"),
150 "Invalid unsigned 32-bit response",
151 )
152 .await?,
153 ))
154 }
155 }
156 "L" => {
157 if is_timer_counter_composite_device(&device)? {
158 let response = read_single_response(client, &device, Some("L")).await?;
159 Ok(HostLinkValue::I32(parse_last_token(
160 &response,
161 "Invalid signed 32-bit response",
162 )?))
163 } else {
164 Ok(HostLinkValue::I32(
165 read_single_parsed(
166 client,
167 &device,
168 Some("L"),
169 "Invalid signed 32-bit response",
170 )
171 .await?,
172 ))
173 }
174 }
175 "U" => Ok(HostLinkValue::U16(
176 read_single_parsed::<u16>(
177 client,
178 &device,
179 Some("U"),
180 "Invalid unsigned 16-bit response",
181 )
182 .await?,
183 )),
184 "" => Ok(HostLinkValue::Bool(
185 read_single_bool(client, &device, None).await?,
186 )),
187 other => Err(HostLinkError::protocol(format!(
188 "Unsupported logical data type '{other}'."
189 ))),
190 }
191}
192
193pub async fn read_timer_counter(
194 client: &HostLinkClient,
195 device: &str,
196) -> Result<TimerCounterValue, HostLinkError> {
197 let mut address = parse_device(device)?;
198 if !matches!(address.device_type.as_str(), "T" | "C") {
199 return Err(HostLinkError::protocol(
200 "read_timer_counter requires a T or C device.",
201 ));
202 }
203
204 address.suffix.clear();
205 let target = address.to_text()?;
206 let response = read_single_response(client, &target, Some("D")).await?;
207 let values = parse_all_tokens::<u32>(
208 &response,
209 "Invalid timer/counter status/current/preset response",
210 )?;
211 if values.len() < 3 {
212 return Err(HostLinkError::protocol(
213 "Timer/counter response did not contain status/current/preset.",
214 ));
215 }
216 Ok(TimerCounterValue {
217 status: values[0],
218 current: values[1],
219 preset: values[2],
220 })
221}
222
223pub async fn read_timer(
224 client: &HostLinkClient,
225 device: &str,
226) -> Result<TimerCounterValue, HostLinkError> {
227 if parse_device(device)?.device_type != "T" {
228 return Err(HostLinkError::protocol("read_timer requires a T device."));
229 }
230 read_timer_counter(client, device).await
231}
232
233pub async fn read_counter(
234 client: &HostLinkClient,
235 device: &str,
236) -> Result<TimerCounterValue, HostLinkError> {
237 if parse_device(device)?.device_type != "C" {
238 return Err(HostLinkError::protocol("read_counter requires a C device."));
239 }
240 read_timer_counter(client, device).await
241}
242
243pub async fn write_typed<T: HostLinkPayloadValue>(
244 client: &HostLinkClient,
245 device: &str,
246 dtype: &str,
247 value: &T,
248) -> Result<(), HostLinkError> {
249 match dtype.trim_start_matches('.').to_ascii_uppercase().as_str() {
250 "F" => {
251 let single = value
252 .format_for_suffix("")
253 .parse::<f32>()
254 .map_err(|_| HostLinkError::protocol("Invalid float32 input"))?;
255 let bits = single.to_bits();
256 let words = [(bits & 0xFFFF) as u16, (bits >> 16) as u16];
257 client.write_consecutive(device, &words, Some("U")).await
258 }
259 "" => client.write(device, value, None).await,
260 "S" | "D" | "L" | "U" => client.write(device, value, Some(dtype)).await,
261 other => Err(HostLinkError::protocol(format!(
262 "Unsupported logical data type '{other}'."
263 ))),
264 }
265}
266
267fn parse_bool_token(token: &str) -> Result<bool, HostLinkError> {
268 let token = token.trim();
269 if token == "1" || token.eq_ignore_ascii_case("ON") || token.eq_ignore_ascii_case("TRUE") {
270 Ok(true)
271 } else if token == "0"
272 || token.eq_ignore_ascii_case("OFF")
273 || token.eq_ignore_ascii_case("FALSE")
274 {
275 Ok(false)
276 } else {
277 Err(HostLinkError::protocol(format!(
278 "Invalid direct bit response token: {token}"
279 )))
280 }
281}
282
283fn response_tokens(response_text: &str) -> impl Iterator<Item = &str> {
284 response_text
285 .split([' ', ','])
286 .filter(|token| !token.is_empty())
287}
288
289fn first_response_token(response_text: &str) -> Result<&str, HostLinkError> {
290 response_tokens(response_text)
291 .next()
292 .ok_or_else(|| HostLinkError::protocol("Missing response token"))
293}
294
295fn last_response_token(response_text: &str) -> Result<&str, HostLinkError> {
296 response_tokens(response_text)
297 .last()
298 .ok_or_else(|| HostLinkError::protocol("Missing response token"))
299}
300
301fn is_timer_counter_composite_device(device: &str) -> Result<bool, HostLinkError> {
302 let address = parse_device(device)?;
303 Ok(matches!(address.device_type.as_str(), "T" | "C"))
304}
305
306fn parse_first_token<T: FromStr>(
307 response_text: &str,
308 invalid_message: &'static str,
309) -> Result<T, HostLinkError> {
310 first_response_token(response_text)?
311 .parse::<T>()
312 .map_err(|_| HostLinkError::protocol(invalid_message))
313}
314
315fn parse_last_token<T: FromStr>(
316 response_text: &str,
317 invalid_message: &'static str,
318) -> Result<T, HostLinkError> {
319 last_response_token(response_text)?
320 .parse::<T>()
321 .map_err(|_| HostLinkError::protocol(invalid_message))
322}
323
324fn parse_all_tokens<T: FromStr>(
325 response_text: &str,
326 invalid_message: &'static str,
327) -> Result<Vec<T>, HostLinkError> {
328 let mut values = Vec::new();
329 for token in response_tokens(response_text) {
330 values.push(
331 token
332 .parse::<T>()
333 .map_err(|_| HostLinkError::protocol(invalid_message))?,
334 );
335 }
336 if values.is_empty() {
337 return Err(HostLinkError::protocol("Missing response token"));
338 }
339 Ok(values)
340}
341
342fn prepare_read_address(
343 device: &str,
344 data_format: Option<&str>,
345 count: usize,
346) -> Result<KvDeviceAddress, HostLinkError> {
347 let mut address = parse_device(device)?;
348 let suffix = if let Some(data_format) = data_format {
349 crate::address::normalize_suffix(data_format)?
350 } else {
351 address.suffix.clone()
352 };
353 let suffix = resolve_effective_format(&address.device_type, &suffix);
354 if count > 1 {
355 validate_device_count(&address.device_type, &suffix, count)?;
356 }
357 validate_device_span(&address.device_type, address.number, &suffix, count)?;
358 address.suffix = suffix;
359 Ok(address)
360}
361
362async fn read_single_response(
363 client: &HostLinkClient,
364 device: &str,
365 data_format: Option<&str>,
366) -> Result<String, HostLinkError> {
367 let address = prepare_read_address(device, data_format, 1)?;
368 client.send_raw(&format!("RD {}", address.to_text()?)).await
369}
370
371async fn read_single_parsed<T: FromStr>(
372 client: &HostLinkClient,
373 device: &str,
374 data_format: Option<&str>,
375 invalid_message: &'static str,
376) -> Result<T, HostLinkError> {
377 let response = read_single_response(client, device, data_format).await?;
378 parse_first_token(&response, invalid_message)
379}
380
381async fn read_single_bool(
382 client: &HostLinkClient,
383 device: &str,
384 data_format: Option<&str>,
385) -> Result<bool, HostLinkError> {
386 let response = read_single_response(client, device, data_format).await?;
387 parse_bool_token(first_response_token(&response)?)
388}
389
390async fn read_consecutive_parsed<T: FromStr>(
391 client: &HostLinkClient,
392 device: &str,
393 count: usize,
394 data_format: Option<&str>,
395 invalid_message: &'static str,
396) -> Result<Vec<T>, HostLinkError> {
397 let address = prepare_read_address(device, data_format, count)?;
398 let response = client
399 .send_raw(&format!("RDS {} {}", address.to_text()?, count))
400 .await?;
401 parse_all_tokens(&response, invalid_message)
402}
403
404pub async fn write_bit_in_word(
405 client: &HostLinkClient,
406 device: &str,
407 bit_index: u8,
408 value: bool,
409) -> Result<(), HostLinkError> {
410 if bit_index > 15 {
411 return Err(HostLinkError::protocol("bitIndex must be 0-15."));
412 }
413
414 let mut current = read_single_parsed::<u16>(
415 client,
416 device,
417 Some("U"),
418 "Invalid unsigned 16-bit response",
419 )
420 .await?;
421 if value {
422 current |= 1 << bit_index;
423 } else {
424 current &= !(1 << bit_index);
425 }
426 client.write(device, current, Some("U")).await
427}
428
429pub async fn read_named<S: AsRef<str>>(
430 client: &HostLinkClient,
431 addresses: &[S],
432) -> Result<NamedSnapshot, HostLinkError> {
433 let addr_list = addresses
434 .iter()
435 .map(|item| item.as_ref().to_owned())
436 .collect::<Vec<_>>();
437 if addr_list.is_empty() {
438 return Ok(NamedSnapshot::new());
439 }
440
441 if let Some(plan) = compile_read_named_plan(&addr_list) {
442 execute_read_named_plan(client, &plan).await
443 } else {
444 read_named_sequential(client, &addr_list).await
445 }
446}
447
448pub(crate) async fn read_named_sequential(
449 client: &HostLinkClient,
450 addresses: &[String],
451) -> Result<NamedSnapshot, HostLinkError> {
452 let mut result = NamedSnapshot::new();
453 for address in addresses {
454 let (base_address, dtype, bit_index) = parse_named_address_parts(address)?;
455 if dtype == "BIT_IN_WORD" {
456 let word = read_single_parsed::<u16>(
457 client,
458 &base_address,
459 Some("U"),
460 "Invalid unsigned 16-bit response",
461 )
462 .await?;
463 let bit_index = bit_index.unwrap_or(0);
464 result.insert(
465 address.clone(),
466 HostLinkValue::Bool(((word >> bit_index) & 1) != 0),
467 );
468 } else if dtype == "COMMENT" {
469 result.insert(
470 address.clone(),
471 HostLinkValue::Text(read_comments(client, &base_address, true).await?),
472 );
473 } else {
474 result.insert(
475 address.clone(),
476 read_typed(client, &base_address, &dtype).await?,
477 );
478 }
479 }
480 Ok(result)
481}
482
483pub(crate) fn compile_read_named_plan(addresses: &[String]) -> Option<CompiledReadNamedPlan> {
484 let mut requests_in_input_order = Vec::new();
485 let mut requests_by_device_type: IndexMap<String, Vec<ReadPlanRequest>> = IndexMap::new();
486
487 for (index, address) in addresses.iter().enumerate() {
488 let request = try_parse_optimizable_read_named_request(address, index)?;
489 requests_by_device_type
490 .entry(request.base_address.device_type.clone())
491 .or_default()
492 .push(request.clone());
493 requests_in_input_order.push(request);
494 }
495
496 let mut segments = Vec::new();
497 for bucket in requests_by_device_type.values() {
498 let mut sorted = bucket.clone();
499 sorted.sort_by_key(|request| {
500 (
501 read_plan_number(request),
502 usize::MAX - get_word_width(request.kind),
503 )
504 });
505
506 let mut pending = Vec::new();
507 let mut current_start: Option<KvDeviceAddress> = None;
508 let mut current_start_number = 0u32;
509 let mut current_end_exclusive = 0u32;
510 let mut current_mode: Option<ReadPlanSegmentMode> = None;
511
512 for request in sorted {
513 let request_start = read_plan_number(&request);
514 let request_end_exclusive = request_start + get_word_width(request.kind) as u32;
515 let request_mode = segment_mode_for_kind(request.kind);
516 if current_start.is_none()
517 || request_start > current_end_exclusive
518 || current_mode != Some(request_mode)
519 {
520 if let Some(start_address) = current_start.take() {
521 segments.push(ReadPlanSegment {
522 start_address,
523 start_number: current_start_number,
524 count: (current_end_exclusive - current_start_number) as usize,
525 mode: current_mode.unwrap_or(ReadPlanSegmentMode::Words),
526 requests: pending.clone(),
527 });
528 pending.clear();
529 }
530 current_start = Some(KvDeviceAddress {
531 device_type: request.base_address.device_type.clone(),
532 number: request.base_address.number,
533 suffix: String::new(),
534 });
535 current_start_number = request_start;
536 current_end_exclusive = request_end_exclusive;
537 current_mode = Some(request_mode);
538 } else if request_end_exclusive > current_end_exclusive {
539 current_end_exclusive = request_end_exclusive;
540 }
541 pending.push(request);
542 }
543
544 if let Some(start_address) = current_start {
545 segments.push(ReadPlanSegment {
546 start_address,
547 start_number: current_start_number,
548 count: (current_end_exclusive - current_start_number) as usize,
549 mode: current_mode.unwrap_or(ReadPlanSegmentMode::Words),
550 requests: pending,
551 });
552 }
553 }
554
555 Some(CompiledReadNamedPlan {
556 requests_in_input_order,
557 segments,
558 })
559}
560
561pub(crate) async fn execute_read_named_plan(
562 client: &HostLinkClient,
563 plan: &CompiledReadNamedPlan,
564) -> Result<NamedSnapshot, HostLinkError> {
565 let mut resolved = vec![HostLinkValue::U16(0); plan.requests_in_input_order.len()];
566 for segment in &plan.segments {
567 match segment.mode {
568 ReadPlanSegmentMode::Words => {
569 let words =
570 read_words(client, &segment.start_address.to_text()?, segment.count).await?;
571 for request in &segment.requests {
572 let offset = (read_plan_number(request) - segment.start_number) as usize;
573 resolved[request.index] =
574 resolve_planned_value(&words, offset, request.kind, request.bit_index)?;
575 }
576 }
577 ReadPlanSegmentMode::DirectBits => {
578 let tokens = client
579 .read_consecutive(&segment.start_address.to_text()?, segment.count, None)
580 .await?;
581 for request in &segment.requests {
582 let offset = (read_plan_number(request) - segment.start_number) as usize;
583 resolved[request.index] = resolve_direct_bit_value(&tokens, offset)?;
584 }
585 }
586 }
587 }
588
589 let mut result = NamedSnapshot::new();
590 for request in &plan.requests_in_input_order {
591 result.insert(request.address.clone(), resolved[request.index].clone());
592 }
593 Ok(result)
594}
595
596pub fn poll<'a, S: AsRef<str> + 'a>(
597 client: &'a HostLinkClient,
598 addresses: &'a [S],
599 interval: Duration,
600) -> impl Stream<Item = Result<NamedSnapshot, HostLinkError>> + 'a {
601 async_stream::try_stream! {
602 let addr_list = addresses.iter().map(|item| item.as_ref().to_owned()).collect::<Vec<_>>();
603 let compiled = compile_read_named_plan(&addr_list);
604 loop {
605 let snapshot = if let Some(plan) = &compiled {
606 execute_read_named_plan(client, plan).await?
607 } else {
608 read_named_sequential(client, &addr_list).await?
609 };
610 yield snapshot;
611 tokio::time::sleep(interval).await;
612 }
613 }
614}
615
616pub async fn read_words(
617 client: &HostLinkClient,
618 device: &str,
619 count: usize,
620) -> Result<Vec<u16>, HostLinkError> {
621 read_words_single_request(client, device, count).await
622}
623
624pub async fn read_dwords(
625 client: &HostLinkClient,
626 device: &str,
627 count: usize,
628) -> Result<Vec<u32>, HostLinkError> {
629 read_dwords_single_request(client, device, count).await
630}
631
632pub async fn read_words_single_request(
633 client: &HostLinkClient,
634 device: &str,
635 count: usize,
636) -> Result<Vec<u16>, HostLinkError> {
637 if count == 0 {
638 return Err(HostLinkError::protocol("count must be 1 or greater."));
639 }
640 read_consecutive_parsed::<u16>(
641 client,
642 device,
643 count,
644 Some("U"),
645 "Invalid unsigned 16-bit response",
646 )
647 .await
648}
649
650pub async fn read_dwords_single_request(
651 client: &HostLinkClient,
652 device: &str,
653 count: usize,
654) -> Result<Vec<u32>, HostLinkError> {
655 if count == 0 {
656 return Err(HostLinkError::protocol("count must be 1 or greater."));
657 }
658 let words = read_words_single_request(client, device, count * 2).await?;
659 let mut result = Vec::with_capacity(count);
660 for index in 0..count {
661 let lo = words[index * 2] as u32;
662 let hi = words[(index * 2) + 1] as u32;
663 result.push(lo | (hi << 16));
664 }
665 Ok(result)
666}
667
668pub async fn write_words_single_request(
669 client: &HostLinkClient,
670 device: &str,
671 values: &[u16],
672) -> Result<(), HostLinkError> {
673 if values.is_empty() {
674 return Err(HostLinkError::protocol("values must not be empty"));
675 }
676 client.write_consecutive(device, values, Some("U")).await
677}
678
679pub async fn write_dwords_single_request(
680 client: &HostLinkClient,
681 device: &str,
682 values: &[u32],
683) -> Result<(), HostLinkError> {
684 if values.is_empty() {
685 return Err(HostLinkError::protocol("values must not be empty"));
686 }
687 let mut words = Vec::with_capacity(values.len() * 2);
688 for value in values {
689 words.push((value & 0xFFFF) as u16);
690 words.push((value >> 16) as u16);
691 }
692 write_words_single_request(client, device, &words).await
693}
694
695pub async fn read_words_chunked(
696 client: &HostLinkClient,
697 device: &str,
698 count: usize,
699 max_words_per_request: usize,
700) -> Result<Vec<u16>, HostLinkError> {
701 validate_chunk_arguments(count, max_words_per_request, "count", "maxWordsPerRequest")?;
702 let mut start = parse_device(device)?;
703 start.suffix.clear();
704 let mut result = vec![0u16; count];
705 let mut offset = 0usize;
706 while offset < count {
707 let chunk_count = max_words_per_request.min(count - offset);
708 let chunk_start = offset_device(&start, offset as u32)?;
709 let chunk = read_words_single_request(client, &chunk_start, chunk_count).await?;
710 result[offset..offset + chunk_count].copy_from_slice(&chunk);
711 offset += chunk_count;
712 }
713 Ok(result)
714}
715
716pub async fn read_dwords_chunked(
717 client: &HostLinkClient,
718 device: &str,
719 count: usize,
720 max_dwords_per_request: usize,
721) -> Result<Vec<u32>, HostLinkError> {
722 validate_chunk_arguments(
723 count,
724 max_dwords_per_request,
725 "count",
726 "maxDwordsPerRequest",
727 )?;
728 let mut start = parse_device(device)?;
729 start.suffix.clear();
730 let mut result = vec![0u32; count];
731 let mut offset = 0usize;
732 while offset < count {
733 let chunk_count = max_dwords_per_request.min(count - offset);
734 let chunk_start = offset_device(&start, (offset * 2) as u32)?;
735 let chunk = read_dwords_single_request(client, &chunk_start, chunk_count).await?;
736 result[offset..offset + chunk_count].copy_from_slice(&chunk);
737 offset += chunk_count;
738 }
739 Ok(result)
740}
741
742pub async fn write_words_chunked(
743 client: &HostLinkClient,
744 device: &str,
745 values: &[u16],
746 max_words_per_request: usize,
747) -> Result<(), HostLinkError> {
748 if values.is_empty() {
749 return Err(HostLinkError::protocol("values must not be empty"));
750 }
751 validate_chunk_size(max_words_per_request, "maxWordsPerRequest")?;
752 let mut start = parse_device(device)?;
753 start.suffix.clear();
754 let mut offset = 0usize;
755 while offset < values.len() {
756 let chunk_count = max_words_per_request.min(values.len() - offset);
757 let chunk_start = offset_device(&start, offset as u32)?;
758 write_words_single_request(client, &chunk_start, &values[offset..offset + chunk_count])
759 .await?;
760 offset += chunk_count;
761 }
762 Ok(())
763}
764
765pub async fn write_dwords_chunked(
766 client: &HostLinkClient,
767 device: &str,
768 values: &[u32],
769 max_dwords_per_request: usize,
770) -> Result<(), HostLinkError> {
771 if values.is_empty() {
772 return Err(HostLinkError::protocol("values must not be empty"));
773 }
774 validate_chunk_size(max_dwords_per_request, "maxDwordsPerRequest")?;
775 let mut start = parse_device(device)?;
776 start.suffix.clear();
777 let mut offset = 0usize;
778 while offset < values.len() {
779 let chunk_count = max_dwords_per_request.min(values.len() - offset);
780 let chunk_start = offset_device(&start, (offset * 2) as u32)?;
781 write_dwords_single_request(client, &chunk_start, &values[offset..offset + chunk_count])
782 .await?;
783 offset += chunk_count;
784 }
785 Ok(())
786}
787
788fn try_parse_optimizable_read_named_request(
789 address: &str,
790 index: usize,
791) -> Option<ReadPlanRequest> {
792 let (base_address, dtype, bit_index) = parse_named_address_parts(address).ok()?;
793 let mut base_address = parse_device(&base_address).ok()?;
794 if !is_optimizable_read_named_device_type(&base_address.device_type)
795 && !is_direct_bit_device_type(&base_address.device_type)
796 {
797 return None;
798 }
799 base_address.suffix.clear();
800
801 let (kind, bit_index) =
802 if dtype.is_empty() && is_direct_bit_device_type(&base_address.device_type) {
803 (ReadPlanValueKind::DirectBit, 0)
804 } else if dtype == "BIT_IN_WORD" {
805 (ReadPlanValueKind::BitInWord, bit_index.unwrap_or(0))
806 } else {
807 (try_map_read_plan_value_kind(&dtype)?, 0)
808 };
809
810 Some(ReadPlanRequest {
811 index,
812 address: address.to_owned(),
813 base_address,
814 kind,
815 bit_index,
816 })
817}
818
819fn try_map_read_plan_value_kind(dtype: &str) -> Option<ReadPlanValueKind> {
820 match dtype.trim_start_matches('.').to_ascii_uppercase().as_str() {
821 "U" => Some(ReadPlanValueKind::Unsigned16),
822 "S" => Some(ReadPlanValueKind::Signed16),
823 "D" => Some(ReadPlanValueKind::Unsigned32),
824 "L" => Some(ReadPlanValueKind::Signed32),
825 "F" => Some(ReadPlanValueKind::Float32),
826 _ => None,
827 }
828}
829
830fn segment_mode_for_kind(kind: ReadPlanValueKind) -> ReadPlanSegmentMode {
831 when_direct_bit(
832 kind,
833 ReadPlanSegmentMode::DirectBits,
834 ReadPlanSegmentMode::Words,
835 )
836}
837
838fn when_direct_bit<T>(kind: ReadPlanValueKind, direct: T, other: T) -> T {
839 match kind {
840 ReadPlanValueKind::DirectBit => direct,
841 _ => other,
842 }
843}
844
845fn get_word_width(kind: ReadPlanValueKind) -> usize {
846 match kind {
847 ReadPlanValueKind::Unsigned32
848 | ReadPlanValueKind::Signed32
849 | ReadPlanValueKind::Float32 => 2,
850 _ => 1,
851 }
852}
853
854fn read_plan_number(request: &ReadPlanRequest) -> u32 {
855 if request.kind == ReadPlanValueKind::DirectBit
856 && uses_bit_bank_address(&request.base_address.device_type)
857 {
858 bit_bank_logical_number(request.base_address.number)
859 } else {
860 request.base_address.number
861 }
862}
863
864fn resolve_planned_value(
865 words: &[u16],
866 offset: usize,
867 kind: ReadPlanValueKind,
868 bit_index: u8,
869) -> Result<HostLinkValue, HostLinkError> {
870 let word = *words
871 .get(offset)
872 .ok_or_else(|| HostLinkError::protocol("Batched read response was too short"))?;
873 let next_word = || {
874 words
875 .get(offset + 1)
876 .copied()
877 .ok_or_else(|| HostLinkError::protocol("Batched read response was too short"))
878 };
879
880 Ok(match kind {
881 ReadPlanValueKind::Unsigned16 => HostLinkValue::U16(word),
882 ReadPlanValueKind::Signed16 => HostLinkValue::I16(word as i16),
883 ReadPlanValueKind::Unsigned32 => {
884 let hi = next_word()? as u32;
885 HostLinkValue::U32((word as u32) | (hi << 16))
886 }
887 ReadPlanValueKind::Signed32 => {
888 let hi = next_word()? as u32;
889 HostLinkValue::I32(((word as u32) | (hi << 16)) as i32)
890 }
891 ReadPlanValueKind::Float32 => {
892 let hi = next_word()? as u32;
893 HostLinkValue::F32(f32::from_bits((word as u32) | (hi << 16)))
894 }
895 ReadPlanValueKind::BitInWord => HostLinkValue::Bool(((word >> bit_index) & 1) != 0),
896 ReadPlanValueKind::DirectBit => {
897 return Err(HostLinkError::protocol(
898 "Direct bit values must be resolved from bit tokens.",
899 ));
900 }
901 })
902}
903
904fn resolve_direct_bit_value(
905 tokens: &[String],
906 offset: usize,
907) -> Result<HostLinkValue, HostLinkError> {
908 let token = tokens
909 .get(offset)
910 .ok_or_else(|| HostLinkError::protocol("Batched direct bit response was too short"))?;
911 Ok(HostLinkValue::Bool(parse_bool_token(token)?))
912}
913
914fn validate_chunk_arguments(
915 count: usize,
916 max_per_request: usize,
917 count_name: &str,
918 chunk_name: &str,
919) -> Result<(), HostLinkError> {
920 if count == 0 {
921 return Err(HostLinkError::protocol(format!(
922 "{count_name} must be 1 or greater."
923 )));
924 }
925 validate_chunk_size(max_per_request, chunk_name)
926}
927
928fn validate_chunk_size(max_per_request: usize, param_name: &str) -> Result<(), HostLinkError> {
929 if max_per_request == 0 {
930 return Err(HostLinkError::protocol(format!(
931 "{param_name} must be 1 or greater."
932 )));
933 }
934 Ok(())
935}