Skip to content

Commit c26c3c2

Browse files
committed
expose dbsize for outstation init (flat args), fixed outstation db name unmached.
1 parent 3a62412 commit c26c3c2

File tree

4 files changed

+412
-211
lines changed

4 files changed

+412
-211
lines changed

examples/outstation.py

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import logging
22
import sys
33

4-
from pydnp3 import opendnp3, openpal, asiopal, asiodnp3
4+
from pydnp3 import asiodnp3, asiopal, opendnp3, openpal
55

66
LOG_LEVELS = opendnp3.levels.NORMAL | opendnp3.levels.ALL_COMMS
77
LOCAL_IP = "0.0.0.0"
88
PORT = 20000
99

1010
stdout_stream = logging.StreamHandler(sys.stdout)
11-
stdout_stream.setFormatter(logging.Formatter('%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s'))
11+
stdout_stream.setFormatter(
12+
logging.Formatter("%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s")
13+
)
1214

1315
_log = logging.getLogger(__name__)
1416
_log.addHandler(stdout_stream)
@@ -17,74 +19,79 @@
1719

1820
class OutstationApplication(opendnp3.IOutstationApplication):
1921
"""
20-
Interface for all outstation callback info except for control requests.
21-
22-
DNP3 spec section 5.1.6.2:
23-
The Application Layer provides the following services for the DNP3 User Layer in an outstation:
24-
- Notifies the DNP3 User Layer when action requests, such as control output,
25-
analog output, freeze and file operations, arrive from a master.
26-
- Requests data and information from the outstation that is wanted by a master
27-
and formats the responses returned to a master.
28-
- Assures that event data is successfully conveyed to a master (using
29-
Application Layer confirmation).
30-
- Sends notifications to the master when the outstation restarts, has queued events,
31-
and requires time synchronization.
32-
33-
DNP3 spec section 5.1.6.3:
34-
The Application Layer requires specific services from the layers beneath it.
35-
- Partitioning of fragments into smaller portions for transport reliability.
36-
- Knowledge of which device(s) were the source of received messages.
37-
- Transmission of messages to specific devices or to all devices.
38-
- Message integrity (i.e., error-free reception and transmission of messages).
39-
- Knowledge of the time when messages arrive.
40-
- Either precise times of transmission or the ability to set time values
41-
into outgoing messages.
22+
Interface for all outstation callback info except for control requests.
23+
24+
DNP3 spec section 5.1.6.2:
25+
The Application Layer provides the following services for the DNP3 User Layer in an outstation:
26+
- Notifies the DNP3 User Layer when action requests, such as control output,
27+
analog output, freeze and file operations, arrive from a master.
28+
- Requests data and information from the outstation that is wanted by a master
29+
and formats the responses returned to a master.
30+
- Assures that event data is successfully conveyed to a master (using
31+
Application Layer confirmation).
32+
- Sends notifications to the master when the outstation restarts, has queued events,
33+
and requires time synchronization.
34+
35+
DNP3 spec section 5.1.6.3:
36+
The Application Layer requires specific services from the layers beneath it.
37+
- Partitioning of fragments into smaller portions for transport reliability.
38+
- Knowledge of which device(s) were the source of received messages.
39+
- Transmission of messages to specific devices or to all devices.
40+
- Message integrity (i.e., error-free reception and transmission of messages).
41+
- Knowledge of the time when messages arrive.
42+
- Either precise times of transmission or the ability to set time values
43+
into outgoing messages.
4244
"""
4345

4446
outstation = None
4547

4648
def __init__(self):
4749
super(OutstationApplication, self).__init__()
4850

49-
_log.debug('Configuring the DNP3 stack.')
51+
_log.debug("Configuring the DNP3 stack.")
5052
self.stack_config = self.configure_stack()
5153

52-
_log.debug('Configuring the outstation database.')
54+
_log.debug("Configuring the outstation database.")
5355
self.configure_database(self.stack_config.dbConfig)
5456

