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