Skip to content

Commit 3a62412

Browse files
#18#30#40 improve dnp3demo (#39)
* droppped new in the file name and class name * updated submodule pybind11 * added db_sizes and event_buffer_config in MyOutStation __init__ * manually set version to 0.3.0b1 * mached with upstream deps/pybind11 commit version * mached with upstream deps/pybind11 commit version * improved run_outstation * improved run_outstation * improved cli tool dnp3demo * added --init-random options to dnp3demo outstation * added <q>-quit program option to dnp3demo * hot-fixed cli instruction --------- Co-authored-by: Kefei Mo <[email protected]>
1 parent 0f0b3e1 commit 3a62412

File tree

10 files changed

+262
-98
lines changed

10 files changed

+262
-98
lines changed

deps/pybind11

examples/stack_config_examples.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import logging
2+
import random
3+
import sys
4+
5+
from pydnp3 import opendnp3
6+
7+
from dnp3_python.dnp3station.master import MyMaster
8+
from dnp3_python.dnp3station.outstation import MyOutStation
9+
10+
import datetime
11+
from time import sleep
12+
13+
stdout_stream = logging.StreamHandler(sys.stdout)
14+
stdout_stream.setFormatter(logging.Formatter('%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s'))
15+
16+
_log = logging.getLogger(__name__)
17+
# _log = logging.getLogger("data_retrieval_demo")
18+
_log.addHandler(stdout_stream)
19+
_log.setLevel(logging.DEBUG)
20+
21+
22+
def main():
23+
24+
# db-sizes configuration examples, more on "opendnp3.DatabaseSizes"
25+
# example 1: use AllType
26+
db_sizes = opendnp3.DatabaseSizes.AllTypes(count=10)
27+
print(f"========== db_sizes.numDoubleBinary {db_sizes.numDoubleBinary}")
28+
29+
db_sizes = opendnp3.DatabaseSizes(numBinary=15,
30+
numBinaryOutputStatus=15,
31+
numAnalog=15,
32+
numAnalogOutputStatus=10,
33+
numDoubleBinary=0,
34+
numCounter=0,
35+
numFrozenCounter=0,
36+
numTimeAndInterval=0)
37+
print(f"========== db_sizes.numDoubleBinary {db_sizes.numDoubleBinary}")
38+
39+
# Tips: use __dir__() to inspect available attributes
40+
print(f"========== db_sizes.__dir__() {db_sizes.__dir__()}")
41+
42+
# Event buffer config example, more on opendnp3.EventBufferConfig, opendnp3.EventType
43+
# example 1: use AllType
44+
event_buffer_config = opendnp3.EventBufferConfig().AllTypes(15)
45+
print(f"========= event_buffer_config.TotalEvents {event_buffer_config.TotalEvents()}")
46+
print(f"========= event_buffer_config.GetMaxEventsForType(opendnp3.EventType.Binary) "
47+
f"{event_buffer_config.GetMaxEventsForType(opendnp3.EventType.Binary)}")
48+
# example 2: specify individual event type
49+
event_buffer_config = opendnp3.EventBufferConfig(maxBinaryEvents=5, maxAnalogEvents=5,
50+
maxBinaryOutputStatusEvents=5, maxAnalogOutputStatusEvents=5,
51+
maxCounterEvents=0, maxFrozenCounterEvents=0,
52+
maxDoubleBinaryEvents=0, maxSecurityStatisticEvents=0)
53+
print(f"========= event_buffer_config.TotalEvents {event_buffer_config.TotalEvents()}")
54+
print(f"========= event_buffer_config.GetMaxEventsForType(opendnp3.EventType.Binary) "
55+
f"{event_buffer_config.GetMaxEventsForType(opendnp3.EventType.Binary)}")
56+
57+
####################
58+
# init an outstation using default configuration, e.g., port=20000. Then start.
59+
outstation_application = MyOutStation(db_sizes=db_sizes, event_buffer_config=event_buffer_config)
60+
outstation_application.start()
61+
_log.debug('Initialization complete. OutStation in command loop.')
62+
63+
# init a master using default configuration, e.g., port=20000. Then start.
64+
master_application = MyMaster()
65+
master_application.start()
66+
_log.debug('Initialization complete. Master Station in command loop.')
67+
68+
def poll_demo():
69+
count = 0
70+
while count < 10:
71+
sleep(2) # Note: hard-coded, master station query every 1 sec.
72+
73+
count += 1
74+
print(datetime.datetime.now(), "============count ", count, )
75+
76+
# plan: there are 3 AnalogInput Points,
77+
# outstation will randomly pick from
78+
# index 0: [4.0, 7.0, 2.0]
79+
# index 1: [14.0, 17.0, 12.0]
80+
# index 1: [24.0, 27.0, 22.0]
81+
82+
# outstation update point value (slower than master station query)
83+
if count % 2 == 1:
84+
point_values_0 = [4.8, 7.8, 2.8]
85+
point_values_1 = [14.1, 17.1, 12.1]
86+
point_values_2 = [24.2, 27.2, 22.2]
87+
point_values_0 = [val + random.random() for val in point_values_0]
88+
point_values_1 = [val + random.random() for val in point_values_1]
89+
point_values_2 = [val + random.random() for val in point_values_2]
90+
for i, pts in enumerate([point_values_0, point_values_1, point_values_2]):
91+
p_val = random.choice(pts)
92+
print(f"====== Outstation update index {i} with {p_val}")
93+
outstation_application.apply_update(opendnp3.Analog(value=float(p_val)), i)
94+
95+
if count % 2 == 1:
96+
point_values_0 = [True, False]
97+
point_values_1 = [True, False]
98+
point_values_2 = [True, False]
99+
for i, pts in enumerate([point_values_0, point_values_1, point_values_2]):
100+
p_val = random.choice(pts)
101+
print(f"====== Outstation update index {i} with {p_val}")
102+
outstation_application.apply_update(opendnp3.Binary(True), i)
103+
104+
# master station retrieve outstation point values
105+
106+
result = master_application.get_db_by_group_variation(group=30, variation=6)
107+
print(f"===important log: case6 get_db_by_group_variation(group=30, variation=6) ==== {count}", "\n",
108+
datetime.datetime.now(),
109+
result)
110+
result = master_application.get_db_by_group_variation(group=1, variation=2)
111+
print(f"===important log: case6b get_db_by_group_variation(group=1, variation=2) ==== {count}", "\n",
112+
datetime.datetime.now(),
113+
result)
114+
result = master_application.get_db_by_group_variation(group=30, variation=1)
115+
print(f"===important log: case6c get_db_by_group_variation(group=30, variation=1) ==== {count}", "\n",
116+
datetime.datetime.now(),
117+
result)
118+
119+
poll_demo()
120+
_log.debug('Exiting.')
121+
master_application.shutdown()
122+
outstation_application.shutdown()
123+
124+
125+
if __name__ == '__main__':
126+
main()

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,9 @@
4747
from distutils.version import LooseVersion
4848

