use serde::{Deserialize, Serialize};
use super::RegisterType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AddressRange {
pub start: u16,
pub end: u16,
}
impl AddressRange {
#[inline]
pub const fn new(start: u16, end: u16) -> Self {
assert!(start <= end, "start must be <= end");
Self { start, end }
}
#[inline]
pub const fn from_count(count: u16) -> Self {
Self {
start: 0,
end: count.saturating_sub(1),
}
}
#[inline]
pub const fn count(&self) -> u32 {
(self.end as u32) - (self.start as u32) + 1
}
#[inline]
pub const fn contains(&self, address: u16) -> bool {
address >= self.start && address <= self.end
}
#[inline]
pub const fn contains_range(&self, start: u16, count: u16) -> bool {
if count == 0 {
return false;
}
let end = match start.checked_add(count.saturating_sub(1)) {
Some(e) => e,
None => return false,
};
start >= self.start && end <= self.end
}
pub fn validate(&self, start: u16, count: u16) -> Result<(), AddressRangeError> {
if count == 0 {
return Err(AddressRangeError::ZeroQuantity);
}
let end = start
.checked_add(count.saturating_sub(1))
.ok_or(AddressRangeError::Overflow)?;
if start < self.start {
return Err(AddressRangeError::BelowMinimum {
address: start,
minimum: self.start,
});
}
if end > self.end {
return Err(AddressRangeError::AboveMaximum {
address: end,
maximum: self.end,
});
}
Ok(())
}
}
impl Default for AddressRange {
fn default() -> Self {
Self::new(0, 9999)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum AddressRangeError {
#[error("Zero quantity not allowed")]
ZeroQuantity,
#[error("Address overflow")]
Overflow,
#[error("Address {address} below minimum {minimum}")]
BelowMinimum { address: u16, minimum: u16 },
#[error("Address {address} above maximum {maximum}")]
AboveMaximum { address: u16, maximum: u16 },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RegisterRangeConfig {
pub range: AddressRange,
pub default_value: DefaultValue,
pub enabled: bool,
}
impl RegisterRangeConfig {
pub const fn new(range: AddressRange, default_value: DefaultValue) -> Self {
Self {
range,
default_value,
enabled: true,
}
}
pub const fn disabled() -> Self {
Self {
range: AddressRange::new(0, 0),
default_value: DefaultValue::Zero,
enabled: false,
}
}
pub const fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
}
impl Default for RegisterRangeConfig {
fn default() -> Self {
Self::new(AddressRange::default(), DefaultValue::Zero)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DefaultValue {
Zero,
Value(u16),
Random,
RandomRange(u16, u16),
}
impl DefaultValue {
#[inline]
pub fn get_bool(&self) -> bool {
match self {
Self::Zero => false,
Self::Value(v) => *v != 0,
Self::Random | Self::RandomRange(_, _) => rand_bool(),
}
}
#[inline]
pub fn get_word(&self) -> u16 {
match self {
Self::Zero => 0,
Self::Value(v) => *v,
Self::Random => rand_u16(),
Self::RandomRange(min, max) => rand_range(*min, *max),
}
}
}
impl Default for DefaultValue {
fn default() -> Self {
Self::Zero
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InitializationMode {
Eager,
Lazy,
Pattern(Vec<u8>),
Snapshot(RegisterSnapshot),
}
impl Default for InitializationMode {
fn default() -> Self {
Self::Lazy
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct RegisterSnapshot {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub coils: Vec<(u16, bool)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub discrete_inputs: Vec<(u16, bool)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub holding_registers: Vec<(u16, u16)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input_registers: Vec<(u16, u16)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RegisterStoreConfig {
pub coils: RegisterRangeConfig,
pub discrete_inputs: RegisterRangeConfig,
pub holding_registers: RegisterRangeConfig,
pub input_registers: RegisterRangeConfig,
pub initialization: InitializationMode,
#[serde(default = "default_true")]
pub callbacks_enabled: bool,
}
fn default_true() -> bool {
true
}
impl RegisterStoreConfig {
pub fn new(
coils_range: AddressRange,
discrete_inputs_range: AddressRange,
holding_registers_range: AddressRange,
input_registers_range: AddressRange,
) -> Self {
Self {
coils: RegisterRangeConfig::new(coils_range, DefaultValue::Zero),
discrete_inputs: RegisterRangeConfig::new(discrete_inputs_range, DefaultValue::Zero),
holding_registers: RegisterRangeConfig::new(
holding_registers_range,
DefaultValue::Zero,
),
input_registers: RegisterRangeConfig::new(input_registers_range, DefaultValue::Zero),
initialization: InitializationMode::default(),
callbacks_enabled: true,
}
}
pub fn with_max_addresses(max: u16) -> Self {
let range = AddressRange::new(0, max);
Self::new(range, range, range, range)
}
pub fn minimal() -> Self {
let range = AddressRange::new(0, 99);
Self::new(range, range, range, range)
}
pub fn large() -> Self {
let range = AddressRange::new(0, 65534);
Self::new(range, range, range, range)
}
pub fn large_scale() -> Self {
let coils = AddressRange::new(0, 9999);
let discrete = AddressRange::new(0, 9999);
let holding = AddressRange::new(0, 9999);
let input = AddressRange::new(0, 9999);
Self::new(coils, discrete, holding, input).without_callbacks()
}
pub fn with_initialization(mut self, mode: InitializationMode) -> Self {
self.initialization = mode;
self
}
pub fn without_callbacks(mut self) -> Self {
self.callbacks_enabled = false;
self
}
pub fn get_range_config(&self, reg_type: RegisterType) -> &RegisterRangeConfig {
match reg_type {
RegisterType::Coil => &self.coils,
RegisterType::DiscreteInput => &self.discrete_inputs,
RegisterType::HoldingRegister => &self.holding_registers,
RegisterType::InputRegister => &self.input_registers,
}
}
pub fn get_range(&self, reg_type: RegisterType) -> AddressRange {
self.get_range_config(reg_type).range
}
pub fn validate_range(
&self,
reg_type: RegisterType,
start: u16,
count: u16,
) -> Result<(), AddressRangeError> {
self.get_range(reg_type).validate(start, count)
}
}
impl Default for RegisterStoreConfig {
fn default() -> Self {
Self::with_max_addresses(9999)
}
}
fn rand_bool() -> bool {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() % 2 == 0)
.unwrap_or(false)
}
fn rand_u16() -> u16 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| (d.as_nanos() & 0xFFFF) as u16)
.unwrap_or(0)
}
fn rand_range(min: u16, max: u16) -> u16 {
if min >= max {
return min;
}
let range = (max - min) as u32 + 1;
let random = rand_u16() as u32;
min + (random % range) as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_address_range() {
let range = AddressRange::new(0, 99);
assert_eq!(range.count(), 100);
assert!(range.contains(0));
assert!(range.contains(99));
assert!(!range.contains(100));
assert!(range.contains_range(0, 100));
assert!(range.contains_range(50, 50));
assert!(!range.contains_range(50, 51));
assert!(!range.contains_range(0, 101));
}
#[test]
fn test_address_range_validation() {
let range = AddressRange::new(10, 99);
assert!(range.validate(10, 1).is_ok());
assert!(range.validate(10, 90).is_ok());
assert!(range.validate(99, 1).is_ok());
assert!(matches!(
range.validate(0, 1),
Err(AddressRangeError::BelowMinimum { .. })
));
assert!(matches!(
range.validate(100, 1),
Err(AddressRangeError::AboveMaximum { .. })
));
assert!(matches!(
range.validate(10, 0),
Err(AddressRangeError::ZeroQuantity)
));
}
#[test]
fn test_default_value() {
assert!(!DefaultValue::Zero.get_bool());
assert_eq!(DefaultValue::Zero.get_word(), 0);
assert!(DefaultValue::Value(1).get_bool());
assert!(!DefaultValue::Value(0).get_bool());
assert_eq!(DefaultValue::Value(12345).get_word(), 12345);
}
#[test]
fn test_config_default() {
let config = RegisterStoreConfig::default();
assert_eq!(config.coils.range.start, 0);
assert_eq!(config.coils.range.end, 9999);
assert!(config.callbacks_enabled);
assert_eq!(config.initialization, InitializationMode::Lazy);
}
#[test]
fn test_config_presets() {
let minimal = RegisterStoreConfig::minimal();
assert_eq!(minimal.coils.range.count(), 100);
let large = RegisterStoreConfig::large();
assert_eq!(large.coils.range.count(), 65535);
}
}