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.tcp.Address, 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: Optional[Callable[[Report], None | Awaitable[None]]] = None, termination_cb: Optional[Callable[[Termination], None | Awaitable[None]]] = None, status_delay: float | None = None, status_timeout: float = 30, **kwargs) -> Client:
28async def connect(addr: tcp.Address,
29                  data_value_types: dict[common.DataRef,
30                                         common.ValueType] = {},
31                  cmd_value_types: dict[common.CommandRef,
32                                        common.ValueType] = {},
33                  report_data_refs: dict[common.ReportId,
34                                         Collection[common.DataRef]] = {},
35                  report_cb: ReportCb | None = None,
36                  termination_cb: TerminationCb | None = None,
37                  status_delay: float | None = None,
38                  status_timeout: float = 30,
39                  **kwargs
40                  ) -> 'Client':
41    """Connect to IEC61850 server
42
43    `data_value_types` include value types used in report processing and
44    writing data.
45
46    `cmd_value_types` include value types used in command actions and
47    termination processing.
48
49    Only reports that are specified by `report_data_refs` are notified with
50    `report_cb`.
51
52    If `status_delay` is ``None``, periodical sending of status requests is
53    disabled.
54
55    Additional arguments are passed directly to `hat.drivers.mms.connect`
56    (`request_cb` and `unconfirmed_cb` are set by this coroutine).
57
58    """
59    client = Client()
60    client._data_value_types = data_value_types
61    client._cmd_value_types = cmd_value_types
62    client._report_cb = report_cb
63    client._termination_cb = termination_cb
64    client._loop = asyncio.get_running_loop()
65    client._status_event = asyncio.Event()
66    client._last_appl_errors = {}
67    client._report_data_defs = {
68        report_id: [encoder.DataDef(ref=data_ref,
69                                    value_type=data_value_types[data_ref])
70                    for data_ref in data_refs]
71        for report_id, data_refs in report_data_refs.items()}
72
73    client._conn = await mms.connect(addr=addr,
74                                     request_cb=None,
75                                     unconfirmed_cb=client._on_unconfirmed,
76                                     **kwargs)
77
78    client._log = _create_logger_adapter(False, client._conn.info)
79    client._comm_log = _create_logger_adapter(True, client._conn.info)
80
81    if client.is_open and status_delay is not None:
82        client.async_group.spawn(client._status_loop, status_delay,
83                                 status_timeout)
84
85    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):
 88class Client(aio.Resource):
 89    """Client"""
 90
 91    @property
 92    def async_group(self):
 93        """Async group"""
 94        return self._conn.async_group
 95
 96    @property
 97    def info(self) -> acse.ConnectionInfo:
 98        """Connection info"""
 99        return self._conn.info
