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    if client.is_open and status_delay is not None:
79        client.async_group.spawn(client._status_loop, status_delay,
80                                 status_timeout)
81
82    return client

Connect to IEC61850 server

data_value_types include value types used in report processing and writing data.

cmd_value_types include value types used in command actions and termination processing.

Only reports that are specified by report_data_refs are notified with report_cb.

If status_delay is None, periodical sending of status requests is disabled.

Additional arguments are passed directly to hat.drivers.mms.connect (request_cb and unconfirmed_cb are set by this coroutine).

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

Client

async_group
88    @property
89    def async_group(self):
90        """Async group"""
91        return self._conn.async_group

Async group

93    @property
94    def info(self) -> acse.ConnectionInfo:
95        """Connection info"""
96        return self._conn.info

Connection info

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

Create dataset

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

Delete dataset

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

Get persisted dataset references associated with logical device

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

Get data references associated with single dataset

async def get_rcb_attrs( self, ref: RcbRef, attr_types: Collection[RcbAttrType]) -> dict[RcbAttrType, str | bool | PersistedDatasetRef | NonPersistedDatasetRef | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime | ServiceError]:
215    async def get_rcb_attrs(self,
216                            ref: common.RcbRef,
217                            attr_types: Collection[common.RcbAttrType]
218                            ) -> dict[common.RcbAttrType,
219                                      common.RcbAttrValue | common.ServiceError]:  # NOQA
220        """Get RCB attribute value"""
221        specification = collections.deque()
222
223        for attr_type in attr_types:
224            if ref.type == common.RcbType.BUFFERED:
225                if attr_type == common.RcbAttrType.RESERVE:
226                    raise ValueError('unsupported attribute type')
227
228            elif ref.type == common.RcbType.UNBUFFERED:
229                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
230                                 common.RcbAttrType.ENTRY_ID,
231                                 common.RcbAttrType.TIME_OF_ENTRY,
232                                 common.RcbAttrType.RESERVATION_TIME):
233                    raise ValueError('unsupported attribute type')
234
235            else:
236                raise TypeError('unsupported rcb type')
237
238            specification.append(
239                mms.NameVariableSpecification(
240                    encoder.data_ref_to_object_name(
241                        common.DataRef(logical_device=ref.logical_device,
242                                       logical_node=ref.logical_node,
243                                       fc=ref.type.value,
244                                       names=(ref.name, attr_type.value)))))
245
246        req = mms.ReadRequest(specification)
247
248        res = await self._send(req)
249
250        if isinstance(res, mms.Error):
251            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
252                    for attr_type in attr_types}
253
254        if not isinstance(res, mms.ReadResponse):
255            raise Exception('unsupported response type')
256
257        if len(res.results) != len(attr_types):
258            raise Exception('invalid results length')
259
260        results = {}
261
262        for attr_type, mms_data in zip(attr_types, res.results):
263            if isinstance(mms_data, mms.DataAccessError):
264                if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
265                    results[attr_type] = common.ServiceError.ACCESS_VIOLATION
266
267                elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
268                    results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE  # NOQA
269
270                else:
271                    results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
272
273            else:
274                results[attr_type] = encoder.rcb_attr_value_from_mms_data(
275                    mms_data, attr_type)
276
277        return results

Get RCB attribute value

