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:
 29async def connect(addr: tcp.Address,
 30                  data_value_types: dict[common.DataRef,
 31                                         common.ValueType] = {},
 32                  cmd_value_types: dict[common.CommandRef,
 33                                        common.ValueType] = {},
 34                  report_data_refs: dict[common.ReportId,
 35                                         Collection[common.DataRef]] = {},
 36                  report_cb: ReportCb | None = None,
 37                  termination_cb: TerminationCb | None = None,
 38                  status_delay: float | None = None,
 39                  status_timeout: float = 30,
 40                  **kwargs
 41                  ) -> 'Client':
 42    """Connect to IEC61850 server
 43
 44    `data_value_types` include value types used in report processing and
 45    writing data.
 46
 47    `cmd_value_types` include value types used in command actions and
 48    termination processing.
 49
 50    Only reports that are specified by `report_data_refs` are notified with
 51    `report_cb`.
 52
 53    If `status_delay` is ``None``, periodical sending of status requests is
 54    disabled.
 55
 56    Additional arguments are passed directly to `hat.drivers.mms.connect`
 57    (`request_cb` and `unconfirmed_cb` are set by this coroutine).
 58
 59    """
 60    client = Client()
 61    client._data_value_types = data_value_types
 62    client._cmd_value_types = cmd_value_types
 63    client._report_cb = report_cb
 64    client._termination_cb = termination_cb
 65    client._loop = asyncio.get_running_loop()
 66    client._status_event = asyncio.Event()
 67    client._last_appl_errors = {}
 68    client._report_data_defs = {
 69        report_id: [encoder.DataDef(ref=data_ref,
 70                                    value_type=data_value_types[data_ref])
 71                    for data_ref in data_refs]
 72        for report_id, data_refs in report_data_refs.items()}
 73
 74    client._log = logger.create_logger(mlog, kwargs.get('name'), None)
 75    client._comm_log = logger.CommunicationLogger(mlog, kwargs.get('name'),
 76                                                  None)
 77
 78    client._conn = await mms.connect(addr=addr,
 79                                     request_cb=None,
 80                                     unconfirmed_cb=client._on_unconfirmed,
 81                                     **kwargs)
 82
 83    try:
 84        client._log = logger.create_logger(mlog, client._conn.info.name,
 85                                           client._conn.info)
 86        client._comm_log = logger.CommunicationLogger(
 87            mlog, client._conn.info.name, client._conn.info)
 88
 89        if status_delay is not None:
 90            client.async_group.spawn(client._status_loop, status_delay,
 91                                     status_timeout)
 92
 93        client.async_group.spawn(aio.call_on_cancel, client._comm_log.log,
 94                                 common.CommLogAction.CLOSE)
 95        client._comm_log.log(common.CommLogAction.OPEN)
 96
 97    except BaseException:
 98        await aio.uncancellable(client.async_close())
 99        raise
100
101    return client

Connect to IEC61850 server

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

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

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

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

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

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

Client

async_group
107    @property
108    def async_group(self):
109        """Async group"""
110        return self._conn.async_group

Async group

112    @property
113    def info(self) -> acse.ConnectionInfo:
114        """Connection info"""
115        return self._conn.info

Connection info

async def create_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef, data: Collection[DataRef]) -> ServiceError | None:
117    async def create_dataset(self,
118                             ref: common.DatasetRef,
119                             data: Collection[common.DataRef]
120                             ) -> common.ServiceError | None:
121        """Create dataset"""
122        req = mms.DefineNamedVariableListRequest(
123            name=encoder.dataset_ref_to_object_name(ref),
124            specification=[
125                mms.NameVariableSpecification(
126                    encoder.data_ref_to_object_name(i))
127                for i in data])
128
129        if self._comm_log.is_enabled:
130            self._comm_log.log(common.CommLogAction.SEND,
131                               logger.CreateDatasetReq(ref=ref,
132                                                       data=data))
133
134        res = await self._send(req)
135
136        if isinstance(res, mms.Error):
137            if res == mms.AccessError.OBJECT_NON_EXISTENT:
138                result = common.ServiceError.INSTANCE_NOT_AVAILABLE
139
140            elif res == mms.AccessError.OBJECT_ACCESS_DENIED:
141                result = common.ServiceError.ACCESS_VIOLATION
142
143            elif res == mms.DefinitionError.OBJECT_EXISTS:
144                result = common.ServiceError.INSTANCE_IN_USE
145
146            elif res == mms.DefinitionError.OBJECT_UNDEFINED:
147                result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT
148
149            elif res == mms.ResourceError.CAPABILITY_UNAVAILABLE:
150                result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
151
152            else:
153                result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
154
155        elif isinstance(res, mms.DefineNamedVariableListResponse):
156            result = None
157
158        else:
159            raise Exception('unsupported response type')
160
161        if self._comm_log.is_enabled:
162            self._comm_log.log(common.CommLogAction.RECEIVE,
163                               logger.CreateDatasetRes(result))
164
165        return result

Create dataset