4949
from setuptools import find_packages, find_namespace_packages
50-
__version__ = '0.2.3b3'
51-
5250
from pathlib import Path
5351

52+
__version__ = '0.3.0b1'
5453

5554

5655
class CMakeExtension(Extension):

src/dnp3_python/dnp3station/master_new.py renamed to src/dnp3_python/dnp3station/master.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
int, DbPointVal]] # e.g., {GroupVariation.Group30Var6: {0: 4.8, 1: 14.1, 2: 27.2, 3: 0.0, 4: 0.0}
3535

3636

37-
class MyMasterNew:
37+
class MyMaster:
3838
"""
3939
4040
DNP3 spec section 5.1.6.1:

src/dnp3_python/dnp3station/outstation_new.py renamed to src/dnp3_python/dnp3station/outstation.py

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
PointValueType = Union[opendnp3.Analog, opendnp3.Binary, opendnp3.AnalogOutputStatus, opendnp3.BinaryOutputStatus]
3333

3434

35-
class MyOutStationNew(opendnp3.IOutstationApplication):
35+
class MyOutStation(opendnp3.IOutstationApplication):
3636
"""
3737
Interface for all outstation callback info except for control requests.
3838
@@ -62,7 +62,7 @@ class MyOutStationNew(opendnp3.IOutstationApplication):
6262
# db_handler = None
6363
outstation_application = None
6464
# outstation_pool = {} # a pool of outstations
65-
outstation_application_pool: Dict[str, MyOutStationNew] = {} # a pool of outstation applications
65+
outstation_application_pool: Dict[str, MyOutStation] = {} # a pool of outstation applications
6666