100
101    async def create_dataset(self,
102                             ref: common.DatasetRef,
103                             data: Iterable[common.DataRef]
104                             ) -> common.ServiceError | None:
105        """Create dataset"""
106        req = mms.DefineNamedVariableListRequest(
107            name=encoder.dataset_ref_to_object_name(ref),
108            specification=[
109                mms.NameVariableSpecification(
110                    encoder.data_ref_to_object_name(i))
111                for i in data])
112
113        res = await self._send(req)
114
115        if isinstance(res, mms.Error):
116            if res == mms.AccessError.OBJECT_NON_EXISTENT:
117                return common.ServiceError.INSTANCE_NOT_AVAILABLE
118
119            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
120                return common.ServiceError.ACCESS_VIOLATION
121
122            if res == mms.DefinitionError.OBJECT_EXISTS:
123                return common.ServiceError.INSTANCE_IN_USE
124
125            if res == mms.DefinitionError.OBJECT_UNDEFINED:
126                return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
127
128            if res == mms.ResourceError.CAPABILITY_UNAVAILABLE:
129                return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
130
131            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
132
133        if not isinstance(res, mms.DefineNamedVariableListResponse):
134            raise Exception('unsupported response type')
135
136    async def delete_dataset(self,
137                             ref: common.DatasetRef
138                             ) -> common.ServiceError | None:
139        """Delete dataset"""
140        req = mms.DeleteNamedVariableListRequest([
141            encoder.dataset_ref_to_object_name(ref)])
142
143        res = await self._send(req)
144
145        if isinstance(res, mms.Error):
146            if res == mms.AccessError.OBJECT_NON_EXISTENT:
147                return common.ServiceError.INSTANCE_NOT_AVAILABLE
148
149            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
150                return common.ServiceError.ACCESS_VIOLATION
151
152            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
153
154        if not isinstance(res, mms.DeleteNamedVariableListResponse):
155            raise Exception('unsupported response type')
156
157        if res.matched == 0 and res.deleted == 0:
158            return common.ServiceError.INSTANCE_NOT_AVAILABLE
159
160        if res.matched != res.deleted:
161            return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
162
163    async def get_persisted_dataset_refs(self,
164                                         logical_device: str
165                                         ) -> Collection[common.PersistedDatasetRef] | common.ServiceError:  # NOQA
166        """Get persisted dataset references associated with logical device"""
167        identifiers = await self._get_name_list(
168            object_class=mms.ObjectClass.NAMED_VARIABLE_LIST,
169            object_scope=mms.DomainSpecificObjectScope(logical_device))
170
171        if isinstance(identifiers, common.ServiceError):
172            return identifiers
173
174        refs = collections.deque()
175        for identifier in identifiers:
176            logical_node, name = identifier.split('$')
177            refs.append(
178                common.PersistedDatasetRef(logical_device=logical_device,
179                                           logical_node=logical_node,
180                                           name=name))
181
182        return refs
183
184    async def get_dataset_data_refs(self,
185                                    ref: common.DatasetRef
186                                    ) -> Collection[common.DataRef] | common.ServiceError:  # NOQA
187        """Get data references associated with single dataset"""
188        req = mms.GetNamedVariableListAttributesRequest(
189            encoder.dataset_ref_to_object_name(ref))
190
191        res = await self._send(req)
192
193        if isinstance(res, mms.Error):
194            if res == mms.AccessError.OBJECT_NON_EXISTENT:
195                return common.ServiceError.INSTANCE_NOT_AVAILABLE
196
197            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
198                return common.ServiceError.ACCESS_VIOLATION
199
200            if res == mms.ServiceError.PDU_SIZE:
201                return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
202
203            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
204
205        if not isinstance(res, mms.GetNamedVariableListAttributesResponse):
206            raise Exception('unsupported response type')
207
208        refs = collections.deque()
209
210        for i in res.specification:
211            if not isinstance(i, mms.NameVariableSpecification):
212                raise Exception('unsupported specification type')
213
214            refs.append(encoder.data_ref_from_object_name(i.name))
215
216        return refs
217
218    async def get_rcb_attrs(self,
219                            ref: common.RcbRef,
220                            attr_types: Collection[common.RcbAttrType]
221                            ) -> dict[common.RcbAttrType,
222                                      common.RcbAttrValue | common.ServiceError]:  # NOQA
223        """Get RCB attribute value"""
224        specification = collections.deque()
225
226        for attr_type in attr_types:
227            if ref.type == common.RcbType.BUFFERED:
228                if attr_type == common.RcbAttrType.RESERVE:
229                    raise ValueError('unsupported attribute type')
230
231            elif ref.type == common.RcbType.UNBUFFERED:
232                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
233                                 common.RcbAttrType.ENTRY_ID,
234                                 common.RcbAttrType.TIME_OF_ENTRY,
235                                 common.RcbAttrType.RESERVATION_TIME):
236                    raise ValueError('unsupported attribute type')
237
238            else:
239                raise TypeError('unsupported rcb type')
240
241            specification.append(
242                mms.NameVariableSpecification(
243                    encoder.data_ref_to_object_name(
244                        common.DataRef(logical_device=ref.logical_device,
245                                       logical_node=ref.logical_node,
246                                       fc=ref.type.value,
247                                       names=(ref.name, attr_type.value)))))
248
249        req = mms.ReadRequest(specification)
250
251        res = await self._send(req)
252
253        if isinstance(res, mms.Error):
254            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
255                    for attr_type in attr_types}
256
257        if not isinstance(res, mms.ReadResponse):
258            raise Exception('unsupported response type')
259
260        if len(res.results) != len(attr_types):
261            raise Exception('invalid results length')
262
263        results = {}
264
265        for attr_type, mms_data in zip(attr_types, res.results):
266            if isinstance(mms_data, mms.DataAccessError):
267                if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
268                    results[attr_type] = common.ServiceError.ACCESS_VIOLATION
269
270                elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
271                    results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE  # NOQA
272
273                else:
274                    results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
275
276            else:
277                results[attr_type] = encoder.rcb_attr_value_from_mms_data(
278                    mms_data, attr_type)
279
280        return results
281
282    async def set_rcb_attrs(self,
283                            ref: common.RcbRef,
284                            attrs: Collection[tuple[common.RcbAttrType,
285                                                    common.RcbAttrValue]]
286                            ) -> dict[common.RcbAttrType,
287                                      common.ServiceError | None]:
288        """Set RCB attribute values"""
289        specification = collections.deque()
290        data = collections.deque()
291
292        for attr_type, attr_value in attrs:
293            if ref.type == common.RcbType.BUFFERED:
294                if attr_type == common.RcbAttrType.RESERVE:
295                    raise ValueError('unsupported attribute type')
296
297            elif ref.type == common.RcbType.UNBUFFERED:
298                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
299                                 common.RcbAttrType.ENTRY_ID,
300                                 common.RcbAttrType.TIME_OF_ENTRY,
301                                 common.RcbAttrType.RESERVATION_TIME):
302                    raise ValueError('unsupported attribute type')
303
304            else:
305                raise TypeError('unsupported rcb type')
306
307            specification.append(
308                mms.NameVariableSpecification(
309                    encoder.data_ref_to_object_name(
310                        common.DataRef(logical_device=ref.logical_device,
311                                       logical_node=ref.logical_node,
312                                       fc=ref.type.value,
313                                       names=(ref.name, attr_type.value)))))
314            data.append(
315                encoder.rcb_attr_value_to_mms_data(attr_value, attr_type))
316
317        req = mms.WriteRequest(specification=specification,
318                               data=data)
319
320        res = await self._send(req)
321
322        if isinstance(res, mms.Error):
323            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
324                    for attr_type, _ in attrs}
325
326        if not isinstance(res, mms.WriteResponse):
327            raise Exception('unsupported response type')
328
329        if len(res.results) != len(attrs):
330            raise Exception('invalid results length')
331
332        results = {}
333
334        for (attr_type, _), mms_data in zip(attrs, res.results):
335            if mms_data is None:
336                results[attr_type] = None
337
338            elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
339                results[attr_type] = common.ServiceError.ACCESS_VIOLATION
340
341            elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
342                results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE
343
344            elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
345                results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT  # NOQA
346
347            elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT:
348                results[attr_type] = common.ServiceError.TYPE_CONFLICT
349
350            elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID:
351                results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT  # NOQA
352
353            else:
354                results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
355
356        return results
357
358    async def write_data(self,
359                         ref: common.DataRef,
360                         value: common.Value
361                         ) -> common.ServiceError | None:
362        """Write data"""
363        value_type = self._data_value_types[ref]
364
365        req = mms.WriteRequest(
366            specification=[
367                mms.NameVariableSpecification(
368                    encoder.data_ref_to_object_name(ref))],
369            data=[encoder.value_to_mms_data(value,  value_type)])
370
371        res = await self._send(req)
372
373        if isinstance(res, mms.Error):
374            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
375
376        if not isinstance(res, mms.WriteResponse):
377            raise Exception('unsupported response type')
378
379        if len(res.results) != 1:
380            raise Exception('invalid results size')
381
382        if res.results[0] is not None:
383            if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
384                return common.ServiceError.ACCESS_VIOLATION
385
386            if res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
387                return common.ServiceError.INSTANCE_NOT_AVAILABLE
388
389            if res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
390                return common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT
391
392            if res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT:
393                return common.ServiceError.TYPE_CONFLICT
394
395            if res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID:
396                return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
397
398            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
399
400    async def select(self,
401                     ref: common.CommandRef,
402                     cmd: common.Command | None
403                     ) -> common.CommandError | None:
404        """Select command"""
405        if cmd is not None:
406            return await self._command_with_last_appl_error(ref=ref,
407                                                            cmd=cmd,
408                                                            attr='SBOw',
409                                                            with_checks=True)
410
411        req = mms.ReadRequest([
412            mms.NameVariableSpecification(
413                encoder.data_ref_to_object_name(
414                    common.DataRef(logical_device=ref.logical_device,
415                                   logical_node=ref.logical_node,
416                                   fc='CO',
417                                   names=(ref.name, 'SBO'))))])
418
419        res = await self._send(req)
420
421        if isinstance(res, mms.Error):
422            return _create_command_error(
423                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
424                last_appl_error=None)
425
426        if not isinstance(res, mms.ReadResponse):
427            raise Exception('unsupported response type')
428
429        if len(res.results) != 1:
430            raise Exception('invalid results size')
431
432        if not isinstance(res.results[0], mms.VisibleStringData):
433            if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
434                service_error = common.ServiceError.ACCESS_VIOLATION
435
436            elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
437                service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE
438
439            else:
440                service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
441
442            return _create_command_error(service_error=service_error,
443                                         last_appl_error=None)
444
445        if res.results[0].value == '':
446            return _create_command_error(
447                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
448                last_appl_error=None)
449
450    async def cancel(self,
451                     ref: common.CommandRef,
452                     cmd: common.Command
453                     ) -> common.CommandError | None:
454        """Cancel command"""
455        return await self._command_with_last_appl_error(ref=ref,
456                                                        cmd=cmd,
457                                                        attr='Cancel',
458                                                        with_checks=False)
459
460    async def operate(self,
461                      ref: common.CommandRef,
462                      cmd: common.Command
463                      ) -> common.CommandError | None:
464        """Operate command"""
465        return await self._command_with_last_appl_error(ref=ref,
466                                                        cmd=cmd,
467                                                        attr='Oper',
468                                                        with_checks=True)
469
470    async def _on_unconfirmed(self, conn, unconfirmed):
471        self._comm_log.debug('received %s', unconfirmed)
472
473        self._status_event.set()
474
475        if _is_unconfirmed_report(unconfirmed):
476            try:
477                await self._process_report(unconfirmed.data)
478
479            except Exception as e:
480                self._log.error("error processing report: %s", e, exc_info=e)
481
482        elif _is_unconfirmed_last_appl_error(unconfirmed):
483            try:
484                self._process_last_appl_error(unconfirmed.data[0])
485
486            except Exception as e:
487                self._log.error("error processing last application error: %s",
488                                e, exc_info=e)
489
490        elif _is_unconfirmed_termination(unconfirmed):
491            names = [i.name for i in unconfirmed.specification]
492            data = list(unconfirmed.data)
493
494            if len(names) != len(data):
495                self._log.warning("names/data size mismatch")
496                return
497
498            data_ref = encoder.data_ref_from_object_name(names[-1])
499
500            try:
501                await self._process_termination(
502                    ref=common.CommandRef(
503                        logical_device=data_ref.logical_device,
504                        logical_node=data_ref.logical_node,
505                        name=data_ref.names[0]),
506                    cmd_mms_data=data[-1],
507                    last_appl_error_mms_data=(data[0] if len(data) > 1
508                                              else None))
509
510            except Exception as e:
511                self._log.error("error processing termination: %s",
512                                e, exc_info=e)
513
514        else:
515            self._log.info("received unprocessed unconfirmed message")
516
517    async def _process_report(self, mms_data):
518        if not self._report_cb:
519            self._log.info("report callback not defined - skipping report")
520            return
521
522        report_id = encoder.value_from_mms_data(
523            mms_data[0], common.BasicValueType.VISIBLE_STRING)
524
525        data_defs = self._report_data_defs.get(report_id)
526        if data_defs is None:
527            self._log.info("report id not defined - skipping report")
528            return
529
530        report = encoder.report_from_mms_data(mms_data, data_defs)
531
532        await aio.call(self._report_cb, report)
533
534    def _process_last_appl_error(self, mms_data):
535        last_appl_error = encoder.last_appl_error_from_mms_data(mms_data)
536
537        key = last_appl_error.name, last_appl_error.control_number
538        if key in self._last_appl_errors:
539            self._last_appl_errors[key] = last_appl_error
540
541    async def _process_termination(self, ref, cmd_mms_data,
542                                   last_appl_error_mms_data):
543        if not self._termination_cb:
544            self._log.info("termination callback not defined - "
545                           "skipping termination")
546            return
547
548        value_type = self._cmd_value_types.get(ref)
549        if value_type is None:
550            self._log.info("command value type not defined - "
551                           "skipping termination")
552            return
553
554        cmd = encoder.command_from_mms_data(mms_data=cmd_mms_data,
555                                            value_type=value_type,
556                                            with_checks=True)
557
558        if last_appl_error_mms_data:
559            error = _create_command_error(
560                service_error=None,
561                last_appl_error=encoder.last_appl_error_from_mms_data(
562                    last_appl_error_mms_data))
563
564        else:
565            error = None
566
567        termination = common.Termination(ref=ref,
568                                         cmd=cmd,
569                                         error=error)
570
571        await aio.call(self._termination_cb, termination)
572
573    async def _send(self, req):
574        self._comm_log.debug('sending %s', req)
575
576        res = await self._conn.send_confirmed(req)
577        self._status_event.set()
578
579        self._comm_log.debug('received %s', res)
580
581        return res
582
583    async def _status_loop(self, delay, timeout):
584        try:
585            self._log.debug("starting status loop")
586            while True:
587                self._status_event.clear()
588
589                with contextlib.suppress(asyncio.TimeoutError):
590                    await aio.wait_for(self._status_event.wait(), delay)
591                    continue
592
593                self._log.debug("sending status request")
594                await aio.wait_for(self._send(mms.StatusRequest()), timeout)
595
596        except asyncio.TimeoutError:
597            self._log.warning("status timeout")
598
599        except ConnectionError:
600            pass
601
602        except Exception as e:
603            self._log.error("status loop error: %s", e, exc_info=e)
604
605        finally:
606            self._log.debug("stopping status loop")
607            self.close()
608
609    async def _get_name_list(self, object_class, object_scope):
610        identifiers = collections.deque()
611        continue_after = None
612
613        while True:
614            req = mms.GetNameListRequest(
615                object_class=object_class,
616                object_scope=object_scope,
617                continue_after=continue_after)
618
619            res = await self._send(req)
620
621            if isinstance(res, mms.Error):
622                if res == mms.AccessError.OBJECT_NON_EXISTENT:
623                    return common.ServiceError.INSTANCE_NOT_AVAILABLE
624
625                if res == mms.AccessError.OBJECT_ACCESS_DENIED:
626                    return common.ServiceError.ACCESS_VIOLATION
627
628                if res == mms.ServiceError.OBJECT_CONSTRAINT_CONFLICT:
629                    return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
630
631                return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
632
633            if not isinstance(res, mms.GetNameListResponse):
634                raise Exception('unsupported response type')
635
636            identifiers.extend(res.identifiers)
637
638            if not res.more_follows:
639                break
640
641            if not res.identifiers:
642                raise Exception('invalid more follows value')
643
644            continue_after = identifiers[-1]
645
646        return identifiers
647
648    async def _command_with_last_appl_error(self, ref, cmd, attr, with_checks):
649        value_type = self._cmd_value_types[ref]
650
651        req = mms.WriteRequest(
652            specification=[
653                mms.NameVariableSpecification(
654                    encoder.data_ref_to_object_name(
655                        common.DataRef(logical_device=ref.logical_device,
656                                       logical_node=ref.logical_node,
657                                       fc='CO',
658                                       names=(ref.name, attr))))],
659            data=[encoder.command_to_mms_data(cmd=cmd,
660                                              value_type=value_type,
661                                              with_checks=with_checks)])
662
663        name = (f'{req.specification[0].name.domain_id}/'
664                f'{req.specification[0].name.item_id}')
665        key = name, cmd.control_number
666
667        if key in self._last_appl_errors:
668            raise Exception('active control number duplicate')
669
670        self._last_appl_errors[key] = None
671
672        try:
673            res = await self._send(req)
674
675        finally:
676            last_appl_error = self._last_appl_errors.pop(key, None)
677
678        if isinstance(res, mms.Error):
679            return _create_command_error(
680                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
681                last_appl_error=last_appl_error)
682
683        if not isinstance(res, mms.WriteResponse):
684            raise Exception('unsupported response type')
685
686        if len(res.results) != 1:
687            raise Exception('invalid results size')
688
689        if res.results[0] is not None:
690            return _create_command_error(service_error=None,
691                                         last_appl_error=last_appl_error)

