hat.gateway.devices.iec104.slave

IEC 60870-5-104 slave device

  1"""IEC 60870-5-104 slave device"""
  2
  3import collections
  4import contextlib
  5import functools
  6import itertools
  7import logging
  8
  9from hat import aio
 10from hat.drivers import iec104
 11from hat.drivers import tcp
 12import hat.event.common
 13import hat.event.eventer
 14
 15from hat.gateway.devices.iec101 import slave as iec101_slave
 16from hat.gateway.devices.iec104 import common
 17from hat.gateway.devices.iec104 import ssl
 18
 19
 20mlog: logging.Logger = logging.getLogger(__name__)
 21
 22
 23async def create(conf: common.DeviceConf,
 24                 eventer_client: hat.event.eventer.Client,
 25                 event_type_prefix: common.EventTypePrefix
 26                 ) -> 'Iec104SlaveDevice':
 27    device = Iec104SlaveDevice()
 28    device._conf = conf
 29    device._eventer_client = eventer_client
 30    device._event_type_prefix = event_type_prefix
 31    device._max_connections = conf['max_connections']
 32    device._next_conn_ids = itertools.count(1)
 33    device._conns = {}
 34    device._remote_hosts = (set(conf['remote_hosts'])
 35                            if conf['remote_hosts'] is not None else None)
 36    device._buffers = {}
 37    device._data_msgs = {}
 38    device._data_buffers = {}
 39    device._log = _create_logger_adapter(conf['name'])
 40
 41    iec101_slave.init_buffers(buffers_conf=conf['buffers'],
 42                              buffers=device._buffers)
 43
 44    await iec101_slave.init_data(log=device._log,
 45                                 data_conf=conf['data'],
 46                                 data_msgs=device._data_msgs,
 47                                 data_buffers=device._data_buffers,
 48                                 data_with_ack=None,
 49                                 buffers=device._buffers,
 50                                 eventer_client=eventer_client,
 51                                 event_type_prefix=event_type_prefix)
 52
 53    ssl_ctx = (ssl.create_ssl_ctx(conf['security'], ssl.SslProtocol.TLS_SERVER)
 54               if conf['security'] else None)
 55
 56    device._srv = await iec104.listen(
 57        connection_cb=device._on_connection,
 58        addr=tcp.Address(host=conf['local_host'],
 59                         port=conf['local_port']),
 60        response_timeout=conf['response_timeout'],
 61        supervisory_timeout=conf['supervisory_timeout'],
 62        test_timeout=conf['test_timeout'],
 63        send_window_size=conf['send_window_size'],
 64        receive_window_size=conf['receive_window_size'],
 65        ssl=ssl_ctx,
 66        name=conf['name'])
 67
 68    try:
 69        await device._register_connections()
 70
 71    except BaseException:
 72        await aio.uncancellable(device.async_close())
 73        raise
 74
 75    return device
 76
 77
 78info: common.DeviceInfo = common.DeviceInfo(
 79    type="iec104_slave",
 80    create=create,
 81    json_schema_id="hat-gateway://iec104.yaml#/$defs/slave",
 82    json_schema_repo=common.json_schema_repo)
 83
 84
 85class Iec104SlaveDevice(common.Device):
 86
 87    @property
 88    def async_group(self) -> aio.Group:
 89        return self._srv.async_group
 90
 91    async def process_event(self, event: hat.event.common.Event):
 92        try:
 93            self._log.debug('received event: %s', event)
 94            await self._process_event(event)
 95
 96        except Exception as e:
 97            self._log.warning('error processing event: %s', e, exc_info=e)
 98
 99    async def _on_connection(self, conn):
