hat.drivers.iec101
IEC 60870-5-101 communication protocol
1"""IEC 60870-5-101 communication protocol""" 2 3from hat.drivers.iec101.common import (AsduTypeError, 4 Address, 5 AddressSize, 6 CauseSize, 7 AsduAddressSize, 8 IoAddressSize, 9 TimeSize, 10 Time, 11 OriginatorAddress, 12 AsduAddress, 13 IoAddress, 14 IndicationQuality, 15 MeasurementQuality, 16 CounterQuality, 17 ProtectionQuality, 18 Quality, 19 FreezeCode, 20 SingleValue, 21 DoubleValue, 22 RegulatingValue, 23 StepPositionValue, 24 BitstringValue, 25 NormalizedValue, 26 ScaledValue, 27 FloatingValue, 28 BinaryCounterValue, 29 ProtectionValue, 30 ProtectionStartValue, 31 ProtectionCommandValue, 32 StatusValue, 33 OtherCause, 34 DataResCause, 35 DataCause, 36 CommandReqCause, 37 CommandResCause, 38 CommandCause, 39 InitializationResCause, 40 InitializationCause, 41 ReadReqCause, 42 ReadResCause, 43 ReadCause, 44 ClockSyncReqCause, 45 ClockSyncResCause, 46 ClockSyncCause, 47 ActivationReqCause, 48 ActivationResCause, 49 ActivationCause, 50 DelayReqCause, 51 DelayResCause, 52 DelayCause, 53 ParameterReqCause, 54 ParameterResCause, 55 ParameterCause, 56 ParameterActivationReqCause, 57 ParameterActivationResCause, 58 ParameterActivationCause, 59 SingleData, 60 DoubleData, 61 StepPositionData, 62 BitstringData, 63 NormalizedData, 64 ScaledData, 65 FloatingData, 66 BinaryCounterData, 67 ProtectionData, 68 ProtectionStartData, 69 ProtectionCommandData, 70 StatusData, 71 Data, 72 SingleCommand, 73 DoubleCommand, 74 RegulatingCommand, 75 NormalizedCommand, 76 ScaledCommand, 77 FloatingCommand, 78 BitstringCommand, 79 Command, 80 NormalizedParameter, 81 ScaledParameter, 82 FloatingParameter, 83 Parameter, 84 DataMsg, 85 CommandMsg, 86 InitializationMsg, 87 InterrogationMsg, 88 CounterInterrogationMsg, 89 ReadMsg, 90 ClockSyncMsg, 91 TestMsg, 92 ResetMsg, 93 DelayMsg, 94 ParameterMsg, 95 ParameterActivationMsg, 96 Msg, 97 time_from_datetime, 98 time_to_datetime) 99from hat.drivers.iec101.connection import Connection 100 101 102__all__ = ['AsduTypeError', 103 'Address', 104 'AddressSize', 105 'CauseSize', 106 'AsduAddressSize', 107 'IoAddressSize', 108 'TimeSize', 109 'Time', 110 'OriginatorAddress', 111 'AsduAddress', 112 'IoAddress', 113 'IndicationQuality', 114 'MeasurementQuality', 115 'CounterQuality', 116 'ProtectionQuality', 117 'Quality', 118 'FreezeCode', 119 'SingleValue', 120 'DoubleValue', 121 'RegulatingValue', 122 'StepPositionValue', 123 'BitstringValue', 124 'NormalizedValue', 125 'ScaledValue', 126 'FloatingValue', 127 'BinaryCounterValue', 128 'ProtectionValue', 129 'ProtectionStartValue', 130 'ProtectionCommandValue', 131 'StatusValue', 132 'OtherCause', 133 'DataResCause', 134 'DataCause', 135 'CommandReqCause', 136 'CommandResCause', 137 'CommandCause', 138 'InitializationResCause', 139 'InitializationCause', 140 'ReadReqCause', 141 'ReadResCause', 142 'ReadCause', 143 'ClockSyncReqCause', 144 'ClockSyncResCause', 145 'ClockSyncCause', 146 'ActivationReqCause', 147 'ActivationResCause', 148 'ActivationCause', 149 'DelayReqCause', 150 'DelayResCause', 151 'DelayCause', 152 'ParameterReqCause', 153 'ParameterResCause', 154 'ParameterCause', 155 'ParameterActivationReqCause', 156 'ParameterActivationResCause', 157 'ParameterActivationCause', 158 'SingleData', 159 'DoubleData', 160 'StepPositionData', 161 'BitstringData', 162 'NormalizedData', 163 'ScaledData', 164 'FloatingData', 165 'BinaryCounterData', 166 'ProtectionData', 167 'ProtectionStartData', 168 'ProtectionCommandData', 169 'StatusData', 170 'Data', 171 'SingleCommand', 172 'DoubleCommand', 173 'RegulatingCommand', 174 'NormalizedCommand', 175 'ScaledCommand', 176 'FloatingCommand', 177 'BitstringCommand', 178 'Command', 179 'NormalizedParameter', 180 'ScaledParameter', 181 'FloatingParameter', 182 'Parameter', 183 'DataMsg', 184 'CommandMsg', 185 'InitializationMsg', 186 'InterrogationMsg', 187 'CounterInterrogationMsg', 188 'ReadMsg', 189 'ClockSyncMsg', 190 'TestMsg', 191 'ResetMsg', 192 'DelayMsg', 193 'ParameterMsg', 194 'ParameterActivationMsg', 195 'Msg', 196 'time_from_datetime', 197 'time_to_datetime', 198 '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 minutes: int | None 42 """available for size THREE, FOUR, SEVEN (minutes in range [0, 59])""" 43 summer_time: bool | None 44 """available for size FOUR, SEVEN""" 45 hours: int | None 46 """available for size FOUR, SEVEN (hours in range [0, 23])""" 47 day_of_week: int | None 48 """available for size SEVEN (day_of_week in range [1, 7])""" 49 day_of_month: int | None 50 """available for size SEVEN (day_of_month in range [1, 31])""" 51 months: int | None 52 """available for size SEVEN (months in range [1, 12])""" 53 years: int | None 54 """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)
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
394class CommandMsg(typing.NamedTuple): 395 is_test: bool 396 originator_address: OriginatorAddress 397 asdu_address: AsduAddress 398 io_address: IoAddress 399 command: Command 400 is_negative_confirm: bool 401 cause: CommandCause
CommandMsg(is_test, originator_address, asdu_address, io_address, command, is_negative_confirm, cause)
Create new instance of CommandMsg(is_test, originator_address, asdu_address, io_address, command, is_negative_confirm, 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)
450class TestMsg(typing.NamedTuple): 451 is_test: bool 452 originator_address: OriginatorAddress 453 asdu_address: AsduAddress 454 cause: ActivationCause
TestMsg(is_test, originator_address, asdu_address, cause)
Create new instance of TestMsg(is_test, originator_address, asdu_address, 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)
466class DelayMsg(typing.NamedTuple): 467 is_test: bool 468 originator_address: OriginatorAddress 469 asdu_address: AsduAddress 470 time: int 471 """time in range [0, 65535]""" 472 cause: DelayCause
DelayMsg(is_test, originator_address, asdu_address, time, cause)
Create new instance of DelayMsg(is_test, originator_address, asdu_address, time, 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)
70def time_from_datetime(dt: datetime.datetime, 71 invalid: bool = False 72 ) -> Time: 73 """Create Time from datetime.datetime""" 74 # TODO document edge cases (local time, os implementation, ...) 75 # rounding microseconds to the nearest millisecond 76 dt_rounded = ( 77 dt.replace(microsecond=0) + 78 datetime.timedelta(milliseconds=round(dt.microsecond / 1000))) 79 local_time = time.localtime(dt_rounded.timestamp()) 80 81 return Time( 82 size=TimeSize.SEVEN, 83 milliseconds=(local_time.tm_sec * 1000 + 84 dt_rounded.microsecond // 1000), 85 invalid=invalid, 86 minutes=local_time.tm_min, 87 summer_time=bool(local_time.tm_isdst), 88 hours=local_time.tm_hour, 89 day_of_week=local_time.tm_wday + 1, 90 day_of_month=local_time.tm_mday, 91 months=local_time.tm_mon, 92 years=local_time.tm_year % 100)
Create Time from datetime.datetime
95def time_to_datetime(t: Time 96 ) -> datetime.datetime: 97 """Convert Time to datetime.datetime""" 98 # TODO document edge cases (local time, os implementation, ...) 99 # TODO support TimeSize.FOUR 100 if t.size == TimeSize.TWO: 101 local_now = datetime.datetime.now() 102 local_dt = local_now.replace( 103 second=int(t.milliseconds / 1000), 104 microsecond=(t.milliseconds % 1000) * 1000) 105 106 local_seconds = local_now.second + local_now.microsecond / 1_000_000 107 t_seconds = t.milliseconds / 1_000 108 109 if abs(local_seconds - t_seconds) > 30: 110 if local_seconds < t_seconds: 111 local_dt = local_dt - datetime.timedelta(minutes=1) 112 113 else: 114 local_dt = local_dt + datetime.timedelta(minutes=1) 115 116 elif t.size == TimeSize.THREE: 117 local_now = datetime.datetime.now() 118 local_dt = local_now.replace( 119 minute=t.minutes, 120 second=int(t.milliseconds / 1000), 121 microsecond=(t.milliseconds % 1000) * 1000) 122 123 local_minutes = (local_now.minute + 124 local_now.second / 60 + 125 local_now.microsecond / 60_000_000) 126 t_minutes = t.minutes + t.milliseconds / 60_000 127 128 if abs(local_minutes - t_minutes) > 30: 129 if local_minutes < t_minutes: 130 local_dt = local_dt - datetime.timedelta(hours=1) 131 132 else: 133 local_dt = local_dt + datetime.timedelta(hours=1) 134 135 elif t.size == TimeSize.SEVEN: 136 local_dt = datetime.datetime( 137 year=2000 + t.years if t.years < 70 else 1900 + t.years, 138 month=t.months, 139 day=t.day_of_month, 140 hour=t.hours, 141 minute=t.minutes, 142 second=int(t.milliseconds / 1000), 143 microsecond=(t.milliseconds % 1000) * 1000, 144 fold=not t.summer_time) 145 146 else: 147 raise ValueError('unsupported time size') 148 149 return local_dt.astimezone(tz=datetime.timezone.utc)
Convert Time to datetime.datetime
16class Connection(aio.Resource): 17 18 def __init__(self, 19 conn: link.Connection, 20 cause_size: common.CauseSize, 21 asdu_address_size: common.AsduAddressSize, 22 io_address_size: common.IoAddressSize): 23 self._conn = conn 24 self._encoder = encoder.Encoder(cause_size=cause_size, 25 asdu_address_size=asdu_address_size, 26 io_address_size=io_address_size) 27 self._comm_log = logger.CommunicationLogger(mlog, conn.info) 28 29 self.async_group.spawn(aio.call_on_cancel, self._comm_log.log, 30 common.CommLogAction.CLOSE) 31 self._comm_log.log(common.CommLogAction.OPEN) 32 33 @property 34 def async_group(self) -> aio.Group: 35 return self._conn.async_group 36 37 @property 38 def info(self) -> link.ConnectionInfo: 39 return self._conn.info 40 41 async def send(self, 42 msgs: list[common.Msg], 43 sent_cb: aio.AsyncCallable[[], None] | None = None): 44 self._comm_log.log(common.CommLogAction.SEND, msgs) 45 46 data = collections.deque(self._encoder.encode(msgs)) 47 48 while data: 49 i = data.popleft() 50 await self._conn.send(i, sent_cb=None if data else sent_cb) 51 52 async def receive(self) -> list[common.Msg]: 53 data = await self._conn.receive() 54 msgs = list(self._encoder.decode(data)) 55 56 self._comm_log.log(common.CommLogAction.RECEIVE, msgs) 57 58 return msgs
Resource with lifetime control based on Group.
18 def __init__(self, 19 conn: link.Connection, 20 cause_size: common.CauseSize, 21 asdu_address_size: common.AsduAddressSize, 22 io_address_size: common.IoAddressSize): 23 self._conn = conn 24 self._encoder = encoder.Encoder(cause_size=cause_size, 25 asdu_address_size=asdu_address_size, 26 io_address_size=io_address_size) 27 self._comm_log = logger.CommunicationLogger(mlog, conn.info) 28 29 self.async_group.spawn(aio.call_on_cancel, self._comm_log.log, 30 common.CommLogAction.CLOSE) 31 self._comm_log.log(common.CommLogAction.OPEN)
41 async def send(self, 42 msgs: list[common.Msg], 43 sent_cb: aio.AsyncCallable[[], None] | None = None): 44 self._comm_log.log(common.CommLogAction.SEND, msgs) 45 46 data = collections.deque(self._encoder.encode(msgs)) 47 48 while data: 49 i = data.popleft() 50 await self._conn.send(i, sent_cb=None if data else sent_cb)