Client

async_group
91    @property
92    def async_group(self):
93        """Async group"""
94        return self._conn.async_group

Async group

96    @property
97    def info(self) -> acse.ConnectionInfo:
98        """Connection info"""
99        return self._conn.info

Connection info

async def create_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef, data: Iterable[DataRef]) -> ServiceError | None:
101    async def create_dataset(self,
102                             ref: common.DatasetRef,
103                             data: Iterable[common.DataRef]
104                             ) -> common.ServiceError | None:
105        """Create dataset"""
106        req = mms.DefineNamedVariableListRequest(
107            name=encoder.dataset_ref_to_object_name(ref),
108            specification=[
109                mms.NameVariableSpecification(
110                    encoder.data_ref_to_object_name(i))
111                for i in data])
112
113        res = await self._send(req)
114
115        if isinstance(res, mms.Error):
116            if res == mms.AccessError.OBJECT_NON_EXISTENT:
117                return common.ServiceError.INSTANCE_NOT_AVAILABLE
118
119            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
120                return common.ServiceError.ACCESS_VIOLATION
121
122            if res == mms.DefinitionError.OBJECT_EXISTS:
123                return common.ServiceError.INSTANCE_IN_USE
124
125            if res == mms.DefinitionError.OBJECT_UNDEFINED:
126                return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
127
128            if res == mms.ResourceError.CAPABILITY_UNAVAILABLE:
129                return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
130
131            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
132
133        if not isinstance(res, mms.DefineNamedVariableListResponse):
134            raise Exception('unsupported response type')