async def set_rcb_attrs( self, ref: RcbRef, attrs: Collection[tuple[RcbAttrType, str | bool | PersistedDatasetRef | NonPersistedDatasetRef | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime]]) -> dict[RcbAttrType, ServiceError | None]:
279    async def set_rcb_attrs(self,
280                            ref: common.RcbRef,
281                            attrs: Collection[tuple[common.RcbAttrType,
282                                                    common.RcbAttrValue]]
283                            ) -> dict[common.RcbAttrType,
284                                      common.ServiceError | None]:
285        """Set RCB attribute values"""
286        specification = collections.deque()
287        data = collections.deque()
288
289        for attr_type, attr_value in attrs:
290            if ref.type == common.RcbType.BUFFERED:
291                if attr_type == common.RcbAttrType.RESERVE:
292                    raise ValueError('unsupported attribute type')
293
294            elif ref.type == common.RcbType.UNBUFFERED:
295                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
296                                 common.RcbAttrType.ENTRY_ID,
297                                 common.RcbAttrType.TIME_OF_ENTRY,
298                                 common.RcbAttrType.RESERVATION_TIME):
299                    raise ValueError('unsupported attribute type')
300
301            else:
302                raise TypeError('unsupported rcb type')
303
304            specification.append(
305                mms.NameVariableSpecification(
306                    encoder.data_ref_to_object_name(
307                        common.DataRef(logical_device=ref.logical_device,
308                                       logical_node=ref.logical_node,
309                                       fc=ref.type.value,
310                                       names=(ref.name, attr_type.value)))))
311            data.append(
312                encoder.rcb_attr_value_to_mms_data(attr_value, attr_type))
313
314        req = mms.WriteRequest(specification=specification,
315                               data=data)
316
317        res = await self._send(req)
318
319        if isinstance(res, mms.Error):
320            return {attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
321                    for attr_type, _ in attrs}
322
323        if not isinstance(res, mms.WriteResponse):
324            raise Exception('unsupported response type')
325
326        if len(res.results) != len(attrs):
327            raise Exception('invalid results length')
328
329        results = {}
330
331        for (attr_type, _), mms_data in zip(attrs, res.results):
332            if mms_data is None:
333                results[attr_type] = None
334
335            elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
336                results[attr_type] = common.ServiceError.ACCESS_VIOLATION
337
338            elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
339                results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE
340
341            elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
342                results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT  # NOQA
343
344            elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT:
345                results[attr_type] = common.ServiceError.TYPE_CONFLICT
346
347            elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID:
348                results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT  # NOQA
349
350            else:
351                results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
352
353        return results

Set RCB attribute values

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

Write data

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

Select command

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

Cancel command

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

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'

ArrayValueType(type,)

ArrayValueType(type: ForwardRef('ValueType'))

Create new instance of ArrayValueType(type,)

Alias for field number 0

class StructValueType(typing.NamedTuple):
84class StructValueType(typing.NamedTuple):
85    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):
 96class Timestamp(typing.NamedTuple):
 97    value: datetime.datetime
 98    leap_second: bool
 99    clock_failure: bool
100    not_synchronized: bool
101    accuracy: int | None
102    """accurate fraction bits [0,24]"""

Timestamp(value, leap_second, clock_failure, not_synchronized, accuracy)

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):
105class QualityValidity(enum.Enum):
106    GOOD = 0
107    INVALID = 1
108    RESERVED = 2
109    QUESTIONABLE = 3
GOOD = <QualityValidity.GOOD: 0>
INVALID = <QualityValidity.INVALID: 1>
RESERVED = <QualityValidity.RESERVED: 2>
QUESTIONABLE = <QualityValidity.QUESTIONABLE: 3>
class QualityDetail(enum.Enum):
112class QualityDetail(enum.Enum):
113    OVERFLOW = 2
114    OUT_OF_RANGE = 3
115    BAD_REFERENCE = 4
116    OSCILLATORY = 5
117    FAILURE = 6
118    OLD_DATA = 7
119    INCONSISTENT = 8
120    INACCURATE = 9
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):
123class QualitySource(enum.Enum):
124    PROCESS = 0
125    SUBSTITUTED = 1
PROCESS = <QualitySource.PROCESS: 0>
SUBSTITUTED = <QualitySource.SUBSTITUTED: 1>
class Quality(typing.NamedTuple):
128class Quality(typing.NamedTuple):
129    validity: QualityValidity
130    details: set[QualityDetail]
131    source: QualitySource
132    test: bool
133    operator_blocked: bool

