modbus-sim 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. modbus_sim-0.2.0/CHANGELOG.md +46 -0
  2. {modbus_sim-0.1.0/src/modbus_sim.egg-info → modbus_sim-0.2.0}/PKG-INFO +54 -11
  3. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/README.md +53 -10
  4. modbus_sim-0.2.0/examples/real-devices.yaml +9 -0
  5. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/pyproject.toml +1 -1
  6. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/__init__.py +1 -1
  7. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/datatypes.py +2 -2
  8. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/device.py +1 -1
  9. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/faults.py +3 -0
  10. modbus_sim-0.2.0/src/modbus_sim/models.py +719 -0
  11. modbus_sim-0.2.0/src/modbus_sim/profiles/eastron-sdm630.yaml +233 -0
  12. modbus_sim-0.2.0/src/modbus_sim/profiles/omron-e5cc.yaml +81 -0
  13. modbus_sim-0.2.0/src/modbus_sim/profiles/sunspec-inverter.yaml +136 -0
  14. {modbus_sim-0.1.0 → modbus_sim-0.2.0/src/modbus_sim.egg-info}/PKG-INFO +54 -11
  15. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/SOURCES.txt +5 -0
  16. modbus_sim-0.2.0/tests/test_device_profiles.py +269 -0
  17. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_profile.py +2 -1
  18. modbus_sim-0.1.0/CHANGELOG.md +0 -24
  19. modbus_sim-0.1.0/src/modbus_sim/models.py +0 -431
  20. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/LICENSE +0 -0
  21. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/MANIFEST.in +0 -0
  22. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/examples/plant.yaml +0 -0
  23. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/examples/test_example.py +0 -0
  24. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/setup.cfg +0 -0
  25. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/__main__.py +0 -0
  26. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/bench.py +0 -0
  27. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/cli.py +0 -0
  28. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/client.py +0 -0
  29. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/control.py +0 -0
  30. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/frame.py +0 -0
  31. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profile.py +0 -0
  32. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/energy-meter.yaml +0 -0
  33. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/temperature-controller.yaml +0 -0
  34. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/vfd.yaml +0 -0
  35. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/pytest_plugin.py +0 -0
  36. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/server.py +0 -0
  37. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/signals.py +0 -0
  38. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/dependency_links.txt +0 -0
  39. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/entry_points.txt +0 -0
  40. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/requires.txt +0 -0
  41. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/top_level.txt +0 -0
  42. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/conftest.py +0 -0
  43. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_cli.py +0 -0
  44. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_datatypes.py +0 -0
  45. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_device.py +0 -0
  46. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_frame.py +0 -0
  47. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_models.py +0 -0
  48. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_plugin.py +0 -0
  49. {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_server.py +0 -0
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## 0.2.0 - 2026-11-01
8
+
9
+ - Profiles after the published register maps of real devices:
10
+ `eastron-sdm630` (Eastron SDM630 protocol V1.5, float32 ABCD, input and set-up
11
+ registers), `omron-e5cc` (Omron E5CC two-byte mode with status words, operation
12
+ commands and the communications-writing enable) and `sunspec-inverter` (SunSpec models 1 and
13
+ 103 with scale factors)
14
+ - New model `pv_inverter`: day curve, SunSpec operating states and event bits,
15
+ faults `grid_loss`, `ground_fault`, `over_temp`
16
+ - `energy_meter` model: apparent and reactive power per phase, phase angles,
17
+ averages, neutral current, per-phase import/export/total kWh and kvarh, kVAh,
18
+ Ah, demand values with maxima; parameters `pf_sign` and `demand_period`
19
+ - `temperature_controller` model: operation command register, an optional
20
+ write enable (`comms_write`) and the Omron E5@C status words
21
+ - A model can refuse a write with exception 04 instead of 03 (the E5CC answers
22
+ 04, operation error, while communications writing is off)
23
+ - Browser demo at https://modbus-sim.steitec.net (`web/`): the three real-device
24
+ profiles in Pyodide with a Modbus terminal that shows the TCP frames, live device
25
+ fronts and fault buttons; example bench `examples/real-devices.yaml`
26
+ - Fixed: the word order table named Eastron among CDAB devices; Eastron meters
27
+ send the most significant register first (ABCD)
28
+
29
+ ## 0.1.0 - 2026-10-31
30
+
31
+ - Modbus TCP, RTU over TCP, and RTU on a serial port (pyserial) or a virtual
32
+ serial port (pty, Linux/macOS); several devices by unit ID behind one server,
33
+ RTU broadcast
34
+ - Function codes 01-08 (08: sub-function 00), 15, 16, 22, 23 and 43/14 (device
35
+ identification) with the exceptions of the specification
36
+ - YAML profiles: points with type (16/32/64-bit integers, floats, strings, bits),
37
+ word order (ABCD/CDAB/BADC/DCBA), scale, access, limits, signal sources
38
+ - Bundled devices with behaviour: `energy-meter`, `temperature-controller`, `vfd`
39
+ - Bench files for several devices with per-device overrides
40
+ - Fault injection (`no_response`, `delay`, `exception`, `busy`, `drop_connection`,
41
+ `bad_crc`, `wrong_transaction`, `wrong_unit`, `truncate`, `freeze`, `drift`, `trip`,
42
+ `sensor_break`, `phase_loss`) through a JSON control channel and the CLI
43
+ - Command line (`serve`, `profiles`, `map`, `read`, `write`, `ident`, `inject`, `clear`,
44
+ `get`, `set`, `list`) and a small Python client
45
+ - pytest plugin with the `modbus_bench` and `modbus_bench_factory` fixtures
46
+ - Test suite cross-checks the simulator against pymodbus
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modbus-sim
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Simulated Modbus devices with behaviour and fault injection: energy meter, temperature controller, VFD over Modbus TCP and RTU
5
5
  Author: Ralf Steinert
6
6
  License-Expression: MIT
@@ -42,13 +42,17 @@ Dynamic: license-file
42
42
  # modbus-sim
43
43
 
44
44
  [![CI](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml)
45
- [![PyPI](https://img.shields.io/pypi/v/modbus-sim)](https://pypi.org/project/modbus-sim/)
46
- [![Python](https://img.shields.io/pypi/pyversions/modbus-sim)](https://pypi.org/project/modbus-sim/)
47
- [![License](https://img.shields.io/pypi/l/modbus-sim)](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
45
+ [![PyPI](https://img.shields.io/pypi/v/modbus-sim?v=0.2.0)](https://pypi.org/project/modbus-sim/)
46
+ [![Python](https://img.shields.io/pypi/pyversions/modbus-sim?v=0.2.0)](https://pypi.org/project/modbus-sim/)
47
+ [![License](https://img.shields.io/pypi/l/modbus-sim?v=0.2.0)](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
48
48
 
49
49
  Simulated Modbus devices that **behave**: an energy meter whose counters count,
50
50
  a temperature controller that heats towards its setpoint, a frequency converter
51
- that ramps up when you set the run bit and trips when you tell it to. They speak
51
+ that ramps up when you set the run bit and trips when you tell it to. Three
52
+ profiles follow the published register maps of real devices: an Eastron SDM630
53
+ energy meter, an Omron E5CC temperature controller and a SunSpec PV inverter.
54
+ **[Try them in your browser](https://modbus-sim.steitec.net)**, no install needed.
55
+ They speak
52
56
  Modbus TCP, RTU over TCP and RTU on a serial line or a virtual serial port, so
53
57
  you can develop and test SCADA links, data loggers, gateways, Node-RED flows or
54
58
  your own Modbus code without the hardware, also in CI.
@@ -137,6 +141,37 @@ such as com0com.
137
141
  | `energy-meter` | three-phase meter: voltages, currents, powers, power factor, frequency, energy import/export | float32 in **CDAB** word order, Wh counters as uint64, reset coil |
138
142
  | `temperature-controller` | PI controller heating a first-order process; run/stop, auto/manual, alarm limits | int16 scaled by 0.1; sensor break reads 3276.7 (0x7FFF) |
139
143
  | `vfd` | frequency converter: control word, speed reference, accel/decel ramps, status word, current, voltage, rpm, run hours | trip is latched until a fault reset (rising edge) and a new run edge |
144
+ | `eastron-sdm630` | Eastron SDM630 three-phase meter: the full input register map of protocol V1.5 (volts, amps, powers, power factor, phase angle, THD, demand, per-phase import/export kWh and kvarh) and the set-up holding registers | float32 in **ABCD**, input registers 30001 ff.; unlisted addresses read 0 |
145
+ | `omron-e5cc` | Omron E5CC temperature controller in two-byte mode: PV, status, set point, MV, manual MV, operation commands (run/stop, auto/manual, write mode) | writes answer exception 04 until **communications writing** is switched on with operation command 0x0001 |
146
+ | `sunspec-inverter` | three-phase PV inverter with the SunSpec map that SMA, Fronius, SolarEdge and others publish: model 1 (common) + model 103, on a day curve | "SunS" at 40000, integer + scale-factor registers, SunSpec states and event bits |
147
+
148
+ ### Profiles after real devices
149
+
150
+ `eastron-sdm630`, `omron-e5cc` and `sunspec-inverter` are built from public
151
+ documentation, so code written against them should work against the real
152
+ device's register map:
153
+
154
+ | profile | source | simplified |
155
+ |---|---|---|
156
+ | `eastron-sdm630` | Eastron "SDM630 Modbus Protocol" V1.5 | odd register counts and requests over 80 registers are accepted; THD is constant with noise; demand is an exponential average; set-up registers are stored only (no password, restart or register-order switching); a `reset_energy` coil is added |
157
+ | `omron-e5cc` | Omron "E5@C Communications Manual" (H175), sections 3-2, 4 and 5 | two-byte mode only; status sets only the bits the model knows (input error, control output, write mode, run/stop, communications writing, auto/manual); no heater current, alarm values or status 2; commands other than communications writing, run/stop, auto/manual and write mode are accepted without effect |
158
+ | `sunspec-inverter` | SunSpec model definitions (github.com/sunspec/models) | fixed scale factors; no control models (120-127); generic manufacturer "steitec" |
159
+
160
+ Each was checked against a third-party client: `sdm_modbus` reads the
161
+ simulated SDM630, and the SunSpec Alliance's `pysunspec2` scans and decodes
162
+ the inverter. steitec is not affiliated with Eastron, Omron or the SunSpec
163
+ Alliance; product names identify the documented register maps only. Firmware
164
+ versions of real devices differ, so check your device's documentation.
165
+
166
+ ```console
167
+ $ modbus-sim serve omron-e5cc &
168
+ $ modbus-sim write holding 0x2103 100 --scale 0.1 # refused: exception 04
169
+ $ modbus-sim write holding 0 0x0001 # communications writing on
170
+ $ modbus-sim write holding 0x2103 100 --scale 0.1 # set point 100.0 degC
171
+ $ modbus-sim serve sunspec-inverter --port 5030 \
172
+ --control-port 5031 &
173
+ $ modbus-sim read holding 40000 -c 2 -t string --port 5030
174
+ ```
140
175
 
141
176
  `modbus-sim map PROFILE` prints the complete register map, with the 0-based
142
177
  protocol address and the conventional reference (`400001` = holding
@@ -192,8 +227,9 @@ points:
192
227
  - **min/max**: written values outside are exception 03
193
228
  - **sources**: `constant` (value), `ramp` (start, stop, period), `sine`
194
229
  (offset, amplitude, period), `noise` (sigma); a list is added up
195
- - **model**: the three bundled profiles bind points to a model with
196
- `model: <variable>`; `model_params` tune it (`time_scale` speeds any model up)
230
+ - **model**: the bundled profiles bind points to a model (`energy_meter`,
231
+ `temperature_controller`, `vfd`, `pv_inverter`) with `model: <variable>`;
232
+ `model_params` tune it (`time_scale` speeds any model up)
197
233
  - **bits**: names for the bits of a uint16 register (shown by `map`)
198
234
 
199
235
  A point named `exception_status` provides the byte returned by function 07.
@@ -232,7 +268,8 @@ the bench file.
232
268
  Anything else answers exception 01. Exceptions follow the specification:
233
269
  02 for unmapped or read-only addresses and for writes that would change only
234
270
  part of a 32/64-bit value, 03 for bad quantities, byte counts and values
235
- outside `min`/`max`.
271
+ outside `min`/`max`, 04 where a device refuses a write in its present state
272
+ (`omron-e5cc` with communications writing off).
236
273
 
237
274
  **Unit IDs.** On Modbus TCP an unknown unit answers exception 0x0B (gateway
238
275
  target failed to respond); a single device also answers unit 0 and 255. On
@@ -260,7 +297,10 @@ clear DEVICE [FAULT]`:
260
297
  | `drift` | point, per_second | a numeric point runs away |
261
298
  | `trip` | code=1 | `vfd`: fault trip, coasts to stop, latched until reset |
262
299
  | `sensor_break` | | `temperature-controller`: PV reads 3276.7, output off, alarm |
263
- | `phase_loss` | phase | `energy-meter`: that phase reads 0 V, 0 A |
300
+ | `phase_loss` | phase | `energy-meter`, `eastron-sdm630`: that phase reads 0 V, 0 A |
301
+ | `grid_loss` | | `sunspec-inverter`: state 7 (fault), event bit 4, AC values 0 |
302
+ | `ground_fault` | | `sunspec-inverter`: state 7 (fault), event bit 0 |
303
+ | `over_temp` | | `sunspec-inverter`: state 5 (throttled) at half power, event bit 7 |
264
304
 
265
305
  ## Control channel
266
306
 
@@ -323,8 +363,11 @@ starts. See [examples/test_example.py](examples/test_example.py).
323
363
 
324
364
  - For development and testing; not a certified or conformance-tested Modbus
325
365
  implementation.
326
- - The bundled profiles are invented devices with plausible register maps, not
327
- copies of real products. Models are plausible, not physically accurate.
366
+ - `energy-meter`, `temperature-controller` and `vfd` are invented devices with
367
+ plausible register maps. The profiles after real devices follow the
368
+ published register maps but not every behaviour of the device (see
369
+ [Profiles after real devices](#profiles-after-real-devices)). Models are
370
+ plausible, not physically accurate.
328
371
  - No Modbus ASCII, no Modbus/TCP Security (TLS), no function codes 11, 12, 17,
329
372
  20, 21, 24; diagnostics only sub-function 00.
330
373
  - RTU framing finds the end of a request from its function code, and the
@@ -6,13 +6,17 @@
6
6
  # modbus-sim
7
7
 
8
8
  [![CI](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml)
9
- [![PyPI](https://img.shields.io/pypi/v/modbus-sim)](https://pypi.org/project/modbus-sim/)
10
- [![Python](https://img.shields.io/pypi/pyversions/modbus-sim)](https://pypi.org/project/modbus-sim/)
11
- [![License](https://img.shields.io/pypi/l/modbus-sim)](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
9
+ [![PyPI](https://img.shields.io/pypi/v/modbus-sim?v=0.2.0)](https://pypi.org/project/modbus-sim/)
10
+ [![Python](https://img.shields.io/pypi/pyversions/modbus-sim?v=0.2.0)](https://pypi.org/project/modbus-sim/)
11
+ [![License](https://img.shields.io/pypi/l/modbus-sim?v=0.2.0)](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
12
12
 
13
13
  Simulated Modbus devices that **behave**: an energy meter whose counters count,
14
14
  a temperature controller that heats towards its setpoint, a frequency converter
15
- that ramps up when you set the run bit and trips when you tell it to. They speak
15
+ that ramps up when you set the run bit and trips when you tell it to. Three
16
+ profiles follow the published register maps of real devices: an Eastron SDM630
17
+ energy meter, an Omron E5CC temperature controller and a SunSpec PV inverter.
18
+ **[Try them in your browser](https://modbus-sim.steitec.net)**, no install needed.
19
+ They speak
16
20
  Modbus TCP, RTU over TCP and RTU on a serial line or a virtual serial port, so
17
21
  you can develop and test SCADA links, data loggers, gateways, Node-RED flows or
18
22
  your own Modbus code without the hardware, also in CI.
@@ -101,6 +105,37 @@ such as com0com.
101
105
  | `energy-meter` | three-phase meter: voltages, currents, powers, power factor, frequency, energy import/export | float32 in **CDAB** word order, Wh counters as uint64, reset coil |
102
106
  | `temperature-controller` | PI controller heating a first-order process; run/stop, auto/manual, alarm limits | int16 scaled by 0.1; sensor break reads 3276.7 (0x7FFF) |
103
107
  | `vfd` | frequency converter: control word, speed reference, accel/decel ramps, status word, current, voltage, rpm, run hours | trip is latched until a fault reset (rising edge) and a new run edge |
108
+ | `eastron-sdm630` | Eastron SDM630 three-phase meter: the full input register map of protocol V1.5 (volts, amps, powers, power factor, phase angle, THD, demand, per-phase import/export kWh and kvarh) and the set-up holding registers | float32 in **ABCD**, input registers 30001 ff.; unlisted addresses read 0 |
109
+ | `omron-e5cc` | Omron E5CC temperature controller in two-byte mode: PV, status, set point, MV, manual MV, operation commands (run/stop, auto/manual, write mode) | writes answer exception 04 until **communications writing** is switched on with operation command 0x0001 |
110
+ | `sunspec-inverter` | three-phase PV inverter with the SunSpec map that SMA, Fronius, SolarEdge and others publish: model 1 (common) + model 103, on a day curve | "SunS" at 40000, integer + scale-factor registers, SunSpec states and event bits |
111
+
112
+ ### Profiles after real devices
113
+
114
+ `eastron-sdm630`, `omron-e5cc` and `sunspec-inverter` are built from public
115
+ documentation, so code written against them should work against the real
116
+ device's register map:
117
+
118
+ | profile | source | simplified |
119
+ |---|---|---|
120
+ | `eastron-sdm630` | Eastron "SDM630 Modbus Protocol" V1.5 | odd register counts and requests over 80 registers are accepted; THD is constant with noise; demand is an exponential average; set-up registers are stored only (no password, restart or register-order switching); a `reset_energy` coil is added |
121
+ | `omron-e5cc` | Omron "E5@C Communications Manual" (H175), sections 3-2, 4 and 5 | two-byte mode only; status sets only the bits the model knows (input error, control output, write mode, run/stop, communications writing, auto/manual); no heater current, alarm values or status 2; commands other than communications writing, run/stop, auto/manual and write mode are accepted without effect |
122
+ | `sunspec-inverter` | SunSpec model definitions (github.com/sunspec/models) | fixed scale factors; no control models (120-127); generic manufacturer "steitec" |
123
+
124
+ Each was checked against a third-party client: `sdm_modbus` reads the
125
+ simulated SDM630, and the SunSpec Alliance's `pysunspec2` scans and decodes
126
+ the inverter. steitec is not affiliated with Eastron, Omron or the SunSpec
127
+ Alliance; product names identify the documented register maps only. Firmware
128
+ versions of real devices differ, so check your device's documentation.
129
+
130
+ ```console
131
+ $ modbus-sim serve omron-e5cc &
132
+ $ modbus-sim write holding 0x2103 100 --scale 0.1 # refused: exception 04
133
+ $ modbus-sim write holding 0 0x0001 # communications writing on
134
+ $ modbus-sim write holding 0x2103 100 --scale 0.1 # set point 100.0 degC
135
+ $ modbus-sim serve sunspec-inverter --port 5030 \
136
+ --control-port 5031 &
137
+ $ modbus-sim read holding 40000 -c 2 -t string --port 5030
138
+ ```
104
139
 
105
140
  `modbus-sim map PROFILE` prints the complete register map, with the 0-based
106
141
  protocol address and the conventional reference (`400001` = holding
@@ -156,8 +191,9 @@ points:
156
191
  - **min/max**: written values outside are exception 03
157
192
  - **sources**: `constant` (value), `ramp` (start, stop, period), `sine`
158
193
  (offset, amplitude, period), `noise` (sigma); a list is added up
159
- - **model**: the three bundled profiles bind points to a model with
160
- `model: <variable>`; `model_params` tune it (`time_scale` speeds any model up)
194
+ - **model**: the bundled profiles bind points to a model (`energy_meter`,
195
+ `temperature_controller`, `vfd`, `pv_inverter`) with `model: <variable>`;
196
+ `model_params` tune it (`time_scale` speeds any model up)
161
197
  - **bits**: names for the bits of a uint16 register (shown by `map`)
162
198
 
163
199
  A point named `exception_status` provides the byte returned by function 07.
@@ -196,7 +232,8 @@ the bench file.
196
232
  Anything else answers exception 01. Exceptions follow the specification:
197
233
  02 for unmapped or read-only addresses and for writes that would change only
198
234
  part of a 32/64-bit value, 03 for bad quantities, byte counts and values
199
- outside `min`/`max`.
235
+ outside `min`/`max`, 04 where a device refuses a write in its present state
236
+ (`omron-e5cc` with communications writing off).
200
237
 
201
238
  **Unit IDs.** On Modbus TCP an unknown unit answers exception 0x0B (gateway
202
239
  target failed to respond); a single device also answers unit 0 and 255. On
@@ -224,7 +261,10 @@ clear DEVICE [FAULT]`:
224
261
  | `drift` | point, per_second | a numeric point runs away |
225
262
  | `trip` | code=1 | `vfd`: fault trip, coasts to stop, latched until reset |
226
263
  | `sensor_break` | | `temperature-controller`: PV reads 3276.7, output off, alarm |
227
- | `phase_loss` | phase | `energy-meter`: that phase reads 0 V, 0 A |
264
+ | `phase_loss` | phase | `energy-meter`, `eastron-sdm630`: that phase reads 0 V, 0 A |
265
+ | `grid_loss` | | `sunspec-inverter`: state 7 (fault), event bit 4, AC values 0 |
266
+ | `ground_fault` | | `sunspec-inverter`: state 7 (fault), event bit 0 |
267
+ | `over_temp` | | `sunspec-inverter`: state 5 (throttled) at half power, event bit 7 |
228
268
 
229
269
  ## Control channel
230
270
 
@@ -287,8 +327,11 @@ starts. See [examples/test_example.py](examples/test_example.py).
287
327
 
288
328
  - For development and testing; not a certified or conformance-tested Modbus
289
329
  implementation.
290
- - The bundled profiles are invented devices with plausible register maps, not
291
- copies of real products. Models are plausible, not physically accurate.
330
+ - `energy-meter`, `temperature-controller` and `vfd` are invented devices with
331
+ plausible register maps. The profiles after real devices follow the
332
+ published register maps but not every behaviour of the device (see
333
+ [Profiles after real devices](#profiles-after-real-devices)). Models are
334
+ plausible, not physically accurate.
292
335
  - No Modbus ASCII, no Modbus/TCP Security (TLS), no function codes 11, 12, 17,
293
336
  20, 21, 24; diagnostics only sub-function 00.
294
337
  - RTU framing finds the end of a request from its function code, and the
@@ -0,0 +1,9 @@
1
+ # The three profiles after real register maps behind one Modbus TCP gateway, as on
2
+ # the browser demo (https://modbus-sim.steitec.net). Run with
3
+ # modbus-sim serve --bench examples/real-devices.yaml
4
+ devices:
5
+ - {profile: eastron-sdm630, unit: 1, name: meter}
6
+ - {profile: omron-e5cc, unit: 2, name: controller,
7
+ model_params: {time_scale: 10}} # heats ten times faster than real time
8
+ - {profile: sunspec-inverter, unit: 3, name: inverter,
9
+ model_params: {time_scale: 120}} # a day in 12 minutes
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modbus-sim"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Simulated Modbus devices with behaviour and fault injection: energy meter, temperature controller, VFD over Modbus TCP and RTU"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  """Simulated Modbus devices (energy meter, temperature controller, frequency converter)
2
2
  with behaviour and fault injection, over Modbus TCP, RTU over TCP and serial RTU."""
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.0"
4
4
 
5
5
  from .bench import Bench, BenchError
6
6
  from .client import ModbusClient, ModbusError
@@ -6,8 +6,8 @@ bytes are A B C D:
6
6
  ===== =============== ================================================
7
7
  order registers seen in
8
8
  ===== =============== ================================================
9
- ABCD [AB, CD] the Modbus default (big endian), most PLCs
10
- CDAB [CD, AB] word-swapped: many energy meters, Schneider, Eastron
9
+ ABCD [AB, CD] the Modbus default (big endian), most PLCs, Eastron
10
+ CDAB [CD, AB] word-swapped: many energy meters, Schneider
11
11
  BADC [BA, DC] byte-swapped
12
12
  DCBA [DC, BA] little endian
13
13
  ===== =============== ================================================
@@ -114,7 +114,7 @@ class Point:
114
114
  try:
115
115
  self.device.model.set(self.spec.model, value)
116
116
  except ModelError as exc:
117
- raise ModbusException(frame.ILLEGAL_DATA_VALUE, str(exc)) from None
117
+ raise ModbusException(exc.code, str(exc)) from None
118
118
  elif self.signal is not None:
119
119
  self.override = value # a written value holds until the control channel resets it
120
120
  else:
@@ -19,6 +19,9 @@ drift point, per_second a numeric point runs away
19
19
  trip code=1 vfd: fault trip, latched until reset
20
20
  sensor_break - temperature controller: sensor broken
21
21
  phase_loss phase (1-3) energy meter: phase lost
22
+ grid_loss - PV inverter: grid gone, state fault
23
+ ground_fault - PV inverter: ground fault, state fault
24
+ over_temp - PV inverter: throttled to half power
22
25
  ================= ================================== ==========================================
23
26
 
24
27
  ``count`` faults wear off after that many requests to the device; the others stay