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']
ReportCb = typing.Callable[[Report], None | collections.abc.Awaitable[None]]
TerminationCb = typing.Callable[[Termination], None | collections.abc.Awaitable[None]]
async def connect( addr: hat.drivers.net.TcpAddress | pathlib.Path, data_value_types: dict[DataRef, BasicValueType | AcsiValueType | ArrayValueType | StructValueType] = {}, cmd_value_types: dict[CommandRef, BasicValueType | AcsiValueType | ArrayValueType | StructValueType] = {}, report_data_refs: dict[str, Collection[DataRef]] = {}, report_cb: Callable[[Report], None | Awaitable[None]] | None = None, termination_cb: Callable[[Termination], None | Awaitable[None]] | None = None, status_delay: float | None = None, status_timeout: float = 30, **kwargs) -> Client:
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).

class Client(hat.aio.group.Resource):
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

async_group
104    @property
105    def async_group(self):
106        """Async group"""
107        return self._conn.async_group

Async group

109    @property
110    def info(self) -> acse.ConnectionInfo:
111        """Connection info"""
112        return self._conn.info

Connection info

async def create_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef, data: Collection[DataRef]) -> ServiceError | None:
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

async def delete_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> ServiceError | None:
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

async def get_persisted_dataset_refs( self, logical_device: str) -> Collection[PersistedDatasetRef] | ServiceError:
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

async def get_dataset_data_refs( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> Collection[DataRef] | ServiceError:
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

async def get_rcb_attrs( self, ref: RcbRef, attr_types: Collection[RcbAttrType]) -> dict[RcbAttrType, str | bool | PersistedDatasetRef | NonPersistedDatasetRef | None | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime | ServiceError]:
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

async def set_rcb_attrs( self, ref: RcbRef, attrs: Collection[tuple[RcbAttrType, str | bool | PersistedDatasetRef | NonPersistedDatasetRef | None | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime]]) -> dict[RcbAttrType, ServiceError | None]:
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

async def write_data( self, ref: DataRef, value: bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection['Value'] | Dict[str, ForwardRef('Value')]) -> ServiceError | None:
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

async def select( self, ref: CommandRef, cmd: Command | None) -> CommandError | None:
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

async def cancel( self, ref: CommandRef, cmd: Command) -> CommandError | None:
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

async def operate( self, ref: CommandRef, cmd: Command) -> CommandError | None:
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)

Operate command

EntryTime = <class 'datetime.datetime'>
ReportId = <class 'str'>
class PersistedDatasetRef(typing.NamedTuple):
19class PersistedDatasetRef(typing.NamedTuple):
20    logical_device: str
21    logical_node: str
22    name: str

PersistedDatasetRef(logical_device, logical_node, name)

PersistedDatasetRef(logical_device: str, logical_node: str, name: str)

Create new instance of PersistedDatasetRef(logical_device, logical_node, name)

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

name: str

Alias for field number 2

class NonPersistedDatasetRef(typing.NamedTuple):
25class NonPersistedDatasetRef(typing.NamedTuple):
26    name: str

NonPersistedDatasetRef(name,)

NonPersistedDatasetRef(name: str)

Create new instance of NonPersistedDatasetRef(name,)

name: str

Alias for field number 0

class DataRef(typing.NamedTuple):
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)

DataRef( logical_device: str, logical_node: str, fc: str, names: tuple[str | int, ...])

Create new instance of DataRef(logical_device, logical_node, fc, names)

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

fc: str

Alias for field number 2

names: tuple[str | int, ...]

Alias for field number 3

class CommandRef(typing.NamedTuple):
39class CommandRef(typing.NamedTuple):
40    logical_device: str
41    logical_node: str
42    name: str

CommandRef(logical_device, logical_node, name)

CommandRef(logical_device: str, logical_node: str, name: str)

Create new instance of CommandRef(logical_device, logical_node, name)

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

name: str

Alias for field number 2