Quality(validity, details, source, test, operator_blocked)

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):
136class DoublePoint(enum.Enum):
137    INTERMEDIATE = 0
138    OFF = 1
139    ON = 2
140    BAD = 3
INTERMEDIATE = <DoublePoint.INTERMEDIATE: 0>
OFF = <DoublePoint.OFF: 1>
ON = <DoublePoint.ON: 2>
BAD = <DoublePoint.BAD: 3>
class Direction(enum.Enum):
143class Direction(enum.Enum):
144    UNKNOWN = 0
145    FORWARD = 1
146    BACKWARD = 2
147    BOTH = 3
UNKNOWN = <Direction.UNKNOWN: 0>
FORWARD = <Direction.FORWARD: 1>
BACKWARD = <Direction.BACKWARD: 2>
BOTH = <Direction.BOTH: 3>
class Severity(enum.Enum):
150class Severity(enum.Enum):
151    UNKNOWN = 0
152    CRITICAL = 1
153    MAJOR = 2
154    MINOR = 3
155    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):
158class Analogue(typing.NamedTuple):
159    i: int | None = None
160    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):
163class Vector(typing.NamedTuple):
164    magnitude: Analogue
165    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):
168class StepPosition(typing.NamedTuple):
169    value: int
170    """value in range [-64, 63]"""
171    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):
174class BinaryControl(enum.Enum):
175    STOP = 0
176    LOWER = 1
177    HIGHER = 2
178    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):
196class ServiceError(enum.Enum):
197    NO_ERROR = 0
198    INSTANCE_NOT_AVAILABLE = 1
199    INSTANCE_IN_USE = 2
200    ACCESS_VIOLATION = 3
201    ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = 4
202    PARAMETER_VALUE_INAPPROPRIATE = 5
203    PARAMETER_VALUE_INCONSISTENT = 6
204    CLASS_NOT_SUPPORTED = 7
205    INSTANCE_LOCKED_BY_OTHER_CLIENT = 8
206    CONTROL_MUST_BE_SELECTED = 9
207    TYPE_CONFLICT = 10
208    FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = 11
209    FAILED_DUE_TO_SERVER_CONSTRAINT = 12
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):
212class AdditionalCause(enum.Enum):
213    UNKNOWN = 0
214    NOT_SUPPORTED = 1
215    BLOCKED_BY_SWITCHING_HIERARCHY = 2
216    SELECT_FAILED = 3
217    INVALID_POSITION = 4
218    POSITION_REACHED = 5
219    PARAMETER_CHANGE_IN_EXECUTION = 6
220    STEP_LIMIT = 7
221    BLOCKED_BY_MODE = 8
222    BLOCKED_BY_PROCESS = 9
223    BLOCKED_BY_INTERLOCKING = 10
224    BLOCKED_BY_SYNCHROCHECK = 11
225    COMMAND_ALREADY_IN_EXECUTION = 12
226    BLOCKED_BY_HEALTH = 13
227    ONE_OF_N_CONTROL = 14
228    ABORTION_BY_CANCEL = 15
229    TIME_LIMIT_OVER = 16
230    ABORTION_BY_TRIP = 17
231    OBJECT_NOT_SELECTED = 18
232    OBJECT_ALREADY_SELECTED = 19
233    NO_ACCESS_AUTHORITY = 20
234    ENDED_WITH_OVERSHOOT = 21
235    ABORTION_DUE_TO_DEVIATION = 22
236    ABORTION_BY_COMMUNICATION_LOSS = 23
237    BLOCKED_BY_COMMAND = 24
238    NONE = 25
239    INCONSISTENT_PARAMETERS = 26
240    LOCKED_BY_OTHER_CLIENT = 27
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):
243class TestError(enum.Enum):
244    NO_ERROR = 0
245    UNKNOWN = 1
246    TIMEOUT_TEST_NOT_OK = 2
247    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):
250class CommandError(typing.NamedTuple):
251    service_error: ServiceError | None
252    additional_cause: AdditionalCause | None
253    test_error: TestError | None

