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: Callable[[Report], None | Awaitable[None]] | None = None, termination_cb: Callable[[Termination], None | Awaitable[None]] | None = None, status_delay: float | None = None, status_timeout: float = 30, **kwargs) -> Client:
 29async def connect(addr: 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 %s not defined - skipping report",
647                           report_id)
648            return
649
650        report = encoder.report_from_mms_data(mms_data, data_defs)
651
652        self._comm_log.log(common.CommLogAction.RECEIVE, report)
653
654        await aio.call(self._report_cb, report)
655
656    def _process_last_appl_error(self, mms_data):
657        last_appl_error = encoder.last_appl_error_from_mms_data(mms_data)
658
659        key = last_appl_error.name, last_appl_error.control_number
660        if key in self._last_appl_errors:
661            self._last_appl_errors[key] = last_appl_error
662
663    async def _process_termination(self, ref, cmd_mms_data,
664                                   last_appl_error_mms_data):
665        if not self._termination_cb:
666            self._log.info("termination callback not defined - "
667                           "skipping termination")
668            return
669
670        value_type = self._cmd_value_types.get(ref)
671        if value_type is None:
672            self._log.info("command value type not defined - "
673                           "skipping termination")
674            return
675
676        cmd = encoder.command_from_mms_data(mms_data=cmd_mms_data,
677                                            value_type=value_type,
678                                            with_checks=True)
679
680        if last_appl_error_mms_data:
681            error = _create_command_error(
682                service_error=None,
683                last_appl_error=encoder.last_appl_error_from_mms_data(
684                    last_appl_error_mms_data))
685
686        else:
687            error = None
688
689        termination = common.Termination(ref=ref,
690                                         cmd=cmd,
691                                         error=error)
692
693        self._comm_log.log(common.CommLogAction.RECEIVE, termination)
694
695        await aio.call(self._termination_cb, termination)
696
697    async def _send(self, req):
698        res = await self._conn.send_confirmed(req)
699        self._status_event.set()
700        return res
701
702    async def _status_loop(self, delay, timeout):
703        try:
704            self._log.debug("starting status loop")
705            while True:
706                self._status_event.clear()
707
708                with contextlib.suppress(asyncio.TimeoutError):
709                    await aio.wait_for(self._status_event.wait(), delay)
710                    continue
711
712                self._log.debug("sending status request")
713                await aio.wait_for(self._send(mms.StatusRequest()), timeout)
714
715        except asyncio.TimeoutError:
716            self._log.warning("status timeout")
717
718        except ConnectionError:
719            pass
720
721        except Exception as e:
722            self._log.error("status loop error: %s", e, exc_info=e)
723
724        finally:
725            self._log.debug("stopping status loop")
726            self.close()
727
728    async def _get_name_list(self, object_class, object_scope):
729        identifiers = collections.deque()
730        continue_after = None
731
732        while True:
733            req = mms.GetNameListRequest(
734                object_class=object_class,
735                object_scope=object_scope,
736                continue_after=continue_after)
737
738            res = await self._send(req)
739
740            if isinstance(res, mms.Error):
741                if res == mms.AccessError.OBJECT_NON_EXISTENT:
742                    return common.ServiceError.INSTANCE_NOT_AVAILABLE
743
744                if res == mms.AccessError.OBJECT_ACCESS_DENIED:
745                    return common.ServiceError.ACCESS_VIOLATION
746
747                if res == mms.ServiceError.OBJECT_CONSTRAINT_CONFLICT:
748                    return common.ServiceError.PARAMETER_VALUE_INCONSISTENT
749
750                return common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT  # NOQA
751
752            if not isinstance(res, mms.GetNameListResponse):
753                raise Exception('unsupported response type')
754
755            identifiers.extend(res.identifiers)
756
757            if not res.more_follows:
758                break
759
760            if not res.identifiers:
761                raise Exception('invalid more follows value')
762
763            continue_after = identifiers[-1]
764
765        return identifiers
766
767    async def _command_with_last_appl_error(self, ref, cmd, attr, with_checks):
768        value_type = self._cmd_value_types[ref]
769
770        req = mms.WriteRequest(
771            specification=[
772                mms.NameVariableSpecification(
773                    encoder.data_ref_to_object_name(
774                        common.DataRef(logical_device=ref.logical_device,
775                                       logical_node=ref.logical_node,
776                                       fc='CO',
777                                       names=(ref.name, attr))))],
778            data=[encoder.command_to_mms_data(cmd=cmd,
779                                              value_type=value_type,
780                                              with_checks=with_checks)])
781
782        name = (f'{req.specification[0].name.domain_id}/'
783                f'{req.specification[0].name.item_id}')
784        key = name, cmd.control_number
785
786        if key in self._last_appl_errors:
787            raise Exception('active control number duplicate')
788
789        self._last_appl_errors[key] = None
790
791        if self._comm_log.is_enabled:
792            self._comm_log.log(common.CommLogAction.SEND,
793                               logger.CommandReq(ref=ref,
794                                                 attr=attr,
795                                                 cmd=cmd))
796
797        try:
798            res = await self._send(req)
799
800        finally:
801            last_appl_error = self._last_appl_errors.pop(key, None)
802
803        if isinstance(res, mms.Error):
804            result = _create_command_error(
805                service_error=common.ServiceError.FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT,  # NOQA
806                last_appl_error=last_appl_error)
807
808        elif isinstance(res, mms.WriteResponse):
809            if len(res.results) != 1:
810                raise Exception('invalid results size')
811
812            if res.results[0] is not None:
813                result = _create_command_error(service_error=None,
814                                               last_appl_error=last_appl_error)
815
816            else:
817                result = None
818
819        else:
820            raise Exception('unsupported response type')
821
822        if self._comm_log.is_enabled:
823            self._comm_log.log(common.CommLogAction.RECEIVE,
824                               logger.CommandRes(result))
825
826        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: 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: ValueType, length: int)

