process_image/
lib.rs

1//! Macros for accessing _tags_ in a _process image_.
2//!
3//! # What's this?
4//! In industrial controls, namely PLC (programmable logic controller) programming, data is usually
5//! stored in _process images_. A process image is nothing more than a chunk of memory that
6//! contains a snapshot of all values known about the system that is being controlled.
7//!
8//! This data is commonly split into a `PII` (process image of inputs) and a `PIQ` (process image
9//! of outputs). The `PII` contains things like sensor data, while the `PIQ` is filled with the
10//! states of solenoid valves or speed setpoints for motors, for example.
11//!
12//! IEC 61131-3 defines a syntax for addressing data in process images.  It works like this:
13//!
14//! ```text
15//!  +------- The process image that is referenced: I=inputs, Q=outputs, M=internal memory
16//!  |+------ The type of the tag: X=bit, B=byte, W=word, D=double word, L=long word
17//!  || +---- The byte offset inside the process image
18//!  || |  +- The bit offset in the addressed byte
19//!  vv v  v
20//! %IX100.4 = bit 4 in byte address 100
21//! %IB8     = byte at address 8
22//! %IW16    = word starting at address 16
23//! ```
24//!
25//! | Specifier | Rust | Type |
26//! | --- | --- | --- |
27//! | `X` (may be omitted) | `bool` | Boolean Bit |
28//! | `B` | `u8` | Byte |
29//! | `W` | `u16` | Word |
30//! | `D` | `u32` | Double Word |
31//! | `L` | `u64` | Long Word |
32//!
33//! The meaning of each bit and byte is defined by the hardware configuration of the PLC and the
34//! equipment connected to it.  Usually the input and output addresses are also referenced in
35//! electrical wiring diagrams of the control system.
36//!
37//! Because directly fiddling with bits and bytes in a chunk of memory to fetch and set process
38//! data is inconvenient, _tags_ are assigned to the addresses in a process image, giving the data
39//! symbolic names.  This crate provides macros to assign such tags to a memory buffer in rust.
40//!
41//! # How To
42//! The [`tag!()`][`tag`] and [`tag_mut!()`][`tag`] macros are used to immediately address a value
43//! inside a buffer slice.  This is meant for fetching data by address directly.
44//!
45//! The [`process_image!()`][`process_image`] and [`process_image_owned!()`][`process_image_owned`]
46//! macros are used to build more permanent definitions of the data inside a process image.  These
47//! macros generate struct-wrappers around a buffer with methods to access the individual tags.
48//! Please see the respective documentation for details.
49//!
50//! The syntax for addresses is slightly different from the IEC 61131-3 syntax, to stay within the
51//! bounds of rust declarative macros.  Here are a few examples that should be self-explanatory:
52//!
53//! ```
54//! // Process Image of Inputs
55//! let pii = [0x00; 16];
56//!
57//! let b1: bool = process_image::tag!(&pii, X, 0, 0); // %IX0.0
58//! let b2: bool = process_image::tag!(&pii, 0, 1);    // %IX0.1
59//! let by: u8   = process_image::tag!(&pii, B, 1);    // %IB1
60//! let w: u16   = process_image::tag!(&pii, W, 2);    // %IW2
61//! let d: u32   = process_image::tag!(&pii, D, 4);    // %ID4
62//! let l: u64   = process_image::tag!(&pii, L, 8);    // %IL8
63//! ```
64//!
65//! # Endianness
66//! All data is accessed in big-endian (MSB-first) byte order.
67//!
68//! # Alignment
69//! By default, addresses of _words, double words,_ and _long words_ must be aligned to the size of
70//! the data type.  Unaligned addresses will lead to a panic at runtime.
71//!
72//! | Type | Alignment |
73//! | --- | --- |
74//! | Boolean Bit | 1 (none) |
75//! | Byte | 1 (none) |
76//! | Word | 2 |
77//! | Double Word | 4 |
78//! | Long Word | 8 |
79//!
80//! However, there are some situations where this behavior is undesired.  In some control systems,
81//! the process image is constructed without alignment.  To cater to such uses,
82//! alignment-enforcement can be disabled globally with the `allow_unaligned_tags` crate feature.
83//! Do note that this will disable alignment globally for all users of `process-image` in the
84//! crate dependency-graph.
85//!
86//! In the future, alignment-enforcement might be dropped entirely.
87#![cfg_attr(not(test), no_std)]
88
89mod access;
90pub use access::{BitMut, DWordMut, LWordMut, WordMut};
91
92#[cfg(feature = "allow_unaligned_tags")]
93#[doc(hidden)]
94#[macro_export]
95macro_rules! alignment_assert {
96    ($align:literal, $addr:literal) => {};
97}
98
99#[cfg(not(feature = "allow_unaligned_tags"))]
100#[doc(hidden)]
101#[macro_export]
102macro_rules! alignment_assert {
103    (2, $addr:literal) => {
104        assert!($addr % 2 == 0, "Word address must be divisible by 2");
105    };
106    (4, $addr:literal) => {
107        assert!($addr % 4 == 0, "Double word address must be divisible by 4");
108    };
109    (8, $addr:literal) => {
110        assert!($addr % 8 == 0, "Long word address must be divisible by 8");
111    };
112}
113
114/// Read tag values from a process image with absolute addressing.
115///
116/// Addresses must be aligned to the size of the datatype (i.e. word=2, dword=4, lword=8).
117///
118/// Multi-byte datatypes are always accessed in big-endian order.
119///
120/// # Example
121/// ```
122/// let pi = [0x00; 16];
123///
124/// // Bit access
125/// let b1: bool = process_image::tag!(&pi, X, 0, 0);   // %MX0.0
126/// let b2: bool = process_image::tag!(&pi, 0, 1);      // %MX0.1
127///
128/// // Byte access
129/// let by: u8 = process_image::tag!(&pi, B, 1);        // %MB1
130///
131/// // Word access
132/// let w: u16 = process_image::tag!(&pi, W, 2);        // %MW2
133///
134/// // Double word access
135/// let d: u32 = process_image::tag!(&pi, D, 4);        // %MD4
136///
137/// // Long word access
138/// let l: u64 = process_image::tag!(&pi, L, 8);        // %ML8
139/// ```
140#[macro_export]
141macro_rules! tag {
142    ($buf:expr, X, $addr1:expr, $addr2:expr) => {{
143        let buffer: &[u8] = $buf;
144        buffer[$addr1] & (1 << $addr2) != 0
145    }};
146    ($buf:expr, B, $addr:expr) => {{
147        let buffer: &[u8] = $buf;
148        buffer[$addr]
149    }};
150    ($buf:expr, W, $addr:expr) => {{
151        let buffer: &[u8] = $buf;
152        $crate::alignment_assert!(2, $addr);
153        u16::from_be_bytes(buffer[$addr..$addr + 2].try_into().unwrap())
154    }};
155    ($buf:expr, D, $addr:expr) => {{
156        let buffer: &[u8] = $buf;
157        $crate::alignment_assert!(4, $addr);
158        u32::from_be_bytes(buffer[$addr..$addr + 4].try_into().unwrap())
159    }};
160    ($buf:expr, L, $addr:expr) => {{
161        let buffer: &[u8] = $buf;
162        $crate::alignment_assert!(8, $addr);
163        u64::from_be_bytes(buffer[$addr..$addr + 8].try_into().unwrap())
164    }};
165    ($buf:expr, $addr1:expr, $addr2:expr) => {{
166        let buffer: &[u8] = $buf;
167        buffer[$addr1] & (1 << $addr2) != 0
168    }};
169}
170
171/// Mutable access to tag values from a process image with absolute addressing.
172///
173/// Addresses must be aligned to the size of the datatype (i.e. word=2, dword=4, lword=8).
174///
175/// Multi-byte datatypes are always accessed in big-endian order.
176///
177/// # Example
178/// ```
179/// let mut pi = [0x00; 16];
180///
181/// // Bit access
182/// *process_image::tag_mut!(&mut pi, X, 0, 0) = true;  // %MX0.0
183/// *process_image::tag_mut!(&mut pi, 0, 1) = true;     // %MX0.1
184///
185/// // Byte access
186/// *process_image::tag_mut!(&mut pi, B, 1) = 42u8;     // %MB1
187///
188/// // Word access
189/// *process_image::tag_mut!(&mut pi, W, 2) = 1337u16;  // %MW2
190///
191/// // Double word access
192/// *process_image::tag_mut!(&mut pi, D, 4) = 0xdeadbeef; // %MD4
193///
194/// // Long word access
195/// *process_image::tag_mut!(&mut pi, L, 8) = 1;        // %ML8
196/// ```
197#[macro_export]
198macro_rules! tag_mut {
199    ($buf:expr, X, $addr1:expr, $addr2:expr) => {{
200        let buffer: &mut [u8] = $buf;
201        $crate::BitMut::new(&mut buffer[$addr1], $addr2)
202    }};
203    ($buf:expr, B, $addr:expr) => {{
204        let buffer: &mut [u8] = $buf;
205        &mut buffer[$addr]
206    }};
207    ($buf:expr, W, $addr:expr) => {{
208        let buffer: &mut [u8] = $buf;
209        $crate::alignment_assert!(2, $addr);
210        $crate::WordMut::new((&mut buffer[$addr..$addr + 2]).try_into().unwrap())
211    }};
212    ($buf:expr, D, $addr:expr) => {{
213        let buffer: &mut [u8] = $buf;
214        $crate::alignment_assert!(4, $addr);
215        $crate::DWordMut::new((&mut buffer[$addr..$addr + 4]).try_into().unwrap())
216    }};
217    ($buf:expr, L, $addr:expr) => {{
218        let buffer: &mut [u8] = $buf;
219        $crate::alignment_assert!(8, $addr);
220        $crate::LWordMut::new((&mut buffer[$addr..$addr + 8]).try_into().unwrap())
221    }};
222    ($buf:expr, $addr1:expr, $addr2:expr) => {{
223        let buffer: &mut [u8] = $buf;
224        $crate::BitMut::new(&mut buffer[$addr1], $addr2)
225    }};
226}
227
228#[doc(hidden)]
229#[macro_export]
230macro_rules! tag_method {
231    ($vis:vis, $name:ident, mut, X, $addr1:literal, $addr2:literal) => {
232        #[inline(always)]
233        $vis fn $name(&mut self) -> $crate::BitMut<'_> {
234            $crate::BitMut::new(&mut self.buf[$addr1], $addr2)
235        }
236    };
237    ($vis:vis, $name:ident, mut, B, $addr:literal) => {
238        #[inline(always)]
239        $vis fn $name(&mut self) -> &mut u8 {
240            &mut self.buf[$addr]
241        }
242    };
243    ($vis:vis, $name:ident, mut, W, $addr:literal) => {
244        #[inline(always)]
245        $vis fn $name(&mut self) -> $crate::WordMut<'_> {
246            $crate::alignment_assert!(2, $addr);
247            $crate::WordMut::new((&mut self.buf[$addr..$addr + 2]).try_into().unwrap())
248        }
249    };
250    ($vis:vis, $name:ident, mut, D, $addr:literal) => {
251        #[inline(always)]
252        $vis fn $name(&mut self) -> $crate::DWordMut<'_> {
253            $crate::alignment_assert!(4, $addr);
254            $crate::DWordMut::new((&mut self.buf[$addr..$addr + 4]).try_into().unwrap())
255        }
256    };
257    ($vis:vis, $name:ident, mut, L, $addr:literal) => {
258        #[inline(always)]
259        $vis fn $name(&mut self) -> $crate::LWordMut<'_> {
260            $crate::alignment_assert!(8, $addr);
261            $crate::LWordMut::new((&mut self.buf[$addr..$addr + 8]).try_into().unwrap())
262        }
263    };
264    ($vis:vis, $name:ident, mut, $addr1:literal, $addr2:literal) => {
265        #[inline(always)]
266        $vis fn $name(&mut self) -> $crate::BitMut<'_> {
267            $crate::BitMut::new(&mut self.buf[$addr1], $addr2)
268        }
269    };
270    ($vis:vis, $name:ident, const, X, $addr1:literal, $addr2:literal) => {
271        #[inline(always)]
272        $vis fn $name(&self) -> bool {
273            self.buf[$addr1] & (1 << $addr2) != 0
274        }
275    };
276    ($vis:vis, $name:ident, const, B, $addr:literal) => {
277        #[inline(always)]
278        $vis fn $name(&self) -> u8 {
279            self.buf[$addr]
280        }
281    };
282    ($vis:vis, $name:ident, const, W, $addr:literal) => {
283        #[inline(always)]
284        $vis fn $name(&self) -> u16 {
285            $crate::alignment_assert!(2, $addr);
286            u16::from_be_bytes(self.buf[$addr..$addr + 2].try_into().unwrap())
287        }
288    };
289    ($vis:vis, $name:ident, const, D, $addr:literal) => {
290        #[inline(always)]
291        $vis fn $name(&self) -> u32 {
292            $crate::alignment_assert!(4, $addr);
293            u32::from_be_bytes(self.buf[$addr..$addr + 4].try_into().unwrap())
294        }
295    };
296    ($vis:vis, $name:ident, const, L, $addr:literal) => {
297        #[inline(always)]
298        $vis fn $name(&self) -> u64 {
299            $crate::alignment_assert!(8, $addr);
300            u64::from_be_bytes(self.buf[$addr..$addr + 8].try_into().unwrap())
301        }
302    };
303    ($vis:vis, $name:ident, const, $addr1:literal, $addr2:literal) => {
304        #[inline(always)]
305        $vis fn $name(&self) -> bool {
306            self.buf[$addr1] & (1 << $addr2) != 0
307        }
308    };
309}
310
311/// Build tag table for symbolic access into a process image buffer.
312///
313/// - You will get two structs, one for mutable and one for immutable access (or just one of them,
314///   if you want).
315/// - The process image has a fixed size which is always enforced.
316/// - The tag addresses are in the format described in the [`tag!()`][`tag`] macro.
317/// - The process image buffer is referenced, for owned buffers,
318///   see [`process_image_owned!{}`][`process_image_owned`].
319///
320/// ## Example
321/// ```
322/// process_image::process_image! {
323///     //                                      +-- Size of the process image in bytes
324///     //                                      V
325///     pub struct PiExample, mut PiExampleMut: 16 {
326///         //  +-- Tag Name  +-- Absolute Address
327///         //  V             V
328///         pub sensor_left: (X, 0, 0),     // %MX0.0
329///         pub sensor_right: (X, 0, 1),    // %MX0.1
330///         pub temperature: (D, 4),        // %MD4
331///         pub setpoint: (W, 2),           // %MW2
332///     }
333/// }
334///
335/// let mut pi_buf = [0x00; 16];
336/// let pi = PiExample::from(&pi_buf);
337///
338/// dbg!(pi.sensor_left());
339/// dbg!(pi.sensor_left());
340///
341/// // You need to use try_from() when using a slice.  The unwrap() will panic when the size of the
342/// // slice does not match the size of the process image.
343/// let pi_slice = &pi_buf[..];
344/// let pi = PiExample::try_from(pi_slice).unwrap();
345///
346/// // Mutable access:
347/// let pi_slice_mut = &mut pi_buf[..];
348/// let mut pi = PiExampleMut::try_from(pi_slice_mut).unwrap();
349/// *pi.temperature() = 1234;
350/// *pi.setpoint() = 72;
351/// *pi.sensor_left() = false;
352/// ```
353///
354/// As mentioned above, you can also generate just the mutable or just the immutable version:
355///
356/// ```
357/// # let buffer_in = [0x00u8; 16];
358/// # let mut buffer_out = [0x00u8; 8];
359/// process_image::process_image! {
360///     pub struct PiInputs: 16 {
361///         pub sensor_left: (X, 0, 0),     // %IX0.0
362///         pub sensor_right: (X, 0, 1),    // %IX0.1
363///         pub temperature: (D, 12),       // %ID12
364///     }
365/// }
366/// process_image::process_image! {
367///     pub struct mut PiOutputs: 8 {
368///         pub indicator_green: (X, 1, 0), // %QX1.0
369///         pub indicator_red: (X, 1, 2),   // %QX1.2
370///         pub setpoint: (W, 2),           // %QW2
371///     }
372/// }
373///
374/// let inp = PiInputs::try_from(&buffer_in).unwrap();
375/// let mut out = PiOutputs::try_from(&mut buffer_out).unwrap();
376///
377/// let left_or_right = inp.sensor_left() || inp.sensor_right();
378/// *out.indicator_green() = !left_or_right;
379/// *out.indicator_red() = left_or_right;
380/// ```
381#[macro_export]
382macro_rules! process_image {
383    (
384        $( #[$meta:meta] )*
385        $vis:vis struct $ProcessImage:ident, mut $ProcessImageMut:ident: $SIZE:literal {
386            $(
387                $( #[$field_meta:meta] )*
388                $field_vis:vis $field_name:ident: ($($tag:tt)+)
389            ),*
390            $(,)?
391        }
392    ) => {
393        $( #[$meta] )*
394        $vis struct $ProcessImage<'a> {
395            buf: &'a [u8; $SIZE],
396        }
397
398        impl<'a> $ProcessImage<'a> {
399            $(
400                $( #[$field_meta] )*
401                $crate::tag_method!($vis, $field_name, const, $($tag)+);
402            )*
403        }
404
405        impl<'a> ::core::convert::From<&'a [u8; $SIZE]> for $ProcessImage<'a> {
406            #[inline(always)]
407            fn from(buf: &'a [u8; $SIZE]) -> Self {
408                Self { buf }
409            }
410        }
411
412        impl<'a> ::core::convert::TryFrom<&'a [u8]> for $ProcessImage<'a> {
413            type Error = ::core::array::TryFromSliceError;
414
415            #[inline(always)]
416            fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
417                buf.try_into().map(|buf| Self { buf })
418            }
419        }
420
421        impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImage<'a> {
422            #[inline(always)]
423            fn as_ref(&self) -> &[u8] {
424                &self.buf[..]
425            }
426        }
427
428        impl<'a> ::core::fmt::Debug for $ProcessImage<'a> {
429            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
430                f.debug_struct(::core::stringify!($ProcessImage))
431                    $(
432                    .field(::core::stringify!($field_name), &self.$field_name())
433                    )*
434                    .finish()
435            }
436        }
437
438        $( #[$meta] )*
439        $vis struct $ProcessImageMut<'a> {
440            buf: &'a mut [u8; $SIZE],
441        }
442
443        impl<'a> ::core::convert::From<&'a mut [u8; $SIZE]> for $ProcessImageMut<'a> {
444            #[inline(always)]
445            fn from(buf: &'a mut [u8; $SIZE]) -> Self {
446                Self { buf }
447            }
448        }
449
450        impl<'a> ::core::convert::TryFrom<&'a mut [u8]> for $ProcessImageMut<'a> {
451            type Error = ::core::array::TryFromSliceError;
452
453            #[inline(always)]
454            fn try_from(buf: &'a mut [u8]) -> Result<Self, Self::Error> {
455                buf.try_into().map(|buf| Self { buf })
456            }
457        }
458
459        impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImageMut<'a> {
460            #[inline(always)]
461            fn as_ref(&self) -> &[u8] {
462                &self.buf[..]
463            }
464        }
465
466        impl<'a> ::core::convert::AsMut<[u8]> for $ProcessImageMut<'a> {
467            #[inline(always)]
468            fn as_mut(&mut self) -> &mut [u8] {
469                &mut self.buf[..]
470            }
471        }
472
473        impl<'a> ::core::fmt::Debug for $ProcessImageMut<'a> {
474            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
475                let pi = $ProcessImage::from(&*self.buf);
476                f.debug_struct(::core::stringify!($ProcessImageMut))
477                    $(
478                    .field(::core::stringify!($field_name), &pi.$field_name())
479                    )*
480                    .finish()
481            }
482        }
483
484        impl<'a> $ProcessImageMut<'a> {
485            $(
486                $( #[$field_meta] )*
487                $crate::tag_method!($vis, $field_name, mut, $($tag)+);
488            )*
489        }
490    };
491    (
492        $( #[$meta:meta] )*
493        $vis:vis struct mut $ProcessImageMut:ident: $SIZE:literal {
494            $(
495                $( #[$field_meta:meta] )*
496                $field_vis:vis $field_name:ident: ($($tag:tt)+)
497            ),*
498            $(,)?
499        }
500    ) => {
501        $( #[$meta] )*
502        $vis struct $ProcessImageMut<'a> {
503            buf: &'a mut [u8; $SIZE],
504        }
505
506        impl<'a> ::core::convert::From<&'a mut [u8; $SIZE]> for $ProcessImageMut<'a> {
507            #[inline(always)]
508            fn from(buf: &'a mut [u8; $SIZE]) -> Self {
509                Self { buf }
510            }
511        }
512
513        impl<'a> ::core::convert::TryFrom<&'a mut [u8]> for $ProcessImageMut<'a> {
514            type Error = ::core::array::TryFromSliceError;
515
516            #[inline(always)]
517            fn try_from(buf: &'a mut [u8]) -> Result<Self, Self::Error> {
518                buf.try_into().map(|buf| Self { buf })
519            }
520        }
521
522        impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImageMut<'a> {
523            #[inline(always)]
524            fn as_ref(&self) -> &[u8] {
525                &self.buf[..]
526            }
527        }
528
529        impl<'a> ::core::convert::AsMut<[u8]> for $ProcessImageMut<'a> {
530            #[inline(always)]
531            fn as_mut(&mut self) -> &mut [u8] {
532                &mut self.buf[..]
533            }
534        }
535
536        impl<'a> $ProcessImageMut<'a> {
537            $(
538                $( #[$field_meta] )*
539                $crate::tag_method!($vis, $field_name, mut, $($tag)+);
540            )*
541        }
542    };
543    (
544        $( #[$meta:meta] )*
545        $vis:vis struct $ProcessImage:ident: $SIZE:literal {
546            $(
547                $( #[$field_meta:meta] )*
548                $field_vis:vis $field_name:ident: ($($tag:tt)+)
549            ),*
550            $(,)?
551        }
552    ) => {
553        $( #[$meta] )*
554        $vis struct $ProcessImage<'a> {
555            buf: &'a [u8; $SIZE],
556        }
557
558        impl<'a> $ProcessImage<'a> {
559            $(
560                $( #[$field_meta] )*
561                $crate::tag_method!($vis, $field_name, const, $($tag)+);
562            )*
563        }
564
565        impl<'a> ::core::convert::From<&'a [u8; $SIZE]> for $ProcessImage<'a> {
566            #[inline(always)]
567            fn from(buf: &'a [u8; $SIZE]) -> Self {
568                Self { buf }
569            }
570        }
571
572        impl<'a> ::core::convert::TryFrom<&'a [u8]> for $ProcessImage<'a> {
573            type Error = ::core::array::TryFromSliceError;
574
575            #[inline(always)]
576            fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
577                buf.try_into().map(|buf| Self { buf })
578            }
579        }
580
581        impl<'a> ::core::convert::AsRef<[u8]> for $ProcessImage<'a> {
582            #[inline(always)]
583            fn as_ref(&self) -> &[u8] {
584                &self.buf[..]
585            }
586        }
587
588        impl<'a> ::core::fmt::Debug for $ProcessImage<'a> {
589            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
590                f.debug_struct(::core::stringify!($ProcessImage))
591                    $(
592                    .field(::core::stringify!($field_name), &self.$field_name())
593                    )*
594                    .finish()
595            }
596        }
597    };
598}
599
600/// Build tag table for symbolic access into an _owned_ process image buffer.
601///
602/// - This macro generates a struct that owns the process image buffer.  For a referenced variant,
603///   see [`process_image!{}`][`process_image`].
604/// - The method immediately available on the struct provide immutable access.  For mutable access,
605///   call `.as_mut()` to get a mutable accessor struct.  The methods on that one then provide mutable
606///   access.
607/// - The tag addresses are in the format described in the [`tag!()`][`tag`] macro.
608/// - You can construct a `process_image_owned` from zeros (`new_zeroed()`) or from a
609///   pre-initialized buffer by using `From<[u8; SIZE]` or `TryFrom<&[u8]>`.
610///
611/// ## Example
612/// ```
613/// process_image::process_image_owned! {
614///     //                                      +-- Size of the process image in bytes
615///     //                                      V
616///     pub struct PiExampleOwned, mut PiExampleMut: 16 {
617///         //  +-- Tag Name  +-- Absolute Address
618///         //  V             V
619///         pub sensor_left:  (X, 0, 0),    // %MX0.0
620///         pub sensor_right: (X, 0, 1),    // %MX0.1
621///         pub temperature:  (D, 4),       // %MD4
622///         pub setpoint:     (W, 2),       // %MW2
623///     }
624/// }
625///
626/// let pi = PiExampleOwned::new_zeroed();
627///
628/// dbg!(pi.sensor_left());
629/// dbg!(pi.sensor_left());
630///
631/// // You need to use try_from() when using a slice.  The unwrap() will panic when the size of the
632/// // slice does not match the size of the process image.
633/// let pi_buf = [0u8; 16];
634/// let pi_slice = &pi_buf[..];
635/// let mut pi = PiExampleOwned::try_from(pi_slice).unwrap();
636///
637/// // Mutable access:
638/// *pi.as_mut().temperature() = 1234;
639/// *pi.as_mut().setpoint() = 72;
640/// *pi.as_mut().sensor_left() = false;
641/// ```
642#[macro_export]
643macro_rules! process_image_owned {
644    (
645        $( #[$meta:meta] )*
646        $vis:vis struct $ProcessImage:ident, mut $ProcessImageMut:ident: $SIZE:literal {
647            $(
648                $( #[$field_meta:meta] )*
649                $field_vis:vis $field_name:ident: ($($tag:tt)+)
650            ),*
651            $(,)?
652        }
653    ) => {
654        $( #[$meta] )*
655        $vis struct $ProcessImage {
656            buf: [u8; $SIZE],
657        }
658
659        impl $ProcessImage {
660            #[allow(dead_code)]
661            #[inline(always)]
662            pub fn new_zeroed() -> Self {
663                Self {
664                    buf: [0u8; $SIZE],
665                }
666            }
667
668            #[allow(dead_code)]
669            #[inline(always)]
670            pub fn as_mut(&mut self) -> $ProcessImageMut {
671                $ProcessImageMut::from(&mut self.buf)
672            }
673
674            #[allow(dead_code)]
675            #[inline(always)]
676            pub fn as_slice(&self) -> &[u8] {
677                &self.buf[..]
678            }
679
680            #[allow(dead_code)]
681            #[inline(always)]
682            pub fn as_slice_mut(&mut self) -> &mut [u8] {
683                &mut self.buf[..]
684            }
685
686            $(
687                $( #[$field_meta] )*
688                $crate::tag_method!($vis, $field_name, const, $($tag)+);
689            )*
690        }
691
692        impl ::core::convert::From<&[u8; $SIZE]> for $ProcessImage {
693            #[inline(always)]
694            fn from(buf_in: &[u8; $SIZE]) -> Self {
695                let mut buf = [0u8; $SIZE];
696                buf.copy_from_slice(buf_in);
697                Self { buf }
698            }
699        }
700
701        impl ::core::convert::TryFrom<&[u8]> for $ProcessImage {
702            type Error = ::core::array::TryFromSliceError;
703
704            #[inline(always)]
705            fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
706                buf.try_into().map(|buf: &[u8; $SIZE]| Self { buf: buf.clone() })
707            }
708        }
709
710        impl ::core::convert::AsRef<[u8]> for $ProcessImage {
711            #[inline(always)]
712            fn as_ref(&self) -> &[u8] {
713                &self.buf[..]
714            }
715        }
716
717        impl ::core::convert::AsMut<[u8]> for $ProcessImage {
718            #[inline(always)]
719            fn as_mut(&mut self) -> &mut [u8] {
720                &mut self.buf[..]
721            }
722        }
723
724        impl ::core::fmt::Debug for $ProcessImage {
725            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
726                f.debug_struct(::core::stringify!($ProcessImage))
727                    $(
728                    .field(::core::stringify!($field_name), &self.$field_name())
729                    )*
730                    .finish()
731            }
732        }
733
734        $crate::process_image! {
735            $(#[$meta])*
736            $vis struct mut $ProcessImageMut: $SIZE {
737                $(
738                    $(#[$field_meta])*
739                    $field_vis $field_name: ($($tag)+),
740                )*
741            }
742        }
743    };
744}
745
746#[cfg(test)]
747mod tests {
748    #[test]
749    fn tag_macro_smoke1() {
750        let mut pi = [0x55, 0xaa, 0x00, 0xff];
751
752        assert_eq!(tag!(&pi, X, 2, 0), false);
753        assert_eq!(tag!(&pi, 3, 0), true);
754        assert_eq!(tag!(&pi, X, 0, 0), true);
755        assert_eq!(tag!(&pi, 0, 1), false);
756
757        *tag_mut!(&mut pi, X, 0, 0) = false;
758        assert_eq!(tag!(&pi, X, 0, 0), false);
759
760        assert_eq!(tag!(&pi, B, 2), 0x00);
761        *tag_mut!(&mut pi, X, 2, 7) = true;
762        assert_eq!(tag!(&pi, B, 2), 0x80);
763
764        assert_eq!(tag!(&pi, W, 2), 0x80ff);
765        assert_eq!(*tag_mut!(&mut pi, W, 2), 0x80ff);
766        assert_eq!(tag!(&pi, D, 0), 0x54aa80ff);
767        assert_eq!(*tag_mut!(&mut pi, D, 0), 0x54aa80ff);
768
769        *tag_mut!(&mut pi, W, 2) = 0xbeef;
770        assert_eq!(tag!(&pi, W, 2), 0xbeef);
771    }
772
773    process_image! {
774        pub struct TestPi, mut TestPiMut: 4 {
775            pub btn_start: (X, 1, 0),
776            pub btn_stop: (1, 1),
777            pub btn_reset: (X, 1, 2),
778            pub speed: (W, 2),
779            pub length: (B, 0),
780        }
781    }
782
783    #[test]
784    fn pi_macro_smoke1() {
785        let mut pi_buffer = [128, 0x55, 0xde, 0xad];
786
787        let pi = TestPi::try_from(&pi_buffer).unwrap();
788        assert_eq!(pi.btn_start(), true);
789        assert_eq!(pi.btn_stop(), false);
790        assert_eq!(pi.btn_reset(), true);
791        assert_eq!(pi.speed(), 0xdead);
792        assert_eq!(pi.length(), 128);
793
794        let mut pi = TestPiMut::try_from(&mut pi_buffer).unwrap();
795        assert_eq!(*pi.btn_start(), true);
796        assert_eq!(*pi.btn_stop(), false);
797        assert_eq!(*pi.btn_reset(), true);
798        assert_eq!(*pi.speed(), 0xdead);
799        assert_eq!(*pi.length(), 128);
800
801        *pi.btn_start() = false;
802        *pi.btn_stop() = true;
803
804        *pi.speed() = 1337;
805        *pi.length() = 1;
806
807        let pi = TestPi::try_from(&pi_buffer).unwrap();
808        assert_eq!(pi.btn_start(), false);
809        assert_eq!(pi.btn_stop(), true);
810        assert_eq!(pi.btn_reset(), true);
811        assert_eq!(pi.speed(), 1337);
812        assert_eq!(pi.length(), 1);
813
814        assert_eq!(tag!(&pi_buffer, 1, 0), false);
815        assert_eq!(tag!(&pi_buffer, W, 2), 1337);
816        assert_eq!(tag!(&pi_buffer, B, 0), 1);
817    }
818
819    process_image_owned! {
820        pub struct TestPiOwned, mut TestPiOwnedMut: 4 {
821            pub btn_start: (X, 1, 0),
822            pub btn_stop: (1, 1),
823            pub btn_reset: (X, 1, 2),
824            pub speed: (W, 2),
825            pub length: (B, 0),
826        }
827    }
828
829    #[test]
830    fn pi_owned_macro_smoke() {
831        let pi_buffer = [128, 0x55, 0xde, 0xad];
832
833        let mut pi = TestPiOwned::new_zeroed();
834        assert_eq!(pi.btn_start(), false);
835        assert_eq!(pi.btn_stop(), false);
836        assert_eq!(pi.btn_reset(), false);
837        assert_eq!(pi.speed(), 0);
838        assert_eq!(pi.length(), 0);
839
840        pi.as_slice_mut().copy_from_slice(&pi_buffer);
841        assert_eq!(pi.btn_start(), true);
842        assert_eq!(pi.btn_stop(), false);
843        assert_eq!(pi.btn_reset(), true);
844        assert_eq!(pi.speed(), 0xdead);
845        assert_eq!(pi.length(), 128);
846
847        let mut pi = TestPiOwned::try_from(&pi_buffer).unwrap();
848        assert_eq!(pi.btn_start(), true);
849        assert_eq!(pi.btn_stop(), false);
850        assert_eq!(pi.btn_reset(), true);
851        assert_eq!(pi.speed(), 0xdead);
852        assert_eq!(pi.length(), 128);
853
854        *pi.as_mut().btn_start() = false;
855        *pi.as_mut().btn_stop() = true;
856        *pi.as_mut().btn_reset() = true;
857
858        *pi.as_mut().speed() = 1337;
859        *pi.as_mut().length() = 1;
860
861        assert_eq!(pi.btn_start(), false);
862        assert_eq!(pi.btn_stop(), true);
863        assert_eq!(pi.btn_reset(), true);
864        assert_eq!(pi.speed(), 1337);
865        assert_eq!(pi.length(), 1);
866
867        let pi_buffer = pi.as_slice();
868        assert_eq!(tag!(&pi_buffer, 1, 0), false);
869        assert_eq!(tag!(&pi_buffer, W, 2), 1337);
870        assert_eq!(tag!(&pi_buffer, B, 0), 1);
871    }
872
873    #[test]
874    #[cfg_attr(
875        not(feature = "allow_unaligned_tags"),
876        should_panic(expected = "Word address must be divisible by 2")
877    )]
878    fn test_unaligned_word_tag() {
879        let buf = [
880            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
881        ];
882        assert_eq!(tag!(&buf, W, 1), 0xadbe);
883    }
884
885    #[test]
886    #[cfg_attr(
887        not(feature = "allow_unaligned_tags"),
888        should_panic(expected = "Word address must be divisible by 2")
889    )]
890    fn test_unaligned_word_tag_mut() {
891        let mut buf = [
892            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
893        ];
894        *tag_mut!(&mut buf, W, 1) = 0xcafe;
895        assert_eq!(tag!(&buf, W, 1), 0xcafe);
896    }
897
898    process_image_owned! {
899        pub struct TestPiPanic, mut TestPiPanicMut: 12 {
900            pub unaligned_word: (W, 1),
901            pub unaligned_dword: (D, 2),
902            pub unaligned_lword: (L, 4),
903        }
904    }
905
906    #[test]
907    #[cfg_attr(
908        not(feature = "allow_unaligned_tags"),
909        should_panic(expected = "Word address must be divisible by 2")
910    )]
911    fn test_unaligned_word() {
912        let pi = TestPiPanic::try_from(&[
913            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
914        ])
915        .unwrap();
916        assert_eq!(pi.unaligned_word(), 0xadbe);
917    }
918
919    #[test]
920    #[cfg_attr(
921        not(feature = "allow_unaligned_tags"),
922        should_panic(expected = "Word address must be divisible by 2")
923    )]
924    fn test_unaligned_word_mut() {
925        let mut pi = TestPiPanic::try_from(&[
926            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
927        ])
928        .unwrap();
929        *pi.as_mut().unaligned_word() = 0xcafe;
930        assert_eq!(pi.unaligned_word(), 0xcafe);
931    }
932
933    #[test]
934    #[cfg_attr(
935        not(feature = "allow_unaligned_tags"),
936        should_panic(expected = "Double word address must be divisible by 4")
937    )]
938    fn test_unaligned_dword() {
939        let pi = TestPiPanic::try_from(&[
940            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
941        ])
942        .unwrap();
943        assert_eq!(pi.unaligned_dword(), 0xbeefdead);
944    }
945
946    #[test]
947    #[cfg_attr(
948        not(feature = "allow_unaligned_tags"),
949        should_panic(expected = "Double word address must be divisible by 4")
950    )]
951    fn test_unaligned_dword_mut() {
952        let mut pi = TestPiPanic::try_from(&[
953            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
954        ])
955        .unwrap();
956        *pi.as_mut().unaligned_dword() = 0xc0ffee77;
957        assert_eq!(pi.unaligned_dword(), 0xc0ffee77);
958    }
959
960    #[test]
961    #[cfg_attr(
962        not(feature = "allow_unaligned_tags"),
963        should_panic(expected = "Long word address must be divisible by 8")
964    )]
965    fn test_unaligned_lword() {
966        let pi = TestPiPanic::try_from(&[
967            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
968        ])
969        .unwrap();
970        assert_eq!(pi.unaligned_lword(), 0xdeadbeefdeadbeef);
971    }
972
973    #[test]
974    #[cfg_attr(
975        not(feature = "allow_unaligned_tags"),
976        should_panic(expected = "Long word address must be divisible by 8")
977    )]
978    fn test_unaligned_lword_mut() {
979        let mut pi = TestPiPanic::try_from(&[
980            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
981        ])
982        .unwrap();
983        *pi.as_mut().unaligned_lword() = 0x7fff000000c0ffee;
984        assert_eq!(pi.unaligned_lword(), 0x7fff000000c0ffee);
985    }
986}