modbus-mapping-derive 0.4.0

Derive macros for modbus-mapping
Documentation
use crate::config::Config;
use crate::entry::{Address, Entry, Quantity, ScaleFactor};
use proc_macro2::Ident;
use syn::{Data, DeriveInput, Fields};

#[derive(Debug, Clone)]
pub struct Mapping(pub Vec<Entry>);

impl Mapping {
    pub fn new(ast: &DeriveInput) -> Self {
        let data_struct = match ast.data.clone() {
            Data::Struct(data_struct) => data_struct,
            _ => panic!("Trait can be implemented only for a struct."),
        };

        let named_fields = match data_struct.fields {
            Fields::Named(fields_named) => fields_named.named,
            _ => panic!("Trait can be implemented only for a struct with named fields."),
        };
        let mut map: Vec<Entry> = named_fields
            .into_iter()
            .filter(|field| {
                field
                    .attrs
                    .iter()
                    .any(|attr| attr.path().is_ident("modbus"))
            })
            .map(From::from)
            .collect::<Vec<_>>();

        map.sort_by_key(|x| x.addr);

        Self(map)
    }

    pub fn field_name_vec(&self) -> Vec<Ident> {
        self.0
            .iter()
            .map(|x| x.field_name_ident())
            .collect::<Vec<_>>()
    }

    pub fn field_ty_vec(&self) -> Vec<Ident> {
        self.0
            .iter()
            .map(|x| x.field_ty_ident())
            .collect::<Vec<_>>()
    }

    pub fn addr_vec(&self) -> Vec<Address> {
        self.0.iter().map(|x| x.addr).collect::<Vec<_>>()
    }

    pub fn ty_vec(&self) -> Vec<Ident> {
        self.0
            .iter()
            .map(|entry| entry.ty_ident())
            .collect::<Vec<_>>()
    }

    pub fn cnt_vec(&self) -> Vec<Quantity> {
        self.0.iter().map(|x| x.ty.word_size()).collect::<Vec<_>>()
    }

    pub fn fn_to_words_vec(&self) -> Vec<Ident> {
        self.0
            .iter()
            .map(|entry| entry.fn_to_words())
            .collect::<Vec<_>>()
    }

    pub fn fn_from_words_vec(&self) -> Vec<Ident> {
        self.0
            .iter()
            .map(|entry| entry.fn_from_words())
            .collect::<Vec<_>>()
    }

    pub fn x_vec(&self) -> Vec<ScaleFactor> {
        self.0.iter().map(|x| x.x).collect::<Vec<_>>()
    }

    pub fn register_range(&self) -> (Address, Address) {
        if self.0.is_empty() {
            (0, 0)
        } else {
            let first = self.0.first().unwrap();
            let last = self.0.last().unwrap();
            (first.addr, last.addr + last.ty.word_size())
        }
    }
}

impl Mapping {
    pub fn split_into_block_mappings(self, config: &Config) -> Vec<Self> {
        let mut block_mappings = vec![];

        let mut entries = Vec::with_capacity(self.0.len());

        for entry in self.0 {
            if entries.is_empty() {
                entries.push(entry);
            } else {
                let first = entries.first().unwrap();
                let last = entries.last().unwrap();
                let max_cond =
                    entry.addr + entry.ty.word_size() - first.addr <= config.max_cnt_per_request;
                let gap_cond = last.addr + last.ty.word_size() == entry.addr;

                if max_cond && (gap_cond || config.allow_register_gaps) {
                    entries.push(entry)
                } else {
                    let mapping = Mapping(entries.clone());
                    block_mappings.push(mapping);
                    entries.clear();
                    entries.push(entry);
                }
            }
        }

        let mapping = Mapping(entries.clone());
        block_mappings.push(mapping);

        block_mappings
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::entry::{DataType, WordOrder};

    #[test]
    fn test_split_into_block_mappings() {
        let addrs = [0, 4, 8, 10];
        let mapping = Mapping(
            addrs
                .into_iter()
                .map(|addr| Entry {
                    field_name: format!("field_{}", addr),
                    field_ty: format!("ty_{}", addr),
                    addr: addr,
                    ty: DataType::F32,
                    ord: WordOrder::BigEndian,
                    x: 1.0,
                    unit: format!("unit_{}", addr),
                })
                .collect(),
        );

        for (allow_register_gaps, expected) in [(true, vec![2, 2]), (false, vec![1, 1, 2])] {
            let config = Config {
                max_cnt_per_request: 8,
                allow_register_gaps,
            };
            let result = mapping
                .clone()
                .split_into_block_mappings(&config)
                .iter()
                .map(|m| m.0.len())
                .collect::<Vec<_>>();
            assert_eq!(result, expected)
        }
    }
}