async def delete_dataset( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> ServiceError | None:
167    async def delete_dataset(self,
168                             ref: common.DatasetRef
169                             ) -> common.ServiceError | None:
170        """Delete dataset"""
171        req = mms.DeleteNamedVariableListRequest([
172            encoder.dataset_ref_to_object_name(ref)])
173
174        if self._comm_log.is_enabled:
175            self._comm_log.log(common.CommLogAction.SEND,
176                               logger.DeleteDatasetReq(ref))
177
178        res = await self._send(req)
179
180        if isinstance(res, mms.Error):
181            if res == mms.AccessError.OBJECT_NON_EXISTENT:
182                result = common.ServiceError.INSTANCE_NOT_AVAILABLE
183
184            elif res == mms.AccessError.OBJECT_ACCESS_DENIED:
185                result = common.ServiceError.ACCESS_VIOLATION
186
187            else:
188                result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
189
190        elif isinstance(res, mms.DeleteNamedVariableListResponse):
191            if res.matched == 0 and res.deleted == 0:
192                result = common.ServiceError.INSTANCE_NOT_AVAILABLE
193
194            elif res.matched != res.deleted:
195                result = common.ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT
196
197            else:
198                result = None
199
200        else:
201            raise Exception('unsupported response type')
202
203        if self._comm_log.is_enabled:
204            self._comm_log.log(common.CommLogAction.RECEIVE,
205                               logger.DeleteDatasetRes(result))
206
207        return result

Delete dataset

async def get_persisted_dataset_refs( self, logical_device: str) -> Collection[PersistedDatasetRef] | ServiceError:
209    async def get_persisted_dataset_refs(self,
210                                         logical_device: str
211                                         ) -> Collection[common.PersistedDatasetRef] | common.ServiceError:  # NOQA
212        """Get persisted dataset references associated with logical device"""
213
214        if self._comm_log.is_enabled:
215            self._comm_log.log(
216                common.CommLogAction.SEND,
217                logger.GetPersistedDatasetRefsReq(logical_device))
218
219        identifiers = await self._get_name_list(
220            object_class=mms.ObjectClass.NAMED_VARIABLE_LIST,
221            object_scope=mms.DomainSpecificObjectScope(logical_device))
222
223        if isinstance(identifiers, common.ServiceError):
224            result = identifiers
225
226        else:
227            result = collections.deque()
228            for identifier in identifiers:
229                logical_node, name = identifier.split('$')
230                result.append(
231                    common.PersistedDatasetRef(logical_device=logical_device,
232                                               logical_node=logical_node,
233                                               name=name))
234
235        if self._comm_log.is_enabled:
236            self._comm_log.log(common.CommLogAction.RECEIVE,
237                               logger.GetPersistedDatasetRefsRes(result))
238
239        return result

Get persisted dataset references associated with logical device

async def get_dataset_data_refs( self, ref: PersistedDatasetRef | NonPersistedDatasetRef) -> Collection[DataRef] | ServiceError:
241    async def get_dataset_data_refs(self,
242                                    ref: common.DatasetRef
243                                    ) -> Collection[common.DataRef] | common.ServiceError:  # NOQA
244        """Get data references associated with single dataset"""
245        req = mms.GetNamedVariableListAttributesRequest(
246            encoder.dataset_ref_to_object_name(ref))
247
248        if self._comm_log.is_enabled:
249            self._comm_log.log(common.CommLogAction.SEND,
250                               logger.GetDatasetDataRefsReq(ref))
251
252        res = await self._send(req)
253
254        if isinstance(res, mms.Error):
255            if res == mms.AccessError.OBJECT_NON_EXISTENT:
256                result = common.ServiceError.INSTANCE_NOT_AVAILABLE
257
258            elif res == mms.AccessError.OBJECT_ACCESS_DENIED:
259                result = common.ServiceError.ACCESS_VIOLATION
260
261            elif res == mms.ServiceError.PDU_SIZE:
262                result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
263
264            else:
265                result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
266
267        elif isinstance(res, mms.GetNamedVariableListAttributesResponse):
268            result = collections.deque()
269
270            for i in res.specification:
271                if not isinstance(i, mms.NameVariableSpecification):
272                    raise Exception('unsupported specification type')
273
274                result.append(encoder.data_ref_from_object_name(i.name))
275
276        else:
277            raise Exception('unsupported response type')
278
279        if self._comm_log.is_enabled:
280            self._comm_log.log(common.CommLogAction.RECEIVE,
281                               logger.GetDatasetDataRefsRes(result))
282
283        return result

Get data references associated with single dataset

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]:
285    async def get_rcb_attrs(self,
286                            ref: common.RcbRef,
287                            attr_types: Collection[common.RcbAttrType]
288                            ) -> dict[common.RcbAttrType,
289                                      common.RcbAttrValue | common.ServiceError]:  # NOQA
290        """Get RCB attribute value"""
291        specification = collections.deque()
292
293        for attr_type in attr_types:
294            if ref.type == common.RcbType.BUFFERED:
295                if attr_type == common.RcbAttrType.RESERVE:
296                    raise ValueError('unsupported attribute type')
297
298            elif ref.type == common.RcbType.UNBUFFERED:
299                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
300                                 common.RcbAttrType.ENTRY_ID,
301                                 common.RcbAttrType.TIME_OF_ENTRY,
302                                 common.RcbAttrType.RESERVATION_TIME):
303                    raise ValueError('unsupported attribute type')
304
305            else:
306                raise TypeError('unsupported rcb type')
307
308            specification.append(
309                mms.NameVariableSpecification(
310                    encoder.data_ref_to_object_name(
311                        common.DataRef(logical_device=ref.logical_device,
312                                       logical_node=ref.logical_node,
313                                       fc=ref.type.value,
314                                       names=(ref.name, attr_type.value)))))
315
316        req = mms.ReadRequest(specification)
317
318        if self._comm_log.is_enabled:
319            self._comm_log.log(common.CommLogAction.SEND,
320                               logger.GetRcbAttrsReq(ref=ref,
321                                                     attr_types=attr_types))
322
323        res = await self._send(req)
324
325        if isinstance(res, mms.Error):
326            results = {
327                attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
328                for attr_type in attr_types}
329
330        elif isinstance(res, mms.ReadResponse):
331            if len(res.results) != len(attr_types):
332                raise Exception('invalid results length')
333
334            results = {}
335
336            for attr_type, mms_data in zip(attr_types, res.results):
337                if isinstance(mms_data, mms.DataAccessError):
338                    if mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
339                        results[attr_type] = common.ServiceError.ACCESS_VIOLATION  # NOQA
340
341                    elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
342                        results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE  # NOQA
343
344                    else:
345                        results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
346
347                else:
348                    results[attr_type] = encoder.rcb_attr_value_from_mms_data(
349                        mms_data, attr_type)
350
351        else:
352            raise Exception('unsupported response type')
353
354        if self._comm_log.is_enabled:
355            self._comm_log.log(common.CommLogAction.RECEIVE,
356                               logger.GetRcbAttrsRes(results))
357
358        return results