CommandError(service_error, additional_cause, test_error)

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):
258class OptionalField(enum.Enum):
259    SEQUENCE_NUMBER = 1
260    REPORT_TIME_STAMP = 2
261    REASON_FOR_INCLUSION = 3
262    DATA_SET_NAME = 4
263    DATA_REFERENCE = 5
264    BUFFER_OVERFLOW = 6
265    ENTRY_ID = 7
266    CONF_REVISION = 8
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):
269class TriggerCondition(enum.Enum):
270    DATA_CHANGE = 1
271    QUALITY_CHANGE = 2
272    DATA_UPDATE = 3
273    INTEGRITY = 4
274    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):
277class RcbAttrType(enum.Enum):
278    REPORT_ID = 'RptID'
279    REPORT_ENABLE = 'RptEna'
280    DATASET = 'DatSet'
281    CONF_REVISION = 'ConfRev'
282    OPTIONAL_FIELDS = 'OptFlds'
283    BUFFER_TIME = 'BufTm'
284    SEQUENCE_NUMBER = 'SqNum'
285    TRIGGER_OPTIONS = 'TrgOps'
286    INTEGRITY_PERIOD = 'IntgPd'
287    GI = 'GI'
288    PURGE_BUFFER = 'PurgeBuf'  # brcb
289    ENTRY_ID = 'EntryID'  # brcb
290    TIME_OF_ENTRY = 'TimeOfEntry'  # brcb
291    RESERVATION_TIME = 'ResvTms'  # brcb
292    RESERVE = 'Resv'  # urcb
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'>
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 | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime
class Reason(enum.Enum):
344class Reason(enum.Enum):
345    DATA_CHANGE = 1
346    QUALITY_CHANGE = 2
347    DATA_UPDATE = 3
348    INTEGRITY = 4
349    GENERAL_INTERROGATION = 5
350    APPLICATION_TRIGGER = 6
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):
353class ReportData(typing.NamedTuple):
354    ref: DataRef
355    value: Value
356    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):
359class Report(typing.NamedTuple):
360    report_id: ReportId
361    sequence_number: int | None
362    subsequence_number: int | None
363    more_segments_follow: bool | None
364    dataset: DatasetRef | None
365    buffer_overflow: bool | None
366    conf_revision: int | None
367    entry_time: EntryTime | None
368    entry_id: util.Bytes | None
369    data: Collection[ReportData]

Report(report_id, sequence_number, subsequence_number, more_segments_follow, dataset, buffer_overflow, conf_revision, entry_time, entry_id, data)

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):
374class ControlModel(enum.Enum):
375    DIRECT_WITH_NORMAL_SECURITY = 1
376    SBO_WITH_NORMAL_SECURITY = 2
377    DIRECT_WITH_ENHANCED_SECURITY = 3
378    SBO_WITH_ENHANCED_SECURITY = 4
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):
381class OriginCategory(enum.Enum):
382    NOT_SUPPORTED = 0
383    BAY_CONTROL = 1
384    STATION_CONTROL = 2
385    REMOTE_CONTROL = 3
386    AUTOMATIC_BAY = 4
387    AUTOMATIC_STATION = 5
388    AUTOMATIC_REMOTE = 6
389    MAINTENANCE = 7
390    PROCESS = 8
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):
393class Origin(typing.NamedTuple):
394    category: OriginCategory
395    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):
398class Check(enum.Enum):
399    SYNCHRO = 0
400    INTERLOCK = 1
SYNCHRO = <Check.SYNCHRO: 0>
INTERLOCK = <Check.INTERLOCK: 1>
class Command(typing.NamedTuple):
403class Command(typing.NamedTuple):
404    value: Value
405    operate_time: Timestamp | None
406    origin: Origin
407    control_number: int
408    """control number in range [0, 255]"""
409    t: Timestamp
410    test: bool
411    checks: set[Check]
412    """ignored in cancel action"""

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

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):
415class Termination(typing.NamedTuple):
416    ref: CommandRef
417    cmd: Command
418    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