hat.gateway.devices.iec101.slave
IEC 60870-5-101 slave device
1"""IEC 60870-5-101 slave device""" 2 3from collections.abc import Iterable 4import asyncio 5import collections 6import contextlib 7import functools 8import itertools 9import logging 10 11from hat import aio 12from hat import json 13from hat.drivers import iec101 14from hat.drivers import serial 15from hat.drivers.iec60870 import link 16import hat.event.common 17import hat.event.eventer 18 19from hat.gateway.devices.iec101 import common 20 21 22mlog: logging.Logger = logging.getLogger(__name__) 23 24 25async def create(conf: common.DeviceConf, 26 eventer_client: hat.event.eventer.Client, 27 event_type_prefix: common.EventTypePrefix 28 ) -> 'Iec101SlaveDevice': 29 device = Iec101SlaveDevice() 30 device._conf = conf 31 device._eventer_client = eventer_client 32 device._event_type_prefix = event_type_prefix 33 device._next_conn_ids = itertools.count(1) 34 device._conns = {} 35 device._send_queues = {} 36 device._buffers = {} 37 device._data_msgs = {} 38 device._data_buffers = {} 39 device._broadcast_asdu_address = _get_broadcast_asdu_address( 40 iec101.AsduAddressSize[conf['asdu_address_size']]) 41 device._log = _create_logger_adapter(conf['name']) 42 43 init_buffers(buffers_conf=conf['buffers'], 44 buffers=device._buffers) 45 46 await init_data(log=device._log, 47 data_conf=conf['data'], 48 data_msgs=device._data_msgs, 49 data_buffers=device._data_buffers, 50 buffers=device._buffers, 51 eventer_client=eventer_client, 52 event_type_prefix=event_type_prefix) 53 54 if conf['link_type'] == 'BALANCED': 55 create_link = link.create_balanced_link 56 57 elif conf['link_type'] == 'UNBALANCED': 58 create_link = link.create_slave_link 59 60 else: 61 raise ValueError('unsupported link type') 62 63 device._link = await create_link( 64 port=conf['port'], 65 address_size=link.AddressSize[conf['device_address_size']], 66 silent_interval=conf['silent_interval'], 67 baudrate=conf['baudrate'], 68 bytesize=serial.ByteSize[conf['bytesize']], 69 parity=serial.Parity[conf['parity']], 70 stopbits=serial.StopBits[conf['stopbits']], 71 xonxoff=conf['flow_control']['xonxoff'], 72 rtscts=conf['flow_control']['rtscts'], 73 dsrdtr=conf['flow_control']['dsrdtr'], 74 name=conf['name']) 75 76 try: 77 for device_conf in conf['devices']: 78 device.async_group.spawn(device._connection_loop, device_conf) 79 80 await device._register_connections() 81 82 except BaseException: 83 await aio.uncancellable(device.async_close()) 84 raise 85 86 return device 87 88 89info: common.DeviceInfo = common.DeviceInfo( 90 type="iec101_slave", 91 create=create, 92 json_schema_id="hat-gateway://iec101.yaml#/$defs/slave", 93 json_schema_repo=common.json_schema_repo) 94 95 96class Buffer: 97 98 def __init__(self, size: int): 99 self._size = size 100 self._data = collections.OrderedDict() 101 102 def add(self, 103 event_id: hat.event.common.EventId, 104 data_msg: iec101.DataMsg): 105 self._data[event_id] = data_msg 106 while len(self._data) > self._size: 107 self._data.popitem(last=False) 108 109 def remove(self, event_id: hat.event.common.EventId): 110 self._data.pop(event_id, None) 111 112 def get_event_id_data_msgs(self) -> Iterable[tuple[hat.event.common.EventId, # NOQA 113 iec101.DataMsg]]: 114 return self._data.items() 115 116 117def init_buffers(buffers_conf: json.Data, 118 buffers: dict[str, Buffer]): 119 for buffer_conf in buffers_conf: 120 buffers[buffer_conf['name']] = Buffer(buffer_conf['size']) 121 122 123async def init_data(log: logging.Logger, 124 data_conf: json.Data, 125 data_msgs: dict[common.DataKey, iec101.DataMsg], 126 data_buffers: dict[common.DataKey, Buffer], 127 buffers: dict[str, Buffer], 128 eventer_client: hat.event.eventer.Client, 129 event_type_prefix: common.EventTypePrefix): 130 for data in data_conf: 131 data_key = common.DataKey(data_type=common.DataType[data['data_type']], 132 asdu_address=data['asdu_address'], 133 io_address=data['io_address']) 134 data_msgs[data_key] = None 135 if data['buffer']: 136 data_buffers[data_key] = buffers[data['buffer']] 137 138 event_types = [(*event_type_prefix, 'system', 'data', '*')] 139 params = hat.event.common.QueryLatestParams(event_types) 140 result = await eventer_client.query(params) 141 142 for event in result.events: 143 try: 144 data_type_str, asdu_address_str, io_address_str = \ 145 event.type[len(event_type_prefix)+2:] 146 data_key = common.DataKey(data_type=common.DataType(data_type_str), 147 asdu_address=int(asdu_address_str), 148 io_address=int(io_address_str)) 149 if data_key not in data_msgs: 150 raise Exception(f'data {data_key} not configured') 151 152 data_msgs[data_key] = data_msg_from_event(data_key, event) 153 154 except Exception as e: 155 log.debug('skipping initial data: %s', e, exc_info=e) 156 157 158class Iec101SlaveDevice(common.Device): 159 160 @property 161 def async_group(self) -> aio.Group: 162 return self._link.async_group 163 164 async def process_event(self, event: hat.event.common.Event): 165 try: 166 await self._process_event(event) 167 168 except Exception as e: 169 self._log.warning('error processing event: %s', e, exc_info=e) 170 171 async def _connection_loop(self, device_conf): 172 conn = None 173 174 try: 175 if self._conf['link_type'] == 'BALANCED': 176 conn_args = { 177 'direction': link.Direction[device_conf['direction']], 178 'addr': device_conf['address'], 179 'response_timeout': device_conf['response_timeout'], 180 'send_retry_count': device_conf['send_retry_count'], 181 'status_delay': device_conf['status_delay'], 182 'name': self._conf['name']} 183 184 elif self._conf['link_type'] == 'UNBALANCED': 185 conn_args = { 186 'addr': device_conf['address'], 187 'keep_alive_timeout': device_conf['keep_alive_timeout'], 188 'name': self._conf['name']} 189 190 else: 191 raise ValueError('unsupported link type') 192 193 while True: 194 try: 195 conn = await self._link.open_connection(**conn_args) 196 197 except Exception as e: 198 self._log.error('connection error for address %s: %s', 199 device_conf['address'], e, exc_info=e) 200 await asyncio.sleep(device_conf['reconnect_delay']) 201 continue 202 203 conn = iec101.Connection( 204 conn=conn, 205 cause_size=iec101.CauseSize[self._conf['cause_size']], 206 asdu_address_size=iec101.AsduAddressSize[ 207 self._conf['asdu_address_size']], 208 io_address_size=iec101.IoAddressSize[ 209 self._conf['io_address_size']]) 210 211 conn_id = next(self._next_conn_ids) 212 self._conns[conn_id] = conn 213 214 send_queue = aio.Queue(1024) 215 self._send_queues[conn_id] = send_queue 216 217 try: 218 conn.async_group.spawn(self._connection_send_loop, conn, 219 send_queue) 220 conn.async_group.spawn(self._connection_receive_loop, conn, 221 conn_id) 222 223 await self._register_connections() 224 225 with contextlib.suppress(Exception): 226 for buffer in self._buffers.values(): 227 for event_id, data_msg in buffer.get_event_id_data_msgs(): # NOQA 228 await self._send_data_msg(conn_id, buffer, 229 event_id, data_msg) 230 231 await conn.wait_closed() 232 233 finally: 234 send_queue.close() 235 236 self._conns.pop(conn_id, None) 237 self._send_queues.pop(conn_id, None) 238 239 with contextlib.suppress(Exception): 240 await aio.uncancellable(self._register_connections()) 241 242 await conn.async_close() 243 244 except Exception as e: 245 self._log.warning('connection loop error: %s', e, exc_info=e) 246 247 finally: 248 self._log.debug('closing connection') 249 self.close() 250 251 if conn: 252 await aio.uncancellable(conn.async_close()) 253 254 async def _connection_send_loop(self, conn, send_queue): 255 try: 256 while True: 257 msgs, sent_cb = await send_queue.get() 258 await conn.send(msgs, sent_cb=sent_cb) 259 260 except ConnectionError: 261 self._log.debug('connection close') 262 263 except Exception as e: 264 self._log.warning('connection send loop error: %s', e, exc_info=e) 265 266 finally: 267 conn.close() 268 269 async def _connection_receive_loop(self, conn, conn_id): 270 try: 271 while True: 272 try: 273 msgs = await conn.receive() 274 275 except iec101.AsduTypeError as e: 276 self._log.warning("asdu type error: %s", e) 277 continue 278 279 for msg in msgs: 280 try: 281 self._log.debug('received message: %s', msg) 282 await self._process_msg(conn_id, msg) 283 284 except Exception as e: 285 self._log.warning('error processing message: %s', 286 e, exc_info=e) 287 288 except ConnectionError: 289 self._log.debug('connection close') 290 291 except Exception as e: 292 self._log.warning('connection receive loop error: %s', 293 e, exc_info=e) 294 295 finally: 296 conn.close() 297 298 async def _register_connections(self): 299 payload = [{'connection_id': conn_id, 300 'address': conn.info.address} 301 for conn_id, conn in self._conns.items()] 302 303 event = hat.event.common.RegisterEvent( 304 type=(*self._event_type_prefix, 'gateway', 'connections'), 305 source_timestamp=None, 306 payload=hat.event.common.EventPayloadJson(payload)) 307 308 await self._eventer_client.register([event]) 309 310 async def _process_event(self, event): 311 suffix = event.type[len(self._event_type_prefix):] 312 313 if suffix[:2] == ('system', 'data'): 314 data_type_str, asdu_address_str, io_address_str = suffix[2:] 315 data_key = common.DataKey(data_type=common.DataType(data_type_str), 316 asdu_address=int(asdu_address_str), 317 io_address=int(io_address_str)) 318 319 await self._process_data_event(data_key, event) 320 321 elif suffix[:2] == ('system', 'command'): 322 cmd_type_str, asdu_address_str, io_address_str = suffix[2:] 323 cmd_key = common.CommandKey( 324 cmd_type=common.CommandType(cmd_type_str), 325 asdu_address=int(asdu_address_str), 326 io_address=int(io_address_str)) 327 328 await self._process_command_event(cmd_key, event) 329 330 else: 331 raise Exception('unsupported event type') 332 333 async def _process_data_event(self, data_key, event): 334 if data_key not in self._data_msgs: 335 raise Exception('data not configured') 336 337 data_msg = data_msg_from_event(data_key, event) 338 self._data_msgs[data_key] = data_msg 339 340 buffer = self._data_buffers.get(data_key) 341 if buffer: 342 buffer.add(event.id, data_msg) 343 344 for conn_id in self._conns.keys(): 345 await self._send_data_msg(conn_id, buffer, event.id, data_msg) 346 347 async def _process_command_event(self, cmd_key, event): 348 cmd_msg = cmd_msg_from_event(cmd_key, event) 349 conn_id = event.payload.data['connection_id'] 350 await self._send(conn_id, [cmd_msg]) 351 352 async def _process_msg(self, conn_id, msg): 353 if isinstance(msg, iec101.CommandMsg): 354 await self._process_command_msg(conn_id, msg) 355 356 elif isinstance(msg, iec101.InterrogationMsg): 357 await self._process_interrogation_msg(conn_id, msg) 358 359 elif isinstance(msg, iec101.CounterInterrogationMsg): 360 await self._process_counter_interrogation_msg(conn_id, msg) 361 362 elif isinstance(msg, iec101.ReadMsg): 363 await self._process_read_msg(conn_id, msg) 364 365 elif isinstance(msg, iec101.ClockSyncMsg): 366 await self._process_clock_sync_msg(conn_id, msg) 367 368 elif isinstance(msg, iec101.TestMsg): 369 await self._process_test_msg(conn_id, msg) 370 371 elif isinstance(msg, iec101.ResetMsg): 372 await self._process_reset_msg(conn_id, msg) 373 374 elif isinstance(msg, iec101.ParameterMsg): 375 await self._process_parameter_msg(conn_id, msg) 376 377 elif isinstance(msg, iec101.ParameterActivationMsg): 378 await self._process_parameter_activation_msg(conn_id, msg) 379 380 else: 381 raise Exception('unsupported message') 382 383 async def _process_command_msg(self, conn_id, msg): 384 if isinstance(msg.cause, iec101.CommandReqCause): 385 event = cmd_msg_to_event(self._event_type_prefix, conn_id, msg) 386 await self._eventer_client.register([event]) 387 388 else: 389 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 390 is_negative_confirm=True) 391 await self._send(conn_id, [res]) 392 393 async def _process_interrogation_msg(self, conn_id, msg): 394 if msg.cause == iec101.CommandReqCause.ACTIVATION: 395 asdu_data_msgs = collections.defaultdict(collections.deque) 396 397 for data_key, data_msg in self._data_msgs.items(): 398 if data_key.data_type == common.DataType.BINARY_COUNTER: 399 continue 400 401 if (msg.asdu_address != self._broadcast_asdu_address and 402 msg.asdu_address != data_key.asdu_address): 403 continue 404 405 asdu_data_msgs[data_key.asdu_address].append(data_msg) 406 407 if msg.asdu_address != self._broadcast_asdu_address: 408 asdu_data_msgs[msg.asdu_address].append(None) 409 410 for asdu_address, data_msgs in asdu_data_msgs.items(): 411 res = msg._replace( 412 asdu_address=asdu_address, 413 cause=iec101.CommandResCause.ACTIVATION_CONFIRMATION, 414 is_negative_confirm=False) 415 await self._send(conn_id, [res]) 416 417 msgs = [ 418 data_msg._replace( 419 is_test=msg.is_test, 420 cause=iec101.DataResCause.INTERROGATED_STATION) 421 for data_msg in data_msgs 422 if data_msg] 423 if msgs: 424 await self._send(conn_id, msgs) 425 426 res = msg._replace( 427 asdu_address=asdu_address, 428 cause=iec101.CommandResCause.ACTIVATION_TERMINATION, 429 is_negative_confirm=False) 430 await self._send(conn_id, [res]) 431 432 elif msg.cause == iec101.CommandReqCause.DEACTIVATION: 433 res = msg._replace( 434 cause=iec101.CommandResCause.DEACTIVATION_CONFIRMATION, 435 is_negative_confirm=True) 436 await self._send(conn_id, [res]) 437 438 else: 439 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 440 is_negative_confirm=True) 441 await self._send(conn_id, [res]) 442 443 async def _process_counter_interrogation_msg(self, conn_id, msg): 444 if msg.cause == iec101.CommandReqCause.ACTIVATION: 445 asdu_data_msgs = collections.defaultdict(collections.deque) 446 447 for data_key, data_msg in self._data_msgs.items(): 448 if data_key.data_type != common.DataType.BINARY_COUNTER: 449 continue 450 451 if (msg.asdu_address != self._broadcast_asdu_address and 452 msg.asdu_address != data_key.asdu_address): 453 continue 454 455 asdu_data_msgs[data_key.asdu_address].append(data_msg) 456 457 if msg.asdu_address != self._broadcast_asdu_address: 458 asdu_data_msgs[msg.asdu_address].append(None) 459 460 for asdu_address, data_msgs in asdu_data_msgs.items(): 461 res = msg._replace( 462 asdu_address=asdu_address, 463 cause=iec101.CommandResCause.ACTIVATION_CONFIRMATION, 464 is_negative_confirm=False) 465 await self._send(conn_id, [res]) 466 467 msgs = [ 468 data_msg._replace( 469 is_test=msg.is_test, 470 cause=iec101.DataResCause.INTERROGATED_COUNTER) 471 for data_msg in data_msgs 472 if data_msg] 473 if msgs: 474 await self._send(conn_id, msgs) 475 476 res = msg._replace( 477 asdu_address=asdu_address, 478 cause=iec101.CommandResCause.ACTIVATION_TERMINATION, 479 is_negative_confirm=False) 480 await self._send(conn_id, [res]) 481 482 elif msg.cause == iec101.CommandReqCause.DEACTIVATION: 483 res = msg._replace( 484 cause=iec101.CommandResCause.DEACTIVATION_CONFIRMATION, 485 is_negative_confirm=True) 486 await self._send(conn_id, [res]) 487 488 else: 489 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 490 is_negative_confirm=True) 491 await self._send(conn_id, [res]) 492 493 async def _process_read_msg(self, conn_id, msg): 494 res = msg._replace(cause=iec101.ReadResCause.UNKNOWN_TYPE) 495 await self._send(conn_id, [res]) 496 497 async def _process_clock_sync_msg(self, conn_id, msg): 498 if isinstance(msg.cause, iec101.ClockSyncReqCause): 499 res = msg._replace( 500 cause=iec101.ClockSyncResCause.ACTIVATION_CONFIRMATION, 501 is_negative_confirm=True) 502 await self._send(conn_id, [res]) 503 504 else: 505 res = msg._replace(cause=iec101.ClockSyncResCause.UNKNOWN_CAUSE, 506 is_negative_confirm=True) 507 await self._send(conn_id, [res]) 508 509 async def _process_test_msg(self, conn_id, msg): 510 res = msg._replace(cause=iec101.ActivationResCause.UNKNOWN_TYPE) 511 await self._send(conn_id, [res]) 512 513 async def _process_reset_msg(self, conn_id, msg): 514 res = msg._replace(cause=iec101.ActivationResCause.UNKNOWN_TYPE) 515 await self._send(conn_id, [res]) 516 517 async def _process_parameter_msg(self, conn_id, msg): 518 res = msg._replace(cause=iec101.ParameterResCause.UNKNOWN_TYPE) 519 await self._send(conn_id, [res]) 520 521 async def _process_parameter_activation_msg(self, conn_id, msg): 522 res = msg._replace( 523 cause=iec101.ParameterActivationResCause.UNKNOWN_TYPE) 524 await self._send(conn_id, [res]) 525 526 async def _send_data_msg(self, conn_id, buffer, event_id, data_msg): 527 sent_cb = (functools.partial(buffer.remove, event_id) 528 if buffer else None) 529 await self._send(conn_id, [data_msg], sent_cb=sent_cb) 530 531 async def _send(self, conn_id, msgs, sent_cb=None): 532 send_queue = self._send_queues.get(conn_id) 533 if send_queue is None: 534 return 535 536 with contextlib.suppress(aio.QueueClosedError): 537 await send_queue.put((msgs, sent_cb)) 538 539 540def cmd_msg_to_event(event_type_prefix: hat.event.common.EventType, 541 conn_id: int, 542 msg: iec101.CommandMsg 543 ) -> hat.event.common.RegisterEvent: 544 command_type = common.get_command_type(msg.command) 545 cause = common.cause_to_json(iec101.CommandReqCause, msg.cause) 546 command = common.command_to_json(msg.command) 547 event_type = (*event_type_prefix, 'gateway', 'command', command_type.value, 548 str(msg.asdu_address), str(msg.io_address)) 549 550 return hat.event.common.RegisterEvent( 551 type=event_type, 552 source_timestamp=None, 553 payload=hat.event.common.EventPayloadJson({ 554 'connection_id': conn_id, 555 'is_test': msg.is_test, 556 'cause': cause, 557 'command': command})) 558 559 560def data_msg_from_event(data_key: common.DataKey, 561 event: hat.event.common.Event 562 ) -> iec101.DataMsg: 563 time = common.time_from_source_timestamp(event.source_timestamp) 564 cause = common.cause_from_json(iec101.DataResCause, 565 event.payload.data['cause']) 566 data = common.data_from_json(data_key.data_type, 567 event.payload.data['data']) 568 569 return iec101.DataMsg(is_test=event.payload.data['is_test'], 570 originator_address=0, 571 asdu_address=data_key.asdu_address, 572 io_address=data_key.io_address, 573 data=data, 574 time=time, 575 cause=cause) 576 577 578def cmd_msg_from_event(cmd_key: common.CommandKey, 579 event: hat.event.common.Event 580 ) -> iec101.CommandMsg: 581 cause = common.cause_from_json(iec101.CommandResCause, 582 event.payload.data['cause']) 583 command = common.command_from_json(cmd_key.cmd_type, 584 event.payload.data['command']) 585 is_negative_confirm = event.payload.data['is_negative_confirm'] 586 587 return iec101.CommandMsg(is_test=event.payload.data['is_test'], 588 originator_address=0, 589 asdu_address=cmd_key.asdu_address, 590 io_address=cmd_key.io_address, 591 command=command, 592 is_negative_confirm=is_negative_confirm, 593 cause=cause) 594 595 596def _get_broadcast_asdu_address(asdu_address_size): 597 if asdu_address_size == iec101.AsduAddressSize.ONE: 598 return 0xFF 599 600 if asdu_address_size == iec101.AsduAddressSize.TWO: 601 return 0xFFFF 602 603 raise ValueError('unsupported asdu address size') 604 605 606def _create_logger_adapter(name): 607 extra = {'meta': {'type': 'Iec101SlaveDevice', 608 'name': name}} 609 610 return logging.LoggerAdapter(mlog, extra)
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]) -> Iec101SlaveDevice:
26async def create(conf: common.DeviceConf, 27 eventer_client: hat.event.eventer.Client, 28 event_type_prefix: common.EventTypePrefix 29 ) -> 'Iec101SlaveDevice': 30 device = Iec101SlaveDevice() 31 device._conf = conf 32 device._eventer_client = eventer_client 33 device._event_type_prefix = event_type_prefix 34 device._next_conn_ids = itertools.count(1) 35 device._conns = {} 36 device._send_queues = {} 37 device._buffers = {} 38 device._data_msgs = {} 39 device._data_buffers = {} 40 device._broadcast_asdu_address = _get_broadcast_asdu_address( 41 iec101.AsduAddressSize[conf['asdu_address_size']]) 42 device._log = _create_logger_adapter(conf['name']) 43 44 init_buffers(buffers_conf=conf['buffers'], 45 buffers=device._buffers) 46 47 await init_data(log=device._log, 48 data_conf=conf['data'], 49 data_msgs=device._data_msgs, 50 data_buffers=device._data_buffers, 51 buffers=device._buffers, 52 eventer_client=eventer_client, 53 event_type_prefix=event_type_prefix) 54 55 if conf['link_type'] == 'BALANCED': 56 create_link = link.create_balanced_link 57 58 elif conf['link_type'] == 'UNBALANCED': 59 create_link = link.create_slave_link 60 61 else: 62 raise ValueError('unsupported link type') 63 64 device._link = await create_link( 65 port=conf['port'], 66 address_size=link.AddressSize[conf['device_address_size']], 67 silent_interval=conf['silent_interval'], 68 baudrate=conf['baudrate'], 69 bytesize=serial.ByteSize[conf['bytesize']], 70 parity=serial.Parity[conf['parity']], 71 stopbits=serial.StopBits[conf['stopbits']], 72 xonxoff=conf['flow_control']['xonxoff'], 73 rtscts=conf['flow_control']['rtscts'], 74 dsrdtr=conf['flow_control']['dsrdtr'], 75 name=conf['name']) 76 77 try: 78 for device_conf in conf['devices']: 79 device.async_group.spawn(device._connection_loop, device_conf) 80 81 await device._register_connections() 82 83 except BaseException: 84 await aio.uncancellable(device.async_close()) 85 raise 86 87 return device
info: hat.gateway.common.DeviceInfo =
DeviceInfo(type='iec101_slave', create=<function create>, json_schema_id='hat-gateway://iec101.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://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://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'], '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']}}}}}}, {'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://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://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://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://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://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']}}}]}}}, '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://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']}}}}})
class
Buffer:
97class Buffer: 98 99 def __init__(self, size: int): 100 self._size = size 101 self._data = collections.OrderedDict() 102 103 def add(self, 104 event_id: hat.event.common.EventId, 105 data_msg: iec101.DataMsg): 106 self._data[event_id] = data_msg 107 while len(self._data) > self._size: 108 self._data.popitem(last=False) 109 110 def remove(self, event_id: hat.event.common.EventId): 111 self._data.pop(event_id, None) 112 113 def get_event_id_data_msgs(self) -> Iterable[tuple[hat.event.common.EventId, # NOQA 114 iec101.DataMsg]]: 115 return self._data.items()
def
add( self, event_id: hat.event.common.common.EventId, data_msg: hat.drivers.iec101.common.DataMsg):
def
init_buffers( buffers_conf: None | bool | int | float | str | List[ForwardRef('Data')] | Dict[str, ForwardRef('Data')], buffers: dict[str, Buffer]):
async def
init_data( log: logging.Logger, data_conf: None | bool | int | float | str | List[ForwardRef('Data')] | Dict[str, ForwardRef('Data')], data_msgs: dict[hat.gateway.devices.iec101.common.DataKey, hat.drivers.iec101.common.DataMsg], data_buffers: dict[hat.gateway.devices.iec101.common.DataKey, Buffer], buffers: dict[str, Buffer], eventer_client: hat.event.eventer.client.Client, event_type_prefix: tuple[str, str, str]):
124async def init_data(log: logging.Logger, 125 data_conf: json.Data, 126 data_msgs: dict[common.DataKey, iec101.DataMsg], 127 data_buffers: dict[common.DataKey, Buffer], 128 buffers: dict[str, Buffer], 129 eventer_client: hat.event.eventer.Client, 130 event_type_prefix: common.EventTypePrefix): 131 for data in data_conf: 132 data_key = common.DataKey(data_type=common.DataType[data['data_type']], 133 asdu_address=data['asdu_address'], 134 io_address=data['io_address']) 135 data_msgs[data_key] = None 136 if data['buffer']: 137 data_buffers[data_key] = buffers[data['buffer']] 138 139 event_types = [(*event_type_prefix, 'system', 'data', '*')] 140 params = hat.event.common.QueryLatestParams(event_types) 141 result = await eventer_client.query(params) 142 143 for event in result.events: 144 try: 145 data_type_str, asdu_address_str, io_address_str = \ 146 event.type[len(event_type_prefix)+2:] 147 data_key = common.DataKey(data_type=common.DataType(data_type_str), 148 asdu_address=int(asdu_address_str), 149 io_address=int(io_address_str)) 150 if data_key not in data_msgs: 151 raise Exception(f'data {data_key} not configured') 152 153 data_msgs[data_key] = data_msg_from_event(data_key, event) 154 155 except Exception as e: 156 log.debug('skipping initial data: %s', e, exc_info=e)
159class Iec101SlaveDevice(common.Device): 160 161 @property 162 def async_group(self) -> aio.Group: 163 return self._link.async_group 164 165 async def process_event(self, event: hat.event.common.Event): 166 try: 167 await self._process_event(event) 168 169 except Exception as e: 170 self._log.warning('error processing event: %s', e, exc_info=e) 171 172 async def _connection_loop(self, device_conf): 173 conn = None 174 175 try: 176 if self._conf['link_type'] == 'BALANCED': 177 conn_args = { 178 'direction': link.Direction[device_conf['direction']], 179 'addr': device_conf['address'], 180 'response_timeout': device_conf['response_timeout'], 181 'send_retry_count': device_conf['send_retry_count'], 182 'status_delay': device_conf['status_delay'], 183 'name': self._conf['name']} 184 185 elif self._conf['link_type'] == 'UNBALANCED': 186 conn_args = { 187 'addr': device_conf['address'], 188 'keep_alive_timeout': device_conf['keep_alive_timeout'], 189 'name': self._conf['name']} 190 191 else: 192 raise ValueError('unsupported link type') 193 194 while True: 195 try: 196 conn = await self._link.open_connection(**conn_args) 197 198 except Exception as e: 199 self._log.error('connection error for address %s: %s', 200 device_conf['address'], e, exc_info=e) 201 await asyncio.sleep(device_conf['reconnect_delay']) 202 continue 203 204 conn = iec101.Connection( 205 conn=conn, 206 cause_size=iec101.CauseSize[self._conf['cause_size']], 207 asdu_address_size=iec101.AsduAddressSize[ 208 self._conf['asdu_address_size']], 209 io_address_size=iec101.IoAddressSize[ 210 self._conf['io_address_size']]) 211 212 conn_id = next(self._next_conn_ids) 213 self._conns[conn_id] = conn 214 215 send_queue = aio.Queue(1024) 216 self._send_queues[conn_id] = send_queue 217 218 try: 219 conn.async_group.spawn(self._connection_send_loop, conn, 220 send_queue) 221 conn.async_group.spawn(self._connection_receive_loop, conn, 222 conn_id) 223 224 await self._register_connections() 225 226 with contextlib.suppress(Exception): 227 for buffer in self._buffers.values(): 228 for event_id, data_msg in buffer.get_event_id_data_msgs(): # NOQA 229 await self._send_data_msg(conn_id, buffer, 230 event_id, data_msg) 231 232 await conn.wait_closed() 233 234 finally: 235 send_queue.close() 236 237 self._conns.pop(conn_id, None) 238 self._send_queues.pop(conn_id, None) 239 240 with contextlib.suppress(Exception): 241 await aio.uncancellable(self._register_connections()) 242 243 await conn.async_close() 244 245 except Exception as e: 246 self._log.warning('connection loop error: %s', e, exc_info=e) 247 248 finally: 249 self._log.debug('closing connection') 250 self.close() 251 252 if conn: 253 await aio.uncancellable(conn.async_close()) 254 255 async def _connection_send_loop(self, conn, send_queue): 256 try: 257 while True: 258 msgs, sent_cb = await send_queue.get() 259 await conn.send(msgs, sent_cb=sent_cb) 260 261 except ConnectionError: 262 self._log.debug('connection close') 263 264 except Exception as e: 265 self._log.warning('connection send loop error: %s', e, exc_info=e) 266 267 finally: 268 conn.close() 269 270 async def _connection_receive_loop(self, conn, conn_id): 271 try: 272 while True: 273 try: 274 msgs = await conn.receive() 275 276 except iec101.AsduTypeError as e: 277 self._log.warning("asdu type error: %s", e) 278 continue 279 280 for msg in msgs: 281 try: 282 self._log.debug('received message: %s', msg) 283 await self._process_msg(conn_id, msg) 284 285 except Exception as e: 286 self._log.warning('error processing message: %s', 287 e, exc_info=e) 288 289 except ConnectionError: 290 self._log.debug('connection close') 291 292 except Exception as e: 293 self._log.warning('connection receive loop error: %s', 294 e, exc_info=e) 295 296 finally: 297 conn.close() 298 299 async def _register_connections(self): 300 payload = [{'connection_id': conn_id, 301 'address': conn.info.address} 302 for conn_id, conn in self._conns.items()] 303 304 event = hat.event.common.RegisterEvent( 305 type=(*self._event_type_prefix, 'gateway', 'connections'), 306 source_timestamp=None, 307 payload=hat.event.common.EventPayloadJson(payload)) 308 309 await self._eventer_client.register([event]) 310 311 async def _process_event(self, event): 312 suffix = event.type[len(self._event_type_prefix):] 313 314 if suffix[:2] == ('system', 'data'): 315 data_type_str, asdu_address_str, io_address_str = suffix[2:] 316 data_key = common.DataKey(data_type=common.DataType(data_type_str), 317 asdu_address=int(asdu_address_str), 318 io_address=int(io_address_str)) 319 320 await self._process_data_event(data_key, event) 321 322 elif suffix[:2] == ('system', 'command'): 323 cmd_type_str, asdu_address_str, io_address_str = suffix[2:] 324 cmd_key = common.CommandKey( 325 cmd_type=common.CommandType(cmd_type_str), 326 asdu_address=int(asdu_address_str), 327 io_address=int(io_address_str)) 328 329 await self._process_command_event(cmd_key, event) 330 331 else: 332 raise Exception('unsupported event type') 333 334 async def _process_data_event(self, data_key, event): 335 if data_key not in self._data_msgs: 336 raise Exception('data not configured') 337 338 data_msg = data_msg_from_event(data_key, event) 339 self._data_msgs[data_key] = data_msg 340 341 buffer = self._data_buffers.get(data_key) 342 if buffer: 343 buffer.add(event.id, data_msg) 344 345 for conn_id in self._conns.keys(): 346 await self._send_data_msg(conn_id, buffer, event.id, data_msg) 347 348 async def _process_command_event(self, cmd_key, event): 349 cmd_msg = cmd_msg_from_event(cmd_key, event) 350 conn_id = event.payload.data['connection_id'] 351 await self._send(conn_id, [cmd_msg]) 352 353 async def _process_msg(self, conn_id, msg): 354 if isinstance(msg, iec101.CommandMsg): 355 await self._process_command_msg(conn_id, msg) 356 357 elif isinstance(msg, iec101.InterrogationMsg): 358 await self._process_interrogation_msg(conn_id, msg) 359 360 elif isinstance(msg, iec101.CounterInterrogationMsg): 361 await self._process_counter_interrogation_msg(conn_id, msg) 362 363 elif isinstance(msg, iec101.ReadMsg): 364 await self._process_read_msg(conn_id, msg) 365 366 elif isinstance(msg, iec101.ClockSyncMsg): 367 await self._process_clock_sync_msg(conn_id, msg) 368 369 elif isinstance(msg, iec101.TestMsg): 370 await self._process_test_msg(conn_id, msg) 371 372 elif isinstance(msg, iec101.ResetMsg): 373 await self._process_reset_msg(conn_id, msg) 374 375 elif isinstance(msg, iec101.ParameterMsg): 376 await self._process_parameter_msg(conn_id, msg) 377 378 elif isinstance(msg, iec101.ParameterActivationMsg): 379 await self._process_parameter_activation_msg(conn_id, msg) 380 381 else: 382 raise Exception('unsupported message') 383 384 async def _process_command_msg(self, conn_id, msg): 385 if isinstance(msg.cause, iec101.CommandReqCause): 386 event = cmd_msg_to_event(self._event_type_prefix, conn_id, msg) 387 await self._eventer_client.register([event]) 388 389 else: 390 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 391 is_negative_confirm=True) 392 await self._send(conn_id, [res]) 393 394 async def _process_interrogation_msg(self, conn_id, msg): 395 if msg.cause == iec101.CommandReqCause.ACTIVATION: 396 asdu_data_msgs = collections.defaultdict(collections.deque) 397 398 for data_key, data_msg in self._data_msgs.items(): 399 if data_key.data_type == common.DataType.BINARY_COUNTER: 400 continue 401 402 if (msg.asdu_address != self._broadcast_asdu_address and 403 msg.asdu_address != data_key.asdu_address): 404 continue 405 406 asdu_data_msgs[data_key.asdu_address].append(data_msg) 407 408 if msg.asdu_address != self._broadcast_asdu_address: 409 asdu_data_msgs[msg.asdu_address].append(None) 410 411 for asdu_address, data_msgs in asdu_data_msgs.items(): 412 res = msg._replace( 413 asdu_address=asdu_address, 414 cause=iec101.CommandResCause.ACTIVATION_CONFIRMATION, 415 is_negative_confirm=False) 416 await self._send(conn_id, [res]) 417 418 msgs = [ 419 data_msg._replace( 420 is_test=msg.is_test, 421 cause=iec101.DataResCause.INTERROGATED_STATION) 422 for data_msg in data_msgs 423 if data_msg] 424 if msgs: 425 await self._send(conn_id, msgs) 426 427 res = msg._replace( 428 asdu_address=asdu_address, 429 cause=iec101.CommandResCause.ACTIVATION_TERMINATION, 430 is_negative_confirm=False) 431 await self._send(conn_id, [res]) 432 433 elif msg.cause == iec101.CommandReqCause.DEACTIVATION: 434 res = msg._replace( 435 cause=iec101.CommandResCause.DEACTIVATION_CONFIRMATION, 436 is_negative_confirm=True) 437 await self._send(conn_id, [res]) 438 439 else: 440 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 441 is_negative_confirm=True) 442 await self._send(conn_id, [res]) 443 444 async def _process_counter_interrogation_msg(self, conn_id, msg): 445 if msg.cause == iec101.CommandReqCause.ACTIVATION: 446 asdu_data_msgs = collections.defaultdict(collections.deque) 447 448 for data_key, data_msg in self._data_msgs.items(): 449 if data_key.data_type != common.DataType.BINARY_COUNTER: 450 continue 451 452 if (msg.asdu_address != self._broadcast_asdu_address and 453 msg.asdu_address != data_key.asdu_address): 454 continue 455 456 asdu_data_msgs[data_key.asdu_address].append(data_msg) 457 458 if msg.asdu_address != self._broadcast_asdu_address: 459 asdu_data_msgs[msg.asdu_address].append(None) 460 461 for asdu_address, data_msgs in asdu_data_msgs.items(): 462 res = msg._replace( 463 asdu_address=asdu_address, 464 cause=iec101.CommandResCause.ACTIVATION_CONFIRMATION, 465 is_negative_confirm=False) 466 await self._send(conn_id, [res]) 467 468 msgs = [ 469 data_msg._replace( 470 is_test=msg.is_test, 471 cause=iec101.DataResCause.INTERROGATED_COUNTER) 472 for data_msg in data_msgs 473 if data_msg] 474 if msgs: 475 await self._send(conn_id, msgs) 476 477 res = msg._replace( 478 asdu_address=asdu_address, 479 cause=iec101.CommandResCause.ACTIVATION_TERMINATION, 480 is_negative_confirm=False) 481 await self._send(conn_id, [res]) 482 483 elif msg.cause == iec101.CommandReqCause.DEACTIVATION: 484 res = msg._replace( 485 cause=iec101.CommandResCause.DEACTIVATION_CONFIRMATION, 486 is_negative_confirm=True) 487 await self._send(conn_id, [res]) 488 489 else: 490 res = msg._replace(cause=iec101.CommandResCause.UNKNOWN_CAUSE, 491 is_negative_confirm=True) 492 await self._send(conn_id, [res]) 493 494 async def _process_read_msg(self, conn_id, msg): 495 res = msg._replace(cause=iec101.ReadResCause.UNKNOWN_TYPE) 496 await self._send(conn_id, [res]) 497 498 async def _process_clock_sync_msg(self, conn_id, msg): 499 if isinstance(msg.cause, iec101.ClockSyncReqCause): 500 res = msg._replace( 501 cause=iec101.ClockSyncResCause.ACTIVATION_CONFIRMATION, 502 is_negative_confirm=True) 503 await self._send(conn_id, [res]) 504 505 else: 506 res = msg._replace(cause=iec101.ClockSyncResCause.UNKNOWN_CAUSE, 507 is_negative_confirm=True) 508 await self._send(conn_id, [res]) 509 510 async def _process_test_msg(self, conn_id, msg): 511 res = msg._replace(cause=iec101.ActivationResCause.UNKNOWN_TYPE) 512 await self._send(conn_id, [res]) 513 514 async def _process_reset_msg(self, conn_id, msg): 515 res = msg._replace(cause=iec101.ActivationResCause.UNKNOWN_TYPE) 516 await self._send(conn_id, [res]) 517 518 async def _process_parameter_msg(self, conn_id, msg): 519 res = msg._replace(cause=iec101.ParameterResCause.UNKNOWN_TYPE) 520 await self._send(conn_id, [res]) 521 522 async def _process_parameter_activation_msg(self, conn_id, msg): 523 res = msg._replace( 524 cause=iec101.ParameterActivationResCause.UNKNOWN_TYPE) 525 await self._send(conn_id, [res]) 526 527 async def _send_data_msg(self, conn_id, buffer, event_id, data_msg): 528 sent_cb = (functools.partial(buffer.remove, event_id) 529 if buffer else None) 530 await self._send(conn_id, [data_msg], sent_cb=sent_cb) 531 532 async def _send(self, conn_id, msgs, sent_cb=None): 533 send_queue = self._send_queues.get(conn_id) 534 if send_queue is None: 535 return 536 537 with contextlib.suppress(aio.QueueClosedError): 538 await send_queue.put((msgs, sent_cb))
Device interface
async def
process_event(self, event: hat.event.common.common.Event):
165 async def process_event(self, event: hat.event.common.Event): 166 try: 167 await self._process_event(event) 168 169 except Exception as e: 170 self._log.warning('error processing event: %s', e, exc_info=e)
Process received event
This method can be coroutine or regular function.
def
cmd_msg_to_event( event_type_prefix: tuple[str, ...], conn_id: int, msg: hat.drivers.iec101.common.CommandMsg) -> hat.event.common.common.RegisterEvent:
541def cmd_msg_to_event(event_type_prefix: hat.event.common.EventType, 542 conn_id: int, 543 msg: iec101.CommandMsg 544 ) -> hat.event.common.RegisterEvent: 545 command_type = common.get_command_type(msg.command) 546 cause = common.cause_to_json(iec101.CommandReqCause, msg.cause) 547 command = common.command_to_json(msg.command) 548 event_type = (*event_type_prefix, 'gateway', 'command', command_type.value, 549 str(msg.asdu_address), str(msg.io_address)) 550 551 return hat.event.common.RegisterEvent( 552 type=event_type, 553 source_timestamp=None, 554 payload=hat.event.common.EventPayloadJson({ 555 'connection_id': conn_id, 556 'is_test': msg.is_test, 557 'cause': cause, 558 'command': command}))
def
data_msg_from_event( data_key: hat.gateway.devices.iec101.common.DataKey, event: hat.event.common.common.Event) -> hat.drivers.iec101.common.DataMsg:
561def data_msg_from_event(data_key: common.DataKey, 562 event: hat.event.common.Event 563 ) -> iec101.DataMsg: 564 time = common.time_from_source_timestamp(event.source_timestamp) 565 cause = common.cause_from_json(iec101.DataResCause, 566 event.payload.data['cause']) 567 data = common.data_from_json(data_key.data_type, 568 event.payload.data['data']) 569 570 return iec101.DataMsg(is_test=event.payload.data['is_test'], 571 originator_address=0, 572 asdu_address=data_key.asdu_address, 573 io_address=data_key.io_address, 574 data=data, 575 time=time, 576 cause=cause)
def
cmd_msg_from_event( cmd_key: hat.gateway.devices.iec101.common.CommandKey, event: hat.event.common.common.Event) -> hat.drivers.iec101.common.CommandMsg:
579def cmd_msg_from_event(cmd_key: common.CommandKey, 580 event: hat.event.common.Event 581 ) -> iec101.CommandMsg: 582 cause = common.cause_from_json(iec101.CommandResCause, 583 event.payload.data['cause']) 584 command = common.command_from_json(cmd_key.cmd_type, 585 event.payload.data['command']) 586 is_negative_confirm = event.payload.data['is_negative_confirm'] 587 588 return iec101.CommandMsg(is_test=event.payload.data['is_test'], 589 originator_address=0, 590 asdu_address=cmd_key.asdu_address, 591 io_address=cmd_key.io_address, 592 command=command, 593 is_negative_confirm=is_negative_confirm, 594 cause=cause)