Get RCB attribute value

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]:
360    async def set_rcb_attrs(self,
361                            ref: common.RcbRef,
362                            attrs: Collection[tuple[common.RcbAttrType,
363                                                    common.RcbAttrValue]]
364                            ) -> dict[common.RcbAttrType,
365                                      common.ServiceError | None]:
366        """Set RCB attribute values"""
367        specification = collections.deque()
368        data = collections.deque()
369
370        for attr_type, attr_value in attrs:
371            if ref.type == common.RcbType.BUFFERED:
372                if attr_type == common.RcbAttrType.RESERVE:
373                    raise ValueError('unsupported attribute type')
374
375            elif ref.type == common.RcbType.UNBUFFERED:
376                if attr_type in (common.RcbAttrType.PURGE_BUFFER,
377                                 common.RcbAttrType.ENTRY_ID,
378                                 common.RcbAttrType.TIME_OF_ENTRY,
379                                 common.RcbAttrType.RESERVATION_TIME):
380                    raise ValueError('unsupported attribute type')
381
382            else:
383                raise TypeError('unsupported rcb type')
384
385            specification.append(
386                mms.NameVariableSpecification(
387                    encoder.data_ref_to_object_name(
388                        common.DataRef(logical_device=ref.logical_device,
389                                       logical_node=ref.logical_node,
390                                       fc=ref.type.value,
391                                       names=(ref.name, attr_type.value)))))
392            data.append(
393                encoder.rcb_attr_value_to_mms_data(attr_value, attr_type))
394
395        req = mms.WriteRequest(specification=specification,
396                               data=data)
397
398        if self._comm_log.is_enabled:
399            self._comm_log.log(common.CommLogAction.SEND,
400                               logger.SetRcbAttrsReq(ref=ref,
401                                                     attrs=attrs))
402
403        res = await self._send(req)
404
405        if isinstance(res, mms.Error):
406            results = {
407                attr_type: common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
408                for attr_type, _ in attrs}
409
410        elif isinstance(res, mms.WriteResponse):
411            if len(res.results) != len(attrs):
412                raise Exception('invalid results length')
413
414            results = {}
415
416            for (attr_type, _), mms_data in zip(attrs, res.results):
417                if mms_data is None:
418                    results[attr_type] = None
419
420                elif mms_data == mms.DataAccessError.OBJECT_ACCESS_DENIED:
421                    results[attr_type] = common.ServiceError.ACCESS_VIOLATION
422
423                elif mms_data == mms.DataAccessError.OBJECT_NON_EXISTENT:
424                    results[attr_type] = common.ServiceError.INSTANCE_NOT_AVAILABLE  # NOQA
425
426                elif mms_data == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:
427                    results[attr_type] = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT  # NOQA
428
429                elif mms_data == mms.DataAccessError.TYPE_INCONSISTENT:
430                    results[attr_type] = common.ServiceError.TYPE_CONFLICT
431
432                elif mms_data == mms.DataAccessError.OBJECT_VALUE_INVALID:
433                    results[attr_type] = common.ServiceError.PARAMETER_VALUE_INCONSISTENT  # NOQA
434
435                else:
436                    results[attr_type] = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
437
438        else:
439            raise Exception('unsupported response type')
440
441        if self._comm_log.is_enabled:
442            self._comm_log.log(common.CommLogAction.RECEIVE,
443                               logger.SetRcbAttrsRes(results))
444
445        return results

Set RCB attribute values

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:
447    async def write_data(self,
448                         ref: common.DataRef,
449                         value: common.Value
450                         ) -> common.ServiceError | None:
451        """Write data"""
452        value_type = self._data_value_types[ref]
453
454        req = mms.WriteRequest(
455            specification=[
456                mms.NameVariableSpecification(
457                    encoder.data_ref_to_object_name(ref))],
458            data=[encoder.value_to_mms_data(value,  value_type)])
459
460        if self._comm_log.is_enabled:
461            self._comm_log.log(common.CommLogAction.SEND,
462                               logger.WriteDataReq(ref=ref,
463                                                   value=value))
464
465        res = await self._send(req)
466
467        if isinstance(res, mms.Error):
468            result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
469
470        elif isinstance(res, mms.WriteResponse):
471            if len(res.results) != 1:
472                raise Exception('invalid results size')
473
474            if res.results[0] is not None:
475                if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
476                    result = common.ServiceError.ACCESS_VIOLATION
477
478                elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
479                    result = common.ServiceError.INSTANCE_NOT_AVAILABLE
480
481                elif res.results[0] == mms.DataAccessError.TEMPORARILY_UNAVAILABLE:  # NOQA
482                    result = common.ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT  # NOQA
483
484                elif res.results[0] == mms.DataAccessError.TYPE_INCONSISTENT:
485                    result = common.ServiceError.TYPE_CONFLICT
486
487                elif res.results[0] == mms.DataAccessError.OBJECT_VALUE_INVALID:  # NOQA
488                    result = common.ServiceError.PARAMETER_VALUE_INCONSISTENT
489
490                else:
491                    result = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
492
493            else:
494                result = None
495
496        else:
497            raise Exception('unsupported response type')
498
499        if self._comm_log.is_enabled:
500            self._comm_log.log(common.CommLogAction.RECEIVE,
501                               logger.WriteDataRes(result))
502
503        return result

Write data