class RcbType(enum.Enum):
45class RcbType(enum.Enum):
46    BUFFERED = 'BR'
47    UNBUFFERED = 'RP'
BUFFERED = <RcbType.BUFFERED: 'BR'>
UNBUFFERED = <RcbType.UNBUFFERED: 'RP'>
class RcbRef(typing.NamedTuple):
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)

RcbRef( logical_device: str, logical_node: str, type: RcbType, name: str)

Create new instance of RcbRef(logical_device, logical_node, type, name)

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

type: RcbType

Alias for field number 2

name: str

Alias for field number 3

class BasicValueType(enum.Enum):
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
BOOLEAN = <BasicValueType.BOOLEAN: 'BOOLEAN'>
INTEGER = <BasicValueType.INTEGER: 'INTEGER'>
UNSIGNED = <BasicValueType.UNSIGNED: 'UNSIGNED'>
FLOAT = <BasicValueType.FLOAT: 'FLOAT'>
BIT_STRING = <BasicValueType.BIT_STRING: 'BIT_STRING'>
OCTET_STRING = <BasicValueType.OCTET_STRING: 'OCTET_STRING'>
VISIBLE_STRING = <BasicValueType.VISIBLE_STRING: 'VISIBLE_STRING'>
MMS_STRING = <BasicValueType.MMS_STRING: 'MMS_STRING'>
class AcsiValueType(enum.Enum):
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'
QUALITY = <AcsiValueType.QUALITY: 'QUALITY'>
TIMESTAMP = <AcsiValueType.TIMESTAMP: 'TIMESTAMP'>
DOUBLE_POINT = <AcsiValueType.DOUBLE_POINT: 'DOUBLE_POINT'>
DIRECTION = <AcsiValueType.DIRECTION: 'DIRECTION'>
SEVERITY = <AcsiValueType.SEVERITY: 'SEVERITY'>
ANALOGUE = <AcsiValueType.ANALOGUE: 'ANALOGUE'>
VECTOR = <AcsiValueType.VECTOR: 'VECTOR'>
STEP_POSITION = <AcsiValueType.STEP_POSITION: 'STEP_POSITION'>
BINARY_CONTROL = <AcsiValueType.BINARY_CONTROL: 'BINARY_CONTROL'>
class ArrayValueType(typing.NamedTuple):
82class ArrayValueType(typing.NamedTuple):
83    type: 'ValueType'
84    length: int

ArrayValueType(type, length)

ArrayValueType(type: ValueType, length: int)

Create new instance of ArrayValueType(type, length)

Alias for field number 0

length: int

Alias for field number 1

class StructValueType(typing.NamedTuple):
87class StructValueType(typing.NamedTuple):
88    elements: Collection[typing.Tuple[str, 'ValueType']]

StructValueType(elements,)

StructValueType(elements: Collection[typing.Tuple[str, ForwardRef('ValueType')]])

Create new instance of StructValueType(elements,)

elements: Collection[typing.Tuple[str, BasicValueType | AcsiValueType | ArrayValueType | StructValueType]]

Alias for field number 0

class Timestamp(typing.NamedTuple):
 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)

Timestamp( value: datetime.datetime, leap_second: bool, clock_failure: bool, not_synchronized: bool, accuracy: int | None)

Create new instance of Timestamp(value, leap_second, clock_failure, not_synchronized, accuracy)

value: datetime.datetime

Alias for field number 0

leap_second: bool

Alias for field number 1

clock_failure: bool

Alias for field number 2

not_synchronized: bool

Alias for field number 3

accuracy: int | None

accurate fraction bits [0,24]

