hat.gateway.devices.iec103.master
IEC 60870-5-103 master device
1"""IEC 60870-5-103 master device""" 2 3from collections.abc import Collection 4import asyncio 5import collections 6import contextlib 7import datetime 8import enum 9import functools 10import logging 11 12from hat import aio 13from hat.drivers import iec103 14from hat.drivers import serial 15from hat.drivers.iec60870 import link 16import hat.event.common 17import hat.event.eventer 18 19from hat.gateway import common 20 21 22mlog: logging.Logger = logging.getLogger(__name__) 23 24command_timeout: float = 100 25 26interrogate_timeout: float = 100 27 28 29async def create(conf: common.DeviceConf, 30 eventer_client: hat.event.eventer.Client, 31 event_type_prefix: common.EventTypePrefix 32 ) -> 'Iec103MasterDevice': 33 event_types = [(*event_type_prefix, 'system', 'remote_device', 34 str(i['address']), 'enable') 35 for i in conf['remote_devices']] 36 params = hat.event.common.QueryLatestParams(event_types) 37 result = await eventer_client.query(params) 38 39 device = Iec103MasterDevice(conf=conf, 40 eventer_client=eventer_client, 41 event_type_prefix=event_type_prefix) 42 try: 43 await device.process_events(result.events) 44 45 except BaseException: 46 await aio.uncancellable(device.async_close()) 47 raise 48 49 return device 50 51 52info: common.DeviceInfo = common.DeviceInfo( 53 type="iec103_master", 54 create=create, 55 json_schema_id="hat-gateway://iec103.yaml#/$defs/master", 56 json_schema_repo=common.json_schema_repo) 57 58 59class Iec103MasterDevice(common.Device): 60 61 def __init__(self, 62 conf: common.DeviceConf, 63 eventer_client: hat.event.eventer.Client, 64 event_type_prefix: common.EventTypePrefix): 65 self._conf = conf 66 self._eventer_client = eventer_client 67 self._event_type_prefix = event_type_prefix 68 self._master = None 69 self._conns = {} 70 self._remote_enabled = {i['address']: False 71 for i in conf['remote_devices']} 72 self._remote_confs = {i['address']: i 73 for i in conf['remote_devices']} 74 self._remote_groups = {} 75 self._async_group = aio.Group() 76 self._log = _create_logger_adapter(conf['name']) 77 78 self.async_group.spawn(self._create_link_master_loop) 79 80 @property 81 def async_group(self) -> aio.Group: 82 return self._async_group 83 84 async def process_events(self, events: Collection[hat.event.common.Event]): 85 for event in events: 86 try: 87 await self._process_event(event) 88 89 except Exception as e: 90 self._log.warning('error processing event: %s', e, exc_info=e) 91 92 async def _create_link_master_loop(self): 93 94 async def cleanup(): 95 with contextlib.suppress(ConnectionError): 96 await self._register_status('DISCONNECTED') 97 98 if self._master: 99 await self._master.async_close() 100 101 try: 102 while True: 103 await self._register_status('CONNECTING') 104 105 try: 106 self._master = await link.create_master_link( 107 port=self._conf['port'], 108 address_size=link.AddressSize.ONE, 109 silent_interval=self._conf['silent_interval'], 110 baudrate=self._conf['baudrate'], 111 bytesize=serial.ByteSize[self._conf['bytesize']], 112 parity=serial.Parity[self._conf['parity']], 113 stopbits=serial.StopBits[self._conf['stopbits']], 114 xonxoff=self._conf['flow_control']['xonxoff'], 115 rtscts=self._conf['flow_control']['rtscts'], 116 dsrdtr=self._conf['flow_control']['dsrdtr'], 117 name=self._conf['name']) 118 119 except Exception as e: 120 self._log.warning( 121 'link master (endpoint) failed to create: %s', 122 e, exc_info=e) 123 await self._register_status('DISCONNECTED') 124 await asyncio.sleep(self._conf['reconnect_delay']) 125 continue 126 127 await self._register_status('CONNECTED') 128 for address, enabled in self._remote_enabled.items(): 129 if enabled: 130 self._enable_remote(address) 131 132 await self._master.wait_closed() 133 await self._register_status('DISCONNECTED') 134 self._master = None 135 136 finally: 137 self._log.debug('closing link master loop') 138 self.close() 139 await aio.uncancellable(cleanup()) 140 141 async def _connection_loop(self, group, address): 142 143 async def cleanup(): 144 with contextlib.suppress(ConnectionError): 145 await self._register_rmt_status(address, 'DISCONNECTED') 146 147 conn = self._conns.pop(address, None) 148 if conn: 149 await conn.async_close() 150 151 remote_conf = self._remote_confs[address] 152 try: 153 while True: 154 await self._register_rmt_status(address, 'CONNECTING') 155 156 try: 157 conn_link = await self._master.open_connection( 158 addr=address, 159 response_timeout=remote_conf['response_timeout'], 160 send_retry_count=remote_conf['send_retry_count'], 161 poll_class1_delay=remote_conf['poll_class1_delay'], 162 poll_class2_delay=remote_conf['poll_class2_delay'], 163 name=self._conf['name']) 164 165 except Exception as e: 166 self._log.error('connection error to address %s: %s', 167 address, e, exc_info=e) 168 await self._register_rmt_status(address, 'DISCONNECTED') 169 await asyncio.sleep(remote_conf['reconnect_delay']) 170 continue 171 172 await self._register_rmt_status(address, 'CONNECTED') 173 conn = iec103.MasterConnection( 174 conn=conn_link, 175 data_cb=functools.partial(self._on_data, address), 176 generic_data_cb=None) 177 self._conns[address] = conn 178 if remote_conf['time_sync_delay'] is not None: 179 group.spawn(self._time_sync_loop, conn, 180 remote_conf['time_sync_delay']) 181 182 await conn.wait_closed() 183 await self._register_rmt_status(address, 'DISCONNECTED') 184 self._conns.pop(address) 185 186 finally: 187 self._log.debug('closing remote device %s', address) 188 group.close() 189 await aio.uncancellable(cleanup()) 190 191 async def _time_sync_loop(self, conn, delay): 192 try: 193 while True: 194 await conn.time_sync() 195 self._log.debug('time sync') 196 await asyncio.sleep(delay) 197 198 except ConnectionError: 199 self._log.debug('connection closed') 200 201 finally: 202 conn.close() 203 204 async def _on_data(self, address, data): 205 events = collections.deque() 206 try: 207 for event in _events_from_data(data, address, 208 self._event_type_prefix): 209 events.append(event) 210 211 except Exception as e: 212 self._log.warning('data %s ignored due to: %s', 213 data, e, exc_info=e) 214 215 if events: 216 await self._eventer_client.register(events) 217 218 async def _process_event(self, event): 219 prefix_len = len(self._event_type_prefix) 220 if event.type[prefix_len + 1] != 'remote_device': 221 raise Exception('unexpected event type') 222 223 address = self._address_from_event(event) 224 etype_suffix = event.type[prefix_len + 3:] 225 226 if etype_suffix[0] == 'enable': 227 self._process_enable(event) 228 229 elif etype_suffix[0] == 'command': 230 asdu = int(etype_suffix[1]) 231 io = iec103.IoAddress( 232 function_type=int(etype_suffix[2]), 233 information_number=int(etype_suffix[3])) 234 self._process_command(event, address, asdu, io) 235 236 elif etype_suffix[0] == 'interrogation': 237 asdu = int(etype_suffix[1]) 238 self._process_interrogation(event, address, asdu) 239 240 else: 241 raise Exception('unexpected event type') 242 243 def _process_enable(self, event): 244 address = self._address_from_event(event) 245 enable = event.payload.data 246 if address not in self._remote_enabled: 247 raise Exception('invalid remote device address') 248 249 if not isinstance(enable, bool): 250 raise Exception('invalid enable event payload') 251 252 self._remote_enabled[address] = enable 253 254 if not self._master: 255 return 256 257 if enable: 258 self._enable_remote(address) 259 260 else: 261 self._disable_remote(address) 262 263 def _enable_remote(self, address): 264 remote_group = self._remote_groups.get(address) 265 if remote_group and remote_group.is_open: 266 return 267 268 remote_group = self._async_group.create_subgroup() 269 self._remote_groups[address] = remote_group 270 remote_group.spawn(self._connection_loop, remote_group, address) 271 272 def _disable_remote(self, address): 273 if address in self._remote_groups: 274 remote_group = self._remote_groups.pop(address) 275 remote_group.close() 276 277 def _process_command(self, event, address, asdu, io): 278 conn = self._conns.get(address) 279 if not conn or not conn.is_open: 280 raise Exception('connection closed') 281 282 value = iec103.DoubleValue[event.payload.data['value']] 283 session_id = event.payload.data['session_id'] 284 self._remote_groups[address].spawn( 285 self._cmd_req_res, conn, address, asdu, io, value, session_id) 286 287 async def _cmd_req_res(self, conn, address, asdu, io, value, session_id): 288 try: 289 success = await asyncio.wait_for( 290 conn.send_command(asdu, io, value), timeout=command_timeout) 291 292 except ConnectionError: 293 self._log.warning( 294 'command %s %s %s to %s failed: connection closed', 295 asdu, io, value, address) 296 return 297 298 except asyncio.TimeoutError: 299 self._log.warning( 300 'command %s %s %s to %s timeout', asdu, io, value, address) 301 return 302 303 event = _create_event( 304 event_type=(*self._event_type_prefix, 'gateway', 'remote_device', 305 str(address), 'command', str(asdu), 306 str(io.function_type), str(io.information_number)), 307 payload={'success': success, 308 'session_id': session_id}) 309 310 await self._eventer_client.register([event]) 311 312 def _process_interrogation(self, event, address, asdu): 313 conn = self._conns.get(address) 314 if not conn or not conn.is_open: 315 self._log.warning("event %s ignored due to connection closed", 316 event) 317 return 318 319 self._remote_groups[address].spawn( 320 self._interrogate_req_res, conn, address, asdu) 321 322 async def _interrogate_req_res(self, conn, address, asdu): 323 try: 324 await asyncio.wait_for(conn.interrogate(asdu), 325 timeout=interrogate_timeout) 326 327 except ConnectionError: 328 self._log.warning( 329 'interrogation on %s to %s failed: connection closed', 330 asdu, address) 331 return 332 333 except asyncio.TimeoutError: 334 self._log.warning('interrogation on %s to %s timeout', 335 asdu, address) 336 return 337 338 event = _create_event( 339 event_type=(*self._event_type_prefix, 'gateway', 'remote_device', 340 str(address), 'interrogation', str(asdu)), 341 payload=None) 342 343 await self._eventer_client.register([event]) 344 345 async def _register_status(self, status): 346 event = _create_event( 347 event_type=(*self._event_type_prefix, 'gateway', 'status'), 348 payload=status) 349 350 await self._eventer_client.register([event]) 351 352 async def _register_rmt_status(self, address, status): 353 event = _create_event( 354 event_type=(*self._event_type_prefix, 355 'gateway', 'remote_device', str(address), 'status'), 356 payload=status) 357 358 await self._eventer_client.register([event]) 359 360 def _address_from_event(self, event): 361 return int(event.type[len(self._event_type_prefix) + 2]) 362 363 364def _events_from_data(data, address, event_type_prefix): 365 cause = (data.cause.name if isinstance(data.cause, enum.Enum) 366 else data.cause) 367 368 if isinstance(data.value, (iec103.DoubleWithTimeValue, 369 iec103.DoubleWithRelativeTimeValue)): 370 data_type = 'double' 371 payload = {'cause': cause, 372 'value': data.value.value.name} 373 source_ts = _time_iec103_to_source_ts(data.value.time) 374 event_type = _data_event_type( 375 data, address, data_type, event_type_prefix) 376 yield _create_event(event_type, payload, source_ts) 377 378 elif isinstance(data.value, iec103.MeasurandValues): 379 for meas_type, meas_value in data.value.values.items(): 380 payload = {'cause': cause, 381 'value': meas_value._asdict()} 382 data_type = meas_type.name.lower() 383 event_type = _data_event_type( 384 data, address, data_type, event_type_prefix) 385 yield _create_event(event_type, payload) 386 387 else: 388 raise Exception('unsupported data value') 389 390 391def _data_event_type(data, address, data_type, event_type_prefix): 392 return ( 393 *event_type_prefix, 'gateway', 'remote_device', str(address), 394 'data', data_type, 395 str(data.asdu_address), 396 str(data.io_address.function_type), 397 str(data.io_address.information_number)) 398 399 400def _time_iec103_to_source_ts(time_four): 401 t_now = datetime.datetime.now(datetime.timezone.utc) 402 candidates_now = [t_now - datetime.timedelta(hours=12), 403 t_now, 404 t_now + datetime.timedelta(hours=12)] 405 candidates_103 = [_upgrade_time_four_to_seven( 406 time_four, iec103.time_from_datetime(t)) 407 for t in candidates_now] 408 candidates_dt = [iec103.time_to_datetime(t) 409 for t in candidates_103 if t] 410 if not candidates_dt: 411 return 412 413 res = min(candidates_dt, key=lambda i: abs(t_now - i)) 414 return hat.event.common.timestamp_from_datetime(res) 415 416 417def _upgrade_time_four_to_seven(time_four, time_seven): 418 if time_four.summer_time != time_seven.summer_time: 419 return 420 421 return time_four._replace( 422 day_of_week=time_seven.day_of_week, 423 day_of_month=time_seven.day_of_month, 424 months=time_seven.months, 425 years=time_seven.years, 426 size=iec103.TimeSize.SEVEN) 427 428 429def _create_event(event_type, payload, source_timestamp=None): 430 return hat.event.common.RegisterEvent( 431 type=event_type, 432 source_timestamp=source_timestamp, 433 payload=hat.event.common.EventPayloadJson(payload)) 434 435 436def _create_logger_adapter(name): 437 extra = {'meta': {'type': 'Iec103MasterDevice', 438 'name': name}} 439 440 return logging.LoggerAdapter(mlog, extra)
command_timeout: float =
100
interrogate_timeout: float =
100
async def
create( conf: Union[NoneType, 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]) -> Iec103MasterDevice:
30async def create(conf: common.DeviceConf, 31 eventer_client: hat.event.eventer.Client, 32 event_type_prefix: common.EventTypePrefix 33 ) -> 'Iec103MasterDevice': 34 event_types = [(*event_type_prefix, 'system', 'remote_device', 35 str(i['address']), 'enable') 36 for i in conf['remote_devices']] 37 params = hat.event.common.QueryLatestParams(event_types) 38 result = await eventer_client.query(params) 39 40 device = Iec103MasterDevice(conf=conf, 41 eventer_client=eventer_client, 42 event_type_prefix=event_type_prefix) 43 try: 44 await device.process_events(result.events) 45 46 except BaseException: 47 await aio.uncancellable(device.async_close()) 48 raise 49 50 return device
info: hat.gateway.common.DeviceInfo =
DeviceInfo(type='iec103_master', create=<function create>, json_schema_id='hat-gateway://iec103.yaml#/$defs/master', 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://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://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', 'report_id', 'value'], 'properties': {'name': {'type': 'string'}, 'report_id': {'type': 'string'}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'quality': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'timestamp': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}, 'selected': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}}}}, 'commands': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'model', 'ref', 'with_operate_time'], 'properties': {'name': {'type': 'string'}, 'model': {'enum': ['DIRECT_WITH_NORMAL_SECURITY', 'SBO_WITH_NORMAL_SECURITY', 'DIRECT_WITH_ENHANCED_SECURITY', 'SBO_WITH_ENHANCED_SECURITY']}, 'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/command'}, 'with_operate_time': {'type': 'boolean'}}}}, 'changes': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'ref'], 'properties': {'name': {'type': 'string'}, 'ref': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/value'}}}}}}, 'events': {'client': {'gateway': {'status': {'enum': ['CONNECTING', 'CONNECTED', 'DISCONNECTED']}, 'data': {'type': 'object', 'required': ['reasons'], 'properties': {'reasons': {'type': 'array', 'items': {'enum': ['DATA_CHANGE', 'QUALITY_CHANGE', 'DATA_UPDATE', 'INTEGRITY', 'GENERAL_INTERROGATION', 'APPLICATION_TRIGGER']}}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}, 'quality': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/quality'}, 'timestamp': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/timestamp'}, 'selected': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/boolean'}}}, 'command': {'allOf': [{'type': 'object', 'required': ['session_id', 'action'], 'properties': {'session_id': {'type': 'string'}, 'action': {'enum': ['SELECT', 'CANCEL', 'OPERATE', 'TERMINATION']}}}, {'oneOf': [{'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': True}}}, {'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': False}, 'service_error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/service_error'}, 'additional_cause': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/additional_cause'}, 'test_error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/test_error'}}}]}]}, 'change': {'allOf': [{'type': 'object', 'required': ['session_id'], 'properties': {'session_id': {'type': 'string'}}}, {'oneOf': [{'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': True}}}, {'type': 'object', 'requried': ['success'], 'properties': {'success': {'const': False}, 'error': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/errors/service_error'}}}]}]}, 'entry_id': {'type': ['string', 'null'], 'description': 'hex encoded bytes'}}, 'system': {'command': {'type': 'object', 'required': ['session_id', 'action', 'value', 'origin', 'test', 'checks'], 'properties': {'session_id': {'type': 'string'}, 'action': {'enum': ['SELECT', 'CANCEL', 'OPERATE']}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}, 'origin': {'type': 'object', 'required': ['category', 'identification'], 'properties': {'category': {'enum': ['BAY_CONTROL', 'STATION_CONTROL', 'REMOTE_CONTROL', 'AUTOMATIC_BAY', 'AUTOMATIC_STATION', 'AUTOMATIC_REMOTE', 'MAINTENANCE', 'PROCESS']}, 'identification': {'type': 'string'}}}, 'test': {'type': 'boolean'}, 'checks': {'type': 'array', 'items': {'enum': ['SYNCHRO', 'INTERLOCK']}}}}, 'change': {'type': 'object', 'requried': ['session_id', 'value'], 'properties': {'session_id': {'type': 'string'}, 'value': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}}}}}, 'value': {'anyOf': [{'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/boolean'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/integer'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/unsigned'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/float'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/bit_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/octet_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/visible_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/mms_string'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/array'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/struct'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/quality'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/timestamp'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/double_point'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/direction'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/severity'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/vector'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/step_position'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/binary_control'}], '$defs': {'boolean': {'type': 'boolean'}, 'integer': {'type': 'integer'}, 'unsigned': {'type': 'integer'}, 'float': {'oneOf': [{'type': 'number'}, {'enum': ['nan', 'inf', '-inf']}]}, 'bit_string': {'type': 'array', 'items': {'type': 'boolean'}}, 'octet_string': {'type': 'string', 'description': 'hex encoded bytes'}, 'visible_string': {'type': 'string'}, 'mms_string': {'type': 'string'}, 'array': {'type': 'array', 'items': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}, 'struct': {'type': 'object', 'patternProperties': {'.+': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value'}}}, 'quality': {'type': 'object', 'required': ['validity', 'details', 'source', 'test', 'operator_blocked'], 'properties': {'validity': {'enum': ['GOOD', 'INVALID', 'RESERVED', 'QUESTIONABLE']}, 'details': {'type': 'array', 'items': {'enum': ['OVERFLOW', 'OUT_OF_RANGE', 'BAD_REFERENCE', 'OSCILLATORY', 'FAILURE', 'OLD_DATA', 'INCONSISTENT', 'INACCURATE']}}, 'source': {'enum': ['PROCESS', 'SUBSTITUTED']}, 'test': {'type': 'boolean'}, 'operator_blocked': {'type': 'boolean'}}}, 'timestamp': {'type': 'object', 'required': ['value', 'leap_second', 'clock_failure', 'not_synchronized'], 'properties': {'value': {'type': 'number', 'description': 'seconds since 1970-01-01'}, 'leap_second': {'type': 'boolean'}, 'clock_failure': {'type': 'boolean'}, 'not_synchronized': {'type': 'boolean'}, 'accuracy': {'type': 'integer'}}}, 'double_point': {'enum': ['INTERMEDIATE', 'OFF', 'ON', 'BAD']}, 'direction': {'enum': ['UNKNOWN', 'FORWARD', 'BACKWARD', 'BOTH']}, 'severity': {'enum': ['UNKNOWN', 'CRITICAL', 'MAJOR', 'MINOR', 'WARNING']}, 'analogue': {'type': 'object', 'properties': {'i': {'type': 'integer'}, 'f': {'oneOf': [{'type': 'number'}, {'enum': ['nan', 'inf', '-inf']}]}}}, 'vector': {'type': 'object', 'required': ['magnitude'], 'properties': {'magnitude': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}, 'angle': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value/$defs/analogue'}}}, 'step_position': {'type': 'object', 'required': ['value'], 'properties': {'value': {'type': 'integer'}, 'transient': {'type': 'boolean'}}}, 'binary_control': {'enum': ['STOP', 'LOWER', 'HIGHER', 'RESERVED']}}}, 'value_type': {'oneOf': [{'enum': ['BOOLEAN', 'INTEGER', 'UNSIGNED', 'FLOAT', 'BIT_STRING', 'OCTET_STRING', 'VISIBLE_STRING', 'MMS_STRING', 'QUALITY', 'TIMESTAMP', 'DOUBLE_POINT', 'DIRECTION', 'SEVERITY', 'ANALOGUE', 'VECTOR', 'STEP_POSITION', 'BINARY_CONTROL']}, {'type': 'object', 'required': ['type', 'element_type', 'length'], 'properties': {'type': {'const': 'ARRAY'}, 'element_type': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value_type'}, 'length': {'type': 'integer'}}}, {'type': 'object', 'required': ['type', 'elements'], 'properties': {'type': {'const': 'STRUCT'}, 'elements': {'type': 'array', 'items': {'type': 'object', 'requried': ['name', 'type'], 'properties': {'name': {'type': 'string'}, 'type': {'$ref': 'hat-gateway://iec61850.yaml#/$defs/value_type'}}}}}}]}, 'refs': {'value': {'type': 'object', 'required': ['logical_device', 'logical_node', 'fc', 'names'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'fc': {'type': 'string'}, 'names': {'type': 'array', 'items': {'type': ['string', 'integer']}}}}, 'command': {'type': 'object', 'required': ['logical_device', 'logical_node', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'name': {'type': 'string'}}}, 'rcb': {'type': 'object', 'required': ['logical_device', 'logical_node', 'type', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'type': {'enum': ['BUFFERED', 'UNBUFFERED']}, 'name': {'type': 'string'}}}, 'dataset': {'oneOf': [{'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset/$defs/nonpersisted'}, {'$ref': 'hat-gateway://iec61850.yaml#/$defs/refs/dataset/$defs/persisted'}], '$defs': {'nonpersisted': {'type': 'string'}, 'persisted': {'type': 'object', 'required': ['logical_device', 'logical_node', 'name'], 'properties': {'logical_device': {'type': 'string'}, 'logical_node': {'type': 'string'}, 'name': {'type': 'string'}}}}}}, 'errors': {'service_error': {'enum': ['NO_ERROR', 'INSTANCE_NOT_AVAILABLE', 'INSTANCE_IN_USE', 'ACCESS_VIOLATION', 'ACCESS_NOT_ALLOWED_IN_CURRENT_STATE', 'PARAMETER_VALUE_INAPPROPRIATE', 'PARAMETER_VALUE_INCONSISTENT', 'CLASS_NOT_SUPPORTED', 'INSTANCE_LOCKED_BY_OTHER_CLIENT', 'CONTROL_MUST_BE_SELECTED', 'TYPE_CONFLICT', 'FAILED_DUE_TO_COMMUNICATIONS_CONSTRAINT', 'FAILED_DUE_TO_SERVER_CONTRAINT']}, 'additional_cause': {'enum': ['UNKNOWN', 'NOT_SUPPORTED', 'BLOCKED_BY_SWITCHING_HIERARCHY', 'SELECT_FAILED', 'INVALID_POSITION', 'POSITION_REACHED', 'PARAMETER_CHANGE_IN_EXECUTION', 'STEP_LIMIT', 'BLOCKED_BY_MODE', 'BLOCKED_BY_PROCESS', 'BLOCKED_BY_INTERLOCKING', 'BLOCKED_BY_SYNCHROCHECK', 'COMMAND_ALREADY_IN_EXECUTION', 'BLOCKED_BY_HEALTH', 'ONE_OF_N_CONTROL', 'ABORTION_BY_CANCEL', 'TIME_LIMIT_OVER', 'ABORTION_BY_TRIP', 'OBJECT_NOT_SELECTED', 'OBJECT_ALREADY_SELECTED', 'NO_ACCESS_AUTHORITY', 'ENDED_WITH_OVERSHOOT', 'ABORTION_DUE_TO_DEVIATION', 'ABORTION_BY_COMMUNICATION_LOSS', 'BLOCKED_BY_COMMAND', 'NONE', 'INCONSISTENT_PARAMETERS', 'LOCKED_BY_OTHER_CLIENT']}, 'test_error': {'enum': ['NO_ERROR', 'UNKNOWN', 'TIMEOUT_TEST_NOT_OK', 'OPERATOR_TEST_NOT_OK']}}}}, 'hat-gateway://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://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://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://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://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://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 silet 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'}}}}}}}}}, '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'}}}}}}}}})
60class Iec103MasterDevice(common.Device): 61 62 def __init__(self, 63 conf: common.DeviceConf, 64 eventer_client: hat.event.eventer.Client, 65 event_type_prefix: common.EventTypePrefix): 66 self._conf = conf 67 self._eventer_client = eventer_client 68 self._event_type_prefix = event_type_prefix 69 self._master = None 70 self._conns = {} 71 self._remote_enabled = {i['address']: False 72 for i in conf['remote_devices']} 73 self._remote_confs = {i['address']: i 74 for i in conf['remote_devices']} 75 self._remote_groups = {} 76 self._async_group = aio.Group() 77 self._log = _create_logger_adapter(conf['name']) 78 79 self.async_group.spawn(self._create_link_master_loop) 80 81 @property 82 def async_group(self) -> aio.Group: 83 return self._async_group 84 85 async def process_events(self, events: Collection[hat.event.common.Event]): 86 for event in events: 87 try: 88 await self._process_event(event) 89 90 except Exception as e: 91 self._log.warning('error processing event: %s', e, exc_info=e) 92 93 async def _create_link_master_loop(self): 94 95 async def cleanup(): 96 with contextlib.suppress(ConnectionError): 97 await self._register_status('DISCONNECTED') 98 99 if self._master: 100 await self._master.async_close() 101 102 try: 103 while True: 104 await self._register_status('CONNECTING') 105 106 try: 107 self._master = await link.create_master_link( 108 port=self._conf['port'], 109 address_size=link.AddressSize.ONE, 110 silent_interval=self._conf['silent_interval'], 111 baudrate=self._conf['baudrate'], 112 bytesize=serial.ByteSize[self._conf['bytesize']], 113 parity=serial.Parity[self._conf['parity']], 114 stopbits=serial.StopBits[self._conf['stopbits']], 115 xonxoff=self._conf['flow_control']['xonxoff'], 116 rtscts=self._conf['flow_control']['rtscts'], 117 dsrdtr=self._conf['flow_control']['dsrdtr'], 118 name=self._conf['name']) 119 120 except Exception as e: 121 self._log.warning( 122 'link master (endpoint) failed to create: %s', 123 e, exc_info=e) 124 await self._register_status('DISCONNECTED') 125 await asyncio.sleep(self._conf['reconnect_delay']) 126 continue 127 128 await self._register_status('CONNECTED') 129 for address, enabled in self._remote_enabled.items(): 130 if enabled: 131 self._enable_remote(address) 132 133 await self._master.wait_closed() 134 await self._register_status('DISCONNECTED') 135 self._master = None 136 137 finally: 138 self._log.debug('closing link master loop') 139 self.close() 140 await aio.uncancellable(cleanup()) 141 142 async def _connection_loop(self, group, address): 143 144 async def cleanup(): 145 with contextlib.suppress(ConnectionError): 146 await self._register_rmt_status(address, 'DISCONNECTED') 147 148 conn = self._conns.pop(address, None) 149 if conn: 150 await conn.async_close() 151 152 remote_conf = self._remote_confs[address] 153 try: 154 while True: 155 await self._register_rmt_status(address, 'CONNECTING') 156 157 try: 158 conn_link = await self._master.open_connection( 159 addr=address, 160 response_timeout=remote_conf['response_timeout'], 161 send_retry_count=remote_conf['send_retry_count'], 162 poll_class1_delay=remote_conf['poll_class1_delay'], 163 poll_class2_delay=remote_conf['poll_class2_delay'], 164 name=self._conf['name']) 165 166 except Exception as e: 167 self._log.error('connection error to address %s: %s', 168 address, e, exc_info=e) 169 await self._register_rmt_status(address, 'DISCONNECTED') 170 await asyncio.sleep(remote_conf['reconnect_delay']) 171 continue 172 173 await self._register_rmt_status(address, 'CONNECTED') 174 conn = iec103.MasterConnection( 175 conn=conn_link, 176 data_cb=functools.partial(self._on_data, address), 177 generic_data_cb=None) 178 self._conns[address] = conn 179 if remote_conf['time_sync_delay'] is not None: 180 group.spawn(self._time_sync_loop, conn, 181 remote_conf['time_sync_delay']) 182 183 await conn.wait_closed() 184 await self._register_rmt_status(address, 'DISCONNECTED') 185 self._conns.pop(address) 186 187 finally: 188 self._log.debug('closing remote device %s', address) 189 group.close() 190 await aio.uncancellable(cleanup()) 191 192 async def _time_sync_loop(self, conn, delay): 193 try: 194 while True: 195 await conn.time_sync() 196 self._log.debug('time sync') 197 await asyncio.sleep(delay) 198 199 except ConnectionError: 200 self._log.debug('connection closed') 201 202 finally: 203 conn.close() 204 205 async def _on_data(self, address, data): 206 events = collections.deque() 207 try: 208 for event in _events_from_data(data, address, 209 self._event_type_prefix): 210 events.append(event) 211 212 except Exception as e: 213 self._log.warning('data %s ignored due to: %s', 214 data, e, exc_info=e) 215 216 if events: 217 await self._eventer_client.register(events) 218 219 async def _process_event(self, event): 220 prefix_len = len(self._event_type_prefix) 221 if event.type[prefix_len + 1] != 'remote_device': 222 raise Exception('unexpected event type') 223 224 address = self._address_from_event(event) 225 etype_suffix = event.type[prefix_len + 3:] 226 227 if etype_suffix[0] == 'enable': 228 self._process_enable(event) 229 230 elif etype_suffix[0] == 'command': 231 asdu = int(etype_suffix[1]) 232 io = iec103.IoAddress( 233 function_type=int(etype_suffix[2]), 234 information_number=int(etype_suffix[3])) 235 self._process_command(event, address, asdu, io) 236 237 elif etype_suffix[0] == 'interrogation': 238 asdu = int(etype_suffix[1]) 239 self._process_interrogation(event, address, asdu) 240 241 else: 242 raise Exception('unexpected event type') 243 244 def _process_enable(self, event): 245 address = self._address_from_event(event) 246 enable = event.payload.data 247 if address not in self._remote_enabled: 248 raise Exception('invalid remote device address') 249 250 if not isinstance(enable, bool): 251 raise Exception('invalid enable event payload') 252 253 self._remote_enabled[address] = enable 254 255 if not self._master: 256 return 257 258 if enable: 259 self._enable_remote(address) 260 261 else: 262 self._disable_remote(address) 263 264 def _enable_remote(self, address): 265 remote_group = self._remote_groups.get(address) 266 if remote_group and remote_group.is_open: 267 return 268 269 remote_group = self._async_group.create_subgroup() 270 self._remote_groups[address] = remote_group 271 remote_group.spawn(self._connection_loop, remote_group, address) 272 273 def _disable_remote(self, address): 274 if address in self._remote_groups: 275 remote_group = self._remote_groups.pop(address) 276 remote_group.close() 277 278 def _process_command(self, event, address, asdu, io): 279 conn = self._conns.get(address) 280 if not conn or not conn.is_open: 281 raise Exception('connection closed') 282 283 value = iec103.DoubleValue[event.payload.data['value']] 284 session_id = event.payload.data['session_id'] 285 self._remote_groups[address].spawn( 286 self._cmd_req_res, conn, address, asdu, io, value, session_id) 287 288 async def _cmd_req_res(self, conn, address, asdu, io, value, session_id): 289 try: 290 success = await asyncio.wait_for( 291 conn.send_command(asdu, io, value), timeout=command_timeout) 292 293 except ConnectionError: 294 self._log.warning( 295 'command %s %s %s to %s failed: connection closed', 296 asdu, io, value, address) 297 return 298 299 except asyncio.TimeoutError: 300 self._log.warning( 301 'command %s %s %s to %s timeout', asdu, io, value, address) 302 return 303 304 event = _create_event( 305 event_type=(*self._event_type_prefix, 'gateway', 'remote_device', 306 str(address), 'command', str(asdu), 307 str(io.function_type), str(io.information_number)), 308 payload={'success': success, 309 'session_id': session_id}) 310 311 await self._eventer_client.register([event]) 312 313 def _process_interrogation(self, event, address, asdu): 314 conn = self._conns.get(address) 315 if not conn or not conn.is_open: 316 self._log.warning("event %s ignored due to connection closed", 317 event) 318 return 319 320 self._remote_groups[address].spawn( 321 self._interrogate_req_res, conn, address, asdu) 322 323 async def _interrogate_req_res(self, conn, address, asdu): 324 try: 325 await asyncio.wait_for(conn.interrogate(asdu), 326 timeout=interrogate_timeout) 327 328 except ConnectionError: 329 self._log.warning( 330 'interrogation on %s to %s failed: connection closed', 331 asdu, address) 332 return 333 334 except asyncio.TimeoutError: 335 self._log.warning('interrogation on %s to %s timeout', 336 asdu, address) 337 return 338 339 event = _create_event( 340 event_type=(*self._event_type_prefix, 'gateway', 'remote_device', 341 str(address), 'interrogation', str(asdu)), 342 payload=None) 343 344 await self._eventer_client.register([event]) 345 346 async def _register_status(self, status): 347 event = _create_event( 348 event_type=(*self._event_type_prefix, 'gateway', 'status'), 349 payload=status) 350 351 await self._eventer_client.register([event]) 352 353 async def _register_rmt_status(self, address, status): 354 event = _create_event( 355 event_type=(*self._event_type_prefix, 356 'gateway', 'remote_device', str(address), 'status'), 357 payload=status) 358 359 await self._eventer_client.register([event]) 360 361 def _address_from_event(self, event): 362 return int(event.type[len(self._event_type_prefix) + 2])
Device interface
Iec103MasterDevice( conf: Union[NoneType, 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])
62 def __init__(self, 63 conf: common.DeviceConf, 64 eventer_client: hat.event.eventer.Client, 65 event_type_prefix: common.EventTypePrefix): 66 self._conf = conf 67 self._eventer_client = eventer_client 68 self._event_type_prefix = event_type_prefix 69 self._master = None 70 self._conns = {} 71 self._remote_enabled = {i['address']: False 72 for i in conf['remote_devices']} 73 self._remote_confs = {i['address']: i 74 for i in conf['remote_devices']} 75 self._remote_groups = {} 76 self._async_group = aio.Group() 77 self._log = _create_logger_adapter(conf['name']) 78 79 self.async_group.spawn(self._create_link_master_loop)
async def
process_events(self, events: Collection[hat.event.common.common.Event]):
85 async def process_events(self, events: Collection[hat.event.common.Event]): 86 for event in events: 87 try: 88 await self._process_event(event) 89 90 except Exception as e: 91 self._log.warning('error processing event: %s', e, exc_info=e)
Process received events
This method can be coroutine or regular function.