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.
35class Time(typing.NamedTuple): 36 size: TimeSize 37 milliseconds: int 38 """milliseconds in range [0, 59999]""" 39 invalid: bool | None 40 """available for size THREE, FOUR, SEVEN""" 41 substituted: bool | None 42 """available for size THREE, FOUR, SEVEN""" 43 minutes: int | None 44 """available for size THREE, FOUR, SEVEN (minutes in range [0, 59])""" 45 summer_time: bool | None 46 """available for size FOUR, SEVEN""" 47 hours: int | None 48 """available for size FOUR, SEVEN (hours in range [0, 23])""" 49 day_of_week: int | None 50 """available for size SEVEN (day_of_week in range [1, 7])""" 51 day_of_month: int | None 52 """available for size SEVEN (day_of_month in range [1, 31])""" 53 months: int | None 54 """available for size SEVEN (months in range [1, 12])""" 55 years: int | None 56 """available for size SEVEN (years in range [0, 99])"""
Time(size, milliseconds, invalid, substituted, minutes, summer_time, hours, day_of_week, day_of_month, months, years)
Create new instance of Time(size, milliseconds, invalid, substituted, minutes, summer_time, hours, day_of_week, day_of_month, months, years)
143class IndicationQuality(typing.NamedTuple): 144 invalid: bool 145 not_topical: bool 146 substituted: bool 147 blocked: bool
IndicationQuality(invalid, not_topical, substituted, blocked)
150class MeasurementQuality(typing.NamedTuple): 151 invalid: bool 152 not_topical: bool 153 substituted: bool 154 blocked: bool 155 overflow: bool
MeasurementQuality(invalid, not_topical, substituted, blocked, overflow)
158class CounterQuality(typing.NamedTuple): 159 invalid: bool 160 adjusted: bool 161 overflow: bool 162 sequence: int 163 """sequence in range [0, 31]"""
CounterQuality(invalid, adjusted, overflow, sequence)
166class ProtectionQuality(typing.NamedTuple): 167 invalid: bool 168 not_topical: bool 169 substituted: bool 170 blocked: bool 171 time_invalid: bool
ProtectionQuality(invalid, not_topical, substituted, blocked, time_invalid)
192class DoubleValue(enum.Enum): 193 """DoubleDataValue 194 195 `FAULT` stands for value 3, defined in the protocol as *INDETERMINATE*. 196 This is in order to make it more distinguishable from ``INTERMEDIATE``. 197 198 """ 199 INTERMEDIATE = 0 200 OFF = 1 201 ON = 2 202 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.
210class StepPositionValue(typing.NamedTuple): 211 value: int 212 """value in range [-64, 63]""" 213 transient: bool
StepPositionValue(value, transient)
216class BitstringValue(typing.NamedTuple): 217 value: util.Bytes 218 """bitstring encoded as 4 bytes"""
BitstringValue(value,)
NormalizedValue(value,)
ScaledValue(value,)
FloatingValue(value,)
235class BinaryCounterValue(typing.NamedTuple): 236 value: int 237 """value in range [-2^31, 2^31-1]"""
BinaryCounterValue(value,)
245class ProtectionStartValue(typing.NamedTuple): 246 general: bool 247 l1: bool 248 l2: bool 249 l3: bool 250 ie: bool 251 reverse: bool
ProtectionStartValue(general, l1, l2, l3, ie, reverse)
254class ProtectionCommandValue(typing.NamedTuple): 255 general: bool 256 l1: bool 257 l2: bool 258 l3: bool
ProtectionCommandValue(general, l1, l2, l3)
261class StatusValue(typing.NamedTuple): 262 value: list[bool] 263 """value length is 16""" 264 change: list[bool] 265 """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)
72def time_from_datetime(dt: datetime.datetime, 73 invalid: bool = False, 74 substituted: bool = False 75 ) -> Time: 76 """Create Time from datetime.datetime""" 77 # TODO document edge cases (local time, os implementation, ...) 78 # rounding microseconds to the nearest millisecond 79 dt_rounded = ( 80 dt.replace(microsecond=0) + 81 datetime.timedelta(milliseconds=round(dt.microsecond / 1000))) 82 local_time = time.localtime(dt_rounded.timestamp()) 83 84 return Time( 85 size=TimeSize.SEVEN, 86 milliseconds=(local_time.tm_sec * 1000 + 87 dt_rounded.microsecond // 1000), 88 invalid=invalid, 89 substituted=substituted, 90 minutes=local_time.tm_min, 91 summer_time=bool(local_time.tm_isdst), 92 hours=local_time.tm_hour, 93 day_of_week=local_time.tm_wday + 1, 94 day_of_month=local_time.tm_mday, 95 months=local_time.tm_mon, 96 years=local_time.tm_year % 100)
Create Time from datetime.datetime
99def time_to_datetime(t: Time 100 ) -> datetime.datetime: 101 """Convert Time to datetime.datetime""" 102 # TODO document edge cases (local time, os implementation, ...) 103 # TODO support TimeSize.FOUR 104 if t.size == TimeSize.TWO: 105 local_now = datetime.datetime.now() 106 local_dt = local_now.replace( 107 second=int(t.milliseconds / 1000), 108 microsecond=(t.milliseconds % 1000) * 1000) 109 110 local_seconds = local_now.second + local_now.microsecond / 1_000_000 111 t_seconds = t.milliseconds / 1_000 112 113 if abs(local_seconds - t_seconds) > 30: 114 if local_seconds < t_seconds: 115 local_dt = local_dt - datetime.timedelta(minutes=1) 116 117 else: 118 local_dt = local_dt + datetime.timedelta(minutes=1) 119 120 elif t.size == TimeSize.THREE: 121 local_now = datetime.datetime.now() 122 local_dt = local_now.replace( 123 minute=t.minutes, 124 second=int(t.milliseconds / 1000), 125 microsecond=(t.milliseconds % 1000) * 1000) 126 127 local_minutes = (local_now.minute + 128 local_now.second / 60 + 129 local_now.microsecond / 60_000_000) 130 t_minutes = t.minutes + t.milliseconds / 60_000 131 132 if abs(local_minutes - t_minutes) > 30: 133 if local_minutes < t_minutes: 134 local_dt = local_dt - datetime.timedelta(hours=1) 135 136 else: 137 local_dt = local_dt + datetime.timedelta(hours=1) 138 139 elif t.size == TimeSize.SEVEN: 140 local_dt = datetime.datetime( 141 year=2000 + t.years if t.years < 70 else 1900 + t.years, 142 month=t.months, 143 day=t.day_of_month, 144 hour=t.hours, 145 minute=t.minutes, 146 second=int(t.milliseconds / 1000), 147 microsecond=(t.milliseconds % 1000) * 1000, 148 fold=not t.summer_time) 149 150 else: 151 raise ValueError('unsupported time size') 152 153 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)