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.
- modbus_sim-0.2.0/CHANGELOG.md +46 -0
- {modbus_sim-0.1.0/src/modbus_sim.egg-info → modbus_sim-0.2.0}/PKG-INFO +54 -11
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/README.md +53 -10
- modbus_sim-0.2.0/examples/real-devices.yaml +9 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/pyproject.toml +1 -1
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/__init__.py +1 -1
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/datatypes.py +2 -2
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/device.py +1 -1
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/faults.py +3 -0
- modbus_sim-0.2.0/src/modbus_sim/models.py +719 -0
- modbus_sim-0.2.0/src/modbus_sim/profiles/eastron-sdm630.yaml +233 -0
- modbus_sim-0.2.0/src/modbus_sim/profiles/omron-e5cc.yaml +81 -0
- modbus_sim-0.2.0/src/modbus_sim/profiles/sunspec-inverter.yaml +136 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0/src/modbus_sim.egg-info}/PKG-INFO +54 -11
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/SOURCES.txt +5 -0
- modbus_sim-0.2.0/tests/test_device_profiles.py +269 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_profile.py +2 -1
- modbus_sim-0.1.0/CHANGELOG.md +0 -24
- modbus_sim-0.1.0/src/modbus_sim/models.py +0 -431
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/LICENSE +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/MANIFEST.in +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/examples/plant.yaml +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/examples/test_example.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/setup.cfg +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/__main__.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/bench.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/cli.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/client.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/control.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/frame.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profile.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/energy-meter.yaml +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/temperature-controller.yaml +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/profiles/vfd.yaml +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/pytest_plugin.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/server.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim/signals.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/dependency_links.txt +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/entry_points.txt +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/requires.txt +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/src/modbus_sim.egg-info/top_level.txt +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/conftest.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_cli.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_datatypes.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_device.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_frame.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_models.py +0 -0
- {modbus_sim-0.1.0 → modbus_sim-0.2.0}/tests/test_plugin.py +0 -0
- {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.
|
|
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
|
[](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml)
|
|
45
|
-
[](https://pypi.org/project/modbus-sim/)
|
|
46
|
-
[](https://pypi.org/project/modbus-sim/)
|
|
47
|
-
[](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
|
|
45
|
+
[](https://pypi.org/project/modbus-sim/)
|
|
46
|
+
[](https://pypi.org/project/modbus-sim/)
|
|
47
|
+
[](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.
|
|
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
|
|
196
|
-
`
|
|
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
|
-
-
|
|
327
|
-
|
|
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
|
[](https://github.com/steitec/modbus-sim/actions/workflows/ci.yml)
|
|
9
|
-
[](https://pypi.org/project/modbus-sim/)
|
|
10
|
-
[](https://pypi.org/project/modbus-sim/)
|
|
11
|
-
[](https://github.com/steitec/modbus-sim/blob/main/LICENSE)
|
|
9
|
+
[](https://pypi.org/project/modbus-sim/)
|
|
10
|
+
[](https://pypi.org/project/modbus-sim/)
|
|
11
|
+
[](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.
|
|
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
|
|
160
|
-
`
|
|
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
|
-
-
|
|
291
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|