async def select( self, ref: CommandRef, cmd: Command | None) -> CommandError | None:
505    async def select(self,
506                     ref: common.CommandRef,
507                     cmd: common.Command | None
508                     ) -> common.CommandError | None:
509        """Select command"""
510        if cmd is not None:
511            return await self._command_with_last_appl_error(ref=ref,
512                                                            cmd=cmd,
513                                                            attr='SBOw',
514                                                            with_checks=True)
515
516        req = mms.ReadRequest([
517            mms.NameVariableSpecification(
518                encoder.data_ref_to_object_name(
519                    common.DataRef(logical_device=ref.logical_device,
520                                   logical_node=ref.logical_node,
521                                   fc='CO',
522                                   names=(ref.name, 'SBO'))))])
523
524        if self._comm_log.is_enabled:
525            self._comm_log.log(common.CommLogAction.SEND,
526                               logger.CommandReq(ref=ref,
527                                                 attr='SBO',
528                                                 cmd=cmd))
529
530        res = await self._send(req)
531
532        if isinstance(res, mms.Error):
533            result = _create_command_error(
534                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
535                last_appl_error=None)
536
537        elif isinstance(res, mms.ReadResponse):
538            if len(res.results) != 1:
539                raise Exception('invalid results size')
540
541            if not isinstance(res.results[0], mms.VisibleStringData):
542                if res.results[0] == mms.DataAccessError.OBJECT_ACCESS_DENIED:
543                    service_error = common.ServiceError.ACCESS_VIOLATION
544
545                elif res.results[0] == mms.DataAccessError.OBJECT_NON_EXISTENT:
546                    service_error = common.ServiceError.INSTANCE_NOT_AVAILABLE
547
548                else:
549                    service_error = common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
550
551                result = _create_command_error(service_error=service_error,
552                                               last_appl_error=None)
553
554            elif res.results[0].value == '':
555                result = _create_command_error(
556                    service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
557                    last_appl_error=None)
558
559            else:
560                result = None
561
562        else:
563            raise Exception('unsupported response type')
564
565        if self._comm_log.is_enabled:
566            self._comm_log.log(common.CommLogAction.RECEIVE,
567                               logger.CommandRes(result))
568
569        return result

Select command

async def cancel( self, ref: CommandRef, cmd: Command) -> CommandError | None:
571    async def cancel(self,
572                     ref: common.CommandRef,
573                     cmd: common.Command
574                     ) -> common.CommandError | None:
575        """Cancel command"""
576        return await self._command_with_last_appl_error(ref=ref,
577                                                        cmd=cmd,
578                                                        attr='Cancel',
579                                                        with_checks=False)

Cancel command

async def operate( self, ref: CommandRef, cmd: Command) -> CommandError | None:
581    async def operate(self,
582                      ref: common.CommandRef,
583                      cmd: common.Command
584                      ) -> common.CommandError | None:
585        """Operate command"""
586        return await self._command_with_last_appl_error(ref=ref,
587                                                        cmd=cmd,
588                                                        attr='Oper',
589                                                        with_checks=True)

Operate command

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

PersistedDatasetRef(logical_device, logical_node, name)

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

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

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

name: str

Alias for field number 2

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

NonPersistedDatasetRef(name,)

NonPersistedDatasetRef(name: str)

Create new instance of NonPersistedDatasetRef(name,)

name: str

Alias for field number 0

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

DataRef(logical_device, logical_node, fc, names)

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

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

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

fc: str

Alias for field number 2

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

Alias for field number 3

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

CommandRef(logical_device, logical_node, name)

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

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

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

name: str

Alias for field number 2

class RcbType(enum.Enum):
45class RcbType(enum.Enum):
46    BUFFERED = 'BR'
47    UNBUFFERED = 'RP'
BUFFERED = <RcbType.BUFFERED: 'BR'>
UNBUFFERED = <RcbType.UNBUFFERED: 'RP'>
class RcbRef(typing.NamedTuple):
50class RcbRef(typing.NamedTuple):
51    logical_device: str
52    logical_node: str
53    type: RcbType
54    name: str

RcbRef(logical_device, logical_node, type, name)

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

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

logical_device: str

Alias for field number 0

logical_node: str

Alias for field number 1

type: RcbType

Alias for field number 2

name: str

Alias for field number 3

class BasicValueType(enum.Enum):
59class BasicValueType(enum.Enum):
60    BOOLEAN = 'BOOLEAN'  # bool
61    INTEGER = 'INTEGER'  # int
62    UNSIGNED = 'UNSIGNED'  # int
63    FLOAT = 'FLOAT'  # float
64    BIT_STRING = 'BIT_STRING'  # Collection[bool]
65    OCTET_STRING = 'OCTET_STRING'  # util.Bytes
66    VISIBLE_STRING = 'VISIBLE_STRING'  # str
67    MMS_STRING = 'MMS_STRING'  # str
BOOLEAN = <BasicValueType.BOOLEAN: 'BOOLEAN'>
INTEGER = <BasicValueType.INTEGER: 'INTEGER'>
UNSIGNED = <BasicValueType.UNSIGNED: 'UNSIGNED'>
FLOAT = <BasicValueType.FLOAT: 'FLOAT'>
BIT_STRING = <BasicValueType.BIT_STRING: 'BIT_STRING'>
OCTET_STRING = <BasicValueType.OCTET_STRING: 'OCTET_STRING'>
VISIBLE_STRING = <BasicValueType.VISIBLE_STRING: 'VISIBLE_STRING'>
MMS_STRING = <BasicValueType.MMS_STRING: 'MMS_STRING'>
class AcsiValueType(enum.Enum):
70class AcsiValueType(enum.Enum):
71    QUALITY = 'QUALITY'
72    TIMESTAMP = 'TIMESTAMP'
73    DOUBLE_POINT = 'DOUBLE_POINT'
74    DIRECTION = 'DIRECTION'
75    SEVERITY = 'SEVERITY'
76    ANALOGUE = 'ANALOGUE'
77    VECTOR = 'VECTOR'
78    STEP_POSITION = 'STEP_POSITION'
79    BINARY_CONTROL = 'BINARY_CONTROL'
QUALITY = <AcsiValueType.QUALITY: 'QUALITY'>
TIMESTAMP = <AcsiValueType.TIMESTAMP: 'TIMESTAMP'>
DOUBLE_POINT = <AcsiValueType.DOUBLE_POINT: 'DOUBLE_POINT'>
DIRECTION = <AcsiValueType.DIRECTION: 'DIRECTION'>
SEVERITY = <AcsiValueType.SEVERITY: 'SEVERITY'>
ANALOGUE = <AcsiValueType.ANALOGUE: 'ANALOGUE'>
VECTOR = <AcsiValueType.VECTOR: 'VECTOR'>
STEP_POSITION = <AcsiValueType.STEP_POSITION: 'STEP_POSITION'>
BINARY_CONTROL = <AcsiValueType.BINARY_CONTROL: 'BINARY_CONTROL'>
class ArrayValueType(typing.NamedTuple):
82class ArrayValueType(typing.NamedTuple):
83    type: 'ValueType'
84    length: int