Create dataset

async def delete_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> ServiceError | None:
136    async def delete_dataset(self,
137                             ref: common.DatasetRef
138                             ) -> common.ServiceError | None:
139        """Delete dataset"""
140        req = mms.DeleteNamedVariableListRequest([
141            encoder.dataset_ref_to_object_name(ref)])
142
143        res = await self._send(req)
144
145        if isinstance(res, mms.Error):
146            if res == mms.AccessError.OBJECT_NON_EXISTENT:
147                return common.ServiceError.INSTANCE_NOT_AVAILABLE
148
149            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
150                return common.ServiceError.ACCESS_VIOLATION
151
152            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
153
154        if not isinstance(res, mms.DeleteNamedVariableListResponse):
155            raise Exception('unsupported response type')
156
157        if res.matched == 0 and res.deleted == 0:
158            return common.ServiceError.INSTANCE_NOT_AVAILABLE
159
160        if res.matched != res.deleted:
161            return common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT

Delete dataset

async def get_persisted_dataset_refs( self, logical_device: str) -> Collection[PersistedDatasetRef] | ServiceError:
163    async def get_persisted_dataset_refs(self,
164                                         logical_device: str
165                                         ) -> Collection[common.PersistedDatasetRef] | common.ServiceError:  # NOQA
166        """Get persisted dataset references associated with logical device"""
167        identifiers = await self._get_name_list(
168            object_class=mms.ObjectClass.NAMED_VARIABLE_LIST,
169            object_scope=mms.DomainSpecificObjectScope(logical_device))
170
171        if isinstance(identifiers, common.ServiceError):
172            return identifiers
173
174        refs = collections.deque()
175        for identifier in identifiers:
176            logical_node, name = identifier.split('$')
177            refs.append(
178                common.PersistedDatasetRef(logical_device=logical_device,
179                                           logical_node=logical_node,
180                                           name=name))
181
182        return refs

