hat.drivers.iec61850
1from hat.drivers.iec61850.client import (ReportCb, 2 TerminationCb, 3 connect, 4 Client) 5from hat.drivers.iec61850.common import (EntryTime, 6 ReportId, 7 PersistedDatasetRef, 8 NonPersistedDatasetRef, 9 DatasetRef, 10 DataRef, 11 CommandRef, 12 RcbType, 13 RcbRef, 14 BasicValueType, 15 AcsiValueType, 16 ArrayValueType, 17 StructValueType, 18 ValueType, 19 Timestamp, 20 QualityValidity, 21 QualityDetail, 22 QualitySource, 23 Quality, 24 DoublePoint, 25 Direction, 26 Severity, 27 Analogue, 28 Vector, 29 StepPosition, 30 BinaryControl, 31 BasicValue, 32 AcsiValue, 33 ArrayValue, 34 StructValue, 35 Value, 36 ServiceError, 37 AdditionalCause, 38 TestError, 39 CommandError, 40 OptionalField, 41 TriggerCondition, 42 RcbAttrType, 43 ReportIdRcbAttrValue, 44 ReportEnableRcbAttrValue, 45 DatasetRcbAttrValue, 46 ConfRevisionRcbAttrValue, 47 OptionalFieldsRcbAttrValue, 48 BufferTimeRcbAttrValue, 49 SequenceNumberRcbAttrValue, 50 TriggerOptionsRcbAttrValue, 51 IntegrityPeriodRcbAttrValue, 52 GiRcbAttrValue, 53 PurgeBufferRcbAttrValue, 54 EntryIdRcbAttrValue, 55 TimeOfEntryRcbAttrValue, 56 ReservationTimeRcbAttrValue, 57 ReserveRcbAttrValue, 58 RcbAttrValue, 59 Reason, 60 ReportData, 61 Report, 62 ControlModel, 63 OriginCategory, 64 Origin, 65 Check, 66 Command, 67 Termination) 68 69 70__all__ = ['ReportCb', 71 'TerminationCb', 72 'connect', 73 'Client', 74 'EntryTime', 75 'ReportId', 76 'PersistedDatasetRef', 77 'NonPersistedDatasetRef', 78 'DatasetRef', 79 'DataRef', 80 'CommandRef', 81 'RcbType', 82 'RcbRef', 83 'BasicValueType', 84 'AcsiValueType', 85 'ArrayValueType', 86 'StructValueType', 87 'ValueType', 88 'Timestamp', 89 'QualityValidity', 90 'QualityDetail', 91 'QualitySource', 92 'Quality', 93 'DoublePoint', 94 'Direction', 95 'Severity', 96 'Analogue', 97 'Vector', 98 'StepPosition', 99 'BinaryControl', 100 'BasicValue', 101 'AcsiValue', 102 'ArrayValue', 103 'StructValue', 104 'Value', 105 'ServiceError', 106 'AdditionalCause', 107 'TestError', 108 'CommandError', 109 'OptionalField', 110 'TriggerCondition', 111 'RcbAttrType', 112 'ReportIdRcbAttrValue', 113 'ReportEnableRcbAttrValue', 114 'DatasetRcbAttrValue', 115 'ConfRevisionRcbAttrValue', 116 'OptionalFieldsRcbAttrValue', 117 'BufferTimeRcbAttrValue', 118 'SequenceNumberRcbAttrValue', 119 'TriggerOptionsRcbAttrValue', 120 'IntegrityPeriodRcbAttrValue', 121 'GiRcbAttrValue', 122 'PurgeBufferRcbAttrValue', 123 'EntryIdRcbAttrValue', 124 'TimeOfEntryRcbAttrValue', 125 'ReservationTimeRcbAttrValue', 126 'ReserveRcbAttrValue', 127 'RcbAttrValue', 128 'Reason', 129 'ReportData', 130 'Report', 131 'ControlModel', 132 'OriginCategory', 133 'Origin', 134 'Check', 135 'Command', 136 'Termination']
28async def connect(addr: tcp.Address, 29 data_value_types: dict[common.DataRef, 30 common.ValueType] = {}, 31 cmd_value_types: dict[common.CommandRef, 32 common.ValueType] = {}, 33 report_data_refs: dict[common.ReportId, 34 Collection[common.DataRef]] = {}, 35 report_cb: ReportCb | None = None, 36 termination_cb: TerminationCb | None = None, 37 status_delay: float | None = None, 38 status_timeout: float = 30, 39 **kwargs 40 ) -> 'Client': 41 """Connect to IEC61850 server 42 43 `data_value_types` include value types used in report processing and 44 writing data. 45 46 `cmd_value_types` include value types used in command actions and 47 termination processing. 48 49 Only reports that are specified by `report_data_refs` are notified with 50 `report_cb`. 51 52 If `status_delay` is ``None``, periodical sending of status requests is 53 disabled. 54 55 Additional arguments are passed directly to `hat.drivers.mms.connect` 56 (`request_cb` and `unconfirmed_cb` are set by this coroutine). 57 58 """ 59 client = Client() 60 client._data_value_types = data_value_types 61 client._cmd_value_types = cmd_value_types 62 client._report_cb = report_cb 63 client._termination_cb = termination_cb 64 client._loop = asyncio.get_running_loop() 65 client._status_event = asyncio.Event() 66 client._last_appl_errors = {} 67 client._report_data_defs = { 68 report_id: [encoder.DataDef(ref=data_ref, 69 value_type=data_value_types[data_ref]) 70 for data_ref in data_refs] 71 for report_id, data_refs in report_data_refs.items()} 72 73 client._conn = await mms.connect(addr=addr, 74 request_cb=None, 75 unconfirmed_cb=client._on_unconfirmed, 76 **kwargs) 77 78 if client.is_open and status_delay is not None: 79 client.async_group.spawn(client._status_loop, status_delay, 80 status_timeout) 81 82 return client
Connect to IEC61850 server
data_value_types
include value types used in report processing and
writing data.
cmd_value_types
include value types used in command actions and
termination processing.
Only reports that are specified by report_data_refs
are notified with
report_cb
.
If status_delay
is None
, periodical sending of status requests is
disabled.
Additional arguments are passed directly to hat.drivers.mms.connect
(request_cb
and unconfirmed_cb
are set by this coroutine).
85class Client(aio.Resource): 86 """Client""" 87 88 @property 89 def async_group(self): 90 """Async group""" 91 return self._conn.async_group 92 93 @property 94 def info(self) -> acse.ConnectionInfo: 95 """Connection info""" 96 return self._conn.info 97 98 async def create_dataset(self, 99 ref: common.DatasetRef, 100 data: Iterable[common.DataRef] 101 ) -> common.ServiceError | None: 102 """Create dataset""" 103 req = mms.DefineNamedVariableListRequest( 104 name=encoder.dataset_ref_to_object_name(ref), 105 specification=[ 106 mms.NameVariableSpecification( 107 encoder.data_ref_to_object_name(i)) 108 for i in data]) 109 110 res = await self._send(req) 111 112 if isinstance(res, mms.Error): 113 if res == mms.AccessError.OBJECT_NON_EXISTENT: 114 return common.ServiceError.INSTANCE_NOT_AVAILABLE 115 116 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 117 return common.ServiceError.ACCESS_VIOLATION 118 119 if res == mms.DefinitionError.OBJECT_EXISTS: 120 return common.ServiceError.INSTANCE_IN_USE 121 122 if res == mms.DefinitionError.OBJECT_UNDEFINED: 123 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 124 125 if res == mms.ResourceError.CAPABILITY_UNAVAILABLE: 126 return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 127 128 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 129 130 if not isinstance(res, mms.DefineNamedVariableListResponse): 131 raise Exception('unsupported response type') 132 133 async def delete_dataset(self, 134 ref: common.DatasetRef 135 ) -> common.ServiceError | None: 136 """Delete dataset""" 137 req = mms.DeleteNamedVariableListRequest([ 138 encoder.dataset_ref_to_object_name(ref)]) 139 140 res = await self._send(req) 141 142 if isinstance(res, mms.Error): 143 if res == mms.AccessError.OBJECT_NON_EXISTENT: 144 return common.ServiceError.INSTANCE_NOT_AVAILABLE 145 146 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 147 return common.ServiceError.ACCESS_VIOLATION 148 149 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 150 151 if not isinstance(res, mms.DeleteNamedVariableListResponse): 152 raise Exception('unsupported response type') 153 154 if res.matched == 0 and res.deleted == 0: 155 return common.ServiceError.INSTANCE_NOT_AVAILABLE 156 157 if res.matched != res.deleted: 158 return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 159 160 async def get_persisted_dataset_refs(self, 161 logical_device: str 162 ) -> Collection[common.PersistedDatasetRef] | common.ServiceError: # NOQA 163 """Get persisted dataset references associated with logical device""" 164 identifiers = await self._get_name_list( 165 object_class=mms.ObjectClass.NAMED_VARIABLE_LIST, 166 object_scope=mms.DomainSpecificObjectScope(logical_device)) 167 168 if isinstance(identifiers, common.ServiceError): 169 return identifiers 170 171 refs = collections.deque() 172 for identifier in identifiers: 173 logical_node, name = identifier.split('$') 174 refs.append( 175 common.PersistedDatasetRef(logical_device=logical_device, 176 logical_node=logical_node, 177 name=name)) 178 179 return refs 180 181 async def get_dataset_data_refs(self, 182 ref: common.DatasetRef 183 ) -> Collection[common.DataRef] | common.ServiceError: # NOQA 184 """Get data references associated with single dataset""" 185 req = mms.GetNamedVariableListAttributesRequest( 186 encoder.dataset_ref_to_object_name(ref)) 187 188 res = await self._send(req) 189 190 if isinstance(res, mms.Error): 191 if res == mms.AccessError.OBJECT_NON_EXISTENT: 192 return common.ServiceError.INSTANCE_NOT_AVAILABLE 193 194 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 195 return common.ServiceError.ACCESS_VIOLATION 196 197 if res == mms.ServiceError.PDU_SIZE: 198 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 199 200 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 201 202 if not isinstance(res, mms.GetNamedVariableListAttributesResponse): 203 raise Exception('unsupported response type') 204 205 refs = collections.deque() 206 207 for i in res.specification: 208 if not isinstance(i, mms.NameVariableSpecification): 209 raise Exception('unsupported specification type') 210 211 refs.append(encoder.data_ref_from_object_name(i.name)) 212 213 return refs 214 215 async def get_rcb_attrs(self, 216 ref: common.RcbRef, 217 attr_types: Collection[common.RcbAttrType] 218 ) -> dict[common.RcbAttrType, 219 common.RcbAttrValue | common.ServiceError]: # NOQA 220 """Get RCB attribute value""" 221 specification = collections.deque() 222 223 for attr_type in attr_types: 224 if ref.type == common.RcbType.BUFFERED: 225 if attr_type == common.RcbAttrType.RESERVE: 226 raise ValueError('unsupported attribute type') 227 228 elif ref.type == common.RcbType.UNBUFFERED: 229 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 230 common.RcbAttrType.ENTRY_ID, 231 common.RcbAttrType.TIME_OF_ENTRY, 232 common.RcbAttrType.RESERVATION_TIME): 233 raise ValueError('unsupported attribute type') 234 235 else: 236 raise TypeError('unsupported rcb type') 237 238 specification.append( 239 mms.NameVariableSpecification( 240 encoder.data_ref_to_object_name( 241 common.DataRef(logical_device=ref.logical_device, 242 logical_node=ref.logical_node, 243 fc=ref.type.value, 244 names=(ref.name, attr_type.value))))) 245 246 req = mms.ReadRequest(specification) 247 248 res = await self._send(req) 249 250 if isinstance(res, mms.Error): 251 return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 252 for attr_type in attr_types} 253 254 if not isinstance(res, mms.ReadResponse): 255 raise Exception('unsupported response type') 256 257 if len(res.results) != len(attr_types): 258 raise Exception('invalid results length') 259 260 results = {} 261 262 for attr_type, mms_data in zip(attr_types, res.results): 263 if isinstance(mms_data, mms.DataAccessError): 264 if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 265 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 266 267 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 268 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 269 270 else: 271 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 272 273 else: 274 results[attr_type] = encoder.rcb_attr_value_from_mms_data( 275 mms_data, attr_type) 276 277 return results 278 279 async def set_rcb_attrs(self, 280 ref: common.RcbRef, 281 attrs: Collection[tuple[common.RcbAttrType, 282 common.RcbAttrValue]] 283 ) -> dict[common.RcbAttrType, 284 common.ServiceError | None]: 285 """Set RCB attribute values""" 286 specification = collections.deque() 287 data = collections.deque() 288 289 for attr_type, attr_value in attrs: 290 if ref.type == common.RcbType.BUFFERED: 291 if attr_type == common.RcbAttrType.RESERVE: 292 raise ValueError('unsupported attribute type') 293 294 elif ref.type == common.RcbType.UNBUFFERED: 295 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 296 common.RcbAttrType.ENTRY_ID, 297 common.RcbAttrType.TIME_OF_ENTRY, 298 common.RcbAttrType.RESERVATION_TIME): 299 raise ValueError('unsupported attribute type') 300 301 else: 302 raise TypeError('unsupported rcb type') 303 304 specification.append( 305 mms.NameVariableSpecification( 306 encoder.data_ref_to_object_name( 307 common.DataRef(logical_device=ref.logical_device, 308 logical_node=ref.logical_node, 309 fc=ref.type.value, 310 names=(ref.name, attr_type.value))))) 311 data.append( 312 encoder.rcb_attr_value_to_mms_data(attr_value, attr_type)) 313 314 req = mms.WriteRequest(specification=specification, 315 data=data) 316 317 res = await self._send(req) 318 319 if isinstance(res, mms.Error): 320 return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 321 for attr_type, _ in attrs} 322 323 if not isinstance(res, mms.WriteResponse): 324 raise Exception('unsupported response type') 325 326 if len(res.results) != len(attrs): 327 raise Exception('invalid results length') 328 329 results = {} 330 331 for (attr_type, _), mms_data in zip(attrs, res.results): 332 if mms_data is None: 333 results[attr_type] = None 334 335 elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 336 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 337 338 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 339 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE 340 341 elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 342 results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 343 344 elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT: 345 results[attr_type] = common.ServiceError.TYPE_CONFLICT 346 347 elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID: 348 results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT # NOQA 349 350 else: 351 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 352 353 return results 354 355 async def write_data(self, 356 ref: common.DataRef, 357 value: common.Value 358 ) -> common.ServiceError | None: 359 """Write data""" 360 value_type = self._data_value_types[ref] 361 362 req = mms.WriteRequest( 363 specification=[ 364 mms.NameVariableSpecification( 365 encoder.data_ref_to_object_name(ref))], 366 data=[encoder.value_to_mms_data(value, value_type)]) 367 368 res = await self._send(req) 369 370 if isinstance(res, mms.Error): 371 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 372 373 if not isinstance(res, mms.WriteResponse): 374 raise Exception('unsupported response type') 375 376 if len(res.results) != 1: 377 raise Exception('invalid results size') 378 379 if res.results[0] is not None: 380 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 381 return common.ServiceError.ACCESS_VIOLATION 382 383 if res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 384 return common.ServiceError.INSTANCE_NOT_AVAILABLE 385 386 if res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 387 return common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT 388 389 if res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT: 390 return common.ServiceError.TYPE_CONFLICT 391 392 if res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID: 393 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 394 395 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 396 397 async def select(self, 398 ref: common.CommandRef, 399 cmd: common.Command | None 400 ) -> common.CommandError | None: 401 """Select command""" 402 if cmd is not None: 403 return await self._command_with_last_appl_error(ref=ref, 404 cmd=cmd, 405 attr='SBOw', 406 with_checks=True) 407 408 req = mms.ReadRequest([ 409 mms.NameVariableSpecification( 410 encoder.data_ref_to_object_name( 411 common.DataRef(logical_device=ref.logical_device, 412 logical_node=ref.logical_node, 413 fc='CO', 414 names=(ref.name, 'SBO'))))]) 415 416 res = await self._send(req) 417 418 if isinstance(res, mms.Error): 419 return _create_command_error( 420 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 421 last_appl_error=None) 422 423 if not isinstance(res, mms.ReadResponse): 424 raise Exception('unsupported response type') 425 426 if len(res.results) != 1: 427 raise Exception('invalid results size') 428 429 if not isinstance(res.results[0], mms.VisibleStringData): 430 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 431 service_error = common.ServiceError.ACCESS_VIOLATION 432 433 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 434 service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE 435 436 else: 437 service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 438 439 return _create_command_error(service_error=service_error, 440 last_appl_error=None) 441 442 if res.results[0].value == '': 443 return _create_command_error( 444 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 445 last_appl_error=None) 446 447 async def cancel(self, 448 ref: common.CommandRef, 449 cmd: common.Command 450 ) -> common.CommandError | None: 451 """Cancel command""" 452 return await self._command_with_last_appl_error(ref=ref, 453 cmd=cmd, 454 attr='Cancel', 455 with_checks=False) 456 457 async def operate(self, 458 ref: common.CommandRef, 459 cmd: common.Command 460 ) -> common.CommandError | None: 461 """Operate command""" 462 return await self._command_with_last_appl_error(ref=ref, 463 cmd=cmd, 464 attr='Oper', 465 with_checks=True) 466 467 async def _on_unconfirmed(self, conn, unconfirmed): 468 self._status_event.set() 469 470 if _is_unconfirmed_report(unconfirmed): 471 try: 472 await self._process_report(unconfirmed.data) 473 474 except Exception as e: 475 mlog.error("error processing report: %s", e, exc_info=e) 476 477 elif _is_unconfirmed_last_appl_error(unconfirmed): 478 try: 479 self._process_last_appl_error(unconfirmed.data[0]) 480 481 except Exception as e: 482 mlog.error("error processing last application error: %s", 483 e, exc_info=e) 484 485 elif _is_unconfirmed_termination(unconfirmed): 486 names = [i.name for i in unconfirmed.specification] 487 data = list(unconfirmed.data) 488 489 if len(names) != len(data): 490 mlog.warning("names/data size mismatch") 491 return 492 493 data_ref = encoder.data_ref_from_object_name(names[-1]) 494 495 try: 496 await self._process_termination( 497 ref=common.CommandRef( 498 logical_device=data_ref.logical_device, 499 logical_node=data_ref.logical_node, 500 name=data_ref.names[0]), 501 cmd_mms_data=data[-1], 502 last_appl_error_mms_data=(data[0] if len(data) > 1 503 else None)) 504 505 except Exception as e: 506 mlog.error("error processing termination: %s", e, exc_info=e) 507 508 else: 509 mlog.info("received unprocessed unconfirmed message") 510 511 async def _process_report(self, mms_data): 512 if not self._report_cb: 513 mlog.info("report callback not defined - skipping report") 514 return 515 516 report_id = encoder.value_from_mms_data( 517 mms_data[0], common.BasicValueType.VISIBLE_STRING) 518 519 data_defs = self._report_data_defs.get(report_id) 520 if data_defs is None: 521 mlog.info("report id not defined - skipping report") 522 return 523 524 report = encoder.report_from_mms_data(mms_data, data_defs) 525 526 await aio.call(self._report_cb, report) 527 528 def _process_last_appl_error(self, mms_data): 529 last_appl_error = encoder.last_appl_error_from_mms_data(mms_data) 530 531 key = last_appl_error.name, last_appl_error.control_number 532 if key in self._last_appl_errors: 533 self._last_appl_errors[key] = last_appl_error 534 535 async def _process_termination(self, ref, cmd_mms_data, 536 last_appl_error_mms_data): 537 if not self._termination_cb: 538 mlog.info("termination callback not defined - " 539 "skipping termination") 540 return 541 542 value_type = self._cmd_value_types.get(ref) 543 if value_type is None: 544 mlog.info("command value type not defined - skipping termination") 545 return 546 547 cmd = encoder.command_from_mms_data(mms_data=cmd_mms_data, 548 value_type=value_type, 549 with_checks=True) 550 551 if last_appl_error_mms_data: 552 error = _create_command_error( 553 service_error=None, 554 last_appl_error=encoder.last_appl_error_from_mms_data( 555 last_appl_error_mms_data)) 556 557 else: 558 error = None 559 560 termination = common.Termination(ref=ref, 561 cmd=cmd, 562 error=error) 563 564 await aio.call(self._termination_cb, termination) 565 566 async def _send(self, req): 567 res = await self._conn.send_confirmed(req) 568 self._status_event.set() 569 return res 570 571 async def _status_loop(self, delay, timeout): 572 try: 573 mlog.debug("starting status loop") 574 while True: 575 self._status_event.clear() 576 577 with contextlib.suppress(asyncio.TimeoutError): 578 await aio.wait_for(self._status_event.wait(), delay) 579 continue 580 581 mlog.debug("sending status request") 582 await aio.wait_for(self._send(mms.StatusRequest()), timeout) 583 584 except asyncio.TimeoutError: 585 mlog.warning("status timeout") 586 587 except ConnectionError: 588 pass 589 590 except Exception as e: 591 mlog.error("status loop error: %s", e, exc_info=e) 592 593 finally: 594 mlog.debug("stopping status loop") 595 self.close() 596 597 async def _get_name_list(self, object_class, object_scope): 598 identifiers = collections.deque() 599 continue_after = None 600 601 while True: 602 req = mms.GetNameListRequest( 603 object_class=object_class, 604 object_scope=object_scope, 605 continue_after=continue_after) 606 607 res = await self._send(req) 608 609 if isinstance(res, mms.Error): 610 if res == mms.AccessError.OBJECT_NON_EXISTENT: 611 return common.ServiceError.INSTANCE_NOT_AVAILABLE 612 613 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 614 return common.ServiceError.ACCESS_VIOLATION 615 616 if res == mms.ServiceError.OBJECT_CONSTRAINT_CONFLICT: 617 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 618 619 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 620 621 if not isinstance(res, mms.GetNameListResponse): 622 raise Exception('unsupported response type') 623 624 identifiers.extend(res.identifiers) 625 626 if not res.more_follows: 627 break 628 629 if not res.identifiers: 630 raise Exception('invalid more follows value') 631 632 continue_after = identifiers[-1] 633 634 return identifiers 635 636 async def _command_with_last_appl_error(self, ref, cmd, attr, with_checks): 637 value_type = self._cmd_value_types[ref] 638 639 req = mms.WriteRequest( 640 specification=[ 641 mms.NameVariableSpecification( 642 encoder.data_ref_to_object_name( 643 common.DataRef(logical_device=ref.logical_device, 644 logical_node=ref.logical_node, 645 fc='CO', 646 names=(ref.name, attr))))], 647 data=[encoder.command_to_mms_data(cmd=cmd, 648 value_type=value_type, 649 with_checks=with_checks)]) 650 651 name = (f'{req.specification[0].name.domain_id}/' 652 f'{req.specification[0].name.item_id}') 653 key = name, cmd.control_number 654 655 if key in self._last_appl_errors: 656 raise Exception('active control number duplicate') 657 658 self._last_appl_errors[key] = None 659 660 try: 661 res = await self._send(req) 662 663 finally: 664 last_appl_error = self._last_appl_errors.pop(key, None) 665 666 if isinstance(res, mms.Error): 667 return _create_command_error( 668 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 669 last_appl_error=last_appl_error) 670 671 if not isinstance(res, mms.WriteResponse): 672 raise Exception('unsupported response type') 673 674 if len(res.results) != 1: 675 raise Exception('invalid results size') 676 677 if res.results[0] is not None: 678 return _create_command_error(service_error=None, 679 last_appl_error=last_appl_error)
Client
93 @property 94 def info(self) -> acse.ConnectionInfo: 95 """Connection info""" 96 return self._conn.info
Connection info
98 async def create_dataset(self, 99 ref: common.DatasetRef, 100 data: Iterable[common.DataRef] 101 ) -> common.ServiceError | None: 102 """Create dataset""" 103 req = mms.DefineNamedVariableListRequest( 104 name=encoder.dataset_ref_to_object_name(ref), 105 specification=[ 106 mms.NameVariableSpecification( 107 encoder.data_ref_to_object_name(i)) 108 for i in data]) 109 110 res = await self._send(req) 111 112 if isinstance(res, mms.Error): 113 if res == mms.AccessError.OBJECT_NON_EXISTENT: 114 return common.ServiceError.INSTANCE_NOT_AVAILABLE 115 116 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 117 return common.ServiceError.ACCESS_VIOLATION 118 119 if res == mms.DefinitionError.OBJECT_EXISTS: 120 return common.ServiceError.INSTANCE_IN_USE 121 122 if res == mms.DefinitionError.OBJECT_UNDEFINED: 123 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 124 125 if res == mms.ResourceError.CAPABILITY_UNAVAILABLE: 126 return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 127 128 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 129 130 if not isinstance(res, mms.DefineNamedVariableListResponse): 131 raise Exception('unsupported response type')
Create dataset
133 async def delete_dataset(self, 134 ref: common.DatasetRef 135 ) -> common.ServiceError | None: 136 """Delete dataset""" 137 req = mms.DeleteNamedVariableListRequest([ 138 encoder.dataset_ref_to_object_name(ref)]) 139 140 res = await self._send(req) 141 142 if isinstance(res, mms.Error): 143 if res == mms.AccessError.OBJECT_NON_EXISTENT: 144 return common.ServiceError.INSTANCE_NOT_AVAILABLE 145 146 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 147 return common.ServiceError.ACCESS_VIOLATION 148 149 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 150 151 if not isinstance(res, mms.DeleteNamedVariableListResponse): 152 raise Exception('unsupported response type') 153 154 if res.matched == 0 and res.deleted == 0: 155 return common.ServiceError.INSTANCE_NOT_AVAILABLE 156 157 if res.matched != res.deleted: 158 return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
Delete dataset
160 async def get_persisted_dataset_refs(self, 161 logical_device: str 162 ) -> Collection[common.PersistedDatasetRef] | common.ServiceError: # NOQA 163 """Get persisted dataset references associated with logical device""" 164 identifiers = await self._get_name_list( 165 object_class=mms.ObjectClass.NAMED_VARIABLE_LIST, 166 object_scope=mms.DomainSpecificObjectScope(logical_device)) 167 168 if isinstance(identifiers, common.ServiceError): 169 return identifiers 170 171 refs = collections.deque() 172 for identifier in identifiers: 173 logical_node, name = identifier.split('$') 174 refs.append( 175 common.PersistedDatasetRef(logical_device=logical_device, 176 logical_node=logical_node, 177 name=name)) 178 179 return refs
Get persisted dataset references associated with logical device
181 async def get_dataset_data_refs(self, 182 ref: common.DatasetRef 183 ) -> Collection[common.DataRef] | common.ServiceError: # NOQA 184 """Get data references associated with single dataset""" 185 req = mms.GetNamedVariableListAttributesRequest( 186 encoder.dataset_ref_to_object_name(ref)) 187 188 res = await self._send(req) 189 190 if isinstance(res, mms.Error): 191 if res == mms.AccessError.OBJECT_NON_EXISTENT: 192 return common.ServiceError.INSTANCE_NOT_AVAILABLE 193 194 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 195 return common.ServiceError.ACCESS_VIOLATION 196 197 if res == mms.ServiceError.PDU_SIZE: 198 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 199 200 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 201 202 if not isinstance(res, mms.GetNamedVariableListAttributesResponse): 203 raise Exception('unsupported response type') 204 205 refs = collections.deque() 206 207 for i in res.specification: 208 if not isinstance(i, mms.NameVariableSpecification): 209 raise Exception('unsupported specification type') 210 211 refs.append(encoder.data_ref_from_object_name(i.name)) 212 213 return refs
Get data references associated with single dataset
215 async def get_rcb_attrs(self, 216 ref: common.RcbRef, 217 attr_types: Collection[common.RcbAttrType] 218 ) -> dict[common.RcbAttrType, 219 common.RcbAttrValue | common.ServiceError]: # NOQA 220 """Get RCB attribute value""" 221 specification = collections.deque() 222 223 for attr_type in attr_types: 224 if ref.type == common.RcbType.BUFFERED: 225 if attr_type == common.RcbAttrType.RESERVE: 226 raise ValueError('unsupported attribute type') 227 228 elif ref.type == common.RcbType.UNBUFFERED: 229 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 230 common.RcbAttrType.ENTRY_ID, 231 common.RcbAttrType.TIME_OF_ENTRY, 232 common.RcbAttrType.RESERVATION_TIME): 233 raise ValueError('unsupported attribute type') 234 235 else: 236 raise TypeError('unsupported rcb type') 237 238 specification.append( 239 mms.NameVariableSpecification( 240 encoder.data_ref_to_object_name( 241 common.DataRef(logical_device=ref.logical_device, 242 logical_node=ref.logical_node, 243 fc=ref.type.value, 244 names=(ref.name, attr_type.value))))) 245 246 req = mms.ReadRequest(specification) 247 248 res = await self._send(req) 249 250 if isinstance(res, mms.Error): 251 return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 252 for attr_type in attr_types} 253 254 if not isinstance(res, mms.ReadResponse): 255 raise Exception('unsupported response type') 256 257 if len(res.results) != len(attr_types): 258 raise Exception('invalid results length') 259 260 results = {} 261 262 for attr_type, mms_data in zip(attr_types, res.results): 263 if isinstance(mms_data, mms.DataAccessError): 264 if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 265 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 266 267 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 268 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 269 270 else: 271 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 272 273 else: 274 results[attr_type] = encoder.rcb_attr_value_from_mms_data( 275 mms_data, attr_type) 276 277 return results
Get RCB attribute value
279 async def set_rcb_attrs(self, 280 ref: common.RcbRef, 281 attrs: Collection[tuple[common.RcbAttrType, 282 common.RcbAttrValue]] 283 ) -> dict[common.RcbAttrType, 284 common.ServiceError | None]: 285 """Set RCB attribute values""" 286 specification = collections.deque() 287 data = collections.deque() 288 289 for attr_type, attr_value in attrs: 290 if ref.type == common.RcbType.BUFFERED: 291 if attr_type == common.RcbAttrType.RESERVE: 292 raise ValueError('unsupported attribute type') 293 294 elif ref.type == common.RcbType.UNBUFFERED: 295 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 296 common.RcbAttrType.ENTRY_ID, 297 common.RcbAttrType.TIME_OF_ENTRY, 298 common.RcbAttrType.RESERVATION_TIME): 299 raise ValueError('unsupported attribute type') 300 301 else: 302 raise TypeError('unsupported rcb type') 303 304 specification.append( 305 mms.NameVariableSpecification( 306 encoder.data_ref_to_object_name( 307 common.DataRef(logical_device=ref.logical_device, 308 logical_node=ref.logical_node, 309 fc=ref.type.value, 310 names=(ref.name, attr_type.value))))) 311 data.append( 312 encoder.rcb_attr_value_to_mms_data(attr_value, attr_type)) 313 314 req = mms.WriteRequest(specification=specification, 315 data=data) 316 317 res = await self._send(req) 318 319 if isinstance(res, mms.Error): 320 return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 321 for attr_type, _ in attrs} 322 323 if not isinstance(res, mms.WriteResponse): 324 raise Exception('unsupported response type') 325 326 if len(res.results) != len(attrs): 327 raise Exception('invalid results length') 328 329 results = {} 330 331 for (attr_type, _), mms_data in zip(attrs, res.results): 332 if mms_data is None: 333 results[attr_type] = None 334 335 elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 336 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 337 338 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 339 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE 340 341 elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 342 results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 343 344 elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT: 345 results[attr_type] = common.ServiceError.TYPE_CONFLICT 346 347 elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID: 348 results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT # NOQA 349 350 else: 351 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 352 353 return results
Set RCB attribute values
355 async def write_data(self, 356 ref: common.DataRef, 357 value: common.Value 358 ) -> common.ServiceError | None: 359 """Write data""" 360 value_type = self._data_value_types[ref] 361 362 req = mms.WriteRequest( 363 specification=[ 364 mms.NameVariableSpecification( 365 encoder.data_ref_to_object_name(ref))], 366 data=[encoder.value_to_mms_data(value, value_type)]) 367 368 res = await self._send(req) 369 370 if isinstance(res, mms.Error): 371 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT 372 373 if not isinstance(res, mms.WriteResponse): 374 raise Exception('unsupported response type') 375 376 if len(res.results) != 1: 377 raise Exception('invalid results size') 378 379 if res.results[0] is not None: 380 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 381 return common.ServiceError.ACCESS_VIOLATION 382 383 if res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 384 return common.ServiceError.INSTANCE_NOT_AVAILABLE 385 386 if res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 387 return common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT 388 389 if res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT: 390 return common.ServiceError.TYPE_CONFLICT 391 392 if res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID: 393 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 394 395 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
Write data
397 async def select(self, 398 ref: common.CommandRef, 399 cmd: common.Command | None 400 ) -> common.CommandError | None: 401 """Select command""" 402 if cmd is not None: 403 return await self._command_with_last_appl_error(ref=ref, 404 cmd=cmd, 405 attr='SBOw', 406 with_checks=True) 407 408 req = mms.ReadRequest([ 409 mms.NameVariableSpecification( 410 encoder.data_ref_to_object_name( 411 common.DataRef(logical_device=ref.logical_device, 412 logical_node=ref.logical_node, 413 fc='CO', 414 names=(ref.name, 'SBO'))))]) 415 416 res = await self._send(req) 417 418 if isinstance(res, mms.Error): 419 return _create_command_error( 420 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 421 last_appl_error=None) 422 423 if not isinstance(res, mms.ReadResponse): 424 raise Exception('unsupported response type') 425 426 if len(res.results) != 1: 427 raise Exception('invalid results size') 428 429 if not isinstance(res.results[0], mms.VisibleStringData): 430 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 431 service_error = common.ServiceError.ACCESS_VIOLATION 432 433 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 434 service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE 435 436 else: 437 service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 438 439 return _create_command_error(service_error=service_error, 440 last_appl_error=None) 441 442 if res.results[0].value == '': 443 return _create_command_error( 444 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 445 last_appl_error=None)
Select command
447 async def cancel(self, 448 ref: common.CommandRef, 449 cmd: common.Command 450 ) -> common.CommandError | None: 451 """Cancel command""" 452 return await self._command_with_last_appl_error(ref=ref, 453 cmd=cmd, 454 attr='Cancel', 455 with_checks=False)
Cancel command
17class PersistedDatasetRef(typing.NamedTuple): 18 logical_device: str 19 logical_node: str 20 name: str
PersistedDatasetRef(logical_device, logical_node, name)
NonPersistedDatasetRef(name,)
30class DataRef(typing.NamedTuple): 31 logical_device: str 32 logical_node: str 33 fc: str 34 names: tuple[str | int, ...]
DataRef(logical_device, logical_node, fc, names)
CommandRef(logical_device, logical_node, name)
48class RcbRef(typing.NamedTuple): 49 logical_device: str 50 logical_node: str 51 type: RcbType 52 name: str
RcbRef(logical_device, logical_node, type, name)
Create new instance of RcbRef(logical_device, logical_node, type, name)
57class BasicValueType(enum.Enum): 58 BOOLEAN = 'BOOLEAN' # bool 59 INTEGER = 'INTEGER' # int 60 UNSIGNED = 'UNSIGNED' # int 61 FLOAT = 'FLOAT' # float 62 BIT_STRING = 'BIT_STRING' # Collection[bool] 63 OCTET_STRING = 'OCTET_STRING' # util.Bytes 64 VISIBLE_STRING = 'VISIBLE_STRING' # str 65 MMS_STRING = 'MMS_STRING' # str
68class AcsiValueType(enum.Enum): 69 QUALITY = 'QUALITY' 70 TIMESTAMP = 'TIMESTAMP' 71 DOUBLE_POINT = 'DOUBLE_POINT' 72 DIRECTION = 'DIRECTION' 73 SEVERITY = 'SEVERITY' 74 ANALOGUE = 'ANALOGUE' 75 VECTOR = 'VECTOR' 76 STEP_POSITION = 'STEP_POSITION' 77 BINARY_CONTROL = 'BINARY_CONTROL'
ArrayValueType(type,)
StructValueType(elements,)
Create new instance of StructValueType(elements,)
Alias for field number 0
96class Timestamp(typing.NamedTuple): 97 value: datetime.datetime 98 leap_second: bool 99 clock_failure: bool 100 not_synchronized: bool 101 accuracy: int | None 102 """accurate fraction bits [0,24]"""
Timestamp(value, leap_second, clock_failure, not_synchronized, accuracy)
105class QualityValidity(enum.Enum): 106 GOOD = 0 107 INVALID = 1 108 RESERVED = 2 109 QUESTIONABLE = 3
112class QualityDetail(enum.Enum): 113 OVERFLOW = 2 114 OUT_OF_RANGE = 3 115 BAD_REFERENCE = 4 116 OSCILLATORY = 5 117 FAILURE = 6 118 OLD_DATA = 7 119 INCONSISTENT = 8 120 INACCURATE = 9
128class Quality(typing.NamedTuple): 129 validity: QualityValidity 130 details: set[QualityDetail] 131 source: QualitySource 132 test: bool 133 operator_blocked: bool
Quality(validity, details, source, test, operator_blocked)
Create new instance of Quality(validity, details, source, test, operator_blocked)
150class Severity(enum.Enum): 151 UNKNOWN = 0 152 CRITICAL = 1 153 MAJOR = 2 154 MINOR = 3 155 WARNING = 4
Analogue(i, f)
Vector(magnitude, angle)
168class StepPosition(typing.NamedTuple): 169 value: int 170 """value in range [-64, 63]""" 171 transient: bool | None
StepPosition(value, transient)
196class ServiceError(enum.Enum): 197 NO_ERROR = 0 198 INSTANCE_NOT_AVAILABLE = 1 199 INSTANCE_IN_USE = 2 200 ACCESS_VIOLATION = 3 201 ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = 4 202 PARAMETER_VALUE_INAPPROPRIATE = 5 203 PARAMETER_VALUE_INCONSISTENT = 6 204 CLASS_NOT_SUPPORTED = 7 205 INSTANCE_LOCKED_BY_OTHER_CLIENT = 8 206 CONTROL_MUST_BE_SELECTED = 9 207 TYPE_CONFLICT = 10 208 FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = 11 209 FAILED_DUE_TO_SERVER_CONSTRAINT = 12
212class AdditionalCause(enum.Enum): 213 UNKNOWN = 0 214 NOT_SUPPORTED = 1 215 BLOCKED_BY_SWITCHING_HIERARCHY = 2 216 SELECT_FAILED = 3 217 INVALID_POSITION = 4 218 POSITION_REACHED = 5 219 PARAMETER_CHANGE_IN_EXECUTION = 6 220 STEP_LIMIT = 7 221 BLOCKED_BY_MODE = 8 222 BLOCKED_BY_PROCESS = 9 223 BLOCKED_BY_INTERLOCKING = 10 224 BLOCKED_BY_SYNCHROCHECK = 11 225 COMMAND_ALREADY_IN_EXECUTION = 12 226 BLOCKED_BY_HEALTH = 13 227 ONE_OF_N_CONTROL = 14 228 ABORTION_BY_CANCEL = 15 229 TIME_LIMIT_OVER = 16 230 ABORTION_BY_TRIP = 17 231 OBJECT_NOT_SELECTED = 18 232 OBJECT_ALREADY_SELECTED = 19 233 NO_ACCESS_AUTHORITY = 20 234 ENDED_WITH_OVERSHOOT = 21 235 ABORTION_DUE_TO_DEVIATION = 22 236 ABORTION_BY_COMMUNICATION_LOSS = 23 237 BLOCKED_BY_COMMAND = 24 238 NONE = 25 239 INCONSISTENT_PARAMETERS = 26 240 LOCKED_BY_OTHER_CLIENT = 27
243class TestError(enum.Enum): 244 NO_ERROR = 0 245 UNKNOWN = 1 246 TIMEOUT_TEST_NOT_OK = 2 247 OPERATOR_TEST_NOT_OK = 3
250class CommandError(typing.NamedTuple): 251 service_error: ServiceError | None 252 additional_cause: AdditionalCause | None 253 test_error: TestError | None
CommandError(service_error, additional_cause, test_error)
Create new instance of CommandError(service_error, additional_cause, test_error)
258class OptionalField(enum.Enum): 259 SEQUENCE_NUMBER = 1 260 REPORT_TIME_STAMP = 2 261 REASON_FOR_INCLUSION = 3 262 DATA_SET_NAME = 4 263 DATA_REFERENCE = 5 264 BUFFER_OVERFLOW = 6 265 ENTRY_ID = 7 266 CONF_REVISION = 8
269class TriggerCondition(enum.Enum): 270 DATA_CHANGE = 1 271 QUALITY_CHANGE = 2 272 DATA_UPDATE = 3 273 INTEGRITY = 4 274 GENERAL_INTERROGATION = 5
277class RcbAttrType(enum.Enum): 278 REPORT_ID = 'RptID' 279 REPORT_ENABLE = 'RptEna' 280 DATASET = 'DatSet' 281 CONF_REVISION = 'ConfRev' 282 OPTIONAL_FIELDS = 'OptFlds' 283 BUFFER_TIME = 'BufTm' 284 SEQUENCE_NUMBER = 'SqNum' 285 TRIGGER_OPTIONS = 'TrgOps' 286 INTEGRITY_PERIOD = 'IntgPd' 287 GI = 'GI' 288 PURGE_BUFFER = 'PurgeBuf' # brcb 289 ENTRY_ID = 'EntryID' # brcb 290 TIME_OF_ENTRY = 'TimeOfEntry' # brcb 291 RESERVATION_TIME = 'ResvTms' # brcb 292 RESERVE = 'Resv' # urcb
344class Reason(enum.Enum): 345 DATA_CHANGE = 1 346 QUALITY_CHANGE = 2 347 DATA_UPDATE = 3 348 INTEGRITY = 4 349 GENERAL_INTERROGATION = 5 350 APPLICATION_TRIGGER = 6
353class ReportData(typing.NamedTuple): 354 ref: DataRef 355 value: Value 356 reasons: set[Reason] | None
ReportData(ref, value, reasons)
Create new instance of ReportData(ref, value, reasons)
Alias for field number 1
359class Report(typing.NamedTuple): 360 report_id: ReportId 361 sequence_number: int | None 362 subsequence_number: int | None 363 more_segments_follow: bool | None 364 dataset: DatasetRef | None 365 buffer_overflow: bool | None 366 conf_revision: int | None 367 entry_time: EntryTime | None 368 entry_id: util.Bytes | None 369 data: Collection[ReportData]
Report(report_id, sequence_number, subsequence_number, more_segments_follow, dataset, buffer_overflow, conf_revision, entry_time, entry_id, data)
Create new instance of Report(report_id, sequence_number, subsequence_number, more_segments_follow, dataset, buffer_overflow, conf_revision, entry_time, entry_id, data)
374class ControlModel(enum.Enum): 375 DIRECT_WITH_NORMAL_SECURITY = 1 376 SBO_WITH_NORMAL_SECURITY = 2 377 DIRECT_WITH_ENHANCED_SECURITY = 3 378 SBO_WITH_ENHANCED_SECURITY = 4
381class OriginCategory(enum.Enum): 382 NOT_SUPPORTED = 0 383 BAY_CONTROL = 1 384 STATION_CONTROL = 2 385 REMOTE_CONTROL = 3 386 AUTOMATIC_BAY = 4 387 AUTOMATIC_STATION = 5 388 AUTOMATIC_REMOTE = 6 389 MAINTENANCE = 7 390 PROCESS = 8
Origin(category, identification)
Create new instance of Origin(category, identification)
403class Command(typing.NamedTuple): 404 value: Value 405 operate_time: Timestamp | None 406 origin: Origin 407 control_number: int 408 """control number in range [0, 255]""" 409 t: Timestamp 410 test: bool 411 checks: set[Check] 412 """ignored in cancel action"""
Command(value, operate_time, origin, control_number, t, test, checks)
Create new instance of Command(value, operate_time, origin, control_number, t, test, checks)
Alias for field number 0
415class Termination(typing.NamedTuple): 416 ref: CommandRef 417 cmd: Command 418 error: CommandError | None
Termination(ref, cmd, error)
Create new instance of Termination(ref, cmd, error)