ArrayValueType(type, length)

ArrayValueType(type: 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):
87class StructValueType(typing.NamedTuple):
88    elements: Collection[typing.Tuple[str, 'ValueType']]

StructValueType(elements,)

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

Create new instance of StructValueType(elements,)

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

Alias for field number 0

class Timestamp(typing.NamedTuple):
 99class Timestamp(typing.NamedTuple):
100    value: datetime.datetime
101    leap_second: bool
102    clock_failure: bool
103    not_synchronized: bool
104    accuracy: int | None
105    """accurate fraction bits [0,24]"""

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

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

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

value: datetime.datetime

Alias for field number 0

leap_second: bool

Alias for field number 1

clock_failure: bool

Alias for field number 2

not_synchronized: bool

Alias for field number 3

accuracy: int | None

accurate fraction bits [0,24]

class QualityValidity(enum.Enum):
108class QualityValidity(enum.Enum):
109    GOOD = 0
110    INVALID = 1
111    RESERVED = 2
112    QUESTIONABLE = 3
GOOD = <QualityValidity.GOOD: 0>
INVALID = <QualityValidity.INVALID: 1>
RESERVED = <QualityValidity.RESERVED: 2>
QUESTIONABLE = <QualityValidity.QUESTIONABLE: 3>
class QualityDetail(enum.Enum):
115class QualityDetail(enum.Enum):
116    OVERFLOW = 2
117    OUT_OF_RANGE = 3
118    BAD_REFERENCE = 4
119    OSCILLATORY = 5
120    FAILURE = 6
121    OLD_DATA = 7
122    INCONSISTENT = 8
123    INACCURATE = 9
OVERFLOW = <QualityDetail.OVERFLOW: 2>
OUT_OF_RANGE = <QualityDetail.OUT_OF_RANGE: 3>
BAD_REFERENCE = <QualityDetail.BAD_REFERENCE: 4>
OSCILLATORY = <QualityDetail.OSCILLATORY: 5>
FAILURE = <QualityDetail.FAILURE: 6>
OLD_DATA = <QualityDetail.OLD_DATA: 7>
INCONSISTENT = <QualityDetail.INCONSISTENT: 8>
INACCURATE = <QualityDetail.INACCURATE: 9>
class QualitySource(enum.Enum):
126class QualitySource(enum.Enum):
127    PROCESS = 0
128    SUBSTITUTED = 1
PROCESS = <QualitySource.PROCESS: 0>
SUBSTITUTED = <QualitySource.SUBSTITUTED: 1>
class Quality(typing.NamedTuple):
131class Quality(typing.NamedTuple):
132    validity: QualityValidity
133    details: set[QualityDetail]
134    source: QualitySource
135    test: bool
136    operator_blocked: bool

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

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

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

validity: QualityValidity

Alias for field number 0

details: set[QualityDetail]

Alias for field number 1

source: QualitySource

Alias for field number 2

test: bool

Alias for field number 3

operator_blocked: bool

Alias for field number 4

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

Analogue(i, f)

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

Create new instance of Analogue(i, f)

i: int | None

Alias for field number 0

f: float | None

Alias for field number 1

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

Vector(magnitude, angle)

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

Create new instance of Vector(magnitude, angle)

magnitude: Analogue

Alias for field number 0

angle: Analogue | None

Alias for field number 1

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

StepPosition(value, transient)

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

Create new instance of StepPosition(value, transient)

value: int

value in range [-64, 63]

transient: bool | None

Alias for field number 1