Get persisted dataset references associated with logical device

async def get_dataset_data_refs( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> Collection[DataRef] | ServiceError:
184    async def get_dataset_data_refs(self,
185                                    ref: common.DatasetRef
186                                    ) -> Collection[common.DataRef] | common.ServiceError:  # NOQA
187        """Get data references associated with single dataset"""
188        req = mms.GetNamedVariableListAttributesRequest(
189            encoder.dataset_ref_to_object_name(ref))
190
191        res = await self._send(req)
192
193        if isinstance(res, mms.Error):
194            if res == mms.AccessError.OBJECT_NON_EXISTENT:
195                return common.ServiceError.INSTANCE_NOT_AVAILABLE
196
197            if res == mms.AccessError.OBJECT_ACCESS_DENIED:
198                return common.ServiceError.ACCESS_VIOLATION
199
200            if res == mms.ServiceError.PDU_SIZE:
201                return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
202
203            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
204
205        if not isinstance(res, mms.GetNamedVariableListAttributesResponse):
206            raise Exception('unsupported response type')
207
208        refs = collections.deque()
209
210        for i in res.specification:
211            if not isinstance(i, mms.NameVariableSpecification):
212                raise Exception('unsupported specification type')
213
214            refs.append(encoder.data_ref_from_object_name(i.name))
215
216        return refs

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]:
218    async def get_rcb_attrs(self,
219                            ref: common.RcbRef,
220                            attr_types: Collection[common.RcbAttrType]
221                            ) -> dict[common.RcbAttrType,
222                                      common.RcbAttrValue | common.ServiceError]:  # NOQA
223        """Get RCB attribute value"""
224        specification = collections.deque()
225
226        for attr_type in attr_types:
227            if ref.type == common.RcbType.BUFFERED:
228                if attr_type == common.RcbAttrType.RESERVE:
229                    raise ValueError('unsupported attribute type')
230
231            elif ref.type == common.RcbType.UNBUFFERED:
232                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
233                                 common.RcbAttrType.ENTRY_ID,
234                                 common.RcbAttrType.TIME_OF_ENTRY,
235                                 common.RcbAttrType.RESERVATION_TIME):
236                    raise ValueError('unsupported attribute type')
237
238            else:
239                raise TypeError('unsupported rcb type')
240
241            specification.append(
242                mms.NameVariableSpecification(
243                    encoder.data_ref_to_object_name(
244                        common.DataRef(logical_device=ref.logical_device,
245                                       logical_node=ref.logical_node,
246                                       fc=ref.type.value,
247                                       names=(ref.name, attr_type.value)))))
248
249        req = mms.ReadRequest(specification)
250
251        res = await self._send(req)
252
253        if isinstance(res, mms.Error):
254            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
255                    for attr_type in attr_types}
256
257        if not isinstance(res, mms.ReadResponse):
258            raise Exception('unsupported response type')
259
260        if len(res.results) != len(attr_types):
261            raise Exception('invalid results length')
262
263        results = {}
264
265        for attr_type, mms_data in zip(attr_types, res.results):
266            if isinstance(mms_data, mms.DataAccessError):
267                if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
268                    results[attr_type] = common.ServiceError.ACCESS_VIOLATION
269
270                elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
271                    results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE  # NOQA
272
273                else:
274                    results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
275
276            else:
277                results[attr_type] = encoder.rcb_attr_value_from_mms_data(
278                    mms_data, attr_type)
279
280        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]:
282    async def set_rcb_attrs(self,
283                            ref: common.RcbRef,
284                            attrs: Collection[tuple[common.RcbAttrType,
285                                                    common.RcbAttrValue]]
286                            ) -> dict[common.RcbAttrType,
287                                      common.ServiceError | None]:
288        """Set RCB attribute values"""
289        specification = collections.deque()
290        data = collections.deque()
291
292        for attr_type, attr_value in attrs:
293            if ref.type == common.RcbType.BUFFERED:
294                if attr_type == common.RcbAttrType.RESERVE:
295                    raise ValueError('unsupported attribute type')
296
297            elif ref.type == common.RcbType.UNBUFFERED:
298                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
299                                 common.RcbAttrType.ENTRY_ID,
300                                 common.RcbAttrType.TIME_OF_ENTRY,
301                                 common.RcbAttrType.RESERVATION_TIME):
302                    raise ValueError('unsupported attribute type')
303
304            else:
305                raise TypeError('unsupported rcb type')
306
307            specification.append(
308                mms.NameVariableSpecification(
309                    encoder.data_ref_to_object_name(
310                        common.DataRef(logical_device=ref.logical_device,
311                                       logical_node=ref.logical_node,
312                                       fc=ref.type.value,
313                                       names=(ref.name, attr_type.value)))))
314            data.append(
315                encoder.rcb_attr_value_to_mms_data(attr_value, attr_type))
316
317        req = mms.WriteRequest(specification=specification,
318                               data=data)
319
320        res = await self._send(req)
321
322        if isinstance(res, mms.Error):
323            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
324                    for attr_type, _ in attrs}
325
326        if not isinstance(res, mms.WriteResponse):
327            raise Exception('unsupported response type')
328
329        if len(res.results) != len(attrs):
330            raise Exception('invalid results length')
331
332        results = {}
333
334        for (attr_type, _), mms_data in zip(attrs, res.results):
335            if mms_data is None:
336                results[attr_type] = None
337
338            elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
339                results[attr_type] = common.ServiceError.ACCESS_VIOLATION
340
341            elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
342                results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE
343
344            elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
345                results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT  # NOQA
346
347            elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT:
348                results[attr_type] = common.ServiceError.TYPE_CONFLICT
349
350            elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID:
351                results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT  # NOQA
352
353            else:
354                results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
355
356        return results