6767
def __init__(self,
6868
outstation_ip: str = "0.0.0.0",
@@ -73,6 +73,9 @@ def __init__(self,
7373

7474
channel_log_level=opendnp3.levels.NORMAL,
7575
outstation_log_level=opendnp3.levels.NORMAL,
76+
77+
db_sizes: opendnp3.DatabaseSizes = None,
78+
event_buffer_config: opendnp3.EventBufferConfig = None
7679
):
7780
super().__init__()
7881

@@ -91,15 +94,25 @@ def __init__(self,
9194
self.master_id: int = master_id
9295
self.outstation_id: int = outstation_id
9396

97+
# Set to default
98+
if db_sizes is None:
99+
db_sizes = opendnp3.DatabaseSizes.AllTypes(count=5)
100+
if event_buffer_config is None:
101+
event_buffer_config = opendnp3.EventBufferConfig().AllTypes(sizes=10)
102+
self.db_sizes = db_sizes
103+
self.event_buffer_config = event_buffer_config
104+
94105
_log.debug('Configuring the DNP3 stack.')
95106
_log.debug('Configuring the outstation database.')
96-
self.stack_config = self.configure_stack() # TODO: refactor it to outside of the class
107+
self.stack_config = self.configure_stack(db_sizes=db_sizes,
108+
event_buffer_config=event_buffer_config)
97109

98110
# TODO: Justify if this is is really working? (Not sure if it really takes effect yet.)
99111
# but needs to add docstring. Search for "intriguing" in "data_retrieval_demo.py"
100112
# Note: dbconfig signature at cpp/libs/include/asiodnp3/DatabaseConfig.h
101113
# which has sizes parameter
102-
self.configure_database(self.stack_config.dbConfig) # TODO: refactor it to outside of the class.
114+
# Note: stack_config is far-reaching, keep this method within the class
115+
self.configure_database(self.stack_config.dbConfig)
103116

104117
# self.log_handler = MyLogger()
105118
self.log_handler = asiodnp3.ConsoleLogger().Create() # (or use this during regression testing)
@@ -132,16 +145,16 @@ def __init__(self,
132145
self.command_handler.post_init(outstation_id=self.outstation_app_id)
133146
# self.command_handler = opendnp3.SuccessCommandHandler().Create() # (or use this during regression testing)
134147
# init outstation applicatioin, # Note: singleton for AddOutstation()
135-
MyOutStationNew.set_outstation_application(outstation_application=self)
148+
MyOutStation.set_outstation_application(outstation_application=self)
136149

137150
# finally, init outstation
138151
self.outstation = self.channel.AddOutstation(id="outstation-" + self.outstation_app_id,
139152
commandHandler=self.command_handler,
140-
application=MyOutStationNew.outstation_application,
153+
application=MyOutStation.outstation_application,
141154
config=self.stack_config)
142155

143-
MyOutStationNew.add_outstation_app(outstation_id=self.outstation_app_id,
144-
outstation_app=self.outstation_application)
156+
MyOutStation.add_outstation_app(outstation_id=self.outstation_app_id,
157+
outstation_app=self.outstation_application)
145158

146159
# Configure log level for channel(tcpclient) and outstation
147160
# note: one of the following
@@ -209,13 +222,13 @@ def get_config(self):
209222
return self._comm_conifg
210223

211224
@classmethod
212-
def add_outstation_app(cls, outstation_id: str, outstation_app: MyOutStationNew):
225+
def add_outstation_app(cls, outstation_id: str, outstation_app: MyOutStation):
213226
"""add outstation instance to outstation pool,
214227
the id is in the format of `ip-port`, e.g., `0.0.0.0-20000`."""
215228
cls.outstation_application_pool[outstation_id] = outstation_app
216229

217230
@classmethod
218-
def get_outstation_app(cls, outstation_id: str) -> MyOutStationNew:
231+
def get_outstation_app(cls, outstation_id: str) -> MyOutStation:
219232
"""get outstation instance from the outstation pool using outstation id,
220233
the id is in the format of `ip-port`, e.g., `0.0.0.0-20000`."""
221234
return cls.outstation_application_pool.get(outstation_id)
@@ -231,22 +244,14 @@ def set_outstation_application(cls, outstation_application):
231244
else:
232245
cls.outstation_application = outstation_application
233246

234-
def configure_stack(self):
247+
def configure_stack(self, db_sizes: opendnp3.DatabaseSizes = None,
248+
event_buffer_config: opendnp3.EventBufferConfig = None,
249+
**kwargs) -> asiodnp3.OutstationStackConfig:
235250
"""Set up the OpenDNP3 configuration."""
236-
stack_config = asiodnp3.OutstationStackConfig(opendnp3.DatabaseSizes.AllTypes(10))
237-
# stack_config = asiodnp3.OutstationStackConfig(opendnp3.DatabaseSizes.Empty())
238-
# stack_config = asiodnp3.OutstationStackConfig(dbSizes=opendnp3.DatabaseSizes.AnalogOnly(8))
239-
# TODO: expose DatabaseSizes to public interface
240-
# stack_config = asiodnp3.OutstationStackConfig(dbSizes=opendnp3.DatabaseSizes(numBinary=10,
241-
# numDoubleBinary=0,
242-
# numAnalog=10,
243-
# numCounter=0,
244-
# numFrozenCounter=0,
245-
# numBinaryOutputStatus=10,
246-
# numAnalogOutputStatus=10,
247-
# numTimeAndInterval=0))
248-
249-
stack_config.outstation.eventBufferConfig = opendnp3.EventBufferConfig().AllTypes(10)
251+
252+
stack_config = asiodnp3.OutstationStackConfig(dbSizes=db_sizes)
253+
254+
stack_config.outstation.eventBufferConfig = event_buffer_config
250255
stack_config.outstation.params.allowUnsolicited = True # TODO: create interface for this
251256
stack_config.link.LocalAddr = self.outstation_id # meaning for outstation, use 1 to follow simulator's default
252257
stack_config.link.RemoteAddr = self.master_id # meaning for master station, use 2 to follow simulator's default
@@ -262,7 +267,6 @@ def configure_database(db_config):
262267
Configure two Analog points (group/variation 30.1) at indexes 0, 1.
263268
Configure two Binary points (group/variation 1.2) at indexes 1 and 2.
264269
"""
265-
# TODO: figure out the right way to configure
266270

267271
# AnalogInput
268272
db_config.analog[0].clazz = opendnp3.PointClass.Class2
@@ -414,7 +418,7 @@ def Select(self, command, index):
414418
:param index: int
415419
:return: CommandStatus
416420
"""
417-
outstation_application_pool = MyOutStationNew.outstation_application_pool
421+
outstation_application_pool = MyOutStation.outstation_application_pool
418422
outstation_app = outstation_application_pool.get(self.outstation_id)
419423

420424
try:
@@ -437,7 +441,7 @@ def Operate(self, command, index, op_type):
437441
:return: CommandStatus
438442
"""
439443

440-
outstation_application_pool = MyOutStationNew.outstation_application_pool
444+
outstation_application_pool = MyOutStation.outstation_application_pool
441445
outstation_app = outstation_application_pool.get(self.outstation_id)
442446
try:
443447
# self.outstation_application.process_point_value('Operate', command, index, op_type)

src/dnp3demo/control_workflow_demo.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from pydnp3 import opendnp3
66
from dnp3_python.dnp3station.station_utils import command_callback
7-
from dnp3_python.dnp3station.master_new import MyMasterNew
8-
from dnp3_python.dnp3station.outstation_new import MyOutStationNew
7+
from dnp3_python.dnp3station.master import MyMaster
8+
from dnp3_python.dnp3station.outstation import MyOutStation
99

1010
from time import sleep
1111
import datetime
@@ -21,15 +21,15 @@
2121

2222
def main():
2323
# cmd_interface_master = MasterCmd()
24-
master_application = MyMasterNew(
24+
master_application = MyMaster(
2525
# channel_log_level=opendnp3.levels.ALL_COMMS,
2626
# master_log_level=opendnp3.levels.ALL_COMMS
2727
# soe_handler=SOEHandler(soehandler_log_level=logging.DEBUG)
2828
)
2929
master_application.start()
3030
_log.debug('Initialization complete. Master Station in command loop.')
3131
# cmd_interface_outstation = OutstationCmd()
32-
outstation_application = MyOutStationNew(
32+
outstation_application = MyOutStation(
3333
# channel_log_level=opendnp3.levels.ALL_COMMS,
3434
# outstation_log_level=opendnp3.levels.ALL_COMMS
3535
)

src/dnp3demo/data_retrieval_demo.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from pydnp3 import opendnp3
66

7-
from dnp3_python.dnp3station.master_new import MyMasterNew
8-
from dnp3_python.dnp3station.outstation_new import MyOutStationNew
7+
from dnp3_python.dnp3station.master import MyMaster
8+
from dnp3_python.dnp3station.outstation import MyOutStation
99

1010
import datetime
1111
from time import sleep
@@ -21,12 +21,12 @@
2121

2222
def main():
2323
# init an outstation using default configuration, e.g., port=20000. Then start.
24-
outstation_application = MyOutStationNew()
24+
outstation_application = MyOutStation()
2525
outstation_application.start()
2626
_log.debug('Initialization complete. OutStation in command loop.')
2727

2828
# init a master using default configuration, e.g., port=20000. Then start.
29-
master_application = MyMasterNew()
29+
master_application = MyMaster()
3030
master_application.start()
3131
_log.debug('Initialization complete. Master Station in command loop.')
3232

src/dnp3demo/multi_stations_demo.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from pydnp3 import opendnp3
66

7-
from dnp3_python.dnp3station.master_new import MyMasterNew
8-
from dnp3_python.dnp3station.outstation_new import MyOutStationNew
7+
from dnp3_python.dnp3station.master import MyMaster
8+
from dnp3_python.dnp3station.outstation import MyOutStation
99

1010
import datetime
1111
from time import sleep
@@ -21,19 +21,19 @@
2121

2222
def main():
2323

24-
outstation_application = MyOutStationNew()
24+
outstation_application = MyOutStation()
2525
outstation_application.start()
2626
_log.debug('Initialization complete. OutStation in command loop.')
2727

28-
master_application = MyMasterNew()
28+
master_application = MyMaster()
2929
master_application.start()
3030
_log.debug('Initialization complete. Master Station in command loop.')
3131

32-
outstation_application_20001 = MyOutStationNew(port=20001)
32+
outstation_application_20001 = MyOutStation(port=20001)
3333
outstation_application_20001.start()
3434
_log.debug('Initialization complete. OutStation p20001 in command loop.')
3535

36-
master_application_20001 = MyMasterNew(port=20001)
36+
master_application_20001 = MyMaster(port=20001)
3737
master_application_20001.start()
3838
_log.debug('Initialization complete. Master p20001 Station in command loop.')
3939

@@ -80,7 +80,7 @@ def main():
8080

8181
if count == 4:
8282
master_application_20001.shutdown()
83-
print(MyOutStationNew.outstation_application_pool)
83+
print(MyOutStation.outstation_application_pool)
8484

8585
# master station retrieve outstation point values
8686

0 commit comments

Comments
 (0)