class QualityValidity(enum.Enum):
108class QualityValidity(enum.Enum):
109    GOOD = 0
110    INVALID = 1
111    RESERVED = 2
112    QUESTIONABLE = 3
GOOD = <QualityValidity.GOOD: 0>
INVALID = <QualityValidity.INVALID: 1>
RESERVED = <QualityValidity.RESERVED: 2>
QUESTIONABLE = <QualityValidity.QUESTIONABLE: 3>
class QualityDetail(enum.Enum):
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
OVERFLOW = <QualityDetail.OVERFLOW: 2>
OUT_OF_RANGE = <QualityDetail.OUT_OF_RANGE: 3>
BAD_REFERENCE = <QualityDetail.BAD_REFERENCE: 4>
OSCILLATORY = <QualityDetail.OSCILLATORY: 5>
FAILURE = <QualityDetail.FAILURE: 6>
OLD_DATA = <QualityDetail.OLD_DATA: 7>
INCONSISTENT = <QualityDetail.INCONSISTENT: 8>
INACCURATE = <QualityDetail.INACCURATE: 9>
class QualitySource(enum.Enum):
126class QualitySource(enum.Enum):
127    PROCESS = 0
128    SUBSTITUTED = 1
PROCESS = <QualitySource.PROCESS: 0>
SUBSTITUTED = <QualitySource.SUBSTITUTED: 1>
class Quality(typing.NamedTuple):
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)

Quality( validity: QualityValidity, details: set[QualityDetail], source: QualitySource, test: bool, operator_blocked: bool)

Create new instance of Quality(validity, details, source, test, operator_blocked)

validity: QualityValidity

Alias for field number 0

details: set[QualityDetail]

Alias for field number 1

source: QualitySource

Alias for field number 2

test: bool

Alias for field number 3

operator_blocked: bool

Alias for field number 4

class DoublePoint(enum.Enum):
139class DoublePoint(enum.Enum):
140    INTERMEDIATE = 0
141    OFF = 1
142    ON = 2
143    BAD = 3
INTERMEDIATE = <DoublePoint.INTERMEDIATE: 0>
OFF = <DoublePoint.OFF: 1>
ON = <DoublePoint.ON: 2>
BAD = <DoublePoint.BAD: 3>
class Direction(enum.Enum):
146class Direction(enum.Enum):
147    UNKNOWN = 0
148    FORWARD = 1
149    BACKWARD = 2
150    BOTH = 3
UNKNOWN = <Direction.UNKNOWN: 0>
FORWARD = <Direction.FORWARD: 1>
BACKWARD = <Direction.BACKWARD: 2>
BOTH = <Direction.BOTH: 3>
class Severity(enum.Enum):
153class Severity(enum.Enum):
154    UNKNOWN = 0
155    CRITICAL = 1
156    MAJOR = 2
157    MINOR = 3
158    WARNING = 4
UNKNOWN = <Severity.UNKNOWN: 0>
CRITICAL = <Severity.CRITICAL: 1>
MAJOR = <Severity.MAJOR: 2>
MINOR = <Severity.MINOR: 3>
WARNING = <Severity.WARNING: 4>
class Analogue(typing.NamedTuple):
161class Analogue(typing.NamedTuple):
162    i: int | None = None
163    f: float | None = None

Analogue(i, f)

Analogue(i: int | None = None, f: float | None = None)

Create new instance of Analogue(i, f)

i: int | None

Alias for field number 0

f: float | None

Alias for field number 1

class Vector(typing.NamedTuple):
166class Vector(typing.NamedTuple):
167    magnitude: Analogue
168    angle: Analogue | None

Vector(magnitude, angle)

Vector( magnitude: Analogue, angle: Analogue | None)

Create new instance of Vector(magnitude, angle)

magnitude: Analogue

Alias for field number 0

angle: Analogue | None

Alias for field number 1

class StepPosition(typing.NamedTuple):
171class StepPosition(typing.NamedTuple):
172    value: int
173    """value in range [-64, 63]"""
174    transient: bool | None

StepPosition(value, transient)

StepPosition(value: int, transient: bool | None)

Create new instance of StepPosition(value, transient)

value: int

value in range [-64, 63]

transient: bool | None

Alias for field number 1