Set RCB attribute values

async def write_data( self, ref: DataRef, value: Union[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:
358    async def write_data(self,
359                         ref: common.DataRef,
360                         value: common.Value
361                         ) -> common.ServiceError | None:
362        """Write data"""
363        value_type = self._data_value_types[ref]
364
365        req = mms.WriteRequest(
366            specification=[
367                mms.NameVariableSpecification(
368                    encoder.data_ref_to_object_name(ref))],
369            data=[encoder.value_to_mms_data(value,  value_type)])
370
371        res = await self._send(req)
372
373        if isinstance(res, mms.Error):
374            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT
375
376        if not isinstance(res, mms.WriteResponse):
377            raise Exception('unsupported response type')
378
379        if len(res.results) != 1:
380            raise Exception('invalid results size')
381
382        if res.results[0] is not None:
383            if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
384                return common.ServiceError.ACCESS_VIOLATION
385
386            if res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
387                return common.ServiceError.INSTANCE_NOT_AVAILABLE
388
389            if res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
390                return common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT
391
392            if res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT:
393                return common.ServiceError.TYPE_CONFLICT
394
395            if res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID:
396                return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
397
398            return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT

Write data

async def select( self, ref: CommandRef, cmd: Command | None) -> CommandError | None:
400    async def select(self,
401                     ref: common.CommandRef,
402                     cmd: common.Command | None
403                     ) -> common.CommandError | None:
404        """Select command"""
405        if cmd is not None:
406            return await self._command_with_last_appl_error(ref=ref,
407                                                            cmd=cmd,
408                                                            attr='SBOw',
409                                                            with_checks=True)
410
411        req = mms.ReadRequest([
412            mms.NameVariableSpecification(
413                encoder.data_ref_to_object_name(
414                    common.DataRef(logical_device=ref.logical_device,
415                                   logical_node=ref.logical_node,
416                                   fc='CO',
417                                   names=(ref.name, 'SBO'))))])
418
419        res = await self._send(req)
420
421        if isinstance(res, mms.Error):
422            return _create_command_error(
423                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
424                last_appl_error=None)
425
426        if not isinstance(res, mms.ReadResponse):
427            raise Exception('unsupported response type')
428
429        if len(res.results) != 1:
430            raise Exception('invalid results size')
431
432        if not isinstance(res.results[0], mms.VisibleStringData):
433            if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
434                service_error = common.ServiceError.ACCESS_VIOLATION
435
436            elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
437                service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE
438
439            else:
440                service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
441
442            return _create_command_error(service_error=service_error,
443                                         last_appl_error=None)
444
445        if res.results[0].value == '':
446            return _create_command_error(
447                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
448                last_appl_error=None)

Select command

async def cancel( self, ref: CommandRef, cmd: Command) -> CommandError | None:
450    async def cancel(self,
451                     ref: common.CommandRef,
452                     cmd: common.Command
453                     ) -> common.CommandError | None:
454        """Cancel command"""
455        return await self._command_with_last_appl_error(ref=ref,
456                                                        cmd=cmd,
457                                                        attr='Cancel',
458                                                        with_checks=False)

Cancel command

async def operate( self, ref: CommandRef, cmd: Command) -> CommandError | None:
460    async def operate(self,
461                      ref: common.CommandRef,
462                      cmd: common.Command
463                      ) -> common.CommandError | None:
464        """Operate command"""
465        return await self._command_with_last_appl_error(ref=ref,
466                                                        cmd=cmd,
467                                                        attr='Oper',
468                                                        with_checks=True)

Operate command

EntryTime = <class 'datetime.datetime'>
ReportId = <class 'str'>
class PersistedDatasetRef(typing.NamedTuple):
17class PersistedDatasetRef(typing.NamedTuple):
18    logical_device: str
19    logical_node: str
20    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):
23class NonPersistedDatasetRef(typing.NamedTuple):
24    name: str

NonPersistedDatasetRef(name,)

NonPersistedDatasetRef(name: str)

Create new instance of NonPersistedDatasetRef(name,)

name: str

Alias for field number 0

class DataRef(typing.NamedTuple):
30class DataRef(typing.NamedTuple):
31    logical_device: str
32    logical_node: str
33    fc: str
34    names: tuple[str | int, ...]

DataRef(logical_device, logical_node, fc, names)

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):
37class CommandRef(typing.NamedTuple):
38    logical_device: str
39    logical_node: str
40    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):
43class RcbType(enum.Enum):
44    BUFFERED = 'BR'
45    UNBUFFERED = 'RP'
BUFFERED = <RcbType.BUFFERED: 'BR'>
UNBUFFERED = <RcbType.UNBUFFERED: 'RP'>
class RcbRef(typing.NamedTuple):
48class RcbRef(typing.NamedTuple):
49    logical_device: str
50    logical_node: str
51    type: RcbType
52    name: str