55-
_log.debug('Creating a DNP3Manager.')
57+
_log.debug("Creating a DNP3Manager.")
5658
threads_to_allocate = 1
5759
# self.log_handler = MyLogger()
58-
self.log_handler = asiodnp3.ConsoleLogger().Create() # (or use this during regression testing)
60+
self.log_handler = (
61+
asiodnp3.ConsoleLogger().Create()
62+
) # (or use this during regression testing)
5963
self.manager = asiodnp3.DNP3Manager(threads_to_allocate, self.log_handler)
6064

61-
_log.debug('Creating the DNP3 channel, a TCP server.')
65+
_log.debug("Creating the DNP3 channel, a TCP server.")
6266
self.retry_parameters = asiopal.ChannelRetry().Default()
6367
self.listener = AppChannelListener()
6468
# self.listener = asiodnp3.PrintingChannelListener().Create() # (or use this during regression testing)
65-
self.channel = self.manager.AddTCPServer("server",
66-
LOG_LEVELS,
67-
self.retry_parameters,
68-
LOCAL_IP,
69-
PORT,
70-
self.listener)
71-
72-
_log.debug('Adding the outstation to the channel.')
69+
self.channel = self.manager.AddTCPServer(
70+
"server", LOG_LEVELS, self.retry_parameters, LOCAL_IP, PORT, self.listener
71+
)
72+
73+
_log.debug("Adding the outstation to the channel.")
7374
self.command_handler = OutstationCommandHandler()
7475
# self.command_handler = opendnp3.SuccessCommandHandler().Create() # (or use this during regression testing)
75-
self.outstation = self.channel.AddOutstation("outstation", self.command_handler, self, self.stack_config)
76+
self.outstation = self.channel.AddOutstation(
77+
"outstation", self.command_handler, self, self.stack_config
78+
)
7679

7780
# Put the Outstation singleton in OutstationApplication so that it can be used to send updates to the Master.
7881
OutstationApplication.set_outstation(self.outstation)
7982

80-
_log.debug('Enabling the outstation. Traffic will now start to flow.')
83+
_log.debug("Enabling the outstation. Traffic will now start to flow.")
8184
self.outstation.Enable()
8285

8386
@staticmethod
8487
def configure_stack():
8588
"""Set up the OpenDNP3 configuration."""
86-
stack_config = asiodnp3.OutstationStackConfig(opendnp3.DatabaseSizes.AllTypes(10))
87-
stack_config.outstation.eventBufferConfig = opendnp3.EventBufferConfig().AllTypes(10)
89+
stack_config = asiodnp3.OutstationStackConfig(
90+
opendnp3.DatabaseSizes.AllTypes(10)
91+
)
92+
stack_config.outstation.eventBufferConfig = (
93+
opendnp3.EventBufferConfig().AllTypes(10)
94+
)
8895
stack_config.outstation.params.allowUnsolicited = True
8996
stack_config.link.LocalAddr = 10
9097
stack_config.link.RemoteAddr = 1
@@ -94,10 +101,10 @@ def configure_stack():
94101
@staticmethod
95102
def configure_database(db_config):
96103
"""
97-
Configure the Outstation's database of input point definitions.
104+
Configure the Outstation's database of input point definitions.
98105
99-
Configure two Analog points (group/variation 30.1) at indexes 1 and 2.
100-
Configure two Binary points (group/variation 1.2) at indexes 1 and 2.
106+
Configure two Analog points (group/variation 30.1) at indexes 1 and 2.
107+
Configure two Binary points (group/variation 1.2) at indexes 1 and 2.
101108
"""
102109
db_config.analog[1].clazz = opendnp3.PointClass.Class2
103110
db_config.analog[1].svariation = opendnp3.StaticAnalogVariation.Group30Var1
@@ -114,9 +121,9 @@ def configure_database(db_config):
114121