class BinaryControl(enum.Enum):
177class BinaryControl(enum.Enum):
178    STOP = 0
179    LOWER = 1
180    HIGHER = 2
181    RESERVED = 3
STOP = <BinaryControl.STOP: 0>
LOWER = <BinaryControl.LOWER: 1>
HIGHER = <BinaryControl.HIGHER: 2>
RESERVED = <BinaryControl.RESERVED: 3>
BasicValue = bool | int | float | str | bytes | bytearray | memoryview | collections.abc.Collection[bool]
ArrayValue = collections.abc.Collection['Value']
StructValue = typing.Dict[str, ForwardRef('Value')]
Value = bool | int | float | str | bytes | bytearray | memoryview | collections.abc.Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | collections.abc.Collection['Value'] | typing.Dict[str, ForwardRef('Value')]
class ServiceError(enum.Enum):
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
NO_ERROR = <ServiceError.NO_ERROR: 0>
INSTANCE_NOT_AVAILABLE = <ServiceError.INSTANCE_NOT_AVAILABLE: 1>
INSTANCE_IN_USE = <ServiceError.INSTANCE_IN_USE: 2>
ACCESS_VIOLATION = <ServiceError.ACCESS_VIOLATION: 3>
ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = <ServiceError.ACCESS_NOT_ALLOWED_IN_CURRENT_STATE: 4>
PARAMETER_VALUE_INAPPROPRIATE = <ServiceError.PARAMETER_VALUE_INAPPROPRIATE: 5>
PARAMETER_VALUE_INCONSISTENT = <ServiceError.PARAMETER_VALUE_INCONSISTENT: 6>
CLASS_NOT_SUPPORTED = <ServiceError.CLASS_NOT_SUPPORTED: 7>
INSTANCE_LOCKED_BY_OTHER_CLIENT = <ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT: 8>
CONTROL_MUST_BE_SELECTED = <ServiceError.CONTROL_MUST_BE_SELECTED: 9>
TYPE_CONFLICT = <ServiceError.TYPE_CONFLICT: 10>
FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = <ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT: 11>
FAILED_DUE_TO_SERVER_CONSTRAINT = <ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT: 12>
class AdditionalCause(enum.Enum):
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
UNKNOWN = <AdditionalCause.UNKNOWN: 0>
NOT_SUPPORTED = <AdditionalCause.NOT_SUPPORTED: 1>
BLOCKED_BY_SWITCHING_HIERARCHY = <AdditionalCause.BLOCKED_BY_SWITCHING_HIERARCHY: 2>
SELECT_FAILED = <AdditionalCause.SELECT_FAILED: 3>
INVALID_POSITION = <AdditionalCause.INVALID_POSITION: 4>
POSITION_REACHED = <AdditionalCause.POSITION_REACHED: 5>
PARAMETER_CHANGE_IN_EXECUTION = <AdditionalCause.PARAMETER_CHANGE_IN_EXECUTION: 6>
STEP_LIMIT = <AdditionalCause.STEP_LIMIT: 7>
BLOCKED_BY_MODE = <AdditionalCause.BLOCKED_BY_MODE: 8>
BLOCKED_BY_PROCESS = <AdditionalCause.BLOCKED_BY_PROCESS: 9>
BLOCKED_BY_INTERLOCKING = <AdditionalCause.BLOCKED_BY_INTERLOCKING: 10>
BLOCKED_BY_SYNCHROCHECK = <AdditionalCause.BLOCKED_BY_SYNCHROCHECK: 11>
COMMAND_ALREADY_IN_EXECUTION = <AdditionalCause.COMMAND_ALREADY_IN_EXECUTION: 12>
BLOCKED_BY_HEALTH = <AdditionalCause.BLOCKED_BY_HEALTH: 13>
ONE_OF_N_CONTROL = <AdditionalCause.ONE_OF_N_CONTROL: 14>
ABORTION_BY_CANCEL = <AdditionalCause.ABORTION_BY_CANCEL: 15>
TIME_LIMIT_OVER = <AdditionalCause.TIME_LIMIT_OVER: 16>
ABORTION_BY_TRIP = <AdditionalCause.ABORTION_BY_TRIP: 17>
OBJECT_NOT_SELECTED = <AdditionalCause.OBJECT_NOT_SELECTED: 18>
OBJECT_ALREADY_SELECTED = <AdditionalCause.OBJECT_ALREADY_SELECTED: 19>
NO_ACCESS_AUTHORITY = <AdditionalCause.NO_ACCESS_AUTHORITY: 20>
ENDED_WITH_OVERSHOOT = <AdditionalCause.ENDED_WITH_OVERSHOOT: 21>
ABORTION_DUE_TO_DEVIATION = <AdditionalCause.ABORTION_DUE_TO_DEVIATION: 22>
ABORTION_BY_COMMUNICATION_LOSS = <AdditionalCause.ABORTION_BY_COMMUNICATION_LOSS: 23>
BLOCKED_BY_COMMAND = <AdditionalCause.BLOCKED_BY_COMMAND: 24>
NONE = <AdditionalCause.NONE: 25>
INCONSISTENT_PARAMETERS = <AdditionalCause.INCONSISTENT_PARAMETERS: 26>
LOCKED_BY_OTHER_CLIENT = <AdditionalCause.LOCKED_BY_OTHER_CLIENT: 27>
class TestError(enum.Enum):
246class TestError(enum.Enum):
247    NO_ERROR = 0
248    UNKNOWN = 1
249    TIMEOUT_TEST_NOT_OK = 2
250    OPERATOR_TEST_NOT_OK = 3
NO_ERROR = <TestError.NO_ERROR: 0>
UNKNOWN = <TestError.UNKNOWN: 1>
TIMEOUT_TEST_NOT_OK = <TestError.TIMEOUT_TEST_NOT_OK: 2>
OPERATOR_TEST_NOT_OK = <TestError.OPERATOR_TEST_NOT_OK: 3>
class CommandError(typing.NamedTuple):
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)