RcbRef(logical_device, logical_node, type, name)

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):
57class BasicValueType(enum.Enum):
58    BOOLEAN = 'BOOLEAN'  # bool
59    INTEGER = 'INTEGER'  # int
60    UNSIGNED = 'UNSIGNED'  # int
61    FLOAT = 'FLOAT'  # float
62    BIT_STRING = 'BIT_STRING'  # Collection[bool]
63    OCTET_STRING = 'OCTET_STRING'  # util.Bytes
64    VISIBLE_STRING = 'VISIBLE_STRING'  # str
65    MMS_STRING = 'MMS_STRING'  # str
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):
68class AcsiValueType(enum.Enum):
69    QUALITY = 'QUALITY'
70    TIMESTAMP = 'TIMESTAMP'
71    DOUBLE_POINT = 'DOUBLE_POINT'
72    DIRECTION = 'DIRECTION'
73    SEVERITY = 'SEVERITY'
74    ANALOGUE = 'ANALOGUE'
75    VECTOR = 'VECTOR'
76    STEP_POSITION = 'STEP_POSITION'
77    BINARY_CONTROL = 'BINARY_CONTROL'
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):
80class ArrayValueType(typing.NamedTuple):
81    type: 'ValueType'
82    length: int

ArrayValueType(type, length)

