Skip to main content

rusty_modbus_sim/
config.rs

1//! YAML-based simulator device configuration.
2
3use serde::{Deserialize, Serialize};
4
5/// Top-level simulator configuration (deserializable from YAML).
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct SimConfig {
8    /// Device identity.
9    pub device: DeviceConfig,
10    /// Register definitions.
11    #[serde(default)]
12    pub registers: RegisterConfig,
13    /// Fault injection rules.
14    #[serde(default)]
15    pub faults: Vec<FaultConfig>,
16}
17
18/// Device identity and settings.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct DeviceConfig {
21    /// Modbus unit ID. Default: 1.
22    #[serde(default = "default_unit_id")]
23    pub unit_id: u8,
24    /// Vendor name (for device identification FC 0x2B).
25    #[serde(default = "default_vendor")]
26    pub vendor_name: String,
27    /// Product code.
28    #[serde(default = "default_product")]
29    pub product_code: String,
30    /// Firmware revision.
31    #[serde(default = "default_revision")]
32    pub revision: String,
33    /// Listen address. Default: `127.0.0.1:0` (ephemeral port).
34    #[serde(default = "default_listen")]
35    pub listen_addr: String,
36}
37
38fn default_unit_id() -> u8 {
39    1
40}
41fn default_vendor() -> String {
42    String::from("rusty-modbus-sim")
43}
44fn default_product() -> String {
45    String::from("SIM")
46}
47fn default_revision() -> String {
48    String::from("0.1.0")
49}
50fn default_listen() -> String {
51    String::from("127.0.0.1:0")
52}
53
54/// Register definitions for all four data tables.
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct RegisterConfig {
57    /// Holding register blocks.
58    #[serde(default)]
59    pub holding: Vec<RegisterBlock>,
60    /// Input register blocks.
61    #[serde(default)]
62    pub input: Vec<RegisterBlock>,
63    /// Coil blocks.
64    #[serde(default)]
65    pub coils: Vec<CoilBlock>,
66    /// Discrete input blocks.
67    #[serde(default)]
68    pub discrete_inputs: Vec<CoilBlock>,
69}
70
71/// A contiguous block of registers with initial values.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct RegisterBlock {
74    /// Starting address.
75    pub address: u16,
76    /// Number of registers in this block.
77    pub count: u16,
78    /// Initial values (padded with 0 if shorter than count).
79    #[serde(default)]
80    pub initial: Vec<u16>,
81    /// Update mode for dynamic simulation.
82    #[serde(default)]
83    pub mode: UpdateMode,
84    /// Minimum value for random mode.
85    #[serde(default)]
86    pub min: u16,
87    /// Maximum value for random mode.
88    #[serde(default = "default_max_u16")]
89    pub max: u16,
90}
91
92fn default_max_u16() -> u16 {
93    1000
94}
95
96/// A contiguous block of coils/discrete inputs.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CoilBlock {
99    /// Starting address.
100    pub address: u16,
101    /// Number of coils.
102    pub count: u16,
103    /// Initial values (padded with `false` if shorter).
104    #[serde(default)]
105    pub initial: Vec<bool>,
106}
107
108/// How register values update between reads.
109#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum UpdateMode {
112    /// Values are static — only change via writes.
113    #[default]
114    Static,
115    /// Values are randomized within `[min, max]` on each read.
116    Random,
117    /// Values increment by 1 on each read, wrapping at `max`.
118    Increment,
119}
120
121/// Fault injection configuration.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct FaultConfig {
124    /// Type of fault to inject.
125    #[serde(rename = "type")]
126    pub fault_type: FaultType,
127    /// Trigger condition — when to inject the fault.
128    #[serde(default)]
129    pub trigger: FaultTrigger,
130    /// Exception code to return (for `exception` type).
131    #[serde(default)]
132    pub exception: Option<String>,
133    /// Delay in milliseconds (for `delay` type).
134    #[serde(default)]
135    pub delay_ms: Option<u64>,
136    /// Probability of fault occurring (0.0–1.0, for `corrupt` type).
137    #[serde(default)]
138    pub probability: Option<f64>,
139}
140
141/// Fault type.
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum FaultType {
145    /// Return an exception response.
146    Exception,
147    /// Add artificial latency.
148    Delay,
149    /// Drop the response entirely (simulate timeout).
150    Timeout,
151    /// Corrupt CRC (RTU only).
152    Corrupt,
153}
154
155/// Trigger condition for fault injection.
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
157pub struct FaultTrigger {
158    /// Match a specific function code name (e.g., `read_holding_registers`).
159    pub function: Option<String>,
160    /// Match a specific address.
161    pub address: Option<u16>,
162    /// Match a specific unit ID.
163    pub unit_id: Option<u8>,
164}