hat.drivers.iec104
IEC 60870-5-104 communication protocol
1"""IEC 60870-5-104 communication protocol""" 2 3from hat.drivers.iec104.common import (AsduTypeError, 4 TimeSize, 5 Time, 6 OriginatorAddress, 7 AsduAddress, 8 IoAddress, 9 IndicationQuality, 10 MeasurementQuality, 11 CounterQuality, 12 ProtectionQuality, 13 Quality, 14 FreezeCode, 15 SingleValue, 16 DoubleValue, 17 RegulatingValue, 18 StepPositionValue, 19 BitstringValue, 20 NormalizedValue, 21 ScaledValue, 22 FloatingValue, 23 BinaryCounterValue, 24 ProtectionValue, 25 ProtectionStartValue, 26 ProtectionCommandValue, 27 StatusValue, 28 OtherCause, 29 DataResCause, 30 DataCause, 31 CommandReqCause, 32 CommandResCause, 33 CommandCause, 34 InitializationResCause, 35 InitializationCause, 36 ReadReqCause, 37 ReadResCause, 38 ReadCause, 39 ClockSyncReqCause, 40 ClockSyncResCause, 41 ClockSyncCause, 42 ActivationReqCause, 43 ActivationResCause, 44 ActivationCause, 45 DelayReqCause, 46 DelayResCause, 47 DelayCause, 48 ParameterReqCause, 49 ParameterResCause, 50 ParameterCause, 51 ParameterActivationReqCause, 52 ParameterActivationResCause, 53 ParameterActivationCause, 54 SingleData, 55 DoubleData, 56 StepPositionData, 57 BitstringData, 58 NormalizedData, 59 ScaledData, 60 FloatingData, 61 BinaryCounterData, 62 ProtectionData, 63 ProtectionStartData, 64 ProtectionCommandData, 65 StatusData, 66 Data, 67 SingleCommand, 68 DoubleCommand, 69 RegulatingCommand, 70 NormalizedCommand, 71 ScaledCommand, 72 FloatingCommand, 73 BitstringCommand, 74 Command, 75 NormalizedParameter, 76 ScaledParameter, 77 FloatingParameter, 78 Parameter, 79 DataMsg, 80 CommandMsg, 81 InitializationMsg, 82 InterrogationMsg, 83 CounterInterrogationMsg, 84 ReadMsg, 85 ClockSyncMsg, 86 TestMsg, 87 ResetMsg, 88 ParameterMsg, 89 ParameterActivationMsg, 90 Msg, 91 time_from_datetime, 92 time_to_datetime) 93from hat.drivers.iec104.connection import (ConnectionCb, 94 connect, 95 listen, 96 Connection) 97 98 99__all__ = ['AsduTypeError', 100 'TimeSize', 101 'Time', 102 'OriginatorAddress', 103 'AsduAddress', 104 'IoAddress', 105 'IndicationQuality', 106 'MeasurementQuality', 107 'CounterQuality', 108 'ProtectionQuality', 109 'Quality', 110 'FreezeCode', 111 'SingleValue', 112 'DoubleValue', 113 'RegulatingValue', 114 'StepPositionValue', 115 'BitstringValue', 116 'NormalizedValue', 117 'ScaledValue', 118 'FloatingValue', 119 'BinaryCounterValue', 120 'ProtectionValue', 121 'ProtectionStartValue', 122 'ProtectionCommandValue', 123 'StatusValue', 124 'OtherCause', 125 'DataResCause', 126 'DataCause', 127 'CommandReqCause', 128 'CommandResCause', 129 'CommandCause', 130 'InitializationResCause', 131 'InitializationCause', 132 'ReadReqCause', 133 'ReadResCause', 134 'ReadCause', 135 'ClockSyncReqCause', 136 'ClockSyncResCause', 137 'ClockSyncCause', 138 'ActivationReqCause', 139 'ActivationResCause', 140 'ActivationCause', 141 'DelayReqCause', 142 'DelayResCause', 143 'DelayCause', 144 'ParameterReqCause', 145 'ParameterResCause', 146 'ParameterCause', 147 'ParameterActivationReqCause', 148 'ParameterActivationResCause', 149 'ParameterActivationCause', 150 'SingleData', 151 'DoubleData', 152 'StepPositionData', 153 'BitstringData', 154 'NormalizedData', 155 'ScaledData', 156 'FloatingData', 157 'BinaryCounterData', 158 'ProtectionData', 159 'ProtectionStartData', 160 'ProtectionCommandData', 161 'StatusData', 162 'Data', 163 'SingleCommand', 164 'DoubleCommand', 165 'RegulatingCommand', 166 'NormalizedCommand', 167 'ScaledCommand', 168 'FloatingCommand', 169 'BitstringCommand', 170 'Command', 171 'NormalizedParameter', 172 'ScaledParameter', 173 'FloatingParameter', 174 'Parameter', 175 'DataMsg', 176 'CommandMsg', 177 'InitializationMsg', 178 'InterrogationMsg', 179 'CounterInterrogationMsg', 180 'ReadMsg', 181 'ClockSyncMsg', 182 'TestMsg', 183 'ResetMsg', 184 'ParameterMsg', 185 'ParameterActivationMsg', 186 'Msg', 187 'time_from_datetime', 188 'time_to_datetime', 189 'ConnectionCb', 190 'connect', 191 'listen', 192 'Connection']
Common base class for all non-exit exceptions.
31class Time(typing.NamedTuple): 32 size: TimeSize 33 milliseconds: int 34 """milliseconds in range [0, 59999]""" 35 invalid: bool | None 36 """available for size THREE, FOUR, SEVEN""" 37 minutes: int | None 38 """available for size THREE, FOUR, SEVEN (minutes in range [0, 59])""" 39 summer_time: bool | None 40 """available for size FOUR, SEVEN""" 41 hours: int | None 42 """available for size FOUR, SEVEN (hours in range [0, 23])""" 43 day_of_week: int | None 44 """available for size SEVEN (day_of_week in range [1, 7])""" 45 day_of_month: int | None 46 """available for size SEVEN (day_of_month in range [1, 31])""" 47 months: int | None 48 """available for size SEVEN (months in range [1, 12])""" 49 years: int | None 50 """available for size SEVEN (years in range [0, 99])"""
Time(size, milliseconds, invalid, minutes, summer_time, hours, day_of_week, day_of_month, months, years)
Create new instance of Time(size, milliseconds, invalid, minutes, summer_time, hours, day_of_week, day_of_month, months, years)
147class IndicationQuality(typing.NamedTuple): 148 invalid: bool 149 not_topical: bool 150 substituted: bool 151 blocked: bool
IndicationQuality(invalid, not_topical, substituted, blocked)
154class MeasurementQuality(typing.NamedTuple): 155 invalid: bool 156 not_topical: bool 157 substituted: bool 158 blocked: bool 159 overflow: bool
MeasurementQuality(invalid, not_topical, substituted, blocked, overflow)
162class CounterQuality(typing.NamedTuple): 163 invalid: bool 164 adjusted: bool 165 overflow: bool 166 sequence: int 167 """sequence in range [0, 31]"""
CounterQuality(invalid, adjusted, overflow, sequence)
170class ProtectionQuality(typing.NamedTuple): 171 invalid: bool 172 not_topical: bool 173 substituted: bool 174 blocked: bool 175 time_invalid: bool
ProtectionQuality(invalid, not_topical, substituted, blocked, time_invalid)
196class DoubleValue(enum.Enum): 197 """DoubleDataValue 198 199 `FAULT` stands for value 3, defined in the protocol as *INDETERMINATE*. 200 This is in order to make it more distinguishable from ``INTERMEDIATE``. 201 202 """ 203 INTERMEDIATE = 0 204 OFF = 1 205 ON = 2 206 FAULT = 3
DoubleDataValue
FAULT stands for value 3, defined in the protocol as INDETERMINATE.
This is in order to make it more distinguishable from INTERMEDIATE.
214class StepPositionValue(typing.NamedTuple): 215 value: int 216 """value in range [-64, 63]""" 217 transient: bool
StepPositionValue(value, transient)
220class BitstringValue(typing.NamedTuple): 221 value: util.Bytes 222 """bitstring encoded as 4 bytes"""
BitstringValue(value,)
NormalizedValue(value,)
ScaledValue(value,)
FloatingValue(value,)
239class BinaryCounterValue(typing.NamedTuple): 240 value: int 241 """value in range [-2^31, 2^31-1]"""
BinaryCounterValue(value,)
249class ProtectionStartValue(typing.NamedTuple): 250 general: bool 251 l1: bool 252 l2: bool 253 l3: bool 254 ie: bool 255 reverse: bool
ProtectionStartValue(general, l1, l2, l3, ie, reverse)
258class ProtectionCommandValue(typing.NamedTuple): 259 general: bool 260 l1: bool 261 l2: bool 262 l3: bool
ProtectionCommandValue(general, l1, l2, l3)
265class StatusValue(typing.NamedTuple): 266 value: list[bool] 267 """value length is 16""" 268 change: list[bool] 269 """change length is 16"""
StatusValue(value, change)
52class DataResCause(enum.Enum): 53 PERIODIC = iec101.CauseType.PERIODIC.value 54 BACKGROUND_SCAN = iec101.CauseType.BACKGROUND_SCAN.value 55 SPONTANEOUS = iec101.CauseType.SPONTANEOUS.value 56 REQUEST = iec101.CauseType.REQUEST.value 57 REMOTE_COMMAND = iec101.CauseType.REMOTE_COMMAND.value 58 LOCAL_COMMAND = iec101.CauseType.LOCAL_COMMAND.value 59 INTERROGATED_STATION = iec101.CauseType.INTERROGATED_STATION.value 60 INTERROGATED_GROUP01 = iec101.CauseType.INTERROGATED_GROUP01.value 61 INTERROGATED_GROUP02 = iec101.CauseType.INTERROGATED_GROUP02.value 62 INTERROGATED_GROUP03 = iec101.CauseType.INTERROGATED_GROUP03.value 63 INTERROGATED_GROUP04 = iec101.CauseType.INTERROGATED_GROUP04.value 64 INTERROGATED_GROUP05 = iec101.CauseType.INTERROGATED_GROUP05.value 65 INTERROGATED_GROUP06 = iec101.CauseType.INTERROGATED_GROUP06.value 66 INTERROGATED_GROUP07 = iec101.CauseType.INTERROGATED_GROUP07.value 67 INTERROGATED_GROUP08 = iec101.CauseType.INTERROGATED_GROUP08.value 68 INTERROGATED_GROUP09 = iec101.CauseType.INTERROGATED_GROUP09.value 69 INTERROGATED_GROUP10 = iec101.CauseType.INTERROGATED_GROUP10.value 70 INTERROGATED_GROUP11 = iec101.CauseType.INTERROGATED_GROUP11.value 71 INTERROGATED_GROUP12 = iec101.CauseType.INTERROGATED_GROUP12.value 72 INTERROGATED_GROUP13 = iec101.CauseType.INTERROGATED_GROUP13.value 73 INTERROGATED_GROUP14 = iec101.CauseType.INTERROGATED_GROUP14.value 74 INTERROGATED_GROUP15 = iec101.CauseType.INTERROGATED_GROUP15.value 75 INTERROGATED_GROUP16 = iec101.CauseType.INTERROGATED_GROUP16.value 76 INTERROGATED_COUNTER = iec101.CauseType.INTERROGATED_COUNTER.value 77 INTERROGATED_COUNTER01 = iec101.CauseType.INTERROGATED_COUNTER01.value 78 INTERROGATED_COUNTER02 = iec101.CauseType.INTERROGATED_COUNTER02.value 79 INTERROGATED_COUNTER03 = iec101.CauseType.INTERROGATED_COUNTER03.value 80 INTERROGATED_COUNTER04 = iec101.CauseType.INTERROGATED_COUNTER04.value
86class CommandReqCause(enum.Enum): 87 ACTIVATION = iec101.CauseType.ACTIVATION.value 88 DEACTIVATION = iec101.CauseType.DEACTIVATION.value
91class CommandResCause(enum.Enum): 92 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 93 DEACTIVATION_CONFIRMATION = iec101.CauseType.DEACTIVATION_CONFIRMATION.value # NOQA 94 ACTIVATION_TERMINATION = iec101.CauseType.ACTIVATION_TERMINATION.value 95 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 96 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 97 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 98 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
104class InitializationResCause(enum.Enum): 105 LOCAL_POWER = 0 106 LOCAL_RESET = 1 107 REMOTE_RESET = 2
117class ReadResCause(enum.Enum): 118 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 119 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 120 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 121 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
131class ClockSyncResCause(enum.Enum): 132 SPONTANEOUS = iec101.CauseType.SPONTANEOUS.value 133 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 134 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 135 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 136 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 137 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
149class ActivationResCause(enum.Enum): 150 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 151 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 152 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 153 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 154 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
162class DelayReqCause(enum.Enum): 163 SPONTANEOUS = iec101.CauseType.SPONTANEOUS.value 164 ACTIVATION = iec101.CauseType.ACTIVATION.value
167class DelayResCause(enum.Enum): 168 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 169 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 170 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 171 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 172 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
178class ParameterReqCause(enum.Enum): 179 SPONTANEOUS = iec101.CauseType.SPONTANEOUS.value 180 ACTIVATION = iec101.CauseType.ACTIVATION.value
183class ParameterResCause(enum.Enum): 184 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 185 INTERROGATED_STATION = iec101.CauseType.INTERROGATED_STATION.value 186 INTERROGATED_GROUP01 = iec101.CauseType.INTERROGATED_GROUP01.value 187 INTERROGATED_GROUP02 = iec101.CauseType.INTERROGATED_GROUP02.value 188 INTERROGATED_GROUP03 = iec101.CauseType.INTERROGATED_GROUP03.value 189 INTERROGATED_GROUP04 = iec101.CauseType.INTERROGATED_GROUP04.value 190 INTERROGATED_GROUP05 = iec101.CauseType.INTERROGATED_GROUP05.value 191 INTERROGATED_GROUP06 = iec101.CauseType.INTERROGATED_GROUP06.value 192 INTERROGATED_GROUP07 = iec101.CauseType.INTERROGATED_GROUP07.value 193 INTERROGATED_GROUP08 = iec101.CauseType.INTERROGATED_GROUP08.value 194 INTERROGATED_GROUP09 = iec101.CauseType.INTERROGATED_GROUP09.value 195 INTERROGATED_GROUP10 = iec101.CauseType.INTERROGATED_GROUP10.value 196 INTERROGATED_GROUP11 = iec101.CauseType.INTERROGATED_GROUP11.value 197 INTERROGATED_GROUP12 = iec101.CauseType.INTERROGATED_GROUP12.value 198 INTERROGATED_GROUP13 = iec101.CauseType.INTERROGATED_GROUP13.value 199 INTERROGATED_GROUP14 = iec101.CauseType.INTERROGATED_GROUP14.value 200 INTERROGATED_GROUP15 = iec101.CauseType.INTERROGATED_GROUP15.value 201 INTERROGATED_GROUP16 = iec101.CauseType.INTERROGATED_GROUP16.value 202 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 203 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 204 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 205 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
213class ParameterActivationReqCause(enum.Enum): 214 ACTIVATION = iec101.CauseType.ACTIVATION.value 215 DEACTIVATION = iec101.CauseType.DEACTIVATION.value
218class ParameterActivationResCause(enum.Enum): 219 ACTIVATION_CONFIRMATION = iec101.CauseType.ACTIVATION_CONFIRMATION.value 220 DEACTIVATION_CONFIRMATION = iec101.CauseType.DEACTIVATION_CONFIRMATION.value # NOQA 221 UNKNOWN_TYPE = iec101.CauseType.UNKNOWN_TYPE.value 222 UNKNOWN_CAUSE = iec101.CauseType.UNKNOWN_CAUSE.value 223 UNKNOWN_ASDU_ADDRESS = iec101.CauseType.UNKNOWN_ASDU_ADDRESS.value 224 UNKNOWN_IO_ADDRESS = iec101.CauseType.UNKNOWN_IO_ADDRESS.value
SingleData(value, quality)
Create new instance of SingleData(value, quality)
DoubleData(value, quality)
Create new instance of DoubleData(value, quality)
242class StepPositionData(typing.NamedTuple): 243 value: StepPositionValue 244 quality: MeasurementQuality
StepPositionData(value, quality)
Create new instance of StepPositionData(value, quality)
247class BitstringData(typing.NamedTuple): 248 value: BitstringValue 249 quality: MeasurementQuality
BitstringData(value, quality)
Create new instance of BitstringData(value, quality)
252class NormalizedData(typing.NamedTuple): 253 value: NormalizedValue 254 quality: MeasurementQuality | None
NormalizedData(value, quality)
Create new instance of NormalizedData(value, quality)
ScaledData(value, quality)
Create new instance of ScaledData(value, quality)
FloatingData(value, quality)
Create new instance of FloatingData(value, quality)
267class BinaryCounterData(typing.NamedTuple): 268 value: BinaryCounterValue 269 quality: CounterQuality
BinaryCounterData(value, quality)
Create new instance of BinaryCounterData(value, quality)
272class ProtectionData(typing.NamedTuple): 273 value: ProtectionValue 274 quality: ProtectionQuality 275 elapsed_time: int 276 """elapsed_time in range [0, 65535]"""
ProtectionData(value, quality, elapsed_time)
Create new instance of ProtectionData(value, quality, elapsed_time)
279class ProtectionStartData(typing.NamedTuple): 280 value: ProtectionStartValue 281 quality: ProtectionQuality 282 duration_time: int 283 """duration_time in range [0, 65535]"""
ProtectionStartData(value, quality, duration_time)
Create new instance of ProtectionStartData(value, quality, duration_time)
286class ProtectionCommandData(typing.NamedTuple): 287 value: ProtectionCommandValue 288 quality: ProtectionQuality 289 operating_time: int 290 """operating_time in range [0, 65535]"""
ProtectionCommandData(value, quality, operating_time)
Create new instance of ProtectionCommandData(value, quality, operating_time)
StatusData(value, quality)
Create new instance of StatusData(value, quality)
312class SingleCommand(typing.NamedTuple): 313 value: SingleValue 314 select: bool 315 qualifier: int 316 """qualifier in range [0, 31]"""
SingleCommand(value, select, qualifier)
Create new instance of SingleCommand(value, select, qualifier)
319class DoubleCommand(typing.NamedTuple): 320 value: DoubleValue 321 select: bool 322 qualifier: int 323 """qualifier in range [0, 31]"""
DoubleCommand(value, select, qualifier)
Create new instance of DoubleCommand(value, select, qualifier)
326class RegulatingCommand(typing.NamedTuple): 327 value: RegulatingValue 328 select: bool 329 qualifier: int 330 """qualifier in range [0, 31]"""
RegulatingCommand(value, select, qualifier)
Create new instance of RegulatingCommand(value, select, qualifier)
NormalizedCommand(value, select)
Create new instance of NormalizedCommand(value, select)
ScaledCommand(value, select)
Create new instance of ScaledCommand(value, select)
FloatingCommand(value, select)
Create new instance of FloatingCommand(value, select)
BitstringCommand(value,)
361class NormalizedParameter(typing.NamedTuple): 362 value: NormalizedValue 363 qualifier: int 364 """qualifier in range [0, 255]"""
NormalizedParameter(value, qualifier)
Create new instance of NormalizedParameter(value, qualifier)
367class ScaledParameter(typing.NamedTuple): 368 value: ScaledValue 369 qualifier: int 370 """qualifier in range [0, 255]"""
ScaledParameter(value, qualifier)
Create new instance of ScaledParameter(value, qualifier)
373class FloatingParameter(typing.NamedTuple): 374 value: FloatingValue 375 qualifier: int 376 """qualifier in range [0, 255]"""
FloatingParameter(value, qualifier)
Create new instance of FloatingParameter(value, qualifier)
384class DataMsg(typing.NamedTuple): 385 is_test: bool 386 originator_address: OriginatorAddress 387 asdu_address: AsduAddress 388 io_address: IoAddress 389 data: Data 390 time: Time | None 391 cause: DataCause
DataMsg(is_test, originator_address, asdu_address, io_address, data, time, cause)
Create new instance of DataMsg(is_test, originator_address, asdu_address, io_address, data, time, cause)
Alias for field number 4
120class CommandMsg(typing.NamedTuple): 121 is_test: bool 122 originator_address: OriginatorAddress 123 asdu_address: AsduAddress 124 io_address: IoAddress 125 command: Command 126 is_negative_confirm: bool 127 time: Time | None 128 cause: CommandCause
CommandMsg(is_test, originator_address, asdu_address, io_address, command, is_negative_confirm, time, cause)
Create new instance of CommandMsg(is_test, originator_address, asdu_address, io_address, command, is_negative_confirm, time, cause)
Alias for field number 4
404class InitializationMsg(typing.NamedTuple): 405 is_test: bool 406 originator_address: OriginatorAddress 407 asdu_address: AsduAddress 408 param_change: bool 409 cause: InitializationCause
InitializationMsg(is_test, originator_address, asdu_address, param_change, cause)
Create new instance of InitializationMsg(is_test, originator_address, asdu_address, param_change, cause)
412class InterrogationMsg(typing.NamedTuple): 413 is_test: bool 414 originator_address: OriginatorAddress 415 asdu_address: AsduAddress 416 request: int 417 """request in range [0, 255]""" 418 is_negative_confirm: bool 419 cause: CommandCause
InterrogationMsg(is_test, originator_address, asdu_address, request, is_negative_confirm, cause)
Create new instance of InterrogationMsg(is_test, originator_address, asdu_address, request, is_negative_confirm, cause)
422class CounterInterrogationMsg(typing.NamedTuple): 423 is_test: bool 424 originator_address: OriginatorAddress 425 asdu_address: AsduAddress 426 request: int 427 """request in range [0, 63]""" 428 freeze: FreezeCode 429 is_negative_confirm: bool 430 cause: CommandCause
CounterInterrogationMsg(is_test, originator_address, asdu_address, request, freeze, is_negative_confirm, cause)
Create new instance of CounterInterrogationMsg(is_test, originator_address, asdu_address, request, freeze, is_negative_confirm, cause)
433class ReadMsg(typing.NamedTuple): 434 is_test: bool 435 originator_address: OriginatorAddress 436 asdu_address: AsduAddress 437 io_address: IoAddress 438 cause: ReadCause
ReadMsg(is_test, originator_address, asdu_address, io_address, cause)
Create new instance of ReadMsg(is_test, originator_address, asdu_address, io_address, cause)
441class ClockSyncMsg(typing.NamedTuple): 442 is_test: bool 443 originator_address: OriginatorAddress 444 asdu_address: AsduAddress 445 time: Time 446 is_negative_confirm: bool 447 cause: ClockSyncCause
ClockSyncMsg(is_test, originator_address, asdu_address, time, is_negative_confirm, cause)
Create new instance of ClockSyncMsg(is_test, originator_address, asdu_address, time, is_negative_confirm, cause)
131class TestMsg(typing.NamedTuple): 132 is_test: bool 133 originator_address: OriginatorAddress 134 asdu_address: AsduAddress 135 counter: int 136 """counter in range [0, 65535]""" 137 time: Time 138 cause: ActivationCause
TestMsg(is_test, originator_address, asdu_address, counter, time, cause)
Create new instance of TestMsg(is_test, originator_address, asdu_address, counter, time, cause)
457class ResetMsg(typing.NamedTuple): 458 is_test: bool 459 originator_address: OriginatorAddress 460 asdu_address: AsduAddress 461 qualifier: int 462 """qualifier in range [0, 255]""" 463 cause: ActivationCause
ResetMsg(is_test, originator_address, asdu_address, qualifier, cause)
Create new instance of ResetMsg(is_test, originator_address, asdu_address, qualifier, cause)
475class ParameterMsg(typing.NamedTuple): 476 is_test: bool 477 originator_address: OriginatorAddress 478 asdu_address: AsduAddress 479 io_address: IoAddress 480 parameter: Parameter 481 cause: ParameterCause
ParameterMsg(is_test, originator_address, asdu_address, io_address, parameter, cause)
Create new instance of ParameterMsg(is_test, originator_address, asdu_address, io_address, parameter, cause)
484class ParameterActivationMsg(typing.NamedTuple): 485 is_test: bool 486 originator_address: OriginatorAddress 487 asdu_address: AsduAddress 488 io_address: IoAddress 489 qualifier: int 490 """qualifier in range [0, 255]""" 491 cause: ParameterActivationCause
ParameterActivationMsg(is_test, originator_address, asdu_address, io_address, qualifier, cause)
Create new instance of ParameterActivationMsg(is_test, originator_address, asdu_address, io_address, qualifier, cause)
66def time_from_datetime(dt: datetime.datetime, 67 invalid: bool = False 68 ) -> Time: 69 """Create Time from datetime.datetime""" 70 # TODO document edge cases (local time, os implementation, ...) 71 # rounding microseconds to the nearest millisecond 72 dt_rounded = ( 73 dt.replace(microsecond=0) + 74 datetime.timedelta(milliseconds=round(dt.microsecond / 1000))) 75 local_time = time.localtime(dt_rounded.timestamp()) 76 77 return Time( 78 size=TimeSize.SEVEN, 79 milliseconds=(local_time.tm_sec * 1000 + 80 dt_rounded.microsecond // 1000), 81 invalid=invalid, 82 minutes=local_time.tm_min, 83 summer_time=bool(local_time.tm_isdst), 84 hours=local_time.tm_hour, 85 day_of_week=local_time.tm_wday + 1, 86 day_of_month=local_time.tm_mday, 87 months=local_time.tm_mon, 88 years=local_time.tm_year % 100)
Create Time from datetime.datetime
91def time_to_datetime(t: Time 92 ) -> datetime.datetime: 93 """Convert Time to datetime.datetime""" 94 # TODO document edge cases (local time, os implementation, ...) 95 # TODO support TimeSize.FOUR 96 if t.size == TimeSize.TWO: 97 local_now = datetime.datetime.now() 98 local_dt = local_now.replace( 99 second=int(t.milliseconds / 1000), 100 microsecond=(t.milliseconds % 1000) * 1000) 101 102 local_seconds = local_now.second + local_now.microsecond / 1_000_000 103 t_seconds = t.milliseconds / 1_000 104 105 if abs(local_seconds - t_seconds) > 30: 106 if local_seconds < t_seconds: 107 local_dt = local_dt - datetime.timedelta(minutes=1) 108 109 else: 110 local_dt = local_dt + datetime.timedelta(minutes=1) 111 112 elif t.size == TimeSize.THREE: 113 local_now = datetime.datetime.now() 114 local_dt = local_now.replace( 115 minute=t.minutes, 116 second=int(t.milliseconds / 1000), 117 microsecond=(t.milliseconds % 1000) * 1000) 118 119 local_minutes = (local_now.minute + 120 local_now.second / 60 + 121 local_now.microsecond / 60_000_000) 122 t_minutes = t.minutes + t.milliseconds / 60_000 123 124 if abs(local_minutes - t_minutes) > 30: 125 if local_minutes < t_minutes: 126 local_dt = local_dt - datetime.timedelta(hours=1) 127 128 else: 129 local_dt = local_dt + datetime.timedelta(hours=1) 130 131 elif t.size == TimeSize.SEVEN: 132 local_dt = datetime.datetime( 133 year=2000 + t.years if t.years < 70 else 1900 + t.years, 134 month=t.months, 135 day=t.day_of_month, 136 hour=t.hours, 137 minute=t.minutes, 138 second=int(t.milliseconds / 1000), 139 microsecond=(t.milliseconds % 1000) * 1000, 140 fold=not t.summer_time) 141 142 else: 143 raise ValueError('unsupported time size') 144 145 return local_dt.astimezone(tz=datetime.timezone.utc)
Convert Time to datetime.datetime
22async def connect(addr: tcp.Address, 23 *, 24 response_timeout: float = 15, 25 supervisory_timeout: float = 10, 26 test_timeout: float = 20, 27 send_window_size: int = 12, 28 receive_window_size: int = 8, 29 **kwargs 30 ) -> 'Connection': 31 conn = await apci.connect(addr=addr, 32 response_timeout=response_timeout, 33 supervisory_timeout=supervisory_timeout, 34 test_timeout=test_timeout, 35 send_window_size=send_window_size, 36 receive_window_size=receive_window_size, 37 **kwargs) 38 39 return Connection(conn)
42async def listen(connection_cb: ConnectionCb, 43 addr: tcp.Address = tcp.Address('0.0.0.0', 2404), 44 *, 45 response_timeout: float = 15, 46 supervisory_timeout: float = 10, 47 test_timeout: float = 20, 48 send_window_size: int = 12, 49 receive_window_size: int = 8, 50 **kwargs 51 ) -> tcp.Server: 52 log = logger.create_server_logger(mlog, kwargs.get('name'), None) 53 54 async def on_connection(conn): 55 try: 56 try: 57 conn = Connection(conn) 58 await aio.call(connection_cb, conn) 59 60 except BaseException: 61 await aio.uncancellable(conn.async_close()) 62 raise 63 64 except Exception as e: 65 log.error("on connection error: %s", e, exc_info=e) 66 67 server = await apci.listen(connection_cb=on_connection, 68 addr=addr, 69 response_timeout=response_timeout, 70 supervisory_timeout=supervisory_timeout, 71 test_timeout=test_timeout, 72 send_window_size=send_window_size, 73 receive_window_size=receive_window_size, 74 **kwargs) 75 76 log = logger.create_server_logger(mlog, server.info.name, server.info) 77 78 return server
81class Connection(aio.Resource): 82 83 def __init__(self, conn: apci.Connection): 84 self._conn = conn 85 self._encoder = encoder.Encoder() 86 self._comm_log = logger.CommunicationLogger(mlog, conn.info) 87 88 self.async_group.spawn(aio.call_on_cancel, self._comm_log.log, 89 common.CommLogAction.CLOSE) 90 self._comm_log.log(common.CommLogAction.OPEN) 91 92 @property 93 def async_group(self) -> aio.Group: 94 return self._conn.async_group 95 96 @property 97 def info(self) -> tcp.ConnectionInfo: 98 return self._conn.info 99 100 @property 101 def is_enabled(self) -> bool: 102 return self._conn.is_enabled 103 104 def register_enabled_cb(self, 105 cb: typing.Callable[[bool], None] 106 ) -> util.RegisterCallbackHandle: 107 return self._conn.register_enabled_cb(cb) 108 109 async def send(self, 110 msgs: typing.List[common.Msg], 111 wait_ack: bool = False): 112 self._comm_log.log(common.CommLogAction.SEND, msgs) 113 114 data = collections.deque(self._encoder.encode(msgs)) 115 while data: 116 head = data.popleft() 117 head_wait_ack = False if data else wait_ack 118 await self._conn.send(head, head_wait_ack) 119 120 async def drain(self, wait_ack: bool = False): 121 await self._conn.drain(wait_ack) 122 123 async def receive(self) -> list[common.Msg]: 124 data = await self._conn.receive() 125 msgs = list(self._encoder.decode(data)) 126 127 self._comm_log.log(common.CommLogAction.RECEIVE, msgs) 128 129 return msgs
Resource with lifetime control based on Group.
83 def __init__(self, conn: apci.Connection): 84 self._conn = conn 85 self._encoder = encoder.Encoder() 86 self._comm_log = logger.CommunicationLogger(mlog, conn.info) 87 88 self.async_group.spawn(aio.call_on_cancel, self._comm_log.log, 89 common.CommLogAction.CLOSE) 90 self._comm_log.log(common.CommLogAction.OPEN)
109 async def send(self, 110 msgs: typing.List[common.Msg], 111 wait_ack: bool = False): 112 self._comm_log.log(common.CommLogAction.SEND, msgs) 113 114 data = collections.deque(self._encoder.encode(msgs)) 115 while data: 116 head = data.popleft() 117 head_wait_ack = False if data else wait_ack 118 await self._conn.send(head, head_wait_ack)