115122
def shutdown(self):
116123
"""
117-
Execute an orderly shutdown of the Outstation.
124+
Execute an orderly shutdown of the Outstation.
118125
119-
The debug messages may be helpful if errors occur during shutdown.
126+
The debug messages may be helpful if errors occur during shutdown.
120127
"""
121128
# _log.debug('Exiting application...')
122129
# _log.debug('Shutting down outstation...')
@@ -138,17 +145,17 @@ def get_outstation(cls):
138145
@classmethod
139146
def set_outstation(cls, outstn):
140147
"""
141-
Set the singleton instance of IOutstation, as returned from the channel's AddOutstation call.
148+
Set the singleton instance of IOutstation, as returned from the channel's AddOutstation call.
142149
143-
Making IOutstation available as a singleton allows other classes (e.g. the command-line UI)
144-
to send commands to it -- see apply_update().
150+
Making IOutstation available as a singleton allows other classes (e.g. the command-line UI)
151+
to send commands to it -- see apply_update().
145152
"""
146153
cls.outstation = outstn
147154

148155
# Overridden method
149156
def ColdRestartSupport(self):
150157
"""Return a RestartMode enumerated value indicating whether cold restart is supported."""
151-
_log.debug('In OutstationApplication.ColdRestartSupport')
158+
_log.debug("In OutstationApplication.ColdRestartSupport")
152159
return opendnp3.RestartMode.UNSUPPORTED
153160

154161
# Overridden method
@@ -161,29 +168,32 @@ def GetApplicationIIN(self):
161168
application_iin.needTime = False
162169
# Just for testing purposes, convert it to an IINField and display the contents of the two bytes.
163170
iin_field = application_iin.ToIIN()
164-
_log.debug('OutstationApplication.GetApplicationIIN: IINField LSB={}, MSB={}'.format(iin_field.LSB,
165-
iin_field.MSB))
171+
_log.debug(
172+
"OutstationApplication.GetApplicationIIN: IINField LSB={}, MSB={}".format(
173+
iin_field.LSB, iin_field.MSB
174+
)
175+
)
166176
return application_iin
167177

168178
# Overridden method
169179
def SupportsAssignClass(self):
170-
_log.debug('In OutstationApplication.SupportsAssignClass')
180+
_log.debug("In OutstationApplication.SupportsAssignClass")
171181
return False
172182

173183
# Overridden method
174184
def SupportsWriteAbsoluteTime(self):
175-
_log.debug('In OutstationApplication.SupportsWriteAbsoluteTime')
185+
_log.debug("In OutstationApplication.SupportsWriteAbsoluteTime")
176186
return False
177187

178188
# Overridden method
179189
def SupportsWriteTimeAndInterval(self):
180-
_log.debug('In OutstationApplication.SupportsWriteTimeAndInterval')
190+
_log.debug("In OutstationApplication.SupportsWriteTimeAndInterval")
181191
return False
182192

183193
# Overridden method
184194
def WarmRestartSupport(self):
185195
"""Return a RestartMode enumerated value indicating whether a warm restart is supported."""
186-
_log.debug('In OutstationApplication.WarmRestartSupport')
196+
_log.debug("In OutstationApplication.WarmRestartSupport")
187197
return opendnp3.RestartMode.UNSUPPORTED
188198