class BinaryControl(enum.Enum):
177class BinaryControl(enum.Enum):
178    STOP = 0
179    LOWER = 1
180    HIGHER = 2
181    RESERVED = 3
STOP = <BinaryControl.STOP: 0>
LOWER = <BinaryControl.LOWER: 1>
HIGHER = <BinaryControl.HIGHER: 2>
RESERVED = <BinaryControl.RESERVED: 3>
BasicValue = bool | int | float | str | bytes | bytearray | memoryview | collections.abc.Collection[bool]
ArrayValue = collections.abc.Collection['Value']
StructValue = typing.Dict[str, ForwardRef('Value')]
Value = 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):
199class ServiceError(enum.Enum):
200    NO_ERROR = 0
201    INSTANCE_NOT_AVAILABLE = 1
202    INSTANCE_IN_USE = 2
203    ACCESS_VIOLATION = 3
204    ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = 4
205    PARAMETER_VALUE_INAPPROPRIATE = 5
206    PARAMETER_VALUE_INCONSISTENT = 6
207    CLASS_NOT_SUPPORTED = 7
208    INSTANCE_LOCKED_BY_OTHER_CLIENT = 8
209    CONTROL_MUST_BE_SELECTED = 9
210    TYPE_CONFLICT = 10
211    FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = 11
212    FAILED_DUE_TO_SERVER_CONSTRAINT = 12
NO_ERROR = <ServiceError.NO_ERROR: 0>
INSTANCE_NOT_AVAILABLE = <ServiceError.INSTANCE_NOT_AVAILABLE: 1>
INSTANCE_IN_USE = <ServiceError.INSTANCE_IN_USE: 2>
ACCESS_VIOLATION = <ServiceError.ACCESS_VIOLATION: 3>
ACCESS_NOT_ALLOWED_IN_CURRENT_STATE = <ServiceError.ACCESS_NOT_ALLOWED_IN_CURRENT_STATE: 4>
PARAMETER_VALUE_INAPPROPRIATE = <ServiceError.PARAMETER_VALUE_INAPPROPRIATE: 5>
PARAMETER_VALUE_INCONSISTENT = <ServiceError.PARAMETER_VALUE_INCONSISTENT: 6>
CLASS_NOT_SUPPORTED = <ServiceError.CLASS_NOT_SUPPORTED: 7>
INSTANCE_LOCKED_BY_OTHER_CLIENT = <ServiceError.INSTANCE_LOCKED_BY_OTHER_CLIENT: 8>
CONTROL_MUST_BE_SELECTED = <ServiceError.CONTROL_MUST_BE_SELECTED: 9>
TYPE_CONFLICT = <ServiceError.TYPE_CONFLICT: 10>
FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT = <ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT: 11>
FAILED_DUE_TO_SERVER_CONSTRAINT = <ServiceError.FAILED_DUE_TO_SERVER_CONSTRAINT: 12>
class AdditionalCause(enum.Enum):
215class AdditionalCause(enum.Enum):
216    UNKNOWN = 0
217    NOT_SUPPORTED = 1
218    BLOCKED_BY_SWITCHING_HIERARCHY = 2
219    SELECT_FAILED = 3
220    INVALID_POSITION = 4
221    POSITION_REACHED = 5
222    PARAMETER_CHANGE_IN_EXECUTION = 6
223    STEP_LIMIT = 7
224    BLOCKED_BY_MODE = 8
225    BLOCKED_BY_PROCESS = 9
226    BLOCKED_BY_INTERLOCKING = 10
227    BLOCKED_BY_SYNCHROCHECK = 11
228    COMMAND_ALREADY_IN_EXECUTION = 12
229    BLOCKED_BY_HEALTH = 13
230    ONE_OF_N_CONTROL = 14
231    ABORTION_BY_CANCEL = 15
232    TIME_LIMIT_OVER = 16
233    ABORTION_BY_TRIP = 17
234    OBJECT_NOT_SELECTED = 18
235    OBJECT_ALREADY_SELECTED = 19
236    NO_ACCESS_AUTHORITY = 20
237    ENDED_WITH_OVERSHOOT = 21
238    ABORTION_DUE_TO_DEVIATION = 22
239    ABORTION_BY_COMMUNICATION_LOSS = 23
240    BLOCKED_BY_COMMAND = 24
241    NONE = 25
242    INCONSISTENT_PARAMETERS = 26
243    LOCKED_BY_OTHER_CLIENT = 27
UNKNOWN = <AdditionalCause.UNKNOWN: 0>
NOT_SUPPORTED = <AdditionalCause.NOT_SUPPORTED: 1>
BLOCKED_BY_SWITCHING_HIERARCHY = <AdditionalCause.BLOCKED_BY_SWITCHING_HIERARCHY: 2>
SELECT_FAILED = <AdditionalCause.SELECT_FAILED: 3>
INVALID_POSITION = <AdditionalCause.INVALID_POSITION: 4>
POSITION_REACHED = <AdditionalCause.POSITION_REACHED: 5>
PARAMETER_CHANGE_IN_EXECUTION = <AdditionalCause.PARAMETER_CHANGE_IN_EXECUTION: 6>
STEP_LIMIT = <AdditionalCause.STEP_LIMIT: 7>
BLOCKED_BY_MODE = <AdditionalCause.BLOCKED_BY_MODE: 8>
BLOCKED_BY_PROCESS = <AdditionalCause.BLOCKED_BY_PROCESS: 9>
BLOCKED_BY_INTERLOCKING = <AdditionalCause.BLOCKED_BY_INTERLOCKING: 10>
BLOCKED_BY_SYNCHROCHECK = <AdditionalCause.BLOCKED_BY_SYNCHROCHECK: 11>
COMMAND_ALREADY_IN_EXECUTION = <AdditionalCause.COMMAND_ALREADY_IN_EXECUTION: 12>
BLOCKED_BY_HEALTH = <AdditionalCause.BLOCKED_BY_HEALTH: 13>
ONE_OF_N_CONTROL = <AdditionalCause.ONE_OF_N_CONTROL: 14>
ABORTION_BY_CANCEL = <AdditionalCause.ABORTION_BY_CANCEL: 15>
TIME_LIMIT_OVER = <AdditionalCause.TIME_LIMIT_OVER: 16>
ABORTION_BY_TRIP = <AdditionalCause.ABORTION_BY_TRIP: 17>
OBJECT_NOT_SELECTED = <AdditionalCause.OBJECT_NOT_SELECTED: 18>
OBJECT_ALREADY_SELECTED = <AdditionalCause.OBJECT_ALREADY_SELECTED: 19>
NO_ACCESS_AUTHORITY = <AdditionalCause.NO_ACCESS_AUTHORITY: 20>
ENDED_WITH_OVERSHOOT = <AdditionalCause.ENDED_WITH_OVERSHOOT: 21>
ABORTION_DUE_TO_DEVIATION = <AdditionalCause.ABORTION_DUE_TO_DEVIATION: 22>
ABORTION_BY_COMMUNICATION_LOSS = <AdditionalCause.ABORTION_BY_COMMUNICATION_LOSS: 23>
BLOCKED_BY_COMMAND = <AdditionalCause.BLOCKED_BY_COMMAND: 24>
NONE = <AdditionalCause.NONE: 25>
INCONSISTENT_PARAMETERS = <AdditionalCause.INCONSISTENT_PARAMETERS: 26>
LOCKED_BY_OTHER_CLIENT = <AdditionalCause.LOCKED_BY_OTHER_CLIENT: 27>
class TestError(enum.Enum):
246class TestError(enum.Enum):
247    NO_ERROR = 0
248    UNKNOWN = 1
249    TIMEOUT_TEST_NOT_OK = 2
250    OPERATOR_TEST_NOT_OK = 3
NO_ERROR = <TestError.NO_ERROR: 0>
UNKNOWN = <TestError.UNKNOWN: 1>
TIMEOUT_TEST_NOT_OK = <TestError.TIMEOUT_TEST_NOT_OK: 2>
OPERATOR_TEST_NOT_OK = <TestError.OPERATOR_TEST_NOT_OK: 3>
class CommandError(typing.NamedTuple):
253class CommandError(typing.NamedTuple):
254    service_error: ServiceError | None
255    additional_cause: AdditionalCause | None
256    test_error: TestError | None