100        remote_host = conn.info.remote_addr.host
101        if (self._remote_hosts is not None and
102                remote_host not in self._remote_hosts):
103            conn.close()
104            self._log.warning('connection closed: remote host %s not allowed',
105                              remote_host)
106            return
107
108        if (self._max_connections is not None and
109                len(self._conns) >= self._max_connections):
110            conn.close()
111            self._log.debug('connection closed: max connections exceeded '
112                            '(remote host %s)', remote_host)
113            return
114
115        if self._conf['security']:
116            try:
117                ssl.init_security(self._conf['security'], conn)
118
119            except Exception as e:
120                self._log.error('init security error: %s', exc_info=e)
121                conn.close()
122                return
123
124        conn_id = next(self._next_conn_ids)
125
126        try:
127            self._conns[conn_id] = conn
128            await self._register_connections()
129
130            enabled_cb = functools.partial(self._on_enabled, conn)
131            with conn.register_enabled_cb(enabled_cb):
132                enabled_cb(conn.is_enabled)
133
134                while True:
135                    try:
136                        msgs = await conn.receive()
137
138                    except iec104.AsduTypeError as e:
139                        self._log.warning("asdu type error: %s", e)
140                        continue
141
142                    for msg in msgs:
143                        try:
144                            self._log.debug('received message: %s', msg)
145                            await self._process_msg(conn_id, conn, msg)
146
147                        except Exception as e:
148                            self._log.warning('error processing message: %s',
149                                              e, exc_info=e)
150
151        except ConnectionError:
152            self._log.debug('connection close')
153
154        except Exception as e:
155            self._log.warning('connection error: %s', e, exc_info=e)
156
157        finally:
158            self._log.debug('closing connection')
159            conn.close()
160
161            with contextlib.suppress(Exception):
162                self._conns.pop(conn_id)
163                await aio.uncancellable(self._register_connections())
164
165    def _on_enabled(self, conn, enabled):
166        if not enabled:
167            return
168
169        with contextlib.suppress(Exception):
170            for buffer in self._buffers.values():
171                for event_id, data_key, data_msg in buffer.get():
172                    self._send_data_msg(conn, buffer, event_id, data_msg)
173
174    async def _register_connections(self):
175        payload = [{'connection_id': conn_id,
176                    'local': {'host': conn.info.local_addr.host,
177                              'port': conn.info.local_addr.port},
178                    'remote': {'host': conn.info.remote_addr.host,
179                               'port': conn.info.remote_addr.port}}
180                   for conn_id, conn in self._conns.items()]
181
182        event = hat.event.common.RegisterEvent(
183            type=(*self._event_type_prefix, 'gateway', 'connections'),
184            source_timestamp=None,
185            payload=hat.event.common.EventPayloadJson(payload))
186
187        await self._eventer_client.register([event])
188
189    async def _process_event(self, event):
190        suffix = event.type[len(self._event_type_prefix):]
191
192        if suffix[:2] == ('system', 'data'):
193            data_type_str, asdu_address_str, io_address_str = suffix[2:]
194            data_key = common.DataKey(data_type=common.DataType(data_type_str),
195                                      asdu_address=int(asdu_address_str),
196                                      io_address=int(io_address_str))
197
198            await self._process_data_event(data_key, event)
199
200        elif suffix[:2] == ('system', 'command'):
201            cmd_type_str, asdu_address_str, io_address_str = suffix[2:]
202            cmd_key = common.CommandKey(
203                cmd_type=common.CommandType(cmd_type_str),
204                asdu_address=int(asdu_address_str),
205                io_address=int(io_address_str))
206
207            await self._process_command_event(cmd_key, event)
208
209        else:
210            raise Exception('unsupported event type')
211
212    async def _process_data_event(self, data_key, event):
213        if data_key not in self._data_msgs:
214            raise Exception('data not configured')
215
216        data_msg = _data_msg_from_event(data_key, event)
217        self._data_msgs[data_key] = data_msg
218
219        buffer = self._data_buffers.get(data_key)
220        if buffer:
221            buffer.add(event.id, data_key, data_msg)
222
223        for conn in self._conns.values():
224            self._send_data_msg(conn, buffer, event.id, data_msg)
225
226    async def _process_command_event(self, cmd_key, event):
227        cmd_msg = _cmd_msg_from_event(cmd_key, event)
228        conn_id = event.payload.data['connection_id']
229        conn = self._conns[conn_id]
230        await conn.send([cmd_msg])
231
232    async def _process_msg(self, conn_id, conn, msg):
233        if isinstance(msg, iec104.CommandMsg):
234            await self._process_command_msg(conn_id, conn, msg)
235
236        elif isinstance(msg, iec104.InterrogationMsg):
237            await self._process_interrogation_msg(conn_id, conn, msg)
238
239        elif isinstance(msg, iec104.CounterInterrogationMsg):
240            await self._process_counter_interrogation_msg(conn_id, conn, msg)
241
242        elif isinstance(msg, iec104.ReadMsg):
243            await self._process_read_msg(conn_id, conn, msg)
244
245        elif isinstance(msg, iec104.ClockSyncMsg):
246            await self._process_clock_sync_msg(conn_id, conn, msg)
247
248        elif isinstance(msg, iec104.TestMsg):
249            await self._process_test_msg(conn_id, conn, msg)
250
251        elif isinstance(msg, iec104.ResetMsg):
252            await self._process_reset_msg(conn_id, conn, msg)
253
254        elif isinstance(msg, iec104.ParameterMsg):
255            await self._process_parameter_msg(conn_id, conn, msg)
256
257        elif isinstance(msg, iec104.ParameterActivationMsg):
258            await self._process_parameter_activation_msg(conn_id, conn, msg)
259
260        else:
261            raise Exception('unsupported message')
262
263    async def _process_command_msg(self, conn_id, conn, msg):
264        if isinstance(msg.cause, iec104.CommandReqCause):
265            event = _cmd_msg_to_event(self._event_type_prefix, conn_id, msg)
266            await self._eventer_client.register([event])
267
268        else:
269            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
270                               is_negative_confirm=True)
271            await conn.send([res])
272
273    async def _process_interrogation_msg(self, conn_id, conn, msg):
274        if msg.cause == iec104.CommandReqCause.ACTIVATION:
275            asdu_data_msgs = collections.defaultdict(collections.deque)
276
277            for data_key, data_msg in self._data_msgs.items():
278                if data_key.data_type == common.DataType.BINARY_COUNTER:
279                    continue
280
281                if (msg.asdu_address != 0xFFFF and
282                        msg.asdu_address != data_key.asdu_address):
283                    continue
284
285                asdu_data_msgs[data_key.asdu_address].append(data_msg)
286
287            if msg.asdu_address != 0xFFFF:
288                asdu_data_msgs[msg.asdu_address].append(None)
289
290            for asdu_address, data_msgs in asdu_data_msgs.items():
291                res = msg._replace(
292                    asdu_address=asdu_address,
293                    cause=iec104.CommandResCause.ACTIVATION_CONFIRMATION,
294                    is_negative_confirm=False)
295                await conn.send([res])
296
297                msgs = [
298                    data_msg._replace(
299                        is_test=msg.is_test,
300                        cause=iec104.DataResCause.INTERROGATED_STATION)
301                    for data_msg in data_msgs
302                    if data_msg]
303                if msgs:
304                    await conn.send(msgs)
305
306                res = msg._replace(
307                    asdu_address=asdu_address,
308                    cause=iec104.CommandResCause.ACTIVATION_TERMINATION,
309                    is_negative_confirm=False)
310                await conn.send([res])
311
312        elif msg.cause == iec104.CommandReqCause.DEACTIVATION:
313            res = msg._replace(
314                cause=iec104.CommandResCause.DEACTIVATION_CONFIRMATION,
315                is_negative_confirm=True)
316            await conn.send([res])
317
318        else:
319            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
320                               is_negative_confirm=True)
321            await conn.send([res])
322
323    async def _process_counter_interrogation_msg(self, conn_id, conn, msg):
324        if msg.cause == iec104.CommandReqCause.ACTIVATION:
325            asdu_data_msgs = collections.defaultdict(collections.deque)
326
327            for data_key, data_msg in self._data_msgs.items():
328                if data_key.data_type != common.DataType.BINARY_COUNTER:
329                    continue
330
331                if (msg.asdu_address != 0xFFFF and
332                        msg.asdu_address != data_key.asdu_address):
333                    continue
334
335                asdu_data_msgs[data_key.asdu_address].append(data_msg)
336
337            if msg.asdu_address != 0xFFFF:
338                asdu_data_msgs[msg.asdu_address].append(None)
339            for asdu_address, data_msgs in asdu_data_msgs.items():
340                res = msg._replace(
341                    asdu_address=asdu_address,
342                    cause=iec104.CommandResCause.ACTIVATION_CONFIRMATION,
343                    is_negative_confirm=False)
344                await conn.send([res])
345
346                msgs = [
347                    data_msg._replace(
348                        is_test=msg.is_test,
349                        cause=iec104.DataResCause.INTERROGATED_COUNTER)
350                    for data_msg in data_msgs
351                    if data_msg]
352                if msgs:
353                    await conn.send(msgs)
354
355                res = msg._replace(
356                    asdu_address=asdu_address,
357                    cause=iec104.CommandResCause.ACTIVATION_TERMINATION,
358                    is_negative_confirm=False)
359                await conn.send([res])
360
361        elif msg.cause == iec104.CommandReqCause.DEACTIVATION:
362            res = msg._replace(
363                cause=iec104.CommandResCause.DEACTIVATION_CONFIRMATION,
364                is_negative_confirm=True)
365            await conn.send([res])
366
367        else:
368            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
369                               is_negative_confirm=True)
370            await conn.send([res])
371
372    async def _process_read_msg(self, conn_id, conn, msg):
373        res = msg._replace(cause=iec104.ReadResCause.UNKNOWN_TYPE)
374        await conn.send([res])
375
376    async def _process_clock_sync_msg(self, conn_id, conn, msg):
377        if isinstance(msg.cause, iec104.ClockSyncReqCause):
378            res = msg._replace(
379                cause=iec104.ClockSyncResCause.ACTIVATION_CONFIRMATION,
380                is_negative_confirm=True)
381            await conn.send([res])
382
383        else:
384            res = msg._replace(cause=iec104.ClockSyncResCause.UNKNOWN_CAUSE,
385                               is_negative_confirm=True)
386            await conn.send([res])
387
388    async def _process_test_msg(self, conn_id, conn, msg):
389        res = msg._replace(cause=iec104.ActivationResCause.UNKNOWN_TYPE)
390        await conn.send([res])
391
392    async def _process_reset_msg(self, conn_id, conn, msg):
393        res = msg._replace(cause=iec104.ActivationResCause.UNKNOWN_TYPE)
394        await conn.send([res])
395
396    async def _process_parameter_msg(self, conn_id, conn, msg):
397        res = msg._replace(cause=iec104.ParameterResCause.UNKNOWN_TYPE)
398        await conn.send([res])
399
400    async def _process_parameter_activation_msg(self, conn_id, conn, msg):
401        res = msg._replace(
402            cause=iec104.ParameterActivationResCause.UNKNOWN_TYPE)
403        await conn.send([res])
404
405    def _send_data_msg(self, conn, buffer, event_id, data_msg):
406        self.async_group.spawn(self._async_send_data_msg, conn, buffer,
407                               event_id, data_msg)
408
409    async def _async_send_data_msg(self, conn, buffer, event_id, data_msg):
410        try:
411            if buffer:
412                await conn.send([data_msg], wait_ack=True)
413                buffer.remove(event_id)
414
415            else:
416                await conn.send([data_msg])
417
418        except ConnectionError:
419            pass
420
421        except Exception as e:
422            self._log.warning('send data message error: %s', e, exc_info=e)
423
424
425def _cmd_msg_to_event(event_type_prefix, conn_id, msg):
426    event = iec101_slave.cmd_msg_to_event(event_type_prefix, conn_id, msg)
427    source_timestamp = common.time_to_source_timestamp(msg.time)
428    return event._replace(source_timestamp=source_timestamp)
429
430
431def _data_msg_from_event(data_key, event):
432    return iec101_slave.data_msg_from_event(data_key, event)
433
434
435def _cmd_msg_from_event(cmd_key, event):
436    msg = iec101_slave.cmd_msg_from_event(cmd_key, event)
437    time = common.time_from_source_timestamp(event.source_timestamp)
438    return iec104.CommandMsg(**msg._asdict(),
439                             time=time)
440
441
442def _create_logger_adapter(name):
443    extra = {'meta': {'type': 'Iec104SlaveDevice',
444                      'name': name}}
445
446    return logging.LoggerAdapter(mlog, extra)
mlog: logging.Logger = <Logger hat.gateway.devices.iec104.slave (WARNING)>
async def create( conf: None | bool | int | float | str | List[ForwardRef('Data')] | Dict[str, ForwardRef('Data')], eventer_client: hat.event.eventer.client.Client, event_type_prefix: tuple[str, str, str]) -> Iec104SlaveDevice:
24async def create(conf: common.DeviceConf,
25                 eventer_client: hat.event.eventer.Client,
26                 event_type_prefix: common.EventTypePrefix
27                 ) -> 'Iec104SlaveDevice':
28    device = Iec104SlaveDevice()
29    device._conf = conf
30    device._eventer_client = eventer_client
31    device._event_type_prefix = event_type_prefix
32    device._max_connections = conf['max_connections']
33    device._next_conn_ids = itertools.count(1)
34    device._conns = {}
35    device._remote_hosts = (set(conf['remote_hosts'])
36                            if conf['remote_hosts'] is not None else None)
37    device._buffers = {}
38    device._data_msgs = {}
39    device._data_buffers = {}
40    device._log = _create_logger_adapter(conf['name'])
41
42    iec101_slave.init_buffers(buffers_conf=conf['buffers'],
43                              buffers=device._buffers)
44
45    await iec101_slave.init_data(log=device._log,
46                                 data_conf=conf['data'],
47                                 data_msgs=device._data_msgs,
48                                 data_buffers=device._data_buffers,
49                                 data_with_ack=None,
50                                 buffers=device._buffers,
51                                 eventer_client=eventer_client,
52                                 event_type_prefix=event_type_prefix)
53
54    ssl_ctx = (ssl.create_ssl_ctx(conf['security'], ssl.SslProtocol.TLS_SERVER)
55               if conf['security'] else None)
56
57    device._srv = await iec104.listen(
58        connection_cb=device._on_connection,
59        addr=tcp.Address(host=conf['local_host'],
60                         port=conf['local_port']),
61        response_timeout=conf['response_timeout'],
62        supervisory_timeout=conf['supervisory_timeout'],
63        test_timeout=conf['test_timeout'],
64        send_window_size=conf['send_window_size'],
65        receive_window_size=conf['receive_window_size'],
66        ssl=ssl_ctx,
67        name=conf['name'])
68
69    try:
70        await device._register_connections()
71
72    except BaseException:
73        await aio.uncancellable(device.async_close())
74        raise
75
76    return device
info: hat.gateway.common.DeviceInfo = DeviceInfo(type='iec104_slave', create=<function create>, json_schema_id='hat-gateway://iec104.yaml#/$defs/slave', json_schema_repo={'hat-json://path.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-json://path.yaml', 'title': 'JSON Path', 'oneOf': [{'type': 'string'}, {'type': 'integer'}, {'type': 'array', 'items': {'$ref': 'hat-json://path.yaml'}}]}, 'hat-json://logging.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-json://logging.yaml', 'title': 'Logging', 'description': 'Logging configuration', 'type': 'object', 'required': ['version'], 'properties': {'version': {'title': 'Version', 'type': 'integer', 'default': 1}, 'formatters': {'title': 'Formatters', 'type': 'object', 'patternProperties': {'.+': {'title': 'Formatter', 'type': 'object', 'properties': {'format': {'title': 'Format', 'type': 'string', 'default': None}, 'datefmt': {'title': 'Date format', 'type': 'string', 'default': None}}}}}, 'filters': {'title': 'Filters', 'type': 'object', 'patternProperties': {'.+': {'title': 'Filter', 'type': 'object', 'properties': {'name': {'title': 'Logger name', 'type': 'string', 'default': ''}}}}}, 'handlers': {'title': 'Handlers', 'type': 'object', 'patternProperties': {'.+': {'title': 'Handler', 'type': 'object', 'description': 'Additional properties are passed as keyword arguments to\nconstructor\n', 'required': ['class'], 'properties': {'class': {'title': 'Class', 'type': 'string'}, 'level': {'title': 'Level', 'type': 'string'}, 'formatter': {'title': 'Formatter', 'type': 'string'}, 'filters': {'title': 'Filters', 'type': 'array', 'items': {'title': 'Filter id', 'type': 'string'}}}}}}, 'loggers': {'title': 'Loggers', 'type': 'object', 'patternProperties': {'.+': {'title': 'Logger', 'type': 'object', 'properties': {'level': {'title': 'Level', 'type': 'string'}, 'propagate': {'title': 'Propagate', 'type': 'boolean'}, 'filters': {'title': 'Filters', 'type': 'array', 'items': {'title': 'Filter id', 'type': 'string'}}, 'handlers': {'title': 'Handlers', 'type': 'array', 'items': {'title': 'Handler id', 'type': 'string'}}}}}}, 'root': {'title': 'Root logger', 'type': 'object', 'properties': {'level': {'title': 'Level', 'type': 'string'}, 'filters': {'title': 'Filters', 'type': 'array', 'items': {'title': 'Filter id', 'type': 'string'}}, 'handlers': {'title': 'Handlers', 'type': 'array', 'items': {'title': 'Handler id', 'type': 'string'}}}}, 'incremental': {'title': 'Incremental configuration', 'type': 'boolean', 'default': False}, 'disable_existing_loggers': {'title': 'Disable existing loggers', 'type': 'boolean', 'default': True}}}, 'hat-gateway://iec103.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://iec103.yaml', '$defs': {'master': {'type': 'object', 'required': ['name', 'port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'flow_control', 'silent_interval', 'reconnect_delay', 'remote_devices'], 'properties': {'name': {'type': 'string'}, 'port': {'type': 'string'}, 'baudrate': {'type': 'integer'}, 'bytesize': {'enum': ['FIVEBITS', 'SIXBITS', 'SEVENBITS', 'EIGHTBITS']}, 'parity': {'enum': ['NONE', 'EVEN', 'ODD', 'MARK', 'SPACE']}, 'stopbits': {'enum': ['ONE', 'ONE_POINT_FIVE', 'TWO']}, 'flow_control': {'type': 'object', 'required': ['xonxoff', 'rtscts', 'dsrdtr'], 'properties': {'xonxoff': {'type': 'boolean'}, 'rtscts': {'type': 'boolean'}, 'dsrdtr': {'type': 'boolean'}}}, 'silent_interval': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}, 'remote_devices': {'type': 'array', 'items': {'type': 'object', 'required': ['address', 'response_timeout', 'send_retry_count', 'poll_class1_delay', 'poll_class2_delay', 'reconnect_delay', 'time_sync_delay'], 'properties': {'address': {'type': 'integer'}, 'response_timeout': {'type': 'number'}, 'send_retry_count': {'type': 'integer'}, 'poll_class1_delay': {'type': ['null', 'number']}, 'poll_class2_delay': {'type': ['null', 'number']}, 'reconnect_delay': {'type': 'number'}, 'time_sync_delay': {'type': ['null', 'number']}}}}}}, 'events': {'master': {'gateway': {'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'data': {'type': 'object', 'required': ['cause', 'value'], 'properties': {'cause': {'oneOf': [{'enum': ['SPONTANEOUS', 'CYCLIC', 'TEST_MODE', 'GENERAL_INTERROGATION', 'LOCAL_OPERATION', 'REMOTE_OPERATION']}, {'type': 'integer', 'description': 'other cause in range [0, 255]\n'}]}, 'value': {'oneOf': [{'$ref': 'hat-gateway://iec103.yaml#/$defs/values/double'}, {'$ref': 'hat-gateway://iec103.yaml#/$defs/values/measurand'}]}}}, 'command': {'type': 'object', 'required': ['session_id', 'success'], 'properties': {'success': {'type': 'boolean'}}}}, 'system': {'enable': {'type': 'boolean'}, 'command': {'type': 'object', 'required': ['session_id', 'value'], 'properties': {'value': {'$ref': 'hat-gateway://iec103.yaml#/$defs/values/double'}}}}}}, 'values': {'double': {'enum': ['TRANSIENT', 'OFF', 'ON', 'ERROR']}, 'measurand': {'type': 'object', 'required': ['overflow', 'invalid', 'value'], 'properties': {'overflow': {'type': 'boolean'}, 'invalid': {'type': 'boolean'}, 'value': {'type': 'number'}}}}}}, 'hat-gateway://modbus.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://modbus.yaml', 'title': 'Modbus devices', '$defs': {'master': {'type': 'object', 'title': 'Modbus master', 'required': ['name', 'connection', 'remote_devices'], 'properties': {'name': {'type': 'string'}, 'connection': {'type': 'object', 'required': ['modbus_type', 'transport', 'connect_timeout', 'connect_delay', 'request_timeout', 'request_delay', 'request_retry_immediate_count', 'request_retry_delayed_count', 'request_retry_delay'], 'properties': {'modbus_type': {'description': 'Modbus message encoding type\n', 'enum': ['TCP', 'RTU', 'ASCII']}, 'transport': {'oneOf': [{'type': 'object', 'required': ['type', 'host', 'port'], 'properties': {'type': {'const': 'TCP'}, 'host': {'type': 'string', 'description': 'Remote host name\n'}, 'port': {'type': 'integer', 'description': 'Remote host TCP port\n', 'default': 502}}}, {'type': 'object', 'required': ['type', 'port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'flow_control', 'silent_interval'], 'properties': {'type': {'const': 'SERIAL'}, 'port': {'type': 'string', 'description': 'Serial port name (e.g. /dev/ttyS0)\n'}, 'baudrate': {'type': 'integer', 'description': 'Baud rate (e.g. 9600)\n'}, 'bytesize': {'description': 'Number of data bits\n', 'enum': ['FIVEBITS', 'SIXBITS', 'SEVENBITS', 'EIGHTBITS']}, 'parity': {'description': 'Parity checking\n', 'enum': ['NONE', 'EVEN', 'ODD', 'MARK', 'SPACE']}, 'stopbits': {'description': 'Number of stop bits\n', 'enum': ['ONE', 'ONE_POINT_FIVE', 'TWO']}, 'flow_control': {'type': 'object', 'required': ['xonxoff', 'rtscts', 'dsrdtr'], 'properties': {'xonxoff': {'type': 'boolean', 'description': 'Enable software flow control\n'}, 'rtscts': {'type': 'boolean', 'description': 'Enable hardware (RTS/CTS) flow control\n'}, 'dsrdtr': {'type': 'boolean', 'description': 'Enable hardware (DSR/DTR) flow control\n'}}}, 'silent_interval': {'type': 'number', 'description': 'Serial communication silent interval\n'}}}]}, 'connect_timeout': {'type': 'number', 'description': 'Maximum number of seconds available to single connection\nattempt\n'}, 'connect_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive connection\nestablishment attempts\n'}, 'request_timeout': {'type': 'number', 'description': 'Maximum duration (in seconds) of read or write\nrequest/response exchange.\n'}, 'request_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive requests\n(minimal duration between response and next request)\n'}, 'request_retry_immediate_count': {'type': 'integer', 'description': 'Number of immediate request retries before remote\ndata is considered unavailable. Total number\nof retries is request_retry_immediate_count *\nrequest_retry_delayed_count.\n'}, 'request_retry_delayed_count': {'type': 'integer', 'description': 'Number of delayed request retries before remote data\nis considered unavailable. Total number\nof retries is request_retry_immediate_count *\nrequest_retry_delayed_count.\n'}, 'request_retry_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive delayed\nrequest retries\n'}}}, 'remote_devices': {'type': 'array', 'items': {'type': 'object', 'required': ['device_id', 'timeout_poll_delay', 'data'], 'properties': {'device_id': {'type': 'integer', 'description': 'Modbus device identifier\n'}, 'timeout_poll_delay': {'type': 'number', 'description': 'Delay (in seconds) after read timeout and\nbefore device polling is resumed\n'}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'interval', 'data_type', 'start_address', 'bit_offset', 'bit_count'], 'properties': {'name': {'type': 'string', 'description': 'Data point name\n'}, 'interval': {'type': ['number', 'null'], 'description': 'Polling interval in seconds or\nnull if polling is disabled\n'}, 'data_type': {'description': 'Modbus register type\n', 'enum': ['COIL', 'DISCRETE_INPUT', 'HOLDING_REGISTER', 'INPUT_REGISTER', 'QUEUE']}, 'start_address': {'type': 'integer', 'description': 'Starting address of modbus register\n'}, 'bit_offset': {'type': 'integer', 'description': 'Bit offset (number of bits skipped)\n'}, 'bit_count': {'type': 'integer', 'description': 'Number of bits used for\nencoding/decoding value (not\nincluding offset bits)\n'}}}}}}}}}, 'slave': {'type': 'object', 'required': ['name', 'modbus_type', 'transport', 'data'], 'properties': {'name': {'type': 'string'}, 'modbus_type': {'description': 'Modbus message encoding type\n', 'enum': ['TCP', 'RTU', 'ASCII']}, 'transport': {'oneOf': [{'type': 'object', 'required': ['type', 'local_host', 'local_port', 'remote_hosts', 'max_connections', 'response_timeout', 'keep_alive_timeout'], 'properties': {'type': {'const': 'TCP'}, 'local_host': {'type': 'string', 'description': 'Local host name\n'}, 'local_port': {'type': 'integer', 'description': 'Local host TCP port\n', 'default': 502}, 'remote_hosts': {'type': ['array', 'null'], 'description': 'if null, all remote hosts are allowed\n', 'items': {'type': 'string'}}, 'max_connections': {'type': ['null', 'integer']}, 'response_timeout': {'type': 'number', 'description': 'Maximum duration (in seconds) of write\nrequest/response exchange.\n'}, 'keep_alive_timeout': {'type': ['number', 'null']}}}, {'type': 'object', 'required': ['type', 'port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'flow_control', 'silent_interval', 'response_timeout', 'keep_alive_timeout'], 'properties': {'type': {'const': 'SERIAL'}, 'port': {'type': 'string', 'description': 'Serial port name (e.g. /dev/ttyS0)\n'}, 'baudrate': {'type': 'integer', 'description': 'Baud rate (e.g. 9600)\n'}, 'bytesize': {'description': 'Number of data bits\n', 'enum': ['FIVEBITS', 'SIXBITS', 'SEVENBITS', 'EIGHTBITS']}, 'parity': {'description': 'Parity checking\n', 'enum': ['NONE', 'EVEN', 'ODD', 'MARK', 'SPACE']}, 'stopbits': {'description': 'Number of stop bits\n', 'enum': ['ONE', 'ONE_POINT_FIVE', 'TWO']}, 'flow_control': {'type': 'object', 'required': ['xonxoff', 'rtscts', 'dsrdtr'], 'properties': {'xonxoff': {'type': 'boolean', 'description': 'Enable software flow control\n'}, 'rtscts': {'type': 'boolean', 'description': 'Enable hardware (RTS/CTS) flow control\n'}, 'dsrdtr': {'type': 'boolean', 'description': 'Enable hardware (DSR/DTR) flow control\n'}}}, 'silent_interval': {'type': 'number', 'description': 'Serial communication silent interval\n'}, 'response_timeout': {'type': 'number', 'description': 'Maximum duration (in seconds) of write\nrequest/response exchange.\n'}, 'keep_alive_timeout': {'type': 'number'}}}]}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'device_id', 'data_type', 'start_address', 'bit_offset', 'bit_count'], 'properties': {'name': {'type': 'string', 'description': 'Data point name\n'}, 'device_id': {'type': 'integer'}, 'data_type': {'description': 'Modbus register type\n', 'enum': ['COIL', 'DISCRETE_INPUT', 'HOLDING_REGISTER', 'INPUT_REGISTER', 'QUEUE']}, 'start_address': {'type': 'integer', 'description': 'Starting address of modbus register\n'}, 'bit_offset': {'type': 'integer', 'description': 'Bit offset (number of bits skipped)\n'}, 'bit_count': {'type': 'integer', 'description': 'Number of bits used for\nencoding/decoding value (not\nincluding offset bits)\n'}}}}}}, 'events': {'master': {'gateway': {'status': {'enum': ['DISCONNECTED', 'CONNECTING', 'CONNECTED']}, 'remote_device_status': {'enum': ['DISABLED', 'CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'read': {'type': 'object', 'required': ['result'], 'properties': {'result': {'enum': ['SUCCESS', 'INVALID_FUNCTION_CODE', 'INVALID_DATA_ADDRESS', 'INVALID_DATA_VALUE', 'FUNCTION_ERROR', 'GATEWAY_PATH_UNAVAILABLE', 'GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND']}, 'value': {'type': 'integer'}, 'cause': {'enum': ['INTERROGATE', 'CHANGE']}}}, 'write': {'type': 'object', 'required': ['request_id', 'result'], 'properties': {'request_id': {'type': 'string'}, 'result': {'enum': ['SUCCESS', 'INVALID_FUNCTION_CODE', 'INVALID_DATA_ADDRESS', 'INVALID_DATA_VALUE', 'FUNCTION_ERROR', 'GATEWAY_PATH_UNAVAILABLE', 'GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND', 'TIMEOUT']}}}}, 'system': {'enable': {'type': 'boolean'}, 'write': {'type': 'object', 'required': ['request_id', 'value'], 'properties': {'request_id': {'type': 'string'}, 'value': {'type': 'integer'}}}}}, 'slave': {'gateway': {'connections': {'type': 'array', 'items': {'oneOf': [{'type': 'object', 'required': ['type', 'connection_id'], 'properties': {'type': {'const': 'SERIAL'}, 'connection_id': {'type': 'integer'}}}, {'type': 'object', 'required': ['type', 'connection_id', 'local', 'remote'], 'properties': {'type': {'const': 'TCP'}, 'connection_id': {'type': 'integer'}, 'local': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}, 'remote': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}}}]}}, 'write': {'type': 'object', 'required': ['request_id', 'connection_id', 'data'], 'properties': {'request_id': {'type': 'string'}, 'connection_id': {'type': 'integer'}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'value'], 'properties': {'name': {'type': 'string'}, 'value': {'type': 'integer'}}}}}}}, 'system': {'data': {'type': 'object', 'required': ['value'], 'properties': {'value': {'type': 'integer'}}}, 'write': {'type': 'object', 'required': ['request_id', 'success'], 'properties': {'request_id': {'type': 'string'}, 'success': {'type': 'boolean'}}}}}}}}, 'hat-gateway://smpp.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://smpp.yaml', '$defs': {'client': {'type': 'object', 'required': ['name', 'remote_address', 'ssl', 'system_id', 'password', 'enquire_link_delay', 'enquire_link_timeout', 'connect_timeout', 'reconnect_delay', 'short_message', 'priority', 'data_coding', 'message_encoding', 'message_timeout'], 'properties': {'name': {'type': 'string'}, 'remote_address': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}, 'ssl': {'type': 'boolean'}, 'system_id': {'type': 'string'}, 'password': {'type': 'string'}, 'enquire_link_delay': {'type': ['null', 'number']}, 'enquire_link_timeout': {'type': 'number'}, 'connect_timeout': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}, 'short_message': {'type': 'boolean'}, 'priority': {'enum': ['BULK', 'NORMAL', 'URGENT', 'VERY_URGENT']}, 'data_coding': {'enum': ['DEFAULT', 'ASCII', 'UNSPECIFIED_1', 'LATIN_1', 'UNSPECIFIED_2', 'JIS', 'CYRLLIC', 'LATIN_HEBREW', 'UCS2', 'PICTOGRAM', 'MUSIC', 'EXTENDED_KANJI', 'KS']}, 'message_encoding': {'type': 'string'}, 'message_timeout': {'type': 'number'}}}, 'events': {'client': {'gateway': {'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}}, 'system': {'message': {'type': 'object', 'required': ['address', 'message'], 'properties': {'address': {'type': 'string'}, 'message': {'type': 'string'}}}}}}}}, 'hat-gateway://main.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://main.yaml', 'title': 'Gateway', 'description': "Gateway's configuration", 'type': 'object', 'required': ['name', 'event_server', 'devices'], 'properties': {'type': {'const': 'gateway', 'description': 'configuration type identification'}, 'version': {'type': 'string', 'description': 'component version'}, 'log': {'$ref': 'hat-json://logging.yaml'}, 'name': {'type': 'string', 'description': 'component name'}, 'event_server': {'allOf': [{'type': 'object', 'properties': {'require_operational': {'type': 'boolean'}}}, {'oneOf': [{'type': 'object', 'required': ['monitor_component'], 'properties': {'monitor_component': {'type': 'object', 'required': ['host', 'port', 'gateway_group', 'event_server_group'], 'properties': {'host': {'type': 'string', 'default': '127.0.0.1'}, 'port': {'type': 'integer', 'default': 23010}, 'gateway_group': {'type': 'string'}, 'event_server_group': {'type': 'string'}}}}}, {'type': 'object', 'required': ['eventer_server'], 'properties': {'eventer_server': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string', 'default': '127.0.0.1'}, 'port': {'type': 'integer', 'default': 23012}}}}}]}]}, 'devices': {'type': 'array', 'items': {'$ref': 'hat-gateway://main.yaml#/$defs/device'}}, 'adminer_server': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string', 'default': '127.0.0.1'}, 'port': {'type': 'integer', 'default': 23016}}}}, '$defs': {'device': {'type': 'object', 'description': 'structure of device configuration depends on device type\n', 'required': ['module', 'name'], 'properties': {'module': {'type': 'string', 'description': 'full python module name that implements device\n'}, 'name': {'type': 'string'}}}}}, 'hat-gateway://iec101.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://iec101.yaml', '$defs': {'master': {'allOf': [{'type': 'object', 'required': ['name', 'port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'flow_control', 'silent_interval', 'cause_size', 'asdu_address_size', 'io_address_size', 'reconnect_delay'], 'properties': {'name': {'type': 'string'}, 'port': {'type': 'string'}, 'baudrate': {'type': 'integer'}, 'bytesize': {'enum': ['FIVEBITS', 'SIXBITS', 'SEVENBITS', 'EIGHTBITS']}, 'parity': {'enum': ['NONE', 'EVEN', 'ODD', 'MARK', 'SPACE']}, 'stopbits': {'enum': ['ONE', 'ONE_POINT_FIVE', 'TWO']}, 'flow_control': {'type': 'object', 'required': ['xonxoff', 'rtscts', 'dsrdtr'], 'properties': {'xonxoff': {'type': 'boolean'}, 'rtscts': {'type': 'boolean'}, 'dsrdtr': {'type': 'boolean'}}}, 'silent_interval': {'type': 'number'}, 'cause_size': {'enum': ['ONE', 'TWO']}, 'asdu_address_size': {'enum': ['ONE', 'TWO']}, 'io_address_size': {'enum': ['ONE', 'TWO', 'THREE']}, 'reconnect_delay': {'type': 'number'}}}, {'oneOf': [{'type': 'object', 'required': ['link_type', 'device_address_size', 'remote_devices'], 'properties': {'link_type': {'const': 'BALANCED'}, 'device_address_size': {'enum': ['ZERO', 'ONE', 'TWO']}, 'remote_devices': {'type': 'array', 'items': {'type': 'object', 'required': ['direction', 'address', 'response_timeout', 'send_retry_count', 'status_delay', 'reconnect_delay', 'time_sync_delay'], 'properties': {'direction': {'enum': ['A_TO_B', 'B_TO_A']}, 'address': {'type': 'integer'}, 'response_timeout': {'type': 'number'}, 'send_retry_count': {'type': 'integer'}, 'status_delay': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}, 'time_sync_delay': {'type': ['null', 'number']}}}}}}, {'type': 'object', 'required': ['link_type', 'device_address_size', 'remote_devices'], 'properties': {'link_type': {'const': 'UNBALANCED'}, 'device_address_size': {'enum': ['ONE', 'TWO']}, 'remote_devices': {'type': 'array', 'items': {'type': 'object', 'required': ['address', 'response_timeout', 'send_retry_count', 'poll_class1_delay', 'poll_class2_delay', 'reconnect_delay', 'time_sync_delay'], 'properties': {'address': {'type': 'integer'}, 'response_timeout': {'type': 'number'}, 'send_retry_count': {'type': 'integer'}, 'poll_class1_delay': {'type': ['null', 'number']}, 'poll_class2_delay': {'type': ['null', 'number']}, 'reconnect_delay': {'type': 'number'}, 'time_sync_delay': {'type': ['null', 'number']}}}}}}]}]}, 'slave': {'allOf': [{'type': 'object', 'required': ['port', 'baudrate', 'bytesize', 'parity', 'stopbits', 'flow_control', 'silent_interval', 'cause_size', 'asdu_address_size', 'io_address_size', 'buffers', 'data'], 'properties': {'port': {'type': 'string'}, 'baudrate': {'type': 'integer'}, 'bytesize': {'enum': ['FIVEBITS', 'SIXBITS', 'SEVENBITS', 'EIGHTBITS']}, 'parity': {'enum': ['NONE', 'EVEN', 'ODD', 'MARK', 'SPACE']}, 'stopbits': {'enum': ['ONE', 'ONE_POINT_FIVE', 'TWO']}, 'flow_control': {'type': 'object', 'required': ['xonxoff', 'rtscts', 'dsrdtr'], 'properties': {'xonxoff': {'type': 'boolean'}, 'rtscts': {'type': 'boolean'}, 'dsrdtr': {'type': 'boolean'}}}, 'silent_interval': {'type': 'number'}, 'cause_size': {'enum': ['ONE', 'TWO']}, 'asdu_address_size': {'enum': ['ONE', 'TWO']}, 'io_address_size': {'enum': ['ONE', 'TWO', 'THREE']}, 'buffers': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'size'], 'properties': {'name': {'type': 'string'}, 'size': {'type': 'integer'}}}}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['data_type', 'asdu_address', 'io_address', 'buffer', 'with_ack'], 'properties': {'data_type': {'enum': ['SINGLE', 'DOUBLE', 'STEP_POSITION', 'BITSTRING', 'NORMALIZED', 'SCALED', 'FLOATING', 'BINARY_COUNTER', 'PROTECTION', 'PROTECTION_START', 'PROTECTION_COMMAND', 'STATUS']}, 'asdu_address': {'type': 'integer'}, 'io_address': {'type': 'integer'}, 'buffer': {'type': ['null', 'string']}, 'with_ack': {'type': 'boolean'}}}}}}, {'oneOf': [{'type': 'object', 'required': ['link_type', 'device_address_size', 'devices'], 'properties': {'link_type': {'const': 'BALANCED'}, 'device_address_size': {'enum': ['ZERO', 'ONE', 'TWO']}, 'devices': {'type': 'array', 'items': {'type': 'object', 'required': ['direction', 'address', 'response_timeout', 'send_retry_count', 'status_delay', 'reconnect_delay'], 'properties': {'direction': {'enum': ['A_TO_B', 'B_TO_A']}, 'address': {'type': 'integer'}, 'response_timeout': {'type': 'number'}, 'send_retry_count': {'type': 'integer'}, 'status_delay': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}}}}}}, {'type': 'object', 'required': ['link_type', 'device_address_size', 'devices'], 'properties': {'link_type': {'const': 'UNBALANCED'}, 'device_address_size': {'enum': ['ONE', 'TWO']}, 'devices': {'type': 'array', 'items': {'type': 'object', 'required': ['address', 'keep_alive_timeout', 'reconnect_delay'], 'properties': {'address': {'type': 'integer'}, 'keep_alive_timeout': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}}}}}}]}]}, 'events': {'master': {'gateway': {'status': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/status'}, 'data': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/data/res'}, 'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/command/res'}, 'interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/interrogation/res'}, 'counter_interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/counter_interrogation/res'}}, 'system': {'enable': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/enable'}, 'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/command/req'}, 'interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/interrogation/req'}, 'counter_interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/counter_interrogation/req'}}}, 'slave': {'gateway': {'connections': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/connections'}, 'command': {'allOf': [{'type': 'object', 'required': ['connection_id'], 'properties': {'connection_id': {'type': 'integer'}}}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/command/req'}]}}, 'system': {'data': {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/data/res'}, 'command': {'allOf': [{'type': 'object', 'required': ['connection_id'], 'properties': {'connection_id': {'type': 'integer'}}}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/command/res'}]}}}}, 'messages': {'enable': {'type': 'boolean'}, 'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'connections': {'type': 'array', 'items': {'type': 'object', 'required': ['connection_id', 'address'], 'properties': {'connection_id': {'type': 'integer'}, 'address': {'type': 'integer'}}}}, 'data': {'res': {'type': 'object', 'required': ['is_test', 'cause', 'data'], 'properties': {'is_test': {'type': 'boolean'}, 'cause': {'$ref': 'hat-gateway://iec101.yaml#/$defs/causes/data/res'}, 'data': {'oneOf': [{'$ref': 'hat-gateway://iec101.yaml#/$defs/data/single'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/double'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/step_position'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/bitstring'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/normalized'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/scaled'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/floating'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/binary_counter'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/protection'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/protection_start'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/protection_command'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/data/status'}]}}}}, 'command': {'req': {'type': 'object', 'required': ['is_test', 'cause', 'command'], 'properties': {'is_test': {'type': 'boolean'}, 'cause': {'$ref': 'hat-gateway://iec101.yaml#/$defs/causes/command/req'}, 'command': {'oneOf': [{'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/single'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/double'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/regulating'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/normalized'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/scaled'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/floating'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/bitstring'}]}}}, 'res': {'type': 'object', 'required': ['is_test', 'is_negative_confirm', 'cause', 'command'], 'properties': {'is_test': {'type': 'boolean'}, 'is_negative_confirm': {'type': 'boolean'}, 'cause': {'$ref': 'hat-gateway://iec101.yaml#/$defs/causes/command/res'}, 'command': {'oneOf': [{'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/single'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/double'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/regulating'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/normalized'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/scaled'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/floating'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/commands/bitstring'}]}}}}, 'interrogation': {'req': {'type': 'object', 'required': ['is_test', 'request', 'cause'], 'properties': {'is_test': {'type': 'boolean'}, 'request': {'type': 'integer', 'description': 'request in range [0, 255]\n'}, 'cause': {'$ref': 'hat-gateway://iec101.yaml#/$defs/causes/command/req'}}}, 'res': {'type': 'object', 'required': ['is_test', 'is_negative_confirm', 'request', 'cause'], 'properties': {'is_test': {'type': 'boolean'}, 'is_negative_confirm': {'type': 'boolean'}, 'request': {'type': 'integer', 'description': 'request in range [0, 255]\n'}, 'cause': {'$ref': 'hat-gateway://iec101.yaml#/$defs/causes/command/res'}}}}, 'counter_interrogation': {'req': {'allOf': [{'type': 'object', 'required': ['freeze'], 'properties': {'freeze': {'enum': ['READ', 'FREEZE', 'FREEZE_AND_RESET', 'RESET']}}}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/interrogation/req'}]}, 'res': {'allOf': [{'type': 'object', 'required': ['freeze'], 'properties': {'freeze': {'enum': ['READ', 'FREEZE', 'FREEZE_AND_RESET', 'RESET']}}}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/messages/interrogation/res'}]}}}, 'data': {'single': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/single'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/indication'}}}, 'double': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/double'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/indication'}}}, 'step_position': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/step_position'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}}}, 'bitstring': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/bitstring'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}}}, 'normalized': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/normalized'}, 'quality': {'oneOf': [{'type': 'null'}, {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}]}}}, 'scaled': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/scaled'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}}}, 'floating': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/floating'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}}}, 'binary_counter': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/binary_counter'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/counter'}}}, 'protection': {'type': 'object', 'required': ['value', 'quality', 'elapsed_time'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/protection'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/protection'}, 'elapsed_time': {'type': 'integer', 'description': 'elapsed_time in range [0, 65535]\n'}}}, 'protection_start': {'type': 'object', 'required': ['value', 'quality', 'duration_time'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/protection_start'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/protection'}, 'duration_time': {'type': 'integer', 'description': 'duration_time in range [0, 65535]\n'}}}, 'protection_command': {'type': 'object', 'required': ['value', 'quality', 'operating_time'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/protection_command'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/protection'}, 'operating_time': {'type': 'integer', 'description': 'operating_time in range [0, 65535]\n'}}}, 'status': {'type': 'object', 'required': ['value', 'quality'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/status'}, 'quality': {'$ref': 'hat-gateway://iec101.yaml#/$defs/qualities/measurement'}}}}, 'commands': {'single': {'type': 'object', 'required': ['value', 'select', 'qualifier'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/single'}, 'select': {'type': 'boolean'}, 'qualifier': {'type': 'integer', 'description': 'qualifier in range [0, 31]\n'}}}, 'double': {'type': 'object', 'required': ['value', 'select', 'qualifier'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/double'}, 'select': {'type': 'boolean'}, 'qualifier': {'type': 'integer', 'description': 'qualifier in range [0, 31]\n'}}}, 'regulating': {'type': 'object', 'required': ['value', 'select', 'qualifier'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/regulating'}, 'select': {'type': 'boolean'}, 'qualifier': {'type': 'integer', 'description': 'qualifier in range [0, 31]\n'}}}, 'normalized': {'type': 'object', 'required': ['value', 'select'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/normalized'}, 'select': {'type': 'boolean'}}}, 'scaled': {'type': 'object', 'required': ['value', 'select'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/scaled'}, 'select': {'type': 'boolean'}}}, 'floating': {'type': 'object', 'required': ['value', 'select'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/floating'}, 'select': {'type': 'boolean'}}}, 'bitstring': {'type': 'object', 'required': ['value'], 'properties': {'value': {'$ref': 'hat-gateway://iec101.yaml#/$defs/values/bitstring'}}}}, 'values': {'single': {'enum': ['OFF', 'ON']}, 'double': {'enum': ['INTERMEDIATE', 'OFF', 'ON', 'FAULT']}, 'regulating': {'enum': ['LOWER', 'HIGHER']}, 'step_position': {'type': 'object', 'required': ['value', 'transient'], 'properties': {'value': {'type': 'integer', 'description': 'value in range [-64, 63]\n'}, 'transient': {'type': 'boolean'}}}, 'bitstring': {'type': 'array', 'description': 'bitstring encoded as 4 bytes\n', 'items': {'type': 'integer'}}, 'normalized': {'type': 'number', 'description': 'value in range [-1.0, 1.0)\n'}, 'scaled': {'type': 'integer', 'description': 'value in range [-2^15, 2^15-1]\n'}, 'floating': {'oneOf': [{'type': 'number'}, {'enum': ['nan', 'inf', '-inf']}]}, 'binary_counter': {'type': 'integer', 'description': 'value in range [-2^31, 2^31-1]\n'}, 'protection': {'enum': ['OFF', 'ON']}, 'protection_start': {'type': 'object', 'required': ['general', 'l1', 'l2', 'l3', 'ie', 'reverse'], 'properties': {'general': {'type': 'boolean'}, 'l1': {'type': 'boolean'}, 'l2': {'type': 'boolean'}, 'l3': {'type': 'boolean'}, 'ie': {'type': 'boolean'}, 'reverse': {'type': 'boolean'}}}, 'protection_command': {'type': 'object', 'required': ['general', 'l1', 'l2', 'l3'], 'properties': {'general': {'type': 'boolean'}, 'l1': {'type': 'boolean'}, 'l2': {'type': 'boolean'}, 'l3': {'type': 'boolean'}}}, 'status': {'type': 'object', 'required': ['value', 'change'], 'properties': {'value': {'type': 'array', 'description': 'value length is 16\n', 'items': {'type': 'boolean'}}, 'change': {'type': 'array', 'description': 'change length is 16\n', 'items': {'type': 'boolean'}}}}}, 'qualities': {'indication': {'type': 'object', 'required': ['invalid', 'not_topical', 'substituted', 'blocked'], 'properties': {'invalid': {'type': 'boolean'}, 'not_topical': {'type': 'boolean'}, 'substituted': {'type': 'boolean'}, 'blocked': {'type': 'boolean'}}}, 'measurement': {'type': 'object', 'required': ['invalid', 'not_topical', 'substituted', 'blocked', 'overflow'], 'properties': {'invalid': {'type': 'boolean'}, 'not_topical': {'type': 'boolean'}, 'substituted': {'type': 'boolean'}, 'blocked': {'type': 'boolean'}, 'overflow': {'type': 'boolean'}}}, 'counter': {'type': 'object', 'required': ['invalid', 'adjusted', 'overflow', 'sequence'], 'properties': {'invalid': {'type': 'boolean'}, 'adjusted': {'type': 'boolean'}, 'overflow': {'type': 'boolean'}, 'sequence': {'type': 'boolean'}}}, 'protection': {'type': 'object', 'required': ['invalid', 'not_topical', 'substituted', 'blocked', 'time_invalid'], 'properties': {'invalid': {'type': 'boolean'}, 'not_topical': {'type': 'boolean'}, 'substituted': {'type': 'boolean'}, 'blocked': {'type': 'boolean'}, 'time_invalid': {'type': 'boolean'}}}}, 'causes': {'data': {'res': {'oneOf': [{'enum': ['PERIODIC', 'BACKGROUND_SCAN', 'SPONTANEOUS', 'REQUEST', 'REMOTE_COMMAND', 'LOCAL_COMMAND', 'INTERROGATED_STATION', 'INTERROGATED_GROUP01', 'INTERROGATED_GROUP02', 'INTERROGATED_GROUP03', 'INTERROGATED_GROUP04', 'INTERROGATED_GROUP05', 'INTERROGATED_GROUP06', 'INTERROGATED_GROUP07', 'INTERROGATED_GROUP08', 'INTERROGATED_GROUP09', 'INTERROGATED_GROUP10', 'INTERROGATED_GROUP11', 'INTERROGATED_GROUP12', 'INTERROGATED_GROUP13', 'INTERROGATED_GROUP14', 'INTERROGATED_GROUP15', 'INTERROGATED_GROUP16', 'INTERROGATED_COUNTER', 'INTERROGATED_COUNTER01', 'INTERROGATED_COUNTER02', 'INTERROGATED_COUNTER03', 'INTERROGATED_COUNTER04']}, {'type': 'integer', 'description': 'other cause in range [0, 63]\n'}]}}, 'command': {'req': {'oneOf': [{'enum': ['ACTIVATION', 'DEACTIVATION']}, {'type': 'integer', 'description': 'other cause in range [0, 63]\n'}]}, 'res': {'oneOf': [{'enum': ['ACTIVATION_CONFIRMATION', 'DEACTIVATION_CONFIRMATION', 'ACTIVATION_TERMINATION', 'UNKNOWN_TYPE', 'UNKNOWN_CAUSE', 'UNKNOWN_ASDU_ADDRESS', 'UNKNOWN_IO_ADDRESS']}, {'type': 'integer', 'description': 'other cause in range [0, 63]\n'}]}}}}}, 'hat-gateway://iec104.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://iec104.yaml', '$defs': {'master': {'type': 'object', 'required': ['name', 'remote_addresses', 'response_timeout', 'supervisory_timeout', 'test_timeout', 'send_window_size', 'receive_window_size', 'reconnect_delay', 'time_sync_delay', 'security'], 'properties': {'name': {'type': 'string'}, 'remote_addresses': {'type': 'array', 'items': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}}, 'response_timeout': {'type': 'number'}, 'supervisory_timeout': {'type': 'number'}, 'test_timeout': {'type': 'number'}, 'send_window_size': {'type': 'integer'}, 'receive_window_size': {'type': 'integer'}, 'reconnect_delay': {'type': 'number'}, 'time_sync_delay': {'type': ['null', 'number']}, 'security': {'oneOf': [{'type': 'null'}, {'$ref': 'hat-gateway://iec104.yaml#/$defs/security'}]}}}, 'slave': {'type': 'object', 'required': ['local_host', 'local_port', 'remote_hosts', 'max_connections', 'response_timeout', 'supervisory_timeout', 'test_timeout', 'send_window_size', 'receive_window_size', 'security', 'buffers', 'data'], 'properties': {'local_host': {'type': 'string'}, 'local_port': {'type': 'integer'}, 'remote_hosts': {'type': ['array', 'null'], 'description': 'if null, all remote hosts are allowed\n', 'items': {'type': 'string'}}, 'max_connections': {'type': ['null', 'integer']}, 'response_timeout': {'type': 'number'}, 'supervisory_timeout': {'type': 'number'}, 'test_timeout': {'type': 'number'}, 'send_window_size': {'type': 'integer'}, 'receive_window_size': {'type': 'integer'}, 'security': {'oneOf': [{'type': 'null'}, {'$ref': 'hat-gateway://iec104.yaml#/$defs/security'}]}, 'buffers': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'size'], 'properties': {'name': {'type': 'string'}, 'size': {'type': 'integer'}}}}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['data_type', 'asdu_address', 'io_address', 'buffer'], 'properties': {'data_type': {'enum': ['SINGLE', 'DOUBLE', 'STEP_POSITION', 'BITSTRING', 'NORMALIZED', 'SCALED', 'FLOATING', 'BINARY_COUNTER', 'PROTECTION', 'PROTECTION_START', 'PROTECTION_COMMAND', 'STATUS']}, 'asdu_address': {'type': 'integer'}, 'io_address': {'type': 'integer'}, 'buffer': {'type': ['null', 'string']}}}}}}, 'events': {'master': {'gateway': {'status': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/gateway/status'}, 'data': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/gateway/data'}, 'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/gateway/command'}, 'interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/gateway/interrogation'}, 'counter_interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/gateway/counter_interrogation'}}, 'system': {'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/system/command'}, 'interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/system/interrogation'}, 'counter_interrogation': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/master/system/counter_interrogation'}}}, 'slave': {'gateway': {'connections': {'$ref': 'hat-gateway://iec104.yaml#/$defs/messages/connections'}, 'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/slave/gateway/command'}}, 'system': {'data': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/slave/system/data'}, 'command': {'$ref': 'hat-gateway://iec101.yaml#/$defs/events/slave/system/command'}}}}, 'messages': {'connections': {'type': 'array', 'items': {'type': 'object', 'required': ['connection_id', 'local', 'remote'], 'properties': {'connection_id': {'type': 'integer'}, 'local': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}, 'remote': {'type': 'object', 'required': ['host', 'port'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}}}}}}}, 'security': {'type': 'object', 'required': ['cert_path', 'key_path', 'verify_cert', 'ca_path'], 'properties': {'cert_path': {'type': 'string'}, 'key_path': {'type': ['null', 'string']}, 'verify_cert': {'type': 'boolean'}, 'ca_path': {'type': ['null', 'string']}, 'strict_mode': {'type': 'boolean'}, 'renegotiate_delay': {'type': ['null', 'number']}}}}}, 'hat-gateway://ping.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://ping.yaml', '$defs': {'device': {'type': 'object', 'required': ['name', 'remote_devices'], 'properties': {'name': {'type': 'string'}, 'remote_devices': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'host', 'ping_delay', 'ping_timeout', 'retry_count', 'retry_delay'], 'properties': {'name': {'type': 'string'}, 'host': {'type': 'string'}, 'ping_delay': {'type': 'number'}, 'ping_timeout': {'type': 'number'}, 'retry_count': {'type': 'number'}, 'retry_delay': {'type': 'number'}}}}}}, 'events': {'status': {'enum': ['AVAILABLE', 'NOT_AVAILABLE']}}}}, 'hat-gateway://iec61850.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://iec61850.yaml', '$defs': {'client': {'type': 'object', 'required': ['name', 'connection', 'value_types', 'datasets', 'rcbs', 'data', 'commands', 'changes'], 'properties': {'name': {'type': 'string'}, 'connection': {'type': 'object', 'required': ['host', 'port', 'connect_timeout', 'reconnect_delay', 'response_timeout', 'status_delay', 'status_timeout'], 'properties': {'host': {'type': 'string'}, 'port': {'type': 'integer'}, 'connect_timeout': {'type': 'number'}, 'reconnect_delay': {'type': 'number'}, 'response_timeout': {'type': 'number'}, 'status_delay': {'type': 'number'}, 'status_timeout': {'type': 'number'}, 'local_tsel': {'type': 'integer'}, 'remote_tsel': {'type': 'integer'}, 'local_ssel': {'type': 'integer'}, 'remote_ssel': {'type': 'integer'}, 'local_psel': {'type': 'integer'}, 'remote_psel': {'type': 'integer'}, 'local_ap_title': {'type': 'array', 'items': {'type': 'integer'}}, 'remote_ap_title': {'type': 'array', 'items': {'type': 'integer'}}, 'local_ae_qualifier': {'type': 'integer'}, 'remote_ae_qualifier': {'type': 'integer'}, 'local_detail_calling': {'type': 'integer'}}}, 'value_types': {'type': 'array', 'items': {'type': 'object', 'required': ['logical_device', 'logical_node', 'fc', 'name', 'type'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'fc': {'type': 'string'}, 'name': {'type': 'string'}, 'type': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value_type'}}}}, 'datasets': {'type': 'array', 'items': {'type': 'object', 'required': ['ref', 'values', 'dynamic'], 'properties': {'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset'}, 'values': {'type': 'array', 'items': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}}, 'dynamic': {'type': 'boolean'}}}}, 'rcbs': {'type': 'array', 'items': {'type': 'object', 'required': ['ref', 'report_id', 'dataset'], 'properties': {'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/rcb'}, 'report_id': {'type': 'string'}, 'dataset': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset'}, 'trigger_options': {'type': 'array', 'items': {'enum': ['DATA_CHANGE', 'QUALITY_CHANGE', 'DATA_UPDATE', 'INTEGRITY', 'GENERAL_INTERROGATION']}}, 'optional_fields': {'type': 'array', 'items': {'enum': ['SEQUENCE_NUMBER', 'REPORT_TIME_STAMP', 'REASON_FOR_INCLUSION', 'DATA_SET_NAME', 'DATA_REFERENCE', 'BUFFER_OVERFLOW', 'ENTRY_ID', 'CONF_REVISION']}}, 'conf_revision': {'type': 'integer'}, 'buffer_time': {'type': 'integer'}, 'integrity_period': {'type': 'integer'}, 'purge_buffer': {'type': 'boolean'}, 'reservation_time': {'type': 'integer'}}}}, 'data': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'rcb', 'value'], 'properties': {'name': {'type': 'string'}, 'rcb': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/rcb'}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'quality': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'timestamp': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'selected': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}}}}, 'commands': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'model', 'ref', 'with_operate_time'], 'properties': {'name': {'type': 'string'}, 'model': {'enum': ['DIRECT_WITH_NORMAL_SECURITY', 'SBO_WITH_NORMAL_SECURITY', 'DIRECT_WITH_ENHANCED_SECURITY', 'SBO_WITH_ENHANCED_SECURITY']}, 'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/command'}, 'with_operate_time': {'type': 'boolean'}}}}, 'changes': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'ref'], 'properties': {'name': {'type': 'string'}, 'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}}}}}}, 'events': {'client': {'gateway': {'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'data': {'type': 'object', 'required': ['reasons'], 'properties': {'reasons': {'type': 'array', 'items': {'enum': ['DATA_CHANGE', 'QUALITY_CHANGE', 'DATA_UPDATE', 'INTEGRITY', 'GENERAL_INTERROGATION', 'APPLICATION_TRIGGER']}}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}, 'quality': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/quality'}, 'timestamp': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/timestamp'}, 'selected': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/boolean'}}}, 'command': {'allOf': [{'type': 'object', 'required': ['session_id', 'action'], 'properties': {'session_id': {'type': 'string'}, 'action': {'enum': ['SELECT', 'CANCEL', 'OPERATE', 'TERMINATION']}}}, {'oneOf': [{'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': True}}}, {'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': False}, 'service_error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/service_error'}, 'additional_cause': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/additional_cause'}, 'test_error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/test_error'}}}]}]}, 'change': {'allOf': [{'type': 'object', 'required': ['session_id'], 'properties': {'session_id': {'type': 'string'}}}, {'oneOf': [{'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': True}}}, {'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': False}, 'error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/service_error'}}}]}]}, 'entry_id': {'type': ['string', 'null'], 'description': 'hex encoded bytes'}}, 'system': {'command': {'type': 'object', 'required': ['session_id', 'action', 'value', 'origin', 'test', 'checks'], 'properties': {'session_id': {'type': 'string'}, 'action': {'enum': ['SELECT', 'CANCEL', 'OPERATE']}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}, 'origin': {'type': 'object', 'required': ['category', 'identification'], 'properties': {'category': {'enum': ['BAY_CONTROL', 'STATION_CONTROL', 'REMOTE_CONTROL', 'AUTOMATIC_BAY', 'AUTOMATIC_STATION', 'AUTOMATIC_REMOTE', 'MAINTENANCE', 'PROCESS']}, 'identification': {'type': 'string'}}}, 'test': {'type': 'boolean'}, 'checks': {'type': 'array', 'items': {'enum': ['SYNCHRO', 'INTERLOCK']}}}}, 'change': {'type': 'object', 'requried': ['session_id', 'value'], 'properties': {'session_id': {'type': 'string'}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}}}}}, 'value': {'anyOf': [{'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/boolean'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/integer'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/unsigned'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/float'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/bit_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/octet_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/visible_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/mms_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/array'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/struct'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/quality'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/timestamp'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/double_point'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/direction'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/severity'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/vector'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/step_position'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/binary_control'}], '$defs': {'boolean': {'type': 'boolean'}, 'integer': {'type': 'integer'}, 'unsigned': {'type': 'integer'}, 'float': {'oneOf': [{'type': 'number'}, {'enum': ['nan', 'inf', '-inf']}]}, 'bit_string': {'type': 'array', 'items': {'type': 'boolean'}}, 'octet_string': {'type': 'string', 'description': 'hex encoded bytes'}, 'visible_string': {'type': 'string'}, 'mms_string': {'type': 'string'}, 'array': {'type': 'array', 'items': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}, 'struct': {'type': 'object', 'patternProperties': {'.+': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}}, 'quality': {'type': 'object', 'required': ['validity', 'details', 'source', 'test', 'operator_blocked'], 'properties': {'validity': {'enum': ['GOOD', 'INVALID', 'RESERVED', 'QUESTIONABLE']}, 'details': {'type': 'array', 'items': {'enum': ['OVERFLOW', 'OUT_OF_RANGE', 'BAD_REFERENCE', 'OSCILLATORY', 'FAILURE', 'OLD_DATA', 'INCONSISTENT', 'INACCURATE']}}, 'source': {'enum': ['PROCESS', 'SUBSTITUTED']}, 'test': {'type': 'boolean'}, 'operator_blocked': {'type': 'boolean'}}}, 'timestamp': {'type': 'object', 'required': ['value', 'leap_second', 'clock_failure', 'not_synchronized'], 'properties': {'value': {'type': 'number', 'description': 'seconds since 1970-01-01'}, 'leap_second': {'type': 'boolean'}, 'clock_failure': {'type': 'boolean'}, 'not_synchronized': {'type': 'boolean'}, 'accuracy': {'type': 'integer'}}}, 'double_point': {'enum': ['INTERMEDIATE', 'OFF', 'ON', 'BAD']}, 'direction': {'enum': ['UNKNOWN', 'FORWARD', 'BACKWARD', 'BOTH']}, 'severity': {'enum': ['UNKNOWN', 'CRITICAL', 'MAJOR', 'MINOR', 'WARNING']}, 'analogue': {'type': 'object', 'properties': {'i': {'type': 'integer'}, 'f': {'oneOf': [{'type': 'number'}, {'enum': ['nan', 'inf', '-inf']}]}}}, 'vector': {'type': 'object', 'required': ['magnitude'], 'properties': {'magnitude': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}, 'angle': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}}}, 'step_position': {'type': 'object', 'required': ['value'], 'properties': {'value': {'type': 'integer'}, 'transient': {'type': 'boolean'}}}, 'binary_control': {'enum': ['STOP', 'LOWER', 'HIGHER', 'RESERVED']}}}, 'value_type': {'oneOf': [{'enum': ['BOOLEAN', 'INTEGER', 'UNSIGNED', 'FLOAT', 'BIT_STRING', 'OCTET_STRING', 'VISIBLE_STRING', 'MMS_STRING', 'QUALITY', 'TIMESTAMP', 'DOUBLE_POINT', 'DIRECTION', 'SEVERITY', 'ANALOGUE', 'VECTOR', 'STEP_POSITION', 'BINARY_CONTROL']}, {'type': 'object', 'required': ['type', 'element_type', 'length'], 'properties': {'type': {'const': 'ARRAY'}, 'element_type': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value_type'}, 'length': {'type': 'integer'}}}, {'type': 'object', 'required': ['type', 'elements'], 'properties': {'type': {'const': 'STRUCT'}, 'elements': {'type': 'array', 'items': {'type': 'object', 'requried': ['name', 'type'], 'properties': {'name': {'type': 'string'}, 'type': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value_type'}}}}}}]}, 'refs': {'value': {'type': 'object', 'required': ['logical_device', 'logical_node', 'fc', 'names'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'fc': {'type': 'string'}, 'names': {'type': 'array', 'items': {'type': ['string', 'integer']}}}}, 'command': {'type': 'object', 'required': ['logical_device', 'logical_node', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'name': {'type': 'string'}}}, 'rcb': {'type': 'object', 'required': ['logical_device', 'logical_node', 'type', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'type': {'enum': ['BUFFERED', 'UNBUFFERED']}, 'name': {'type': 'string'}}}, 'dataset': {'oneOf': [{'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset/$defs/nonpersisted'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset/$defs/persisted'}], '$defs': {'nonpersisted': {'type': 'string'}, 'persisted': {'type': 'object', 'required': ['logical_device', 'logical_node', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'name': {'type': 'string'}}}}}}, 'errors': {'service_error': {'enum': ['NO_ERROR', 'INSTANCE_NOT_AVAILABLE', 'INSTANCE_IN_USE', 'ACCESS_VIOLATION', 'ACCESS_NOT_ALLOWED_IN_CURRENT_STATE', 'PARAMETER_VALUE_INAPPROPRIATE', 'PARAMETER_VALUE_INCONSISTENT', 'CLASS_NOT_SUPPORTED', 'INSTANCE_LOCKED_BY_OTHER_CLIENT', 'CONTROL_MUST_BE_SELECTED', 'TYPE_CONFLICT', 'FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT', 'FAILED_DUE_TO_SERVER_CONTRAINT']}, 'additional_cause': {'enum': ['UNKNOWN', 'NOT_SUPPORTED', 'BLOCKED_BY_SWITCHING_HIERARCHY', 'SELECT_FAILED', 'INVALID_POSITION', 'POSITION_REACHED', 'PARAMETER_CHANGE_IN_EXECUTION', 'STEP_LIMIT', 'BLOCKED_BY_MODE', 'BLOCKED_BY_PROCESS', 'BLOCKED_BY_INTERLOCKING', 'BLOCKED_BY_SYNCHROCHECK', 'COMMAND_ALREADY_IN_EXECUTION', 'BLOCKED_BY_HEALTH', 'ONE_OF_N_CONTROL', 'ABORTION_BY_CANCEL', 'TIME_LIMIT_OVER', 'ABORTION_BY_TRIP', 'OBJECT_NOT_SELECTED', 'OBJECT_ALREADY_SELECTED', 'NO_ACCESS_AUTHORITY', 'ENDED_WITH_OVERSHOOT', 'ABORTION_DUE_TO_DEVIATION', 'ABORTION_BY_COMMUNICATION_LOSS', 'BLOCKED_BY_COMMAND', 'NONE', 'INCONSISTENT_PARAMETERS', 'LOCKED_BY_OTHER_CLIENT']}, 'test_error': {'enum': ['NO_ERROR', 'UNKNOWN', 'TIMEOUT_TEST_NOT_OK', 'OPERATOR_TEST_NOT_OK']}}}}, 'hat-gateway://snmp.yaml': {'$schema': 'https://json-schema.org/draft/2020-12/schema', '$id': 'hat-gateway://snmp.yaml', '$defs': {'manager': {'allOf': [{'oneOf': [{'$ref': 'hat-gateway://snmp.yaml#/$defs/managers/v1'}, {'$ref': 'hat-gateway://snmp.yaml#/$defs/managers/v2c'}, {'$ref': 'hat-gateway://snmp.yaml#/$defs/managers/v3'}]}, {'type': 'object', 'required': ['name', 'remote_host', 'remote_port', 'connect_delay', 'request_timeout', 'request_retry_count', 'request_retry_delay', 'polling_delay', 'polling_oids', 'string_hex_oids'], 'properties': {'name': {'type': 'string', 'description': 'Device name\n'}, 'remote_host': {'type': 'string', 'description': 'Remote hostname or IP address\n'}, 'remote_port': {'type': 'integer', 'description': 'Remote UDP port\n'}, 'connect_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive connection\nestablishment attempts\n'}, 'request_timeout': {'type': 'number', 'description': 'Maximum duration (in seconds) of request/response\nexchange\n'}, 'request_retry_count': {'type': 'integer', 'description': 'Number of request retries before remote data is\nconsidered unavailable\n'}, 'request_retry_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive request\nretries\n'}, 'polling_delay': {'type': 'number', 'description': 'Delay (in seconds) between two consecutive polling\ncycles\n'}, 'polling_oids': {'type': 'array', 'items': {'type': 'string', 'description': "OID read during polling cycle formated as integers\nseparated by '.'\n"}}, 'string_hex_oids': {'type': 'array', 'items': {'type': 'string', 'description': "OID associated to string hex value formated as\nintegers separated by '.'\n"}}}}]}, 'trap_listener': {'type': 'object', 'required': ['name', 'local_host', 'local_port', 'users', 'remote_devices'], 'properties': {'name': {'type': 'string', 'description': 'Device name\n'}, 'local_host': {'type': 'string', 'description': 'Local listening hostname or IP address\n'}, 'local_port': {'type': 'integer', 'description': 'Local listening UDP port\n'}, 'users': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'authentication', 'privacy'], 'properties': {'name': {'type': 'string'}, 'authentication': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['type', 'password'], 'properties': {'type': {'enum': ['MD5', 'SHA']}, 'password': {'type': 'string'}}}]}, 'privacy': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['type', 'password'], 'properties': {'type': {'const': 'DES'}, 'password': {'type': 'string'}}}]}}}}, 'remote_devices': {'type': 'array', 'items': {'allOf': [{'oneOf': [{'type': 'object', 'required': ['version', 'community'], 'properties': {'version': {'enum': ['V1', 'V2C']}, 'community': {'type': ['null', 'string']}}}, {'type': 'object', 'required': ['version', 'context'], 'properties': {'version': {'const': 'V3'}, 'context': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['engine_id', 'name'], 'properties': {'engine_id': {'type': 'string', 'description': 'sequence of hexadecimal\ndigits\n'}, 'name': {'type': 'string'}}}]}}}]}, {'type': 'object', 'required': ['name', 'oids', 'string_hex_oids'], 'properties': {'name': {'type': 'string', 'description': 'remote device name\n'}, 'oids': {'type': 'array', 'items': {'type': 'string', 'description': "data OID formated as integers separated\nby '.'\n"}}, 'string_hex_oids': {'type': 'array', 'items': {'type': 'string', 'description': "OID associated to string hex value\nformated as integers separated by '.'\n"}}}}]}}}}, 'managers': {'v1': {'type': 'object', 'required': ['version', 'community'], 'properties': {'version': {'const': 'V1'}, 'community': {'type': 'string'}}}, 'v2c': {'type': 'object', 'required': ['version', 'community'], 'properties': {'version': {'const': 'V2C'}, 'community': {'type': 'string'}}}, 'v3': {'type': 'object', 'required': ['version', 'context', 'user', 'authentication', 'privacy'], 'properties': {'version': {'const': 'V3'}, 'context': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['engine_id', 'name'], 'properties': {'engine_id': {'type': 'string', 'description': 'sequence of hexadecimal digits\n'}, 'name': {'type': 'string'}}}]}, 'user': {'type': 'string'}, 'authentication': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['type', 'password'], 'properties': {'type': {'enum': ['MD5', 'SHA']}, 'password': {'type': 'string'}}}]}, 'privacy': {'oneOf': [{'type': 'null'}, {'type': 'object', 'required': ['type', 'password'], 'properties': {'type': {'const': 'DES'}, 'password': {'type': 'string'}}}]}}}}, 'events': {'manager': {'gateway': {'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'read': {'type': 'object', 'required': ['session_id', 'cause', 'data'], 'properties': {'session_id': {'oneOf': [{'type': 'null', 'description': 'In case of INTERROGATE or CHANGE cause\n'}, {'description': 'In case of REQUESTED cause\n'}]}, 'cause': ['INTERROGATE', 'CHANGE', 'REQUESTED'], 'data': {'$ref': 'hat-gateway://snmp.yaml#/$defs/data'}}}, 'write': {'type': 'object', 'required': ['session_id', 'success'], 'properties': {'success': {'type': 'boolean'}}}}, 'system': {'read': {'type': 'object', 'required': ['session_id']}, 'write': {'type': 'object', 'required': ['session_id', 'data'], 'properties': {'data': {'$ref': 'hat-gateway://snmp.yaml#/$defs/data'}}}}}, 'trap_listener': {'gateway': {'data': {'$ref': 'hat-gateway://snmp.yaml#/$defs/data'}}}}, 'data': {'oneOf': [{'type': 'object', 'required': ['type', 'value'], 'properties': {'type': {'enum': ['INTEGER', 'UNSIGNED', 'COUNTER', 'BIG_COUNTER', 'TIME_TICKS']}, 'value': {'type': 'integer'}}}, {'type': 'object', 'required': ['type', 'value'], 'properties': {'type': {'enum': ['STRING', 'STRING_HEX', 'OBJECT_ID', 'IP_ADDRESS', 'ARBITRARY']}, 'value': {'type': 'string'}}}, {'type': 'object', 'required': ['type', 'value'], 'properties': {'type': {'const': 'ERROR'}, 'value': {'enum': ['TOO_BIG', 'NO_SUCH_NAME', 'BAD_VALUE', 'READ_ONLY', 'GEN_ERR', 'NO_ACCESS', 'WRONG_TYPE', 'WRONG_LENGTH', 'WRONG_ENCODING', 'WRONG_VALUE', 'NO_CREATION', 'INCONSISTENT_VALUE', 'RESOURCE_UNAVAILABLE', 'COMMIT_FAILED', 'UNDO_FAILED', 'AUTHORIZATION_ERROR', 'NOT_WRITABLE', 'INCONSISTENT_NAME', 'EMPTY', 'UNSPECIFIED', 'NO_SUCH_OBJECT', 'NO_SUCH_INSTANCE', 'END_OF_MIB_VIEW', 'NOT_IN_TIME_WINDOWS', 'UNKNOWN_USER_NAMES', 'UNKNOWN_ENGINE_IDS', 'WRONG_DIGESTS', 'DECRYPTION_ERRORS']}}}]}}}})
class Iec104SlaveDevice(hat.gateway.common.Device):
 86class Iec104SlaveDevice(common.Device):
 87
 88    @property
 89    def async_group(self) -> aio.Group:
 90        return self._srv.async_group
 91
 92    async def process_event(self, event: hat.event.common.Event):
 93        try:
 94            self._log.debug('received event: %s', event)
 95            await self._process_event(event)
 96
 97        except Exception as e:
 98            self._log.warning('error processing event: %s', e, exc_info=e)
 99
100    async def _on_connection(self, conn):
101        remote_host = conn.info.remote_addr.host
102        if (self._remote_hosts is not None and
103                remote_host not in self._remote_hosts):
104            conn.close()
105            self._log.warning('connection closed: remote host %s not allowed',
106                              remote_host)
107            return
108
109        if (self._max_connections is not None and
110                len(self._conns) >= self._max_connections):
111            conn.close()
112            self._log.debug('connection closed: max connections exceeded '
113                            '(remote host %s)', remote_host)
114            return
115
116        if self._conf['security']:
117            try:
118                ssl.init_security(self._conf['security'], conn)
119
120            except Exception as e:
121                self._log.error('init security error: %s', exc_info=e)
122                conn.close()
123                return
124
125        conn_id = next(self._next_conn_ids)
126
127        try:
128            self._conns[conn_id] = conn
129            await self._register_connections()
130
131            enabled_cb = functools.partial(self._on_enabled, conn)
132            with conn.register_enabled_cb(enabled_cb):
133                enabled_cb(conn.is_enabled)
134
135                while True:
136                    try:
137                        msgs = await conn.receive()
138
139                    except iec104.AsduTypeError as e:
140                        self._log.warning("asdu type error: %s", e)
141                        continue
142
143                    for msg in msgs:
144                        try:
145                            self._log.debug('received message: %s', msg)
146                            await self._process_msg(conn_id, conn, msg)
147
148                        except Exception as e:
149                            self._log.warning('error processing message: %s',
150                                              e, exc_info=e)
151
152        except ConnectionError:
153            self._log.debug('connection close')
154
155        except Exception as e:
156            self._log.warning('connection error: %s', e, exc_info=e)
157
158        finally:
159            self._log.debug('closing connection')
160            conn.close()
161
162            with contextlib.suppress(Exception):
163                self._conns.pop(conn_id)
164                await aio.uncancellable(self._register_connections())
165
166    def _on_enabled(self, conn, enabled):
167        if not enabled:
168            return
169
170        with contextlib.suppress(Exception):
171            for buffer in self._buffers.values():
172                for event_id, data_key, data_msg in buffer.get():
173                    self._send_data_msg(conn, buffer, event_id, data_msg)
174
175    async def _register_connections(self):
176        payload = [{'connection_id': conn_id,
177                    'local': {'host': conn.info.local_addr.host,
178                              'port': conn.info.local_addr.port},
179                    'remote': {'host': conn.info.remote_addr.host,
180                               'port': conn.info.remote_addr.port}}
181                   for conn_id, conn in self._conns.items()]
182
183        event = hat.event.common.RegisterEvent(
184            type=(*self._event_type_prefix, 'gateway', 'connections'),
185            source_timestamp=None,
186            payload=hat.event.common.EventPayloadJson(payload))
187
188        await self._eventer_client.register([event])
189
190    async def _process_event(self, event):
191        suffix = event.type[len(self._event_type_prefix):]
192
193        if suffix[:2] == ('system', 'data'):
194            data_type_str, asdu_address_str, io_address_str = suffix[2:]
195            data_key = common.DataKey(data_type=common.DataType(data_type_str),
196                                      asdu_address=int(asdu_address_str),
197                                      io_address=int(io_address_str))
198
199            await self._process_data_event(data_key, event)
200
201        elif suffix[:2] == ('system', 'command'):
202            cmd_type_str, asdu_address_str, io_address_str = suffix[2:]
203            cmd_key = common.CommandKey(
204                cmd_type=common.CommandType(cmd_type_str),
205                asdu_address=int(asdu_address_str),
206                io_address=int(io_address_str))
207
208            await self._process_command_event(cmd_key, event)
209
210        else:
211            raise Exception('unsupported event type')
212
213    async def _process_data_event(self, data_key, event):
214        if data_key not in self._data_msgs:
215            raise Exception('data not configured')
216
217        data_msg = _data_msg_from_event(data_key, event)
218        self._data_msgs[data_key] = data_msg
219
220        buffer = self._data_buffers.get(data_key)
221        if buffer:
222            buffer.add(event.id, data_key, data_msg)
223
224        for conn in self._conns.values():
225            self._send_data_msg(conn, buffer, event.id, data_msg)
226
227    async def _process_command_event(self, cmd_key, event):
228        cmd_msg = _cmd_msg_from_event(cmd_key, event)
229        conn_id = event.payload.data['connection_id']
230        conn = self._conns[conn_id]
231        await conn.send([cmd_msg])
232
233    async def _process_msg(self, conn_id, conn, msg):
234        if isinstance(msg, iec104.CommandMsg):
235            await self._process_command_msg(conn_id, conn, msg)
236
237        elif isinstance(msg, iec104.InterrogationMsg):
238            await self._process_interrogation_msg(conn_id, conn, msg)
239
240        elif isinstance(msg, iec104.CounterInterrogationMsg):
241            await self._process_counter_interrogation_msg(conn_id, conn, msg)
242
243        elif isinstance(msg, iec104.ReadMsg):
244            await self._process_read_msg(conn_id, conn, msg)
245
246        elif isinstance(msg, iec104.ClockSyncMsg):
247            await self._process_clock_sync_msg(conn_id, conn, msg)
248
249        elif isinstance(msg, iec104.TestMsg):
250            await self._process_test_msg(conn_id, conn, msg)
251
252        elif isinstance(msg, iec104.ResetMsg):
253            await self._process_reset_msg(conn_id, conn, msg)
254
255        elif isinstance(msg, iec104.ParameterMsg):
256            await self._process_parameter_msg(conn_id, conn, msg)
257
258        elif isinstance(msg, iec104.ParameterActivationMsg):
259            await self._process_parameter_activation_msg(conn_id, conn, msg)
260
261        else:
262            raise Exception('unsupported message')
263
264    async def _process_command_msg(self, conn_id, conn, msg):
265        if isinstance(msg.cause, iec104.CommandReqCause):
266            event = _cmd_msg_to_event(self._event_type_prefix, conn_id, msg)
267            await self._eventer_client.register([event])
268
269        else:
270            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
271                               is_negative_confirm=True)
272            await conn.send([res])
273
274    async def _process_interrogation_msg(self, conn_id, conn, msg):
275        if msg.cause == iec104.CommandReqCause.ACTIVATION:
276            asdu_data_msgs = collections.defaultdict(collections.deque)
277
278            for data_key, data_msg in self._data_msgs.items():
279                if data_key.data_type == common.DataType.BINARY_COUNTER:
280                    continue
281
282                if (msg.asdu_address != 0xFFFF and
283                        msg.asdu_address != data_key.asdu_address):
284                    continue
285
286                asdu_data_msgs[data_key.asdu_address].append(data_msg)
287
288            if msg.asdu_address != 0xFFFF:
289                asdu_data_msgs[msg.asdu_address].append(None)
290
291            for asdu_address, data_msgs in asdu_data_msgs.items():
292                res = msg._replace(
293                    asdu_address=asdu_address,
294                    cause=iec104.CommandResCause.ACTIVATION_CONFIRMATION,
295                    is_negative_confirm=False)
296                await conn.send([res])
297
298                msgs = [
299                    data_msg._replace(
300                        is_test=msg.is_test,
301                        cause=iec104.DataResCause.INTERROGATED_STATION)
302                    for data_msg in data_msgs
303                    if data_msg]
304                if msgs:
305                    await conn.send(msgs)
306
307                res = msg._replace(
308                    asdu_address=asdu_address,
309                    cause=iec104.CommandResCause.ACTIVATION_TERMINATION,
310                    is_negative_confirm=False)
311                await conn.send([res])
312
313        elif msg.cause == iec104.CommandReqCause.DEACTIVATION:
314            res = msg._replace(
315                cause=iec104.CommandResCause.DEACTIVATION_CONFIRMATION,
316                is_negative_confirm=True)
317            await conn.send([res])
318
319        else:
320            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
321                               is_negative_confirm=True)
322            await conn.send([res])
323
324    async def _process_counter_interrogation_msg(self, conn_id, conn, msg):
325        if msg.cause == iec104.CommandReqCause.ACTIVATION:
326            asdu_data_msgs = collections.defaultdict(collections.deque)
327
328            for data_key, data_msg in self._data_msgs.items():
329                if data_key.data_type != common.DataType.BINARY_COUNTER:
330                    continue
331
332                if (msg.asdu_address != 0xFFFF and
333                        msg.asdu_address != data_key.asdu_address):
334                    continue
335
336                asdu_data_msgs[data_key.asdu_address].append(data_msg)
337
338            if msg.asdu_address != 0xFFFF:
339                asdu_data_msgs[msg.asdu_address].append(None)
340            for asdu_address, data_msgs in asdu_data_msgs.items():
341                res = msg._replace(
342                    asdu_address=asdu_address,
343                    cause=iec104.CommandResCause.ACTIVATION_CONFIRMATION,
344                    is_negative_confirm=False)
345                await conn.send([res])
346
347                msgs = [
348                    data_msg._replace(
349                        is_test=msg.is_test,
350                        cause=iec104.DataResCause.INTERROGATED_COUNTER)
351                    for data_msg in data_msgs
352                    if data_msg]
353                if msgs:
354                    await conn.send(msgs)
355
356                res = msg._replace(
357                    asdu_address=asdu_address,
358                    cause=iec104.CommandResCause.ACTIVATION_TERMINATION,
359                    is_negative_confirm=False)
360                await conn.send([res])
361
362        elif msg.cause == iec104.CommandReqCause.DEACTIVATION:
363            res = msg._replace(
364                cause=iec104.CommandResCause.DEACTIVATION_CONFIRMATION,
365                is_negative_confirm=True)
366            await conn.send([res])
367
368        else:
369            res = msg._replace(cause=iec104.CommandResCause.UNKNOWN_CAUSE,
370                               is_negative_confirm=True)
371            await conn.send([res])
372
373    async def _process_read_msg(self, conn_id, conn, msg):
374        res = msg._replace(cause=iec104.ReadResCause.UNKNOWN_TYPE)
375        await conn.send([res])
376
377    async def _process_clock_sync_msg(self, conn_id, conn, msg):
378        if isinstance(msg.cause, iec104.ClockSyncReqCause):
379            res = msg._replace(
380                cause=iec104.ClockSyncResCause.ACTIVATION_CONFIRMATION,
381                is_negative_confirm=True)
382            await conn.send([res])
383
384        else:
385            res = msg._replace(cause=iec104.ClockSyncResCause.UNKNOWN_CAUSE,
386                               is_negative_confirm=True)
387            await conn.send([res])
388
389    async def _process_test_msg(self, conn_id, conn, msg):
390        res = msg._replace(cause=iec104.ActivationResCause.UNKNOWN_TYPE)
391        await conn.send([res])
392
393    async def _process_reset_msg(self, conn_id, conn, msg):
394        res = msg._replace(cause=iec104.ActivationResCause.UNKNOWN_TYPE)
395        await conn.send([res])
396
397    async def _process_parameter_msg(self, conn_id, conn, msg):
398        res = msg._replace(cause=iec104.ParameterResCause.UNKNOWN_TYPE)
399        await conn.send([res])
400
401    async def _process_parameter_activation_msg(self, conn_id, conn, msg):
402        res = msg._replace(
403            cause=iec104.ParameterActivationResCause.UNKNOWN_TYPE)
404        await conn.send([res])
405
406    def _send_data_msg(self, conn, buffer, event_id, data_msg):
407        self.async_group.spawn(self._async_send_data_msg, conn, buffer,
408                               event_id, data_msg)
409
410    async def _async_send_data_msg(self, conn, buffer, event_id, data_msg):
411        try:
412            if buffer:
413                await conn.send([data_msg], wait_ack=True)
414                buffer.remove(event_id)
415
416            else:
417                await conn.send([data_msg])
418
419        except ConnectionError:
420            pass
421
422        except Exception as e:
423            self._log.warning('send data message error: %s', e, exc_info=e)

Device interface

async_group: hat.aio.group.Group
88    @property
89    def async_group(self) -> aio.Group:
90        return self._srv.async_group

Group controlling resource's lifetime.

async def process_event(self, event: hat.event.common.common.Event):
92    async def process_event(self, event: hat.event.common.Event):
93        try:
94            self._log.debug('received event: %s', event)
95            await self._process_event(event)
96
97        except Exception as e:
98            self._log.warning('error processing event: %s', e, exc_info=e)

Process received event

This method can be coroutine or regular function.