CommandError( service_error: ServiceError | None, additional_cause: AdditionalCause | None, test_error: TestError | None)

Create new instance of CommandError(service_error, additional_cause, test_error)

service_error: ServiceError | None

Alias for field number 0

additional_cause: AdditionalCause | None

Alias for field number 1

test_error: TestError | None

Alias for field number 2

class OptionalField(enum.Enum):
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
SEQUENCE_NUMBER = <OptionalField.SEQUENCE_NUMBER: 1>
REPORT_TIME_STAMP = <OptionalField.REPORT_TIME_STAMP: 2>
REASON_FOR_INCLUSION = <OptionalField.REASON_FOR_INCLUSION: 3>
DATA_SET_NAME = <OptionalField.DATA_SET_NAME: 4>
DATA_REFERENCE = <OptionalField.DATA_REFERENCE: 5>
BUFFER_OVERFLOW = <OptionalField.BUFFER_OVERFLOW: 6>
ENTRY_ID = <OptionalField.ENTRY_ID: 7>
CONF_REVISION = <OptionalField.CONF_REVISION: 8>
class TriggerCondition(enum.Enum):
272class TriggerCondition(enum.Enum):
273    DATA_CHANGE = 1
274    QUALITY_CHANGE = 2
275    DATA_UPDATE = 3
276    INTEGRITY = 4
277    GENERAL_INTERROGATION = 5
DATA_CHANGE = <TriggerCondition.DATA_CHANGE: 1>
QUALITY_CHANGE = <TriggerCondition.QUALITY_CHANGE: 2>
DATA_UPDATE = <TriggerCondition.DATA_UPDATE: 3>
INTEGRITY = <TriggerCondition.INTEGRITY: 4>
GENERAL_INTERROGATION = <TriggerCondition.GENERAL_INTERROGATION: 5>
class RcbAttrType(enum.Enum):
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
REPORT_ID = <RcbAttrType.REPORT_ID: 'RptID'>
REPORT_ENABLE = <RcbAttrType.REPORT_ENABLE: 'RptEna'>
DATASET = <RcbAttrType.DATASET: 'DatSet'>
CONF_REVISION = <RcbAttrType.CONF_REVISION: 'ConfRev'>
OPTIONAL_FIELDS = <RcbAttrType.OPTIONAL_FIELDS: 'OptFlds'>
BUFFER_TIME = <RcbAttrType.BUFFER_TIME: 'BufTm'>
SEQUENCE_NUMBER = <RcbAttrType.SEQUENCE_NUMBER: 'SqNum'>
TRIGGER_OPTIONS = <RcbAttrType.TRIGGER_OPTIONS: 'TrgOps'>
INTEGRITY_PERIOD = <RcbAttrType.INTEGRITY_PERIOD: 'IntgPd'>
GI = <RcbAttrType.GI: 'GI'>
PURGE_BUFFER = <RcbAttrType.PURGE_BUFFER: 'PurgeBuf'>
ENTRY_ID = <RcbAttrType.ENTRY_ID: 'EntryID'>
TIME_OF_ENTRY = <RcbAttrType.TIME_OF_ENTRY: 'TimeOfEntry'>
RESERVATION_TIME = <RcbAttrType.RESERVATION_TIME: 'ResvTms'>
RESERVE = <RcbAttrType.RESERVE: 'Resv'>
ReportIdRcbAttrValue = <class 'str'>
ReportEnableRcbAttrValue = <class 'bool'>
DatasetRcbAttrValue = PersistedDatasetRef | NonPersistedDatasetRef | None
ConfRevisionRcbAttrValue = <class 'int'>
OptionalFieldsRcbAttrValue = set[OptionalField]
BufferTimeRcbAttrValue = <class 'int'>
SequenceNumberRcbAttrValue = <class 'int'>
TriggerOptionsRcbAttrValue = set[TriggerCondition]
IntegrityPeriodRcbAttrValue = <class 'int'>
GiRcbAttrValue = <class 'bool'>
PurgeBufferRcbAttrValue = <class 'bool'>
EntryIdRcbAttrValue = bytes | bytearray | memoryview
TimeOfEntryRcbAttrValue = <class 'datetime.datetime'>
ReservationTimeRcbAttrValue = <class 'int'>
ReserveRcbAttrValue = <class 'bool'>
RcbAttrValue = str | bool | PersistedDatasetRef | NonPersistedDatasetRef | None | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime
class Reason(enum.Enum):
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
DATA_CHANGE = <Reason.DATA_CHANGE: 1>
QUALITY_CHANGE = <Reason.QUALITY_CHANGE: 2>
DATA_UPDATE = <Reason.DATA_UPDATE: 3>
INTEGRITY = <Reason.INTEGRITY: 4>
GENERAL_INTERROGATION = <Reason.GENERAL_INTERROGATION: 5>
APPLICATION_TRIGGER = <Reason.APPLICATION_TRIGGER: 6>
class ReportData(typing.NamedTuple):
356class ReportData(typing.NamedTuple):
357    ref: DataRef
358    value: Value
359    reasons: set[Reason] | None

