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: tcp.Address, 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 = logger.create_logger(mlog, kwargs.get('name'), None) 75 client._comm_log = logger.CommunicationLogger(mlog, kwargs.get('name'), 76 None) 77 78 client._conn = await mms.connect(addr=addr, 79 request_cb=None, 80 unconfirmed_cb=client._on_unconfirmed, 81 **kwargs) 82 83 try: 84 client._log = logger.create_logger(mlog, client._conn.info.name, 85 client._conn.info) 86 client._comm_log = logger.CommunicationLogger( 87 mlog, client._conn.info.name, client._conn.info) 88 89 if status_delay is not None: 90 client.async_group.spawn(client._status_loop, status_delay, 91 status_timeout) 92 93 client.async_group.spawn(aio.call_on_cancel, client._comm_log.log, 94 common.CommLogAction.CLOSE) 95 client._comm_log.log(common.CommLogAction.OPEN) 96 97 except BaseException: 98 await aio.uncancellable(client.async_close()) 99 raise 100 101 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).
104class Client(aio.Resource): 105 """Client""" 106 107 @property 108 def async_group(self): 109 """Async group""" 110 return self._conn.async_group 111 112 @property 113 def info(self) -> acse.ConnectionInfo: 114 """Connection info""" 115 return self._conn.info 116 117 async def create_dataset(self, 118 ref: common.DatasetRef, 119 data: Collection[common.DataRef] 120 ) -> common.ServiceError | None: 121 """Create dataset""" 122 req = mms.DefineNamedVariableListRequest( 123 name=encoder.dataset_ref_to_object_name(ref), 124 specification=[ 125 mms.NameVariableSpecification( 126 encoder.data_ref_to_object_name(i)) 127 for i in data]) 128 129 if self._comm_log.is_enabled: 130 self._comm_log.log(common.CommLogAction.SEND, 131 logger.CreateDatasetReq(ref=ref, 132 data=data)) 133 134 res = await self._send(req) 135 136 if isinstance(res, mms.Error): 137 if res == mms.AccessError.OBJECT_NON_EXISTENT: 138 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 139 140 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 141 result = common.ServiceError.ACCESS_VIOLATION 142 143 elif res == mms.DefinitionError.OBJECT_EXISTS: 144 result = common.ServiceError.INSTANCE_IN_USE 145 146 elif res == mms.DefinitionError.OBJECT_UNDEFINED: 147 result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT 148 149 elif res == mms.ResourceError.CAPABILITY_UNAVAILABLE: 150 result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 151 152 else: 153 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 154 155 elif isinstance(res, mms.DefineNamedVariableListResponse): 156 result = None 157 158 else: 159 raise Exception('unsupported response type') 160 161 if self._comm_log.is_enabled: 162 self._comm_log.log(common.CommLogAction.RECEIVE, 163 logger.CreateDatasetRes(result)) 164 165 return result 166 167 async def delete_dataset(self, 168 ref: common.DatasetRef 169 ) -> common.ServiceError | None: 170 """Delete dataset""" 171 req = mms.DeleteNamedVariableListRequest([ 172 encoder.dataset_ref_to_object_name(ref)]) 173 174 if self._comm_log.is_enabled: 175 self._comm_log.log(common.CommLogAction.SEND, 176 logger.DeleteDatasetReq(ref)) 177 178 res = await self._send(req) 179 180 if isinstance(res, mms.Error): 181 if res == mms.AccessError.OBJECT_NON_EXISTENT: 182 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 183 184 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 185 result = common.ServiceError.ACCESS_VIOLATION 186 187 else: 188 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 189 190 elif isinstance(res, mms.DeleteNamedVariableListResponse): 191 if res.matched == 0 and res.deleted == 0: 192 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 193 194 elif res.matched != res.deleted: 195 result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 196 197 else: 198 result = None 199 200 else: 201 raise Exception('unsupported response type') 202 203 if self._comm_log.is_enabled: 204 self._comm_log.log(common.CommLogAction.RECEIVE, 205 logger.DeleteDatasetRes(result)) 206 207 return result 208 209 async def get_persisted_dataset_refs(self, 210 logical_device: str 211 ) -> Collection[common.PersistedDatasetRef] | common.ServiceError: # NOQA 212 """Get persisted dataset references associated with logical device""" 213 214 if self._comm_log.is_enabled: 215 self._comm_log.log( 216 common.CommLogAction.SEND, 217 logger.GetPersistedDatasetRefsReq(logical_device)) 218 219 identifiers = await self._get_name_list( 220 object_class=mms.ObjectClass.NAMED_VARIABLE_LIST, 221 object_scope=mms.DomainSpecificObjectScope(logical_device)) 222 223 if isinstance(identifiers, common.ServiceError): 224 result = identifiers 225 226 else: 227 result = collections.deque() 228 for identifier in identifiers: 229 logical_node, name = identifier.split('$') 230 result.append( 231 common.PersistedDatasetRef(logical_device=logical_device, 232 logical_node=logical_node, 233 name=name)) 234 235 if self._comm_log.is_enabled: 236 self._comm_log.log(common.CommLogAction.RECEIVE, 237 logger.GetPersistedDatasetRefsRes(result)) 238 239 return result 240 241 async def get_dataset_data_refs(self, 242 ref: common.DatasetRef 243 ) -> Collection[common.DataRef] | common.ServiceError: # NOQA 244 """Get data references associated with single dataset""" 245 req = mms.GetNamedVariableListAttributesRequest( 246 encoder.dataset_ref_to_object_name(ref)) 247 248 if self._comm_log.is_enabled: 249 self._comm_log.log(common.CommLogAction.SEND, 250 logger.GetDatasetDataRefsReq(ref)) 251 252 res = await self._send(req) 253 254 if isinstance(res, mms.Error): 255 if res == mms.AccessError.OBJECT_NON_EXISTENT: 256 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 257 258 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 259 result = common.ServiceError.ACCESS_VIOLATION 260 261 elif res == mms.ServiceError.PDU_SIZE: 262 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 263 264 else: 265 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 266 267 elif isinstance(res, mms.GetNamedVariableListAttributesResponse): 268 result = collections.deque() 269 270 for i in res.specification: 271 if not isinstance(i, mms.NameVariableSpecification): 272 raise Exception('unsupported specification type') 273 274 result.append(encoder.data_ref_from_object_name(i.name)) 275 276 else: 277 raise Exception('unsupported response type') 278 279 if self._comm_log.is_enabled: 280 self._comm_log.log(common.CommLogAction.RECEIVE, 281 logger.GetDatasetDataRefsRes(result)) 282 283 return result 284 285 async def get_rcb_attrs(self, 286 ref: common.RcbRef, 287 attr_types: Collection[common.RcbAttrType] 288 ) -> dict[common.RcbAttrType, 289 common.RcbAttrValue | common.ServiceError]: # NOQA 290 """Get RCB attribute value""" 291 specification = collections.deque() 292 293 for attr_type in attr_types: 294 if ref.type == common.RcbType.BUFFERED: 295 if attr_type == common.RcbAttrType.RESERVE: 296 raise ValueError('unsupported attribute type') 297 298 elif ref.type == common.RcbType.UNBUFFERED: 299 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 300 common.RcbAttrType.ENTRY_ID, 301 common.RcbAttrType.TIME_OF_ENTRY, 302 common.RcbAttrType.RESERVATION_TIME): 303 raise ValueError('unsupported attribute type') 304 305 else: 306 raise TypeError('unsupported rcb type') 307 308 specification.append( 309 mms.NameVariableSpecification( 310 encoder.data_ref_to_object_name( 311 common.DataRef(logical_device=ref.logical_device, 312 logical_node=ref.logical_node, 313 fc=ref.type.value, 314 names=(ref.name, attr_type.value))))) 315 316 req = mms.ReadRequest(specification) 317 318 if self._comm_log.is_enabled: 319 self._comm_log.log(common.CommLogAction.SEND, 320 logger.GetRcbAttrsReq(ref=ref, 321 attr_types=attr_types)) 322 323 res = await self._send(req) 324 325 if isinstance(res, mms.Error): 326 results = { 327 attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 328 for attr_type in attr_types} 329 330 elif isinstance(res, mms.ReadResponse): 331 if len(res.results) != len(attr_types): 332 raise Exception('invalid results length') 333 334 results = {} 335 336 for attr_type, mms_data in zip(attr_types, res.results): 337 if isinstance(mms_data, mms.DataAccessError): 338 if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 339 results[attr_type] = common.ServiceError.ACCESS_VIOLATION # NOQA 340 341 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 342 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 343 344 else: 345 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 346 347 else: 348 results[attr_type] = encoder.rcb_attr_value_from_mms_data( 349 mms_data, attr_type) 350 351 else: 352 raise Exception('unsupported response type') 353 354 if self._comm_log.is_enabled: 355 self._comm_log.log(common.CommLogAction.RECEIVE, 356 logger.GetRcbAttrsRes(results)) 357 358 return results 359 360 async def set_rcb_attrs(self, 361 ref: common.RcbRef, 362 attrs: Collection[tuple[common.RcbAttrType, 363 common.RcbAttrValue]] 364 ) -> dict[common.RcbAttrType, 365 common.ServiceError | None]: 366 """Set RCB attribute values""" 367 specification = collections.deque() 368 data = collections.deque() 369 370 for attr_type, attr_value in attrs: 371 if ref.type == common.RcbType.BUFFERED: 372 if attr_type == common.RcbAttrType.RESERVE: 373 raise ValueError('unsupported attribute type') 374 375 elif ref.type == common.RcbType.UNBUFFERED: 376 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 377 common.RcbAttrType.ENTRY_ID, 378 common.RcbAttrType.TIME_OF_ENTRY, 379 common.RcbAttrType.RESERVATION_TIME): 380 raise ValueError('unsupported attribute type') 381 382 else: 383 raise TypeError('unsupported rcb type') 384 385 specification.append( 386 mms.NameVariableSpecification( 387 encoder.data_ref_to_object_name( 388 common.DataRef(logical_device=ref.logical_device, 389 logical_node=ref.logical_node, 390 fc=ref.type.value, 391 names=(ref.name, attr_type.value))))) 392 data.append( 393 encoder.rcb_attr_value_to_mms_data(attr_value, attr_type)) 394 395 req = mms.WriteRequest(specification=specification, 396 data=data) 397 398 if self._comm_log.is_enabled: 399 self._comm_log.log(common.CommLogAction.SEND, 400 logger.SetRcbAttrsReq(ref=ref, 401 attrs=attrs)) 402 403 res = await self._send(req) 404 405 if isinstance(res, mms.Error): 406 results = { 407 attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 408 for attr_type, _ in attrs} 409 410 elif isinstance(res, mms.WriteResponse): 411 if len(res.results) != len(attrs): 412 raise Exception('invalid results length') 413 414 results = {} 415 416 for (attr_type, _), mms_data in zip(attrs, res.results): 417 if mms_data is None: 418 results[attr_type] = None 419 420 elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 421 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 422 423 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 424 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 425 426 elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 427 results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 428 429 elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT: 430 results[attr_type] = common.ServiceError.TYPE_CONFLICT 431 432 elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID: 433 results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT # NOQA 434 435 else: 436 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 437 438 else: 439 raise Exception('unsupported response type') 440 441 if self._comm_log.is_enabled: 442 self._comm_log.log(common.CommLogAction.RECEIVE, 443 logger.SetRcbAttrsRes(results)) 444 445 return results 446 447 async def write_data(self, 448 ref: common.DataRef, 449 value: common.Value 450 ) -> common.ServiceError | None: 451 """Write data""" 452 value_type = self._data_value_types[ref] 453 454 req = mms.WriteRequest( 455 specification=[ 456 mms.NameVariableSpecification( 457 encoder.data_ref_to_object_name(ref))], 458 data=[encoder.value_to_mms_data(value, value_type)]) 459 460 if self._comm_log.is_enabled: 461 self._comm_log.log(common.CommLogAction.SEND, 462 logger.WriteDataReq(ref=ref, 463 value=value)) 464 465 res = await self._send(req) 466 467 if isinstance(res, mms.Error): 468 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 469 470 elif isinstance(res, mms.WriteResponse): 471 if len(res.results) != 1: 472 raise Exception('invalid results size') 473 474 if res.results[0] is not None: 475 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 476 result = common.ServiceError.ACCESS_VIOLATION 477 478 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 479 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 480 481 elif res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: # NOQA 482 result = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 483 484 elif res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT: 485 result = common.ServiceError.TYPE_CONFLICT 486 487 elif res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID: # NOQA 488 result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT 489 490 else: 491 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 492 493 else: 494 result = None 495 496 else: 497 raise Exception('unsupported response type') 498 499 if self._comm_log.is_enabled: 500 self._comm_log.log(common.CommLogAction.RECEIVE, 501 logger.WriteDataRes(result)) 502 503 return result 504 505 async def select(self, 506 ref: common.CommandRef, 507 cmd: common.Command | None 508 ) -> common.CommandError | None: 509 """Select command""" 510 if cmd is not None: 511 return await self._command_with_last_appl_error(ref=ref, 512 cmd=cmd, 513 attr='SBOw', 514 with_checks=True) 515 516 req = mms.ReadRequest([ 517 mms.NameVariableSpecification( 518 encoder.data_ref_to_object_name( 519 common.DataRef(logical_device=ref.logical_device, 520 logical_node=ref.logical_node, 521 fc='CO', 522 names=(ref.name, 'SBO'))))]) 523 524 if self._comm_log.is_enabled: 525 self._comm_log.log(common.CommLogAction.SEND, 526 logger.CommandReq(ref=ref, 527 attr='SBO', 528 cmd=cmd)) 529 530 res = await self._send(req) 531 532 if isinstance(res, mms.Error): 533 result = _create_command_error( 534 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 535 last_appl_error=None) 536 537 elif isinstance(res, mms.ReadResponse): 538 if len(res.results) != 1: 539 raise Exception('invalid results size') 540 541 if not isinstance(res.results[0], mms.VisibleStringData): 542 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 543 service_error = common.ServiceError.ACCESS_VIOLATION 544 545 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 546 service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE 547 548 else: 549 service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 550 551 result = _create_command_error(service_error=service_error, 552 last_appl_error=None) 553 554 elif res.results[0].value == '': 555 result = _create_command_error( 556 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 557 last_appl_error=None) 558 559 else: 560 result = None 561 562 else: 563 raise Exception('unsupported response type') 564 565 if self._comm_log.is_enabled: 566 self._comm_log.log(common.CommLogAction.RECEIVE, 567 logger.CommandRes(result)) 568 569 return result 570 571 async def cancel(self, 572 ref: common.CommandRef, 573 cmd: common.Command 574 ) -> common.CommandError | None: 575 """Cancel command""" 576 return await self._command_with_last_appl_error(ref=ref, 577 cmd=cmd, 578 attr='Cancel', 579 with_checks=False) 580 581 async def operate(self, 582 ref: common.CommandRef, 583 cmd: common.Command 584 ) -> common.CommandError | None: 585 """Operate command""" 586 return await self._command_with_last_appl_error(ref=ref, 587 cmd=cmd, 588 attr='Oper', 589 with_checks=True) 590 591 async def _on_unconfirmed(self, conn, unconfirmed): 592 self._status_event.set() 593 594 if _is_unconfirmed_report(unconfirmed): 595 try: 596 await self._process_report(unconfirmed.data) 597 598 except Exception as e: 599 self._log.error("error processing report: %s", e, exc_info=e) 600 601 elif _is_unconfirmed_last_appl_error(unconfirmed): 602 try: 603 self._process_last_appl_error(unconfirmed.data[0]) 604 605 except Exception as e: 606 self._log.error("error processing last application error: %s", 607 e, exc_info=e) 608 609 elif _is_unconfirmed_termination(unconfirmed): 610 names = [i.name for i in unconfirmed.specification] 611 data = list(unconfirmed.data) 612 613 if len(names) != len(data): 614 self._log.warning("names/data size mismatch") 615 return 616 617 data_ref = encoder.data_ref_from_object_name(names[-1]) 618 619 try: 620 await self._process_termination( 621 ref=common.CommandRef( 622 logical_device=data_ref.logical_device, 623 logical_node=data_ref.logical_node, 624 name=data_ref.names[0]), 625 cmd_mms_data=data[-1], 626 last_appl_error_mms_data=(data[0] if len(data) > 1 627 else None)) 628 629 except Exception as e: 630 self._log.error("error processing termination: %s", 631 e, exc_info=e) 632 633 else: 634 self._log.info("received unprocessed unconfirmed message") 635 636 async def _process_report(self, mms_data): 637 if not self._report_cb: 638 self._log.info("report callback not defined - skipping report") 639 return 640 641 report_id = encoder.value_from_mms_data( 642 mms_data[0], common.BasicValueType.VISIBLE_STRING) 643 644 data_defs = self._report_data_defs.get(report_id) 645 if data_defs is None: 646 self._log.info("report id not defined - skipping report") 647 return 648 649 report = encoder.report_from_mms_data(mms_data, data_defs) 650 651 self._comm_log.log(common.CommLogAction.RECEIVE, report) 652 653 await aio.call(self._report_cb, report) 654 655 def _process_last_appl_error(self, mms_data): 656 last_appl_error = encoder.last_appl_error_from_mms_data(mms_data) 657 658 key = last_appl_error.name, last_appl_error.control_number 659 if key in self._last_appl_errors: 660 self._last_appl_errors[key] = last_appl_error 661 662 async def _process_termination(self, ref, cmd_mms_data, 663 last_appl_error_mms_data): 664 if not self._termination_cb: 665 self._log.info("termination callback not defined - " 666 "skipping termination") 667 return 668 669 value_type = self._cmd_value_types.get(ref) 670 if value_type is None: 671 self._log.info("command value type not defined - " 672 "skipping termination") 673 return 674 675 cmd = encoder.command_from_mms_data(mms_data=cmd_mms_data, 676 value_type=value_type, 677 with_checks=True) 678 679 if last_appl_error_mms_data: 680 error = _create_command_error( 681 service_error=None, 682 last_appl_error=encoder.last_appl_error_from_mms_data( 683 last_appl_error_mms_data)) 684 685 else: 686 error = None 687 688 termination = common.Termination(ref=ref, 689 cmd=cmd, 690 error=error) 691 692 self._comm_log.log(common.CommLogAction.RECEIVE, termination) 693 694 await aio.call(self._termination_cb, termination) 695 696 async def _send(self, req): 697 res = await self._conn.send_confirmed(req) 698 self._status_event.set() 699 return res 700 701 async def _status_loop(self, delay, timeout): 702 try: 703 self._log.debug("starting status loop") 704 while True: 705 self._status_event.clear() 706 707 with contextlib.suppress(asyncio.TimeoutError): 708 await aio.wait_for(self._status_event.wait(), delay) 709 continue 710 711 self._log.debug("sending status request") 712 await aio.wait_for(self._send(mms.StatusRequest()), timeout) 713 714 except asyncio.TimeoutError: 715 self._log.warning("status timeout") 716 717 except ConnectionError: 718 pass 719 720 except Exception as e: 721 self._log.error("status loop error: %s", e, exc_info=e) 722 723 finally: 724 self._log.debug("stopping status loop") 725 self.close() 726 727 async def _get_name_list(self, object_class, object_scope): 728 identifiers = collections.deque() 729 continue_after = None 730 731 while True: 732 req = mms.GetNameListRequest( 733 object_class=object_class, 734 object_scope=object_scope, 735 continue_after=continue_after) 736 737 res = await self._send(req) 738 739 if isinstance(res, mms.Error): 740 if res == mms.AccessError.OBJECT_NON_EXISTENT: 741 return common.ServiceError.INSTANCE_NOT_AVAILABLE 742 743 if res == mms.AccessError.OBJECT_ACCESS_DENIED: 744 return common.ServiceError.ACCESS_VIOLATION 745 746 if res == mms.ServiceError.OBJECT_CONSTRAINT_CONFLICT: 747 return common.ServiceError.PARAMETER_VALUE_INCONSISTENT 748 749 return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 750 751 if not isinstance(res, mms.GetNameListResponse): 752 raise Exception('unsupported response type') 753 754 identifiers.extend(res.identifiers) 755 756 if not res.more_follows: 757 break 758 759 if not res.identifiers: 760 raise Exception('invalid more follows value') 761 762 continue_after = identifiers[-1] 763 764 return identifiers 765 766 async def _command_with_last_appl_error(self, ref, cmd, attr, with_checks): 767 value_type = self._cmd_value_types[ref] 768 769 req = mms.WriteRequest( 770 specification=[ 771 mms.NameVariableSpecification( 772 encoder.data_ref_to_object_name( 773 common.DataRef(logical_device=ref.logical_device, 774 logical_node=ref.logical_node, 775 fc='CO', 776 names=(ref.name, attr))))], 777 data=[encoder.command_to_mms_data(cmd=cmd, 778 value_type=value_type, 779 with_checks=with_checks)]) 780 781 name = (f'{req.specification[0].name.domain_id}/' 782 f'{req.specification[0].name.item_id}') 783 key = name, cmd.control_number 784 785 if key in self._last_appl_errors: 786 raise Exception('active control number duplicate') 787 788 self._last_appl_errors[key] = None 789 790 if self._comm_log.is_enabled: 791 self._comm_log.log(common.CommLogAction.SEND, 792 logger.CommandReq(ref=ref, 793 attr=attr, 794 cmd=cmd)) 795 796 try: 797 res = await self._send(req) 798 799 finally: 800 last_appl_error = self._last_appl_errors.pop(key, None) 801 802 if isinstance(res, mms.Error): 803 result = _create_command_error( 804 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 805 last_appl_error=last_appl_error) 806 807 elif isinstance(res, mms.WriteResponse): 808 if len(res.results) != 1: 809 raise Exception('invalid results size') 810 811 if res.results[0] is not None: 812 result = _create_command_error(service_error=None, 813 last_appl_error=last_appl_error) 814 815 else: 816 result = None 817 818 else: 819 raise Exception('unsupported response type') 820 821 if self._comm_log.is_enabled: 822 self._comm_log.log(common.CommLogAction.RECEIVE, 823 logger.CommandRes(result)) 824 825 return result
Client
112 @property 113 def info(self) -> acse.ConnectionInfo: 114 """Connection info""" 115 return self._conn.info
Connection info
117 async def create_dataset(self, 118 ref: common.DatasetRef, 119 data: Collection[common.DataRef] 120 ) -> common.ServiceError | None: 121 """Create dataset""" 122 req = mms.DefineNamedVariableListRequest( 123 name=encoder.dataset_ref_to_object_name(ref), 124 specification=[ 125 mms.NameVariableSpecification( 126 encoder.data_ref_to_object_name(i)) 127 for i in data]) 128 129 if self._comm_log.is_enabled: 130 self._comm_log.log(common.CommLogAction.SEND, 131 logger.CreateDatasetReq(ref=ref, 132 data=data)) 133 134 res = await self._send(req) 135 136 if isinstance(res, mms.Error): 137 if res == mms.AccessError.OBJECT_NON_EXISTENT: 138 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 139 140 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 141 result = common.ServiceError.ACCESS_VIOLATION 142 143 elif res == mms.DefinitionError.OBJECT_EXISTS: 144 result = common.ServiceError.INSTANCE_IN_USE 145 146 elif res == mms.DefinitionError.OBJECT_UNDEFINED: 147 result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT 148 149 elif res == mms.ResourceError.CAPABILITY_UNAVAILABLE: 150 result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 151 152 else: 153 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 154 155 elif isinstance(res, mms.DefineNamedVariableListResponse): 156 result = None 157 158 else: 159 raise Exception('unsupported response type') 160 161 if self._comm_log.is_enabled: 162 self._comm_log.log(common.CommLogAction.RECEIVE, 163 logger.CreateDatasetRes(result)) 164 165 return result
Create dataset
167 async def delete_dataset(self, 168 ref: common.DatasetRef 169 ) -> common.ServiceError | None: 170 """Delete dataset""" 171 req = mms.DeleteNamedVariableListRequest([ 172 encoder.dataset_ref_to_object_name(ref)]) 173 174 if self._comm_log.is_enabled: 175 self._comm_log.log(common.CommLogAction.SEND, 176 logger.DeleteDatasetReq(ref)) 177 178 res = await self._send(req) 179 180 if isinstance(res, mms.Error): 181 if res == mms.AccessError.OBJECT_NON_EXISTENT: 182 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 183 184 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 185 result = common.ServiceError.ACCESS_VIOLATION 186 187 else: 188 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 189 190 elif isinstance(res, mms.DeleteNamedVariableListResponse): 191 if res.matched == 0 and res.deleted == 0: 192 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 193 194 elif res.matched != res.deleted: 195 result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT 196 197 else: 198 result = None 199 200 else: 201 raise Exception('unsupported response type') 202 203 if self._comm_log.is_enabled: 204 self._comm_log.log(common.CommLogAction.RECEIVE, 205 logger.DeleteDatasetRes(result)) 206 207 return result
Delete dataset
209 async def get_persisted_dataset_refs(self, 210 logical_device: str 211 ) -> Collection[common.PersistedDatasetRef] | common.ServiceError: # NOQA 212 """Get persisted dataset references associated with logical device""" 213 214 if self._comm_log.is_enabled: 215 self._comm_log.log( 216 common.CommLogAction.SEND, 217 logger.GetPersistedDatasetRefsReq(logical_device)) 218 219 identifiers = await self._get_name_list( 220 object_class=mms.ObjectClass.NAMED_VARIABLE_LIST, 221 object_scope=mms.DomainSpecificObjectScope(logical_device)) 222 223 if isinstance(identifiers, common.ServiceError): 224 result = identifiers 225 226 else: 227 result = collections.deque() 228 for identifier in identifiers: 229 logical_node, name = identifier.split('$') 230 result.append( 231 common.PersistedDatasetRef(logical_device=logical_device, 232 logical_node=logical_node, 233 name=name)) 234 235 if self._comm_log.is_enabled: 236 self._comm_log.log(common.CommLogAction.RECEIVE, 237 logger.GetPersistedDatasetRefsRes(result)) 238 239 return result
Get persisted dataset references associated with logical device
241 async def get_dataset_data_refs(self, 242 ref: common.DatasetRef 243 ) -> Collection[common.DataRef] | common.ServiceError: # NOQA 244 """Get data references associated with single dataset""" 245 req = mms.GetNamedVariableListAttributesRequest( 246 encoder.dataset_ref_to_object_name(ref)) 247 248 if self._comm_log.is_enabled: 249 self._comm_log.log(common.CommLogAction.SEND, 250 logger.GetDatasetDataRefsReq(ref)) 251 252 res = await self._send(req) 253 254 if isinstance(res, mms.Error): 255 if res == mms.AccessError.OBJECT_NON_EXISTENT: 256 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 257 258 elif res == mms.AccessError.OBJECT_ACCESS_DENIED: 259 result = common.ServiceError.ACCESS_VIOLATION 260 261 elif res == mms.ServiceError.PDU_SIZE: 262 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 263 264 else: 265 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 266 267 elif isinstance(res, mms.GetNamedVariableListAttributesResponse): 268 result = collections.deque() 269 270 for i in res.specification: 271 if not isinstance(i, mms.NameVariableSpecification): 272 raise Exception('unsupported specification type') 273 274 result.append(encoder.data_ref_from_object_name(i.name)) 275 276 else: 277 raise Exception('unsupported response type') 278 279 if self._comm_log.is_enabled: 280 self._comm_log.log(common.CommLogAction.RECEIVE, 281 logger.GetDatasetDataRefsRes(result)) 282 283 return result
Get data references associated with single dataset
285 async def get_rcb_attrs(self, 286 ref: common.RcbRef, 287 attr_types: Collection[common.RcbAttrType] 288 ) -> dict[common.RcbAttrType, 289 common.RcbAttrValue | common.ServiceError]: # NOQA 290 """Get RCB attribute value""" 291 specification = collections.deque() 292 293 for attr_type in attr_types: 294 if ref.type == common.RcbType.BUFFERED: 295 if attr_type == common.RcbAttrType.RESERVE: 296 raise ValueError('unsupported attribute type') 297 298 elif ref.type == common.RcbType.UNBUFFERED: 299 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 300 common.RcbAttrType.ENTRY_ID, 301 common.RcbAttrType.TIME_OF_ENTRY, 302 common.RcbAttrType.RESERVATION_TIME): 303 raise ValueError('unsupported attribute type') 304 305 else: 306 raise TypeError('unsupported rcb type') 307 308 specification.append( 309 mms.NameVariableSpecification( 310 encoder.data_ref_to_object_name( 311 common.DataRef(logical_device=ref.logical_device, 312 logical_node=ref.logical_node, 313 fc=ref.type.value, 314 names=(ref.name, attr_type.value))))) 315 316 req = mms.ReadRequest(specification) 317 318 if self._comm_log.is_enabled: 319 self._comm_log.log(common.CommLogAction.SEND, 320 logger.GetRcbAttrsReq(ref=ref, 321 attr_types=attr_types)) 322 323 res = await self._send(req) 324 325 if isinstance(res, mms.Error): 326 results = { 327 attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 328 for attr_type in attr_types} 329 330 elif isinstance(res, mms.ReadResponse): 331 if len(res.results) != len(attr_types): 332 raise Exception('invalid results length') 333 334 results = {} 335 336 for attr_type, mms_data in zip(attr_types, res.results): 337 if isinstance(mms_data, mms.DataAccessError): 338 if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 339 results[attr_type] = common.ServiceError.ACCESS_VIOLATION # NOQA 340 341 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 342 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 343 344 else: 345 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 346 347 else: 348 results[attr_type] = encoder.rcb_attr_value_from_mms_data( 349 mms_data, attr_type) 350 351 else: 352 raise Exception('unsupported response type') 353 354 if self._comm_log.is_enabled: 355 self._comm_log.log(common.CommLogAction.RECEIVE, 356 logger.GetRcbAttrsRes(results)) 357 358 return results
Get RCB attribute value
360 async def set_rcb_attrs(self, 361 ref: common.RcbRef, 362 attrs: Collection[tuple[common.RcbAttrType, 363 common.RcbAttrValue]] 364 ) -> dict[common.RcbAttrType, 365 common.ServiceError | None]: 366 """Set RCB attribute values""" 367 specification = collections.deque() 368 data = collections.deque() 369 370 for attr_type, attr_value in attrs: 371 if ref.type == common.RcbType.BUFFERED: 372 if attr_type == common.RcbAttrType.RESERVE: 373 raise ValueError('unsupported attribute type') 374 375 elif ref.type == common.RcbType.UNBUFFERED: 376 if attr_type in (common.RcbAttrType.PURGE_BUFFER, 377 common.RcbAttrType.ENTRY_ID, 378 common.RcbAttrType.TIME_OF_ENTRY, 379 common.RcbAttrType.RESERVATION_TIME): 380 raise ValueError('unsupported attribute type') 381 382 else: 383 raise TypeError('unsupported rcb type') 384 385 specification.append( 386 mms.NameVariableSpecification( 387 encoder.data_ref_to_object_name( 388 common.DataRef(logical_device=ref.logical_device, 389 logical_node=ref.logical_node, 390 fc=ref.type.value, 391 names=(ref.name, attr_type.value))))) 392 data.append( 393 encoder.rcb_attr_value_to_mms_data(attr_value, attr_type)) 394 395 req = mms.WriteRequest(specification=specification, 396 data=data) 397 398 if self._comm_log.is_enabled: 399 self._comm_log.log(common.CommLogAction.SEND, 400 logger.SetRcbAttrsReq(ref=ref, 401 attrs=attrs)) 402 403 res = await self._send(req) 404 405 if isinstance(res, mms.Error): 406 results = { 407 attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 408 for attr_type, _ in attrs} 409 410 elif isinstance(res, mms.WriteResponse): 411 if len(res.results) != len(attrs): 412 raise Exception('invalid results length') 413 414 results = {} 415 416 for (attr_type, _), mms_data in zip(attrs, res.results): 417 if mms_data is None: 418 results[attr_type] = None 419 420 elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED: 421 results[attr_type] = common.ServiceError.ACCESS_VIOLATION 422 423 elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT: 424 results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE # NOQA 425 426 elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: 427 results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 428 429 elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT: 430 results[attr_type] = common.ServiceError.TYPE_CONFLICT 431 432 elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID: 433 results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT # NOQA 434 435 else: 436 results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 437 438 else: 439 raise Exception('unsupported response type') 440 441 if self._comm_log.is_enabled: 442 self._comm_log.log(common.CommLogAction.RECEIVE, 443 logger.SetRcbAttrsRes(results)) 444 445 return results
Set RCB attribute values
447 async def write_data(self, 448 ref: common.DataRef, 449 value: common.Value 450 ) -> common.ServiceError | None: 451 """Write data""" 452 value_type = self._data_value_types[ref] 453 454 req = mms.WriteRequest( 455 specification=[ 456 mms.NameVariableSpecification( 457 encoder.data_ref_to_object_name(ref))], 458 data=[encoder.value_to_mms_data(value, value_type)]) 459 460 if self._comm_log.is_enabled: 461 self._comm_log.log(common.CommLogAction.SEND, 462 logger.WriteDataReq(ref=ref, 463 value=value)) 464 465 res = await self._send(req) 466 467 if isinstance(res, mms.Error): 468 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 469 470 elif isinstance(res, mms.WriteResponse): 471 if len(res.results) != 1: 472 raise Exception('invalid results size') 473 474 if res.results[0] is not None: 475 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 476 result = common.ServiceError.ACCESS_VIOLATION 477 478 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 479 result = common.ServiceError.INSTANCE_NOT_AVAILABLE 480 481 elif res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE: # NOQA 482 result = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT # NOQA 483 484 elif res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT: 485 result = common.ServiceError.TYPE_CONFLICT 486 487 elif res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID: # NOQA 488 result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT 489 490 else: 491 result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 492 493 else: 494 result = None 495 496 else: 497 raise Exception('unsupported response type') 498 499 if self._comm_log.is_enabled: 500 self._comm_log.log(common.CommLogAction.RECEIVE, 501 logger.WriteDataRes(result)) 502 503 return result
Write data
505 async def select(self, 506 ref: common.CommandRef, 507 cmd: common.Command | None 508 ) -> common.CommandError | None: 509 """Select command""" 510 if cmd is not None: 511 return await self._command_with_last_appl_error(ref=ref, 512 cmd=cmd, 513 attr='SBOw', 514 with_checks=True) 515 516 req = mms.ReadRequest([ 517 mms.NameVariableSpecification( 518 encoder.data_ref_to_object_name( 519 common.DataRef(logical_device=ref.logical_device, 520 logical_node=ref.logical_node, 521 fc='CO', 522 names=(ref.name, 'SBO'))))]) 523 524 if self._comm_log.is_enabled: 525 self._comm_log.log(common.CommLogAction.SEND, 526 logger.CommandReq(ref=ref, 527 attr='SBO', 528 cmd=cmd)) 529 530 res = await self._send(req) 531 532 if isinstance(res, mms.Error): 533 result = _create_command_error( 534 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 535 last_appl_error=None) 536 537 elif isinstance(res, mms.ReadResponse): 538 if len(res.results) != 1: 539 raise Exception('invalid results size') 540 541 if not isinstance(res.results[0], mms.VisibleStringData): 542 if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED: 543 service_error = common.ServiceError.ACCESS_VIOLATION 544 545 elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT: 546 service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE 547 548 else: 549 service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT # NOQA 550 551 result = _create_command_error(service_error=service_error, 552 last_appl_error=None) 553 554 elif res.results[0].value == '': 555 result = _create_command_error( 556 service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT, # NOQA 557 last_appl_error=None) 558 559 else: 560 result = None 561 562 else: 563 raise Exception('unsupported response type') 564 565 if self._comm_log.is_enabled: 566 self._comm_log.log(common.CommLogAction.RECEIVE, 567 logger.CommandRes(result)) 568 569 return result
Select command
571 async def cancel(self, 572 ref: common.CommandRef, 573 cmd: common.Command 574 ) -> common.CommandError | None: 575 """Cancel command""" 576 return await self._command_with_last_appl_error(ref=ref, 577 cmd=cmd, 578 attr='Cancel', 579 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)