ArrayValueType(type: ForwardRef('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):
85class StructValueType(typing.NamedTuple):
86    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):
 97class Timestamp(typing.NamedTuple):
 98    value: datetime.datetime
 99    leap_second: bool
100    clock_failure: bool
101    not_synchronized: bool
102    accuracy: int | None
103    """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):
106class QualityValidity(enum.Enum):
107    GOOD = 0
108    INVALID = 1
109    RESERVED = 2
110    QUESTIONABLE = 3
GOOD = <QualityValidity.GOOD: 0>
INVALID = <QualityValidity.INVALID: 1>
RESERVED = <QualityValidity.RESERVED: 2>
QUESTIONABLE = <QualityValidity.QUESTIONABLE: 3>
class QualityDetail(enum.Enum):
113class QualityDetail(enum.Enum):
114    OVERFLOW = 2
115    OUT_OF_RANGE = 3
116    BAD_REFERENCE = 4
117    OSCILLATORY = 5
118    FAILURE = 6
119    OLD_DATA = 7
120    INCONSISTENT = 8
121    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):
124class QualitySource(enum.Enum):
125    PROCESS = 0
126    SUBSTITUTED = 1
PROCESS = <QualitySource.PROCESS: 0>
SUBSTITUTED = <QualitySource.SUBSTITUTED: 1>
class Quality(typing.NamedTuple):
129class Quality(typing.NamedTuple):
130    validity: QualityValidity
131    details: set[QualityDetail]
132    source: QualitySource
133    test: bool
134    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):
137class DoublePoint(enum.Enum):
138    INTERMEDIATE = 0
139    OFF = 1
140    ON = 2
141    BAD = 3
INTERMEDIATE = <DoublePoint.INTERMEDIATE: 0>
OFF = <DoublePoint.OFF: 1>
ON = <DoublePoint.ON: 2>
BAD = <DoublePoint.BAD: 3>
class Direction(enum.Enum):
144class Direction(enum.Enum):
145    UNKNOWN = 0
146    FORWARD = 1
147    BACKWARD = 2
148    BOTH = 3
UNKNOWN = <Direction.UNKNOWN: 0>
FORWARD = <Direction.FORWARD: 1>
BACKWARD = <Direction.BACKWARD: 2>
BOTH = <Direction.BOTH: 3>
class Severity(enum.Enum):
151class Severity(enum.Enum):
152    UNKNOWN = 0
153    CRITICAL = 1
154    MAJOR = 2
155    MINOR = 3
156    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):
159class Analogue(typing.NamedTuple):
160    i: int | None = None
161    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):
164class Vector(typing.NamedTuple):
165    magnitude: Analogue
166    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):
169class StepPosition(typing.NamedTuple):
170    value: int
171    """value in range [-64, 63]"""
172    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):
175class BinaryControl(enum.Enum):
176    STOP = 0
177    LOWER = 1
178    HIGHER = 2
179    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 = typing.Union[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):
197class ServiceError(enum.Enum):
198    NO_ERROR = 0
199    INSTANCE_NOT_AVAILABLE = 1
200    INSTANCE_IN_USE = 2
201    ACCESS_VIOLATION = 3
202    ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = 4
203    PARAMETER_VALUE_INAPPROPRIATE = 5
204    PARAMETER_VALUE_INCONSISTENT = 6
205    CLASS_NOT_SUPPORTED = 7
206    INSTANCE_LOCKED_BY_OTHER_CLIENT = 8
207    CONTROL_MUST_BE_SELECTED = 9
208    TYPE_CONFLICT = 10
209    FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = 11
210    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):
213class AdditionalCause(enum.Enum):
214    UNKNOWN = 0
215    NOT_SUPPORTED = 1
216    BLOCKED_BY_SWITCHING_HIERARCHY = 2
217    SELECT_FAILED = 3
218    INVALID_POSITION = 4
219    POSITION_REACHED = 5
220    PARAMETER_CHANGE_IN_EXECUTION = 6
221    STEP_LIMIT = 7
222    BLOCKED_BY_MODE = 8
223    BLOCKED_BY_PROCESS = 9
224    BLOCKED_BY_INTERLOCKING = 10
225    BLOCKED_BY_SYNCHROCHECK = 11
226    COMMAND_ALREADY_IN_EXECUTION = 12
227    BLOCKED_BY_HEALTH = 13
228    ONE_OF_N_CONTROL = 14
229    ABORTION_BY_CANCEL = 15
230    TIME_LIMIT_OVER = 16
231    ABORTION_BY_TRIP = 17
232    OBJECT_NOT_SELECTED = 18
233    OBJECT_ALREADY_SELECTED = 19
234    NO_ACCESS_AUTHORITY = 20
235    ENDED_WITH_OVERSHOOT = 21
236    ABORTION_DUE_TO_DEVIATION = 22
237    ABORTION_BY_COMMUNICATION_LOSS = 23
238    BLOCKED_BY_COMMAND = 24
239    NONE = 25
240    INCONSISTENT_PARAMETERS = 26
241    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):
244class TestError(enum.Enum):
245    NO_ERROR = 0
246    UNKNOWN = 1
247    TIMEOUT_TEST_NOT_OK = 2
248    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):
251class CommandError(typing.NamedTuple):
252    service_error: ServiceError | None
253    additional_cause: AdditionalCause | None
254    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):
259class OptionalField(enum.Enum):
260    SEQUENCE_NUMBER = 1
261    REPORT_TIME_STAMP = 2
262    REASON_FOR_INCLUSION = 3
263    DATA_SET_NAME = 4
264    DATA_REFERENCE = 5
265    BUFFER_OVERFLOW = 6
266    ENTRY_ID = 7
267    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):
270class TriggerCondition(enum.Enum):
271    DATA_CHANGE = 1
272    QUALITY_CHANGE = 2
273    DATA_UPDATE = 3
274    INTEGRITY = 4
275    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):
278class RcbAttrType(enum.Enum):
279    REPORT_ID = 'RptID'
280    REPORT_ENABLE = 'RptEna'
281    DATASET = 'DatSet'
282    CONF_REVISION = 'ConfRev'
283    OPTIONAL_FIELDS = 'OptFlds'
284    BUFFER_TIME = 'BufTm'
285    SEQUENCE_NUMBER = 'SqNum'
286    TRIGGER_OPTIONS = 'TrgOps'
287    INTEGRITY_PERIOD = 'IntgPd'
288    GI = 'GI'
289    PURGE_BUFFER = 'PurgeBuf'  # brcb
290    ENTRY_ID = 'EntryID'  # brcb
291    TIME_OF_ENTRY = 'TimeOfEntry'  # brcb
292    RESERVATION_TIME = 'ResvTms'  # brcb
293    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):
345class Reason(enum.Enum):
346    DATA_CHANGE = 1
347    QUALITY_CHANGE = 2
348    DATA_UPDATE = 3
349    INTEGRITY = 4
350    GENERAL_INTERROGATION = 5
351    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):
354class ReportData(typing.NamedTuple):
355    ref: DataRef
356    value: Value
357    reasons: set[Reason] | None

ReportData(ref, value, reasons)

ReportData( ref: DataRef, value: Union[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: Union[bool, int, float, str, bytes, bytearray, memoryview, Collection[bool], Quality, Timestamp, DoublePoint, Direction, Severity, Analogue, Vector, StepPosition, BinaryControl, Collection[Union[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, Union[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):
360class Report(typing.NamedTuple):
361    report_id: ReportId
362    sequence_number: int | None
363    subsequence_number: int | None
364    more_segments_follow: bool | None
365    dataset: DatasetRef | None
366    buffer_overflow: bool | None
367    conf_revision: int | None
368    entry_time: EntryTime | None
369    entry_id: util.Bytes | None
370    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):
375class ControlModel(enum.Enum):
376    DIRECT_WITH_NORMAL_SECURITY = 1
377    SBO_WITH_NORMAL_SECURITY = 2
378    DIRECT_WITH_ENHANCED_SECURITY = 3
379    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):
382class OriginCategory(enum.Enum):
383    NOT_SUPPORTED = 0
384    BAY_CONTROL = 1
385    STATION_CONTROL = 2
386    REMOTE_CONTROL = 3
387    AUTOMATIC_BAY = 4
388    AUTOMATIC_STATION = 5
389    AUTOMATIC_REMOTE = 6
390    MAINTENANCE = 7
391    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):
394class Origin(typing.NamedTuple):
395    category: OriginCategory
396    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):
399class Check(enum.Enum):
400    SYNCHRO = 0
401    INTERLOCK = 1
SYNCHRO = <Check.SYNCHRO: 0>
INTERLOCK = <Check.INTERLOCK: 1>
class Command(typing.NamedTuple):
404class Command(typing.NamedTuple):
405    value: Value
406    operate_time: Timestamp | None
407    origin: Origin
408    control_number: int
409    """control number in range [0, 255]"""
410    t: Timestamp
411    test: bool
412    checks: set[Check]
413    """ignored in cancel action"""

Command(value, operate_time, origin, control_number, t, test, checks)

Command( value: Union[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: Union[bool, int, float, str, bytes, bytearray, memoryview, Collection[bool], Quality, Timestamp, DoublePoint, Direction, Severity, Analogue, Vector, StepPosition, BinaryControl, Collection[Union[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, Union[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):
416class Termination(typing.NamedTuple):
417    ref: CommandRef
418    cmd: Command
419    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