pydnp3-stepfunc 1.6.0.3__cp39-cp39-manylinux1_x86_64.whl

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.
dnp3/types.py ADDED
@@ -0,0 +1,517 @@
1
+ """
2
+ DNP3 data types, enums, and flags.
3
+
4
+ Provides Pythonic wrappers around the C FFI enum and struct types.
5
+ """
6
+
7
+ from enum import IntEnum
8
+ from dataclasses import dataclass
9
+ from typing import Optional
10
+ import time as _time
11
+
12
+ from ._ffi import ffi, lib
13
+
14
+
15
+ # --- Enums ---
16
+
17
+ class ParamError(IntEnum):
18
+ OK = 0
19
+ INVALID_TIMEOUT = 1
20
+ NULL_PARAMETER = 2
21
+ STRING_NOT_UTF8 = 3
22
+ NO_SUPPORT = 4
23
+ ASSOCIATION_DOES_NOT_EXIST = 5
24
+ ASSOCIATION_DUPLICATE_ADDRESS = 6
25
+ INVALID_SOCKET_ADDRESS = 7
26
+ INVALID_DNP3_ADDRESS = 8
27
+ INVALID_BUFFER_SIZE = 9
28
+ ADDRESS_FILTER_CONFLICT = 10
29
+ SERVER_ALREADY_STARTED = 11
30
+ SERVER_BIND_ERROR = 12
31
+ MASTER_ALREADY_SHUTDOWN = 13
32
+ RUNTIME_CREATION_FAILURE = 14
33
+ RUNTIME_DESTROYED = 15
34
+ RUNTIME_CANNOT_BLOCK_WITHIN_ASYNC = 16
35
+ LOGGING_ALREADY_CONFIGURED = 17
36
+ POINT_DOES_NOT_EXIST = 18
37
+ INVALID_PEER_CERTIFICATE = 19
38
+ INVALID_LOCAL_CERTIFICATE = 20
39
+ INVALID_PRIVATE_KEY = 21
40
+ INVALID_DNS_NAME = 22
41
+ OTHER_TLS_ERROR = 23
42
+ WRONG_CHANNEL_TYPE = 24
43
+ CONSUMED = 25
44
+
45
+
46
+ class CommandStatus(IntEnum):
47
+ SUCCESS = 0
48
+ TIMEOUT = 1
49
+ NO_SELECT = 2
50
+ FORMAT_ERROR = 3
51
+ NOT_SUPPORTED = 4
52
+ ALREADY_ACTIVE = 5
53
+ HARDWARE_ERROR = 6
54
+ LOCAL = 7
55
+ TOO_MANY_OPS = 8
56
+ NOT_AUTHORIZED = 9
57
+ AUTOMATION_INHIBIT = 10
58
+ PROCESSING_LIMITED = 11
59
+ OUT_OF_RANGE = 12
60
+ DOWNSTREAM_LOCAL = 13
61
+ ALREADY_COMPLETE = 14
62
+ BLOCKED = 15
63
+ CANCELED = 16
64
+ BLOCKED_OTHER_MASTER = 17
65
+ DOWNSTREAM_FAIL = 18
66
+ NON_PARTICIPATING = 19
67
+ UNKNOWN = 20
68
+
69
+
70
+ class TripCloseCode(IntEnum):
71
+ NUL = 0
72
+ CLOSE = 1
73
+ TRIP = 2
74
+ RESERVED = 3
75
+
76
+
77
+ class OpType(IntEnum):
78
+ NUL = 0
79
+ PULSE_ON = 1
80
+ PULSE_OFF = 2
81
+ LATCH_ON = 3
82
+ LATCH_OFF = 4
83
+
84
+
85
+ class DoubleBit(IntEnum):
86
+ INTERMEDIATE = 0
87
+ DETERMINED_OFF = 1
88
+ DETERMINED_ON = 2
89
+ INDETERMINATE = 3
90
+
91
+
92
+ class TimeQuality(IntEnum):
93
+ SYNCHRONIZED = 0
94
+ UNSYNCHRONIZED = 1
95
+ INVALID = 2
96
+
97
+
98
+ class CommandMode(IntEnum):
99
+ DIRECT_OPERATE = 0
100
+ SELECT_BEFORE_OPERATE = 1
101
+
102
+
103
+ class TimeSyncMode(IntEnum):
104
+ LAN = 0
105
+ NON_LAN = 1
106
+
107
+
108
+ class LinkErrorMode(IntEnum):
109
+ DISCARD = 0
110
+ CLOSE = 1
111
+
112
+
113
+ class AppDecodeLevel(IntEnum):
114
+ NOTHING = 0
115
+ HEADER = 1
116
+ OBJECT_HEADERS = 2
117
+ OBJECT_VALUES = 3
118
+
119
+
120
+ class TransportDecodeLevel(IntEnum):
121
+ NOTHING = 0
122
+ HEADER = 1
123
+ PAYLOAD = 2
124
+
125
+
126
+ class LinkDecodeLevel(IntEnum):
127
+ NOTHING = 0
128
+ HEADER = 1
129
+ PAYLOAD = 2
130
+
131
+
132
+ class PhysDecodeLevel(IntEnum):
133
+ NOTHING = 0
134
+ LENGTH = 1
135
+ DATA = 2
136
+
137
+
138
+ class FunctionCode(IntEnum):
139
+ CONFIRM = 0
140
+ READ = 1
141
+ WRITE = 2
142
+ SELECT = 3
143
+ OPERATE = 4
144
+ DIRECT_OPERATE = 5
145
+ DIRECT_OPERATE_NO_ACK = 6
146
+ IMMEDIATE_FREEZE = 7
147
+ IMMEDIATE_FREEZE_NO_ACK = 8
148
+ FREEZE_AND_CLEAR = 9
149
+ FREEZE_AND_CLEAR_NO_ACK = 10
150
+ FREEZE_AT_TIME = 11
151
+ FREEZE_AT_TIME_NO_ACK = 12
152
+ COLD_RESTART = 13
153
+ WARM_RESTART = 14
154
+ INITIALIZE_DATA = 15
155
+ INITIALIZE_APPLICATION = 16
156
+ START_APPLICATION = 17
157
+ STOP_APPLICATION = 18
158
+ SAVE_CONFIGURATION = 19
159
+ ENABLE_UNSOLICITED = 20
160
+ DISABLE_UNSOLICITED = 21
161
+ ASSIGN_CLASS = 22
162
+ DELAY_MEASURE = 23
163
+ RECORD_CURRENT_TIME = 24
164
+ OPEN_FILE = 25
165
+ CLOSE_FILE = 26
166
+ DELETE_FILE = 27
167
+ GET_FILE_INFO = 28
168
+ AUTHENTICATE_FILE = 29
169
+ ABORT_FILE = 30
170
+ RESPONSE = 129
171
+ UNSOLICITED_RESPONSE = 130
172
+
173
+
174
+ class ClientState(IntEnum):
175
+ DISABLED = 0
176
+ CONNECTING = 1
177
+ CONNECTED = 2
178
+ WAIT_AFTER_FAILED_CONNECT = 3
179
+ WAIT_AFTER_DISCONNECT = 4
180
+ SHUTDOWN = 5
181
+
182
+
183
+ class ReadType(IntEnum):
184
+ STARTUP_INTEGRITY = 0
185
+ UNSOLICITED = 1
186
+ SINGLE_POLL = 2
187
+ PERIODIC_POLL = 3
188
+
189
+
190
+ class TaskType(IntEnum):
191
+ USER_READ = 0
192
+ PERIODIC_POLL = 1
193
+ STARTUP_INTEGRITY = 2
194
+ AUTO_EVENT_SCAN = 3
195
+ COMMAND = 4
196
+ CLEAR_RESTART_BIT = 5
197
+ ENABLE_UNSOLICITED = 6
198
+ DISABLE_UNSOLICITED = 7
199
+ TIME_SYNC = 8
200
+ RESTART = 9
201
+ FILE_READ = 10
202
+ GET_FILE_INFO = 11
203
+ READ_DIRECTORY = 12
204
+ FILE_AUTH = 13
205
+ FILE_OPEN = 14
206
+ FILE_WRITE = 15
207
+ FILE_CLOSE = 16
208
+ GENERIC_EMPTY_RESPONSE = 17
209
+ WRITE_DEAD_BANDS = 18
210
+ LINK_STATUS = 19
211
+
212
+
213
+ class AutoTimeSync(IntEnum):
214
+ NONE = 0
215
+ LAN = 1
216
+ NON_LAN = 2
217
+
218
+
219
+ def _build_variation_enum():
220
+ """Build Variation enum dynamically from C library constants."""
221
+ members = {}
222
+ for attr_name in dir(lib):
223
+ if attr_name.startswith("DNP3_VARIATION_GROUP"):
224
+ name = attr_name.replace("DNP3_VARIATION_", "")
225
+ members[name] = getattr(lib, attr_name)
226
+ return IntEnum("Variation", members)
227
+
228
+
229
+ Variation = _build_variation_enum()
230
+
231
+
232
+ # Convenience group/variation mappings
233
+ BINARY_INPUT = Variation["GROUP1_VAR0"]
234
+ BINARY_OUTPUT = Variation["GROUP10_VAR0"]
235
+ ANALOG_INPUT = Variation["GROUP30_VAR0"]
236
+ ANALOG_OUTPUT = Variation["GROUP40_VAR0"]
237
+ COUNTER = Variation["GROUP20_VAR0"]
238
+ FROZEN_COUNTER = Variation["GROUP21_VAR0"]
239
+
240
+
241
+ # --- Data classes ---
242
+
243
+ @dataclass
244
+ class Flags:
245
+ value: int
246
+
247
+ @property
248
+ def online(self) -> bool:
249
+ return bool(self.value & 0x01)
250
+
251
+ @property
252
+ def restart(self) -> bool:
253
+ return bool(self.value & 0x02)
254
+
255
+ @property
256
+ def comm_lost(self) -> bool:
257
+ return bool(self.value & 0x04)
258
+
259
+ @property
260
+ def remote_forced(self) -> bool:
261
+ return bool(self.value & 0x08)
262
+
263
+ @property
264
+ def local_forced(self) -> bool:
265
+ return bool(self.value & 0x10)
266
+
267
+ def to_ffi(self):
268
+ return {"value": self.value}
269
+
270
+
271
+ @dataclass
272
+ class Timestamp:
273
+ value: int # milliseconds since UNIX epoch
274
+ quality: TimeQuality
275
+
276
+ @classmethod
277
+ def now(cls) -> "Timestamp":
278
+ return cls(int(_time.time() * 1000), TimeQuality.SYNCHRONIZED)
279
+
280
+ @classmethod
281
+ def invalid(cls) -> "Timestamp":
282
+ return cls(0, TimeQuality.INVALID)
283
+
284
+ def to_ffi(self):
285
+ return {"value": self.value, "quality": self.quality.value}
286
+
287
+
288
+ @dataclass
289
+ class BinaryInput:
290
+ index: int
291
+ value: bool
292
+ flags: Flags
293
+ time: Timestamp
294
+
295
+ @classmethod
296
+ def from_ffi(cls, c_obj) -> "BinaryInput":
297
+ return cls(
298
+ index=c_obj.index,
299
+ value=bool(c_obj.value),
300
+ flags=Flags(c_obj.flags.value),
301
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
302
+ )
303
+
304
+
305
+ @dataclass
306
+ class DoubleBitBinaryInput:
307
+ index: int
308
+ value: DoubleBit
309
+ flags: Flags
310
+ time: Timestamp
311
+
312
+ @classmethod
313
+ def from_ffi(cls, c_obj) -> "DoubleBitBinaryInput":
314
+ return cls(
315
+ index=c_obj.index,
316
+ value=DoubleBit(c_obj.value),
317
+ flags=Flags(c_obj.flags.value),
318
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
319
+ )
320
+
321
+
322
+ @dataclass
323
+ class BinaryOutputStatus:
324
+ index: int
325
+ value: bool
326
+ flags: Flags
327
+ time: Timestamp
328
+
329
+ @classmethod
330
+ def from_ffi(cls, c_obj) -> "BinaryOutputStatus":
331
+ return cls(
332
+ index=c_obj.index,
333
+ value=bool(c_obj.value),
334
+ flags=Flags(c_obj.flags.value),
335
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
336
+ )
337
+
338
+
339
+ @dataclass
340
+ class Counter:
341
+ index: int
342
+ value: int
343
+ flags: Flags
344
+ time: Timestamp
345
+
346
+ @classmethod
347
+ def from_ffi(cls, c_obj) -> "Counter":
348
+ return cls(
349
+ index=c_obj.index,
350
+ value=c_obj.value,
351
+ flags=Flags(c_obj.flags.value),
352
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
353
+ )
354
+
355
+
356
+ @dataclass
357
+ class FrozenCounter:
358
+ index: int
359
+ value: int
360
+ flags: Flags
361
+ time: Timestamp
362
+
363
+ @classmethod
364
+ def from_ffi(cls, c_obj) -> "FrozenCounter":
365
+ return cls(
366
+ index=c_obj.index,
367
+ value=c_obj.value,
368
+ flags=Flags(c_obj.flags.value),
369
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
370
+ )
371
+
372
+
373
+ @dataclass
374
+ class AnalogInput:
375
+ index: int
376
+ value: float
377
+ flags: Flags
378
+ time: Timestamp
379
+
380
+ @classmethod
381
+ def from_ffi(cls, c_obj) -> "AnalogInput":
382
+ return cls(
383
+ index=c_obj.index,
384
+ value=c_obj.value,
385
+ flags=Flags(c_obj.flags.value),
386
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
387
+ )
388
+
389
+
390
+ @dataclass
391
+ class AnalogOutputStatus:
392
+ index: int
393
+ value: float
394
+ flags: Flags
395
+ time: Timestamp
396
+
397
+ @classmethod
398
+ def from_ffi(cls, c_obj) -> "AnalogOutputStatus":
399
+ return cls(
400
+ index=c_obj.index,
401
+ value=c_obj.value,
402
+ flags=Flags(c_obj.flags.value),
403
+ time=Timestamp(c_obj.time.value, TimeQuality(c_obj.time.quality)),
404
+ )
405
+
406
+
407
+ @dataclass
408
+ class ControlCode:
409
+ tcc: TripCloseCode = TripCloseCode.NUL
410
+ clear: bool = False
411
+ op_type: OpType = OpType.LATCH_ON
412
+
413
+ def to_ffi(self):
414
+ return {
415
+ "tcc": self.tcc.value,
416
+ "clear": self.clear,
417
+ "queue": False,
418
+ "op_type": self.op_type.value,
419
+ }
420
+
421
+
422
+ @dataclass
423
+ class Group12Var1:
424
+ """Control Relay Output Block (CROB)."""
425
+ code: ControlCode
426
+ count: int = 1
427
+ on_time: int = 1000
428
+ off_time: int = 1000
429
+
430
+ def to_ffi(self):
431
+ return {
432
+ "code": self.code.to_ffi(),
433
+ "count": self.count,
434
+ "on_time": self.on_time,
435
+ "off_time": self.off_time,
436
+ }
437
+
438
+
439
+ @dataclass
440
+ class DecodeLevel:
441
+ application: AppDecodeLevel = AppDecodeLevel.NOTHING
442
+ transport: TransportDecodeLevel = TransportDecodeLevel.NOTHING
443
+ link: LinkDecodeLevel = LinkDecodeLevel.NOTHING
444
+ physical: PhysDecodeLevel = PhysDecodeLevel.NOTHING
445
+
446
+ def to_ffi(self):
447
+ return {
448
+ "application": self.application.value,
449
+ "transport": self.transport.value,
450
+ "link": self.link.value,
451
+ "physical": self.physical.value,
452
+ }
453
+
454
+
455
+ @dataclass
456
+ class ConnectStrategy:
457
+ min_connect_delay: int = 1000 # ms
458
+ max_connect_delay: int = 10000 # ms
459
+ reconnect_delay: int = 1000 # ms
460
+
461
+ def to_ffi(self):
462
+ return {
463
+ "min_connect_delay": self.min_connect_delay,
464
+ "max_connect_delay": self.max_connect_delay,
465
+ "reconnect_delay": self.reconnect_delay,
466
+ }
467
+
468
+
469
+ @dataclass
470
+ class IIN1:
471
+ broadcast: bool = False
472
+ class_1_events: bool = False
473
+ class_2_events: bool = False
474
+ class_3_events: bool = False
475
+ need_time: bool = False
476
+ local_control: bool = False
477
+ device_trouble: bool = False
478
+ device_restart: bool = False
479
+
480
+
481
+ @dataclass
482
+ class IIN2:
483
+ no_func_code_support: bool = False
484
+ object_unknown: bool = False
485
+ parameter_error: bool = False
486
+ event_buffer_overflow: bool = False
487
+ already_executing: bool = False
488
+ config_corrupt: bool = False
489
+
490
+
491
+ @dataclass
492
+ class IIN:
493
+ iin1: IIN1
494
+ iin2: IIN2
495
+
496
+ @classmethod
497
+ def from_ffi(cls, c_iin):
498
+ return cls(
499
+ iin1=IIN1(
500
+ broadcast=bool(c_iin.iin1.broadcast),
501
+ class_1_events=bool(c_iin.iin1.class_1_events),
502
+ class_2_events=bool(c_iin.iin1.class_2_events),
503
+ class_3_events=bool(c_iin.iin1.class_3_events),
504
+ need_time=bool(c_iin.iin1.need_time),
505
+ local_control=bool(c_iin.iin1.local_control),
506
+ device_trouble=bool(c_iin.iin1.device_trouble),
507
+ device_restart=bool(c_iin.iin1.device_restart),
508
+ ),
509
+ iin2=IIN2(
510
+ no_func_code_support=bool(c_iin.iin2.no_func_code_support),
511
+ object_unknown=bool(c_iin.iin2.object_unknown),
512
+ parameter_error=bool(c_iin.iin2.parameter_error),
513
+ event_buffer_overflow=bool(c_iin.iin2.event_buffer_overflow),
514
+ already_executing=bool(c_iin.iin2.already_executing),
515
+ config_corrupt=bool(c_iin.iin2.config_corrupt),
516
+ ),
517
+ )