189199
@classmethod
@@ -196,7 +206,9 @@ def process_point_value(cls, command_type, command, index, op_type):
196206
:param index: (integer) DNP3 index of the payload's data definition.
197207
:param op_type: An OperateType, or None if command_type == 'Select'.
198208
"""
199-
_log.debug('Processing received point value for index {}: {}'.format(index, command))
209+
_log.debug(
210+
"Processing received point value for index {}: {}".format(index, command)
211+
)
200212

201213
def apply_update(self, value, index):
202214
"""
@@ -207,7 +219,11 @@ def apply_update(self, value, index):
207219
:param value: An instance of Analog, Binary, or another opendnp3 data value.
208220
:param index: (integer) Index of the data definition in the opendnp3 database.
209221
"""
210-
_log.debug('Recording {} measurement, index={}, value={}'.format(type(value).__name__, index, value.value))
222+
_log.debug(
223+
"Recording {} measurement, index={}, value={}".format(
224+
type(value).__name__, index, value.value
225+
)
226+
)
211227
builder = asiodnp3.UpdateBuilder()
212228
builder.Update(value, index)
213229
update = builder.Build()
@@ -216,17 +232,17 @@ def apply_update(self, value, index):
216232

217233
class OutstationCommandHandler(opendnp3.ICommandHandler):
218234
"""
219-
Override ICommandHandler in this manner to implement application-specific command handling.
235+
Override ICommandHandler in this manner to implement application-specific command handling.
220236
221-
ICommandHandler implements the Outstation's handling of Select and Operate,
222-
which relay commands and data from the Master to the Outstation.
237+
ICommandHandler implements the Outstation's handling of Select and Operate,
238+
which relay commands and data from the Master to the Outstation.
223239
"""
224240

225241
def Start(self):
226-
_log.debug('In OutstationCommandHandler.Start')
242+
_log.debug("In OutstationCommandHandler.Start")
227243

228244
def End(self):
229-
_log.debug('In OutstationCommandHandler.End')
245+
_log.debug("In OutstationCommandHandler.End")
230246

231247
def Select(self, command, index):
232248
"""
@@ -237,7 +253,7 @@ def Select(self, command, index):
237253
:param index: int
238254
:return: CommandStatus
239255
"""
240-
OutstationApplication.process_point_value('Select', command, index, None)
256+
OutstationApplication.process_point_value("Select", command, index, None)
241257
return opendnp3.CommandStatus.SUCCESS
242258

243259
def Operate(self, command, index, op_type):
@@ -250,46 +266,48 @@ def Operate(self, command, index, op_type):
250266
:param op_type: OperateType
251267
:return: CommandStatus
252268
"""
253-
OutstationApplication.process_point_value('Operate', command, index, op_type)
269+
OutstationApplication.process_point_value("Operate", command, index, op_type)
254270
return opendnp3.CommandStatus.SUCCESS
255271

256272

257273
class AppChannelListener(asiodnp3.IChannelListener):
258274
"""
259-
Override IChannelListener in this manner to implement application-specific channel behavior.
275+
Override IChannelListener in this manner to implement application-specific channel behavior.
260276
"""
261277

262278
def __init__(self):
263279
super(AppChannelListener, self).__init__()
264280

265281
def OnStateChange(self, state):
266-
_log.debug('In AppChannelListener.OnStateChange: state={}'.format(state))
282+
_log.debug("In AppChannelListener.OnStateChange: state={}".format(state))
267283

268284

269285
class MyLogger(openpal.ILogHandler):
270286
"""
271-
Override ILogHandler in this manner to implement application-specific logging behavior.
287+
Override ILogHandler in this manner to implement application-specific logging behavior.
272288
"""
273289

274290
def __init__(self):
275291
super(MyLogger, self).__init__()
276292

277293
def Log(self, entry):
278294
filters = entry.filters.GetBitfield()
279-
location = entry.location.rsplit('/')[-1] if entry.location else ''
295+
location = entry.location.rsplit("/")[-1] if entry.location else ""
280296
message = entry.message
281-
_log.debug('Log\tfilters={}\tlocation={}\tentry={}'.format(filters, location, message))
297+
_log.debug(
298+
"Log\tfilters={}\tlocation={}\tentry={}".format(filters, location, message)
299+
)
282300

283301

284302
def main():
285303
"""The Outstation has been started from the command line. Execute ad-hoc tests if desired."""
286304
app = OutstationApplication()
287-
_log.debug('Initialization complete. In command loop.')
305+
_log.debug("Initialization complete. In command loop.")
288306
# Ad-hoc tests can be inserted here if desired. See outstation_cmd.py for examples.
289307
app.shutdown()
290-
_log.debug('Exiting.')
308+
_log.debug("Exiting.")
291309
exit()
292310

293311

294-
if __name__ == '__main__':
312+
if __name__ == "__main__":
295313
main()

0 commit comments

Comments
 (0)