Skip to content

Commit 1bcdd8e

Browse files
committed
contrib example: TCP drainage simulator with two devices
Simulates two Modbus TCP slave servers: Digital IO (DIO) with 8 discrete inputs and 8 coils. The first two coils each control a simulated pump. Inputs are not used. Water level meter (WLM) returning the current water level in the input register. It increases chronologically and decreases rapidly when one or two pumps are active.
1 parent a7ca62a commit 1bcdd8e

File tree

1 file changed

+68
-0
lines changed

1 file changed

+68
-0
lines changed

examples/contrib/drainage_sim.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
3+
# Simulates two Modbus TCP slave servers:
4+
#
5+
# Port 5020: Digital IO (DIO) with 8 discrete inputs and 8 coils. The first two coils each control
6+
# a simulated pump. Inputs are not used.
7+
#
8+
# Port 5021: Water level meter (WLM) returning the current water level in the input register. It
9+
# increases chronologically and decreases rapidly when one or two pumps are active.
10+
11+
import asyncio
12+
import logging
13+
from datetime import datetime
14+
15+
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext
16+
from pymodbus.server import StartAsyncTcpServer
17+
18+
INITIAL_WATER_LEVEL = 300
19+
WATER_INFLOW = 1
20+
PUMP_OUTFLOW = 8
21+
22+
logging.basicConfig(level = logging.INFO)
23+
24+
dio_di = ModbusSequentialDataBlock(1, [False] * 8)
25+
dio_co = ModbusSequentialDataBlock(1, [False] * 8)
26+
dio_context = ModbusSlaveContext(di = dio_di, co = dio_co)
27+
wlm_ir = ModbusSequentialDataBlock(1, [INITIAL_WATER_LEVEL])
28+
wlm_context = ModbusSlaveContext(ir = wlm_ir)
29+
30+
async def update():
31+
while True:
32+
await asyncio.sleep(1)
33+
34+
# Update water level based on DIO output values (simulating pumps)
35+
water_level = wlm_ir.getValues(1, 1)[0]
36+
dio_outputs = dio_co.getValues(1, 2)
37+
38+
water_level += WATER_INFLOW
39+
water_level -= (int(dio_outputs[0]) + int(dio_outputs[1])) * PUMP_OUTFLOW
40+
water_level = max(0, min(INITIAL_WATER_LEVEL * 10, water_level))
41+
wlm_ir.setValues(1, [water_level])
42+
43+
async def log():
44+
while True:
45+
await asyncio.sleep(10)
46+
47+
dio_outputs = dio_co.getValues(1, 8)
48+
wlm_level = wlm_ir.getValues(1, 1)[0]
49+
50+
logging.info(f"{datetime.now()}: WLM water level: {wlm_level}, DIO outputs: {dio_outputs}")
51+
52+
async def run():
53+
ctx = ModbusServerContext(slaves = dio_context)
54+
dio_server = asyncio.create_task(StartAsyncTcpServer(context = ctx, address = ("0.0.0.0", 5020)))
55+
logging.info("Initialising slave server DIO on port 5020")
56+
57+
ctx = ModbusServerContext(slaves = wlm_context)
58+
wlm_server = asyncio.create_task(StartAsyncTcpServer(context = ctx, address = ("0.0.0.0", 5021)))
59+
logging.info("Initialising slave server WLM on port 5021")
60+
61+
update_task = asyncio.create_task(update())
62+
logging_task = asyncio.create_task(log())
63+
64+
logging.info("Init complete")
65+
await asyncio.gather(dio_server, wlm_server, update_task, logging_task)
66+
67+
if __name__ == "__main__":
68+
asyncio.run(run(), debug=True)

0 commit comments

Comments
 (0)