ReportData(ref, value, reasons)

ReportData( ref: DataRef, value: bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection['Value'] | Dict[str, ForwardRef('Value')], reasons: set[Reason] | None)

Create new instance of ReportData(ref, value, reasons)

ref: DataRef

Alias for field number 0

value: bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[ForwardRef('Value')] | Dict[str, ForwardRef('Value')]] | Dict[str, bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[ForwardRef('Value')] | Dict[str, ForwardRef('Value')]]

Alias for field number 1

reasons: set[Reason] | None

Alias for field number 2

class Report(typing.NamedTuple):
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)

Report( report_id: str, sequence_number: int | None, subsequence_number: int | None, more_segments_follow: bool | None, dataset: PersistedDatasetRef | NonPersistedDatasetRef | None, buffer_overflow: bool | None, conf_revision: int | None, entry_time: datetime.datetime | None, entry_id: bytes | bytearray | memoryview | None, data: Collection[ReportData])

Create new instance of Report(report_id, sequence_number, subsequence_number, more_segments_follow, dataset, buffer_overflow, conf_revision, entry_time, entry_id, data)

report_id: str

Alias for field number 0

sequence_number: int | None

Alias for field number 1

subsequence_number: int | None

Alias for field number 2

more_segments_follow: bool | None

Alias for field number 3