Create new instance of ArrayValueType(type, length)

Alias for field number 0

length: int

Alias for field number 1

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

StructValueType(elements,)

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

Create new instance of StructValueType(elements,)

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

Alias for field number 0

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

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

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

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

value: datetime.datetime

Alias for field number 0

leap_second: bool

Alias for field number 1

clock_failure: bool

Alias for field number 2

not_synchronized: bool

Alias for field number 3

accuracy: int | None

accurate fraction bits [0,24]

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

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

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

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

validity: QualityValidity

Alias for field number 0

details: set[QualityDetail]

Alias for field number 1

source: QualitySource

Alias for field number 2

test: bool

Alias for field number 3

operator_blocked: bool

Alias for field number 4

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

Analogue(i, f)

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

Create new instance of Analogue(i, f)

i: int | None

Alias for field number 0

f: float | None

Alias for field number 1

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

Vector(magnitude, angle)

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

Create new instance of Vector(magnitude, angle)

magnitude: Analogue

Alias for field number 0

angle: Analogue | None

Alias for field number 1

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

StepPosition(value, transient)

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

Create new instance of StepPosition(value, transient)

value: int

value in range [-64, 63]

transient: bool | None

Alias for field number 1

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

CommandError(service_error, additional_cause, test_error)

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

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

service_error: ServiceError | None

Alias for field number 0

additional_cause: AdditionalCause | None

Alias for field number 1

test_error: TestError | None

Alias for field number 2

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

ReportData(ref, value, reasons)

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

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

ref: DataRef

Alias for field number 0

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

Alias for field number 1

reasons: set[Reason] | None

Alias for field number 2

class Report(typing.NamedTuple):
362class Report(typing.NamedTuple):
363    report_id: ReportId
364    sequence_number: int | None
365    subsequence_number: int | None
366    more_segments_follow: bool | None
367    dataset: DatasetRef | None
368    buffer_overflow: bool | None
369    conf_revision: int | None
370    entry_time: EntryTime | None
371    entry_id: util.Bytes | None
372    data: Collection[ReportData]

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

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

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

report_id: str

Alias for field number 0

sequence_number: int | None

Alias for field number 1

subsequence_number: int | None

Alias for field number 2

more_segments_follow: bool | None

Alias for field number 3

Alias for field number 4

buffer_overflow: bool | None

Alias for field number 5

conf_revision: int | None

Alias for field number 6

entry_time: datetime.datetime | None

Alias for field number 7

entry_id: bytes | bytearray | memoryview | None

Alias for field number 8

data: Collection[ReportData]

Alias for field number 9

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

Origin(category, identification)

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

Create new instance of Origin(category, identification)

category: OriginCategory

Alias for field number 0

identification: bytes | bytearray | memoryview

Alias for field number 1

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

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

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

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

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

Alias for field number 0

operate_time: Timestamp | None

Alias for field number 1

origin: Origin

Alias for field number 2

control_number: int

control number in range [0, 255]

Alias for field number 4

test: bool

Alias for field number 5

checks: set[Check]

ignored in cancel action

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

Termination(ref, cmd, error)

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

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

ref: CommandRef

Alias for field number 0

cmd: Command

Alias for field number 1

error: CommandError | None

Alias for field number 2