CommandError(service_error, additional_cause, test_error)

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

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

service_error: ServiceError | None

Alias for field number 0

additional_cause: AdditionalCause | None

Alias for field number 1

test_error: TestError | None

Alias for field number 2

class OptionalField(enum.Enum):
261class OptionalField(enum.Enum):
262    SEQUENCE_NUMBER = 1
263    REPORT_TIME_STAMP = 2
264    REASON_FOR_INCLUSION = 3
265    DATA_SET_NAME = 4
266    DATA_REFERENCE = 5
267    BUFFER_OVERFLOW = 6
268    ENTRY_ID = 7
269    CONF_REVISION = 8
SEQUENCE_NUMBER = <OptionalField.SEQUENCE_NUMBER: 1>
REPORT_TIME_STAMP = <OptionalField.REPORT_TIME_STAMP: 2>
REASON_FOR_INCLUSION = <OptionalField.REASON_FOR_INCLUSION: 3>
DATA_SET_NAME = <OptionalField.DATA_SET_NAME: 4>
DATA_REFERENCE = <OptionalField.DATA_REFERENCE: 5>
BUFFER_OVERFLOW = <OptionalField.BUFFER_OVERFLOW: 6>
ENTRY_ID = <OptionalField.ENTRY_ID: 7>
CONF_REVISION = <OptionalField.CONF_REVISION: 8>
class TriggerCondition(enum.Enum):
272class TriggerCondition(enum.Enum):
273    DATA_CHANGE = 1
274    QUALITY_CHANGE = 2
275    DATA_UPDATE = 3
276    INTEGRITY = 4
277    GENERAL_INTERROGATION = 5
DATA_CHANGE = <TriggerCondition.DATA_CHANGE: 1>
QUALITY_CHANGE = <TriggerCondition.QUALITY_CHANGE: 2>
DATA_UPDATE = <TriggerCondition.DATA_UPDATE: 3>
INTEGRITY = <TriggerCondition.INTEGRITY: 4>
GENERAL_INTERROGATION = <TriggerCondition.GENERAL_INTERROGATION: 5>
class RcbAttrType(enum.Enum):
280class RcbAttrType(enum.Enum):
281    REPORT_ID = 'RptID'
282    REPORT_ENABLE = 'RptEna'
283    DATASET = 'DatSet'
284    CONF_REVISION = 'ConfRev'
285    OPTIONAL_FIELDS = 'OptFlds'
286    BUFFER_TIME = 'BufTm'
287    SEQUENCE_NUMBER = 'SqNum'
288    TRIGGER_OPTIONS = 'TrgOps'
289    INTEGRITY_PERIOD = 'IntgPd'
290    GI = 'GI'
291    PURGE_BUFFER = 'PurgeBuf'  # brcb
292    ENTRY_ID = 'EntryID'  # brcb
293    TIME_OF_ENTRY = 'TimeOfEntry'  # brcb
294    RESERVATION_TIME = 'ResvTms'  # brcb
295    RESERVE = 'Resv'  # urcb
REPORT_ID = <RcbAttrType.REPORT_ID: 'RptID'>
REPORT_ENABLE = <RcbAttrType.REPORT_ENABLE: 'RptEna'>
DATASET = <RcbAttrType.DATASET: 'DatSet'>
CONF_REVISION = <RcbAttrType.CONF_REVISION: 'ConfRev'>
OPTIONAL_FIELDS = <RcbAttrType.OPTIONAL_FIELDS: 'OptFlds'>
BUFFER_TIME = <RcbAttrType.BUFFER_TIME: 'BufTm'>
SEQUENCE_NUMBER = <RcbAttrType.SEQUENCE_NUMBER: 'SqNum'>
TRIGGER_OPTIONS = <RcbAttrType.TRIGGER_OPTIONS: 'TrgOps'>
INTEGRITY_PERIOD = <RcbAttrType.INTEGRITY_PERIOD: 'IntgPd'>
GI = <RcbAttrType.GI: 'GI'>
PURGE_BUFFER = <RcbAttrType.PURGE_BUFFER: 'PurgeBuf'>
ENTRY_ID = <RcbAttrType.ENTRY_ID: 'EntryID'>
TIME_OF_ENTRY = <RcbAttrType.TIME_OF_ENTRY: 'TimeOfEntry'>
RESERVATION_TIME = <RcbAttrType.RESERVATION_TIME: 'ResvTms'>
RESERVE = <RcbAttrType.RESERVE: 'Resv'>
ReportIdRcbAttrValue = <class 'str'>
ReportEnableRcbAttrValue = <class 'bool'>
DatasetRcbAttrValue = PersistedDatasetRef | NonPersistedDatasetRef | None
ConfRevisionRcbAttrValue = <class 'int'>
OptionalFieldsRcbAttrValue = set[OptionalField]
BufferTimeRcbAttrValue = <class 'int'>
SequenceNumberRcbAttrValue = <class 'int'>
TriggerOptionsRcbAttrValue = set[TriggerCondition]
IntegrityPeriodRcbAttrValue = <class 'int'>
GiRcbAttrValue = <class 'bool'>
PurgeBufferRcbAttrValue = <class 'bool'>
EntryIdRcbAttrValue = bytes | bytearray | memoryview
TimeOfEntryRcbAttrValue = <class 'datetime.datetime'>
ReservationTimeRcbAttrValue = <class 'int'>
ReserveRcbAttrValue = <class 'bool'>
RcbAttrValue = str | bool | PersistedDatasetRef | NonPersistedDatasetRef | None | int | set[OptionalField] | set[TriggerCondition] | bytes | bytearray | memoryview | datetime.datetime
class Reason(enum.Enum):
347class Reason(enum.Enum):
348    DATA_CHANGE = 1
349    QUALITY_CHANGE = 2
350    DATA_UPDATE = 3
351    INTEGRITY = 4
352    GENERAL_INTERROGATION = 5
353    APPLICATION_TRIGGER = 6
DATA_CHANGE = <Reason.DATA_CHANGE: 1>
QUALITY_CHANGE = <Reason.QUALITY_CHANGE: 2>
DATA_UPDATE = <Reason.DATA_UPDATE: 3>
INTEGRITY = <Reason.INTEGRITY: 4>
GENERAL_INTERROGATION = <Reason.GENERAL_INTERROGATION: 5>
APPLICATION_TRIGGER = <Reason.APPLICATION_TRIGGER: 6>
class ReportData(typing.NamedTuple):
356class ReportData(typing.NamedTuple):
357    ref: DataRef
358    value: Value
359    reasons: set[Reason] | None

ReportData(ref, value, reasons)

ReportData( ref: DataRef, value: 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):
362class Report(typing.NamedTuple):
363    report_id: ReportId
364    sequence_number: int | None
365    subsequence_number: int | None
366    more_segments_follow: bool | None
367    dataset: DatasetRef | None
368    buffer_overflow: bool | None
369    conf_revision: int | None
370    entry_time: EntryTime | None
371    entry_id: util.Bytes | None
372    data: Collection[ReportData]

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

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

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

report_id: str

Alias for field number 0

sequence_number: int | None

Alias for field number 1

subsequence_number: int | None

Alias for field number 2

more_segments_follow: bool | None

Alias for field number 3

Alias for field number 4

buffer_overflow: bool | None

Alias for field number 5

conf_revision: int | None

Alias for field number 6

entry_time: datetime.datetime | None

Alias for field number 7

entry_id: bytes | bytearray | memoryview | None

Alias for field number 8

data: Collection[ReportData]

Alias for field number 9

class ControlModel(enum.Enum):
377class ControlModel(enum.Enum):
378    DIRECT_WITH_NORMAL_SECURITY = 1
379    SBO_WITH_NORMAL_SECURITY = 2
380    DIRECT_WITH_ENHANCED_SECURITY = 3
381    SBO_WITH_ENHANCED_SECURITY = 4
DIRECT_WITH_NORMAL_SECURITY = <ControlModel.DIRECT_WITH_NORMAL_SECURITY: 1>
SBO_WITH_NORMAL_SECURITY = <ControlModel.SBO_WITH_NORMAL_SECURITY: 2>
DIRECT_WITH_ENHANCED_SECURITY = <ControlModel.DIRECT_WITH_ENHANCED_SECURITY: 3>
SBO_WITH_ENHANCED_SECURITY = <ControlModel.SBO_WITH_ENHANCED_SECURITY: 4>
class OriginCategory(enum.Enum):
384class OriginCategory(enum.Enum):
385    NOT_SUPPORTED = 0
386    BAY_CONTROL = 1
387    STATION_CONTROL = 2
388    REMOTE_CONTROL = 3
389    AUTOMATIC_BAY = 4
390    AUTOMATIC_STATION = 5
391    AUTOMATIC_REMOTE = 6
392    MAINTENANCE = 7
393    PROCESS = 8
NOT_SUPPORTED = <OriginCategory.NOT_SUPPORTED: 0>
BAY_CONTROL = <OriginCategory.BAY_CONTROL: 1>
STATION_CONTROL = <OriginCategory.STATION_CONTROL: 2>
REMOTE_CONTROL = <OriginCategory.REMOTE_CONTROL: 3>
AUTOMATIC_BAY = <OriginCategory.AUTOMATIC_BAY: 4>
AUTOMATIC_STATION = <OriginCategory.AUTOMATIC_STATION: 5>
AUTOMATIC_REMOTE = <OriginCategory.AUTOMATIC_REMOTE: 6>
MAINTENANCE = <OriginCategory.MAINTENANCE: 7>
PROCESS = <OriginCategory.PROCESS: 8>
class Origin(typing.NamedTuple):
396class Origin(typing.NamedTuple):
397    category: OriginCategory
398    identification: util.Bytes

Origin(category, identification)

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

Create new instance of Origin(category, identification)

category: OriginCategory

Alias for field number 0

identification: bytes | bytearray | memoryview

Alias for field number 1

class Check(enum.Enum):
401class Check(enum.Enum):
402    SYNCHRO = 0
403    INTERLOCK = 1
SYNCHRO = <Check.SYNCHRO: 0>
INTERLOCK = <Check.INTERLOCK: 1>
class Command(typing.NamedTuple):
406class Command(typing.NamedTuple):
407    value: Value
408    operate_time: Timestamp | None
409    origin: Origin
410    control_number: int
411    """control number in range [0, 255]"""
412    t: Timestamp
413    test: bool
414    checks: set[Check]
415    """ignored in cancel action"""

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

Command( value: 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):
418class Termination(typing.NamedTuple):
419    ref: CommandRef
420    cmd: Command
421    error: CommandError | None

Termination(ref, cmd, error)

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

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

ref: CommandRef

Alias for field number 0

cmd: Command

Alias for field number 1

error: CommandError | None

Alias for field number 2