Alias for field number 4

buffer_overflow: bool | None

Alias for field number 5

conf_revision: int | None

Alias for field number 6

entry_time: datetime.datetime | None

Alias for field number 7

entry_id: bytes | bytearray | memoryview | None

Alias for field number 8

data: Collection[ReportData]

Alias for field number 9

class ControlModel(enum.Enum):
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
DIRECT_WITH_NORMAL_SECURITY = <ControlModel.DIRECT_WITH_NORMAL_SECURITY: 1>
SBO_WITH_NORMAL_SECURITY = <ControlModel.SBO_WITH_NORMAL_SECURITY: 2>
DIRECT_WITH_ENHANCED_SECURITY = <ControlModel.DIRECT_WITH_ENHANCED_SECURITY: 3>
SBO_WITH_ENHANCED_SECURITY = <ControlModel.SBO_WITH_ENHANCED_SECURITY: 4>
class OriginCategory(enum.Enum):
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
NOT_SUPPORTED = <OriginCategory.NOT_SUPPORTED: 0>
BAY_CONTROL = <OriginCategory.BAY_CONTROL: 1>
STATION_CONTROL = <OriginCategory.STATION_CONTROL: 2>
REMOTE_CONTROL = <OriginCategory.REMOTE_CONTROL: 3>
AUTOMATIC_BAY = <OriginCategory.AUTOMATIC_BAY: 4>
AUTOMATIC_STATION = <OriginCategory.AUTOMATIC_STATION: 5>
AUTOMATIC_REMOTE = <OriginCategory.AUTOMATIC_REMOTE: 6>
MAINTENANCE = <OriginCategory.MAINTENANCE: 7>
PROCESS = <OriginCategory.PROCESS: 8>
class Origin(typing.NamedTuple):
396class Origin(typing.NamedTuple):
397    category: OriginCategory
398    identification: util.Bytes

Origin(category, identification)

Origin( category: OriginCategory, identification: bytes | bytearray | memoryview)

Create new instance of Origin(category, identification)

category: OriginCategory

Alias for field number 0

identification: bytes | bytearray | memoryview

Alias for field number 1

class Check(enum.Enum):
401class Check(enum.Enum):
402    SYNCHRO = 0
403    INTERLOCK = 1
SYNCHRO = <Check.SYNCHRO: 0>
INTERLOCK = <Check.INTERLOCK: 1>
class Command(typing.NamedTuple):
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)

Command( value: bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection['Value'] | Dict[str, ForwardRef('Value')], operate_time: Timestamp | None, origin: Origin, control_number: int, t: Timestamp, test: bool, checks: set[Check])

Create new instance of Command(value, operate_time, origin, control_number, t, test, checks)

value: bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[ForwardRef('Value')] | Dict[str, ForwardRef('Value')]] | Dict[str, bool | int | float | str | bytes | bytearray | memoryview | Collection[bool] | Quality | Timestamp | DoublePoint | Direction | Severity | Analogue | Vector | StepPosition | BinaryControl | Collection[ForwardRef('Value')] | Dict[str, ForwardRef('Value')]]

Alias for field number 0

operate_time: Timestamp | None

Alias for field number 1

origin: Origin

Alias for field number 2

control_number: int

control number in range [0, 255]

Alias for field number 4

test: bool

Alias for field number 5

checks: set[Check]

ignored in cancel action

class Termination(typing.NamedTuple):
418class Termination(typing.NamedTuple):
419    ref: CommandRef
420    cmd: Command
421    error: CommandError | None

Termination(ref, cmd, error)

Termination( ref: CommandRef, cmd: Command, error: CommandError | None)

Create new instance of Termination(ref, cmd, error)

ref: CommandRef

Alias for field number 0

cmd: Command

Alias for field number 1

error: CommandError | None

Alias for field number 2