diff --git a/smartmeter/__init__.py b/smartmeter/__init__.py new file mode 100755 index 000000000..771403715 --- /dev/null +++ b/smartmeter/__init__.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2012-2014 Oliver Hinckel github@ollisnet.de +# Copyright 2018-2024 Bernd Meiners Bernd.Meiners@mail.de +# Copyright 2022- Michael Wenzel wenzel_michael@web.de +# Copyright 2024- Sebastian Helms morg @ knx-user-forum.de +######################################################################### +# +# This file is part of SmartHomeNG. https://github.com/smarthomeNG// +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +######################################################################### + +import asyncio +import os +import threading +import time +import sys + +# find out if we can import serial - if not, the plugin might not start anyway +# serial is not needed in the plugin itself, but in the modules SML and DLMS, +# which will import the serial module by themselves, if serial is configured +try: + import serial # noqa + REQUIRED_PACKAGE_IMPORTED = True +except Exception: + REQUIRED_PACKAGE_IMPORTED = False + +from lib.model.smartplugin import SmartPlugin +from lib.item.item import Item +from lib.shtime import Shtime +from lib.shyaml import yaml_save +from collections.abc import Callable +from typing import (Union, Any) + +from . import dlms # noqa +from . import sml # noqa +from .conversion import Conversion +from .webif import WebInterface + +shtime = Shtime.get_instance() + +# item attributes handled by this plugin +OBIS_CODE = 'obis_code' # single code, '1-1:1.8.0' or '1.8.0' +OBIS_INDEX = 'obis_index' # optional: index of obis value, default 0 +OBIS_PROPERTY = 'obis_property' # optional: property to read ('value', 'unit', ...) default 'value'' +OBIS_VTYPE = 'obis_vtype' # optional: type of value (str, num, int, float, ZST12, ZST10, D6, Z6, Z4, '') default '' +OBIS_READOUT = 'obis_readout' # complete readout (dlms only) + +ITEM_ATTRS = (OBIS_CODE, OBIS_INDEX, OBIS_PROPERTY, OBIS_VTYPE, OBIS_READOUT) + +# obis properties +PROPS = [ + 'value', 'unit', 'name', 'valueRaw', 'scaler', 'status', 'valTime', 'actTime', 'signature', 'unitCode', + 'statRun', 'statFraudMagnet', 'statFraudCover', 'statEnergyTotal', 'statEnergyL1', 'statEnergyL2', 'statEnergyL3', + 'statRotaryField', 'statBackstop', 'statCalFault', 'statVoltageL1', 'statVoltageL2', 'statVoltageL3', 'obis' +] + +# mapping separator. set to something not probable to be in obis, index or prop +SEP = '-#-' + + +class Smartmeter(SmartPlugin, Conversion): + """ + Main class of the Plugin. Does all plugin specific stuff and provides + the update functions for the items + """ + + # move to 1.0.0 as soon as DLMS asyncio is tested + PLUGIN_VERSION = '0.9.0' + + def __init__(self, sh): + """ + Initializes the plugin. The parameters described for this method are pulled from the entry in plugin.conf. + """ + + # Call init code of parent class (SmartPlugin) + super().__init__() + + self.connected = False + self._autoreconnect = self.get_parameter_value('autoreconnect') + self.alive = False + self._lock = threading.Lock() + + # store "wanted" obis codes + self.obis_codes = [] + + # store last response(s) + self.obis_results = {} + + # set or discovered protocol (SML/DLMS) + self.protocol = None + + # protocol auto-detected? + self.proto_detected = False + + self.use_asyncio = False + + # update items only every x seconds + self.timefilter = -1 + self._last_item_update = -1 + + # load parameters from config + self._load_parameters() + + # quit if errors on parameter read + if not self._init_complete: + return + + self.init_webinterface(WebInterface) + + def discover(self, protocol=None) -> bool: + """ + try to identify protocol of smartmeter + + if protocol is given, only test this protocol + otherwise, test DLMS and SML + """ + if not protocol: + disc_protos = ['DLMS', 'SML'] + else: + disc_protos = [protocol] + + for proto in disc_protos: + if self._get_module(proto).discover(self._config): + self.logger.info(f'discovery of {protocol} was successful') + self.protocol = proto + if len(disc_protos) > 1: + self.proto_detected = True + return True + else: + self.logger.info(f'discovery of {protocol} was unsuccessful') + + return False + + def query(self, assign_values: bool = True, protocol=None) -> dict: + """ + query smartmeter resp. listen for data + + if protocol is given, try to use the given protocol + otherwise, use self._protocol as default + + if assign_values is set, assign received values to items + if assign_values is not set, just return the results + """ + if not protocol: + protocol = self.protocol + ref = self._get_module(protocol) + if not ref: + self.logger.error(f'could not get module for protocol {protocol}, aborting.') + return {} + + result = {} + try: + result = ref.query(self._config) + if not result: + self.logger.warning('no results from smartmeter query received') + else: + self.logger.debug(f'got result: {result}') + if assign_values: + self._update_values(result) + except Exception as e: + self.logger.error(f'error: {e}', exc_info=True) + + return result + + def create_items(self, data: dict = {}, file: str = '') -> bool: + """ + create itemdefinitions from read obis numbers + + dict should be the result dict, or (if empty) self.obis_results will be used + file is the filename to write. default is: + /smartmeter-.yaml + + return indicates success or error + """ + if not data: + data = self.obis_results + if not data: + return False + + try: + id = data['1-0:96.1.0*255'][0]['value'] + except (KeyError, IndexError, AttributeError): + try: + id = data['1-0:0.0.9*255'][0]['value'] + except (KeyError, IndexError, AttributeError): + id = int(time.time()) + + if not file: + dir = self._sh._items_dir + file = os.path.join(dir, f'smartmeter-{id}.yaml') + + if os.path.exists(file): + self.logger.warning(f'output file {file} exists, not overwriting.') + return False + + result = {} + for nr, code in enumerate(data): + item = f'item_{nr}' + if len(data[code]) == 0: + continue + d = data[code][0] + name = d.get('name', '') + unit = d.get('unit') + if isinstance(d['value'], str): + typ = 'str' + elif type(d['value']) in (int, float): + typ = 'num' + else: + typ = 'foo' + + result[item] = { + 'type': typ, + 'cache': True, + 'remark': name, + 'obis_code': code, + } + + if unit: + result[item]['unit'] = { + 'type': 'str', + 'cache': True, + 'obis_code': '..:.', + 'obis_property': 'unit' + } + + try: + yaml_save(file, {id: result}) + except Exception as e: + self.logger.warning(f'saving item file {file} failed with error: {e}') + return False + + return True + + def run(self): + """ + Run method for the plugin + """ + self.logger.debug('run method called') + + # TODO: reload parameters - why? + self._load_parameters() + + if not self.protocol: + self.discover() + + self.alive = True + if self.protocol: + self.logger.info(f'{"detected" if self.proto_detected else "set"} protocol {self.protocol}') + else: + # skip cycle / crontab scheduler if no protocol set (only manual control from web interface) + self.logger.error('unable to auto-detect device protocol (SML/DLMS). Try manual disconvery via standalone mode or Web Interface.') + return + + # Setup scheduler for device poll loop, if protocol set + if self.use_asyncio: + self.start_asyncio(self.plugin_coro()) + else: + if (self.cycle or self.crontab) and self.protocol: + if self.crontab: + next = None # adhere to the crontab + else: + # no crontab given so we might just query immediately + next = shtime.now() + self.scheduler_add(self.get_fullname(), self.poll_device, prio=5, cycle=self.cycle, cron=self.crontab, next=next) + self.logger.debug('run method finished') + + def stop(self): + """ + Stop method for the plugin + """ + self.logger.debug('stop method called') + self.alive = False + if self.use_asyncio: + self.stop_asyncio() + else: + try: + self.scheduler_remove(self.get_fullname()) + except Exception: + pass + + def _load_parameters(self): + + # + # connection configuration + # + self._config = {} + + # not really a config value, but easier than having another parameter everywhere + self._config['lock'] = self._lock + + # first try connections; abort loading plugin if no connection is configured + self._config['serial_port'] = self.get_parameter_value('serialport') + if self._config['serial_port'] and not REQUIRED_PACKAGE_IMPORTED: + self.logger.error('serial port requested but package "pyserial" could not be imported.') + self._init_complete = False + return + + # serial has priority, as DLMS only uses serial + if self._config['serial_port']: + self._config['connection'] = 'serial' + else: + host = self.get_parameter_value('host') + port = self.get_parameter_value('port') + if host and port: + self._config['host'] = host + self._config['port'] = port + self._config['connection'] = 'network' + else: + self.logger.error('neither serial nor network connection configured.') + self._init_complete = False + return + + # there is a possibility of using a named device + # normally this will be empty since only one meter will be attached + # to one serial interface but the standard allows for it and we honor that. + self._config['timeout'] = self.get_parameter_value('timeout') + self._config['baudrate'] = self.get_parameter_value('baudrate') + + # get mode (SML/DLMS) if set by user + # if not set, try to get at runtime + if not self.protocol: + self.protocol = self.get_parameter_value('protocol').upper() + + # DLMS only + self._config['dlms'] = {} + self._config['dlms']['device'] = self.get_parameter_value('device_address') + self._config['dlms']['querycode'] = self.get_parameter_value('querycode') + self._config['dlms']['baudrate_min'] = self.get_parameter_value('baudrate_min') + self._config['dlms']['use_checksum'] = self.get_parameter_value('use_checksum') + self._config['dlms']['only_listen'] = self.get_parameter_value('only_listen') + self._config['dlms']['normalize'] = self.get_parameter_value('normalize') + + # SML only + self._config['sml'] = {} + self._config['sml']['buffersize'] = self.get_parameter_value('buffersize') # 1024 + self._config['sml']['device'] = self.get_parameter_value('device_type') + self._config['sml']['date_offset'] = self.get_parameter_value('date_offset') # 0 + + # + # general plugin parameters + # + self.cycle = self.get_parameter_value('cycle') + if self.cycle == 0: + self.cycle = None + + self.crontab = self.get_parameter_value('crontab') # the more complex way to specify the device query frequency + if self.crontab == '': + self.crontab = None + + self._config['poll'] = True + poll = self.get_parameter_value('poll') + if not poll: + # TODO: remove if/else as soon as async DLMS is tested with live device + if self.protocol == 'SML': + self.use_asyncio = True + self._config['poll'] = False + else: + self.logger.warning('async mode requested, but not yet available for DLMS. Plugin will be polling regularly...') + + if self.use_asyncio: + self.timefilter = self.get_parameter_value('time_filter') + if self.timefilter == -1 and self.cycle is not None: + self.timefilter = self.cycle + if self.timefilter < 0: + self.timefilter = 0 + self._config['timefilter'] = self.timefilter + + if not self.use_asyncio and not (self.cycle or self.crontab): + self.logger.warning(f'{self.get_fullname()}: no update cycle or crontab set. The smartmeter will not be queried automatically') + + def _get_module(self, protocol=None): + """ return module reference for SML/DMLS module """ + if not protocol: + protocol = self.protocol + name = __name__ + '.' + str(protocol).lower() + ref = sys.modules.get(name) + if not ref: + self.logger.warning(f"couldn't get reference for module {name}...") + return ref + + def _to_mapping(self, obis: str, index: Any) -> str: + return f'{obis}{SEP}{index}' + + def parse_item(self, item: Item) -> Union[Callable, None]: + """ + Default plugin parse_item method. Is called when the plugin is initialized. + + :param item: The item to process. + :return: returns update_item function if changes are to be watched + """ + if self.has_iattr(item.conf, OBIS_CODE): + obis = self.get_iattr_value(item.conf, OBIS_CODE) + prop = self.get_iattr_value(item.conf, OBIS_PROPERTY, default='value') + if prop not in PROPS: + self.logger.warning(f'item {item}: invalid property {prop} requested for obis {obis}, setting default "value"') + prop = 'value' + vtype = self.get_iattr_value(item.conf, OBIS_VTYPE, default='') + if vtype: + if prop.startswith('value'): + if vtype in ('int', 'num', 'float', 'str') and vtype != item.type(): + self.logger.warning(f'item {item}: item type is {item.type()}, but obis_vtype is "{vtype}", please fix item definition') + vtype = None + else: + self.logger.warning(f'item {item} has obis_vtype set, which is only valid for "value" property, not "{prop}", ignoring.') + vtype = None + index = self.get_iattr_value(item.conf, OBIS_INDEX, default=0) + + self.add_item(item, {'property': prop, 'index': index, 'vtype': vtype}, self._to_mapping(obis, index)) + self.obis_codes.append(obis) + self.logger.debug(f'Attach {item.property.path} with obis={obis}, prop={prop} and index={index}') + + if self.has_iattr(item.conf, OBIS_READOUT): + self.add_item(item, mapping='readout') + self.logger.debug(f'Attach {item.property.path} for readout') + + def _is_obis_code_wanted(self, code: str) -> bool: + """ + this stub function detects whether code is in the list of user defined OBIS codes to scan for + """ + return code in self.obis_codes + + def poll_device(self): + """ + This function just calls the 'query device' method of the + respective module + """ + self.query() + + def _update_values(self, result: dict): + """ + this function takes the OBIS Code as text and accepts a list of dictionaries with Values + :param Code: OBIS Code + :param Values: list of dictionaries with Value / Unit entries + """ + # self.logger.debug(f'running _update_values with {result}') + self.obis_results.update(result) + + # if "update items only every x seconds" is set: + if self.timefilter > 0 and self._last_item_update + self.timefilter > time.time(): + self.logger.debug(f'timefilter active, {int(self._last_item_update + self.timefilter - time.time())} seconds remaining') + return + + if 'readout' in result: + for item in self.get_items_for_mapping('readout'): + item(result['readout'], self.get_fullname()) + self.logger.debug(f'set item {item} to readout {result["readout"]}') + del result['readout'] + + update = -1 + # check all obis codes + for obis, vlist in result.items(): + if not self._is_obis_code_wanted(obis): + continue + for idx, vdict in enumerate(vlist): + for item in self.get_items_for_mapping(self._to_mapping(obis, idx)): + conf = self.get_item_config(item) + # self.logger.debug(f'processing item {item} with {conf} for index {idx}...') + if conf.get('index', 0) == idx: + prop = conf.get('property', 'value') + val = None + try: + val = vdict[prop] + except KeyError: + self.logger.warning(f'item {item} wants property {prop} which has not been recceived') + continue + + # skip processing if val is None, save cpu cycles + if val is not None: + try: + converter = conf['vtype'] + itemValue = self._convert_value(val, converter) + # self.logger.debug(f'conversion yielded {itemValue} from {val} for converter "{converter}"') + item(itemValue, self.get_fullname()) + if update < 0: + update = time.time() + self.logger.debug(f'set item {item} for obis code {obis}:{prop} to value {itemValue}') + except ValueError as e: + self.logger.error(f'error while converting value {val} for item {item}, obis code {obis}: {e}') + else: + self.logger.debug(f'for item {item} and obis code {obis}:{prop} no content was received') + if update > 0: + self._last_item_update = update + + async def plugin_coro(self): + """ + Coroutine for the session that starts the serial connection and listens + """ + self.logger.info("plugin_coro started") + try: + self.reader = self._get_module().AsyncReader(self.logger, self, self._config) + except ImportError as e: + # serial_asyncio not loaded/present + self.logger.error(e) + return + + # start listener and queue listener in parallel + await asyncio.gather(self.reader.stop_on_queue(), self._run_listener()) + + # reader quit, exit loop + self.alive = False + self.logger.info("plugin_coro finished") + + async def _run_listener(self): + """ call async listener and restart if requested """ + while self.alive: + # reader created, run reader + try: + await self.reader.listen() + except Exception as e: + self.logger.warning(f'while running listener, the following error occured: {e}') + + if not self._autoreconnect: + self.logger.debug('listener quit, autoreconnect not set, exiting') + break + + self.logger.debug('listener quit, autoreconnecting after 2 seconds...') + await asyncio.sleep(2) + + @property + def item_list(self): + return self.get_item_list() + + @property + def log_level(self): + return self.logger.getEffectiveLevel() diff --git a/smartmeter/conversion.py b/smartmeter/conversion.py new file mode 100755 index 000000000..76d276349 --- /dev/null +++ b/smartmeter/conversion.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2016 - 2018 Bernd Meiners Bernd.Meiners@mail.de +######################################################################### +# +# This file is part of SmartHomeNG.py. +# Visit: https://github.com/smarthomeNG/ +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# +# SmartHomeNG.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG.py. If not, see . +######################################################################### + + +__license__ = "GPL" +__version__ = "2.0" +__revision__ = "0.1" +__docformat__ = 'reStructuredText' + +import datetime + +CONVERTERS = { + 'int': 'int', + 'float': 'float', + 'ZST10': 'datetime', + 'ZST12': 'datetime', + 'D6': 'date', + 'Z6': 'time', + 'Z4': 'time', + 'num': 'num' +} + + +class Conversion: + def _from_ZST10(self, text: str) -> datetime.datetime: + """ + this function converts a string of form "YYMMDDhhmm" into a datetime object + :param text: string to convert + :return: a datetime object upon success or None if error found by malformed string + """ + if len(text) != 10: + raise ValueError("too few characters for date/time code from OBIS") + + year = int(text[0:2]) + 2000 + month = int(text[2:4]) + day = int(text[4:6]) + hour = int(text[6:8]) + minute = int(text[8:10]) + return datetime.datetime(year, month, day, hour, minute, 0) + + def _from_ZST12(self, text: str) -> datetime.datetime: + """ + this function converts a string of form "YYMMDDhhmmss" into a datetime object + :param text: string to convert + :return: a datetime object upon success or None if error found by malformed string + """ + if len(text) != 12: + raise ValueError("too few characters for date/time code from OBIS") + + year = int(text[0:2]) + 2000 + month = int(text[2:4]) + day = int(text[4:6]) + hour = int(text[6:8]) + minute = int(text[8:10]) + second = int(text[10:12]) + return datetime.datetime(year, month, day, hour, minute, second) + + def _from_D6(self, text: str) -> datetime.date: + """ + this function converts a string of form "YYMMDD" into a datetime.date object + :param text: string to convert + :return: a datetime.date object upon success or None if error found by malformed string + """ + if len(text) != 6: + raise ValueError("too few characters for date code from OBIS") + + year = int(text[0:2]) + 2000 + month = int(text[2:4]) + day = int(text[4:6]) + return datetime.date(year, month, day) + + def _from_Z4(self, text: str) -> datetime.time: + """ + this function converts a string of form "hhmm" into a datetime.time object + :param text: string to convert + :return: a datetime.time object upon success or None if error found by malformed string + """ + if len(text) != 4: + raise ValueError("too few characters for time code from OBIS") + + hour = int(text[0:2]) + minute = int(text[2:4]) + return datetime.time(hour, minute) + + def _from_Z6(self, text: str) -> datetime.time: + """ + this function converts a string of form "hhmmss" into a datetime.time object + :param text: string to convert + :return: a datetime.time object upon success or None if error found by malformed string + """ + if len(text) != 6: + raise ValueError("too few characters for time code from OBIS") + + hour = int(text[0:2]) + minute = int(text[2:4]) + second = int(text[4:6]) + return datetime.time(hour, minute, second) + + def _convert_value(self, val, converter: str = ''): + """ + This function converts the OBIS value to a user chosen valalue + :param val: the value to convert given as string + :param converter: type of value, should contain one of CONVERTERS + :return: after successful conversion the value in converted form + """ + if converter not in CONVERTERS: + return val + + try: + if converter in ('num', 'float', 'int'): + + if converter in ('num', 'int'): + try: + return int(val) + except (ValueError, AttributeError): + if converter == 'int': + raise ValueError + + # try/except to catch floats like '1.0' and '1,0' + try: + return float(val) + except ValueError: + if ',' in val: + val = val.replace(',', '.') + return float(val) + else: + raise ValueError + + if not val.isdigit(): + raise ValueError("only digits allowed for date/time code from OBIS") + + if converter == 'int': + return int(val) + + # try to find self._from_ -> run it and return result + if hasattr(self, f'_from_{converter}'): + return getattr(self, f'_from_{converter}')(val) + + # no suitable converter found + raise ValueError + + except ValueError as e: + raise ValueError(f'could not convert from "{val}" to a {CONVERTERS[converter]} ({e})') diff --git a/smartmeter/dlms.py b/smartmeter/dlms.py new file mode 100755 index 000000000..ec8cd7e09 --- /dev/null +++ b/smartmeter/dlms.py @@ -0,0 +1,1131 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2013 - 2015 KNX-User-Forum e.V. http://knx-user-forum.de/ +# Copyright 2016 - 2022 Bernd Meiners Bernd.Meiners@mail.de +# Copyright 2024 - Sebastian Helms morg @ knx-user-forum.de +######################################################################### +# +# DLMS module for SmartMeter plugin for SmartHomeNG +# +# This file is part of SmartHomeNG.py. +# Visit: https://github.com/smarthomeNG/ +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# https://smarthomeng.de +# +# SmartHomeNG.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG.py. If not, see . +######################################################################### + +import asyncio +import logging +import time +import serial +try: + import serial_asyncio + ASYNC_IMPORTED = True +except ImportError: + ASYNC_IMPORTED = False +import socket # not needed, just for code portability + +from ruamel.yaml import YAML +from smllib import const as smlConst +from threading import Lock +from typing import (Union, Tuple, Any) + +# only for syntax/type checking +try: + from lib.model.smartplugin import SmartPlugin +except ImportError: + class SmartPlugin(): + pass + + class SmartPluginWebIf(): + pass + + +""" +This module implements the query of a smartmeter using the DLMS protocol. +The smartmeter needs to have an infrared interface and an IR-Adapter is needed for USB. + +The Character Format for protocol mode A - D is defined as 1 start bit, 7 data bits, 1 parity bit, 1 stop bit and even parity +In protocol mode E it is defined as 1 start bit, 8 data bits, 1 stop bit is allowed, see Annex E of IEC62056-21 +For this plugin the protocol mode E is neither implemented nor supported. + +Abbreviations +------------- +COSEM + COmpanion Specification for Energy Metering + +OBIS + OBject Identification System (see iec62056-61{ed1.0}en_obis_protocol.pdf) + +""" + +# +# protocol constants +# + +SOH = 0x01 # start of header +STX = 0x02 # start of text +ETX = 0x03 # end of text +ACK = 0x06 # acknowledge +CR = 0x0D # carriage return +LF = 0x0A # linefeed +BCC = 0x00 # Block check Character will contain the checksum immediately following the data packet + +OBIS_NAMES = { + **smlConst.OBIS_NAMES, + '010000020000': 'Firmware Version, Firmware Prüfsumme CRC, Datum', + '0100010800ff': 'Bezug Zählerstand Total', + '0100010801ff': 'Bezug Zählerstand Tarif 1', + '0100010802ff': 'Bezug Zählerstand Tarif 2', + '0100011100ff': 'Total-Zählerstand', + '0100020800ff': 'Einspeisung Zählerstand Total', + '0100020801ff': 'Einspeisung Zählerstand Tarif 1', + '0100020802ff': 'Einspeisung Zählerstand Tarif 2', + '0100600100ff': 'Server-ID', + '010060320101': 'Hersteller-Identifikation', + '0100605a0201': 'Prüfsumme', +} + +# serial config +S_BITS = serial.SEVENBITS +S_PARITY = serial.PARITY_EVEN +S_STOP = serial.STOPBITS_ONE + + +if __name__ == '__main__': + logger = logging.getLogger(__name__) + logger.debug(f"init standalone {__name__}") +else: + logger = logging.getLogger(__name__) + logger.debug(f"init plugin component {__name__}") + + +manufacturer_ids = {} +exportfile = 'manufacturer.yaml' +try: + with open(exportfile, 'r') as infile: + y = YAML(typ='safe') + manufacturer_ids = y.load(infile) +except Exception: + pass + + +# +# internal testing +# +TESTING = False +# TESTING = True + +if TESTING: + if __name__ == '__main__': + from dlms_test import RESULT + else: + from .dlms_test import RESULT + logger.error('DLMS testing mode enabled, no serial communication, no real results!') +else: + RESULT = '' + + +# +# start module code +# + + +def hex_obis(code: str) -> str: + """ convert obis to hex """ + + # form x.x.x.x.x.x from x-x:x.x.x*x + l1 = code.replace(':', '.').replace('-', '.').replace('*', '.').split('.') + # fill missing fields + if len(l1) in (3, 5): + l1 = l1 + ['255'] + if len(l1) == 4: + l1 = ['1', '0'] + l1 + + # fix for DLMS testing, codes from SmlLib all have pattern '1-0:x.y.z*255' + l1[1] = '0' + + # convert to string, take care for letters instead of numbers + return ''.join(['{:02x}'.format(x) for x in [int(y) if y.isnumeric() else ord(y) for y in (l1)]]) + + +def normalize_unit(value: Union[int, float], unit: str) -> Tuple[Union[int, float], str]: + """ normalize units, i.e. remove prefixes and recalculate value """ + # in this environment, smaller or larger prefixes don't seem sensible... + _prefix = { + 'u': 1e-6, # micro + 'm': 1e-3, # mili + 'c': 1e-2, # centi + 'd': 1e-1, # deci + 'k': 1e3, # kilo + 'M': 1e6, # mega + 'G': 1e9, # giga + } + + nval = value + nunit = unit + + for p in _prefix: + if unit.startswith(p): + nunit = unit[1:] + nval = nval * _prefix[p] + break + + # check if we made a float without necessity... + if unit != nunit and type(value) is int and type(nval) is float and _prefix[unit[0]] > 1: + nval = int(nval) + + return nval, nunit + + +def get_unit_code(value: Any, unit: str, normalize: bool = True) -> Tuple[Any, str, Union[int, None]]: + """ + try to get unit code for u from sml Units. If normalize is set, first try to + normalize value/unit. + As SML only lists base units, prefixes units like kW or MWh don't match + the value/unit pair for prefixed units and we don't return unit codes that + need normalizing. + """ + unit_code = None + + # check if value is numeric + x = None + try: + x = float(value) + except Exception: + pass + try: + x = int(value) + except Exception: + pass + + # only check for numeric values... + if type(x) in (int, float) and unit: + if normalize: + value, unit = normalize_unit(x, unit) + if unit in smlConst.UNITS.values(): + unit_code = list(smlConst.UNITS.keys())[list(smlConst.UNITS.values()).index(unit)] + + return value, unit, unit_code + + +def format_time(timedelta: float) -> str: + """ + returns a pretty formatted string according to the size of the timedelta + :param timediff: time delta given in seconds + :return: returns a string + """ + if timedelta > 1000: + return f"{timedelta:.2f} s" + elif timedelta > 1: + return f"{timedelta:.2f} s" + elif timedelta > 1 / 10 ** 3: + return f"{timedelta * 10 ** 3 :.2f} ms" + elif timedelta > 1 / 10 ** 6: + return f"{timedelta * 10 ** 6:.2f} µs" + else: + return f"{timedelta * 10 ** 9:.2f} ns" + + +# TODO: asyncio for DLMS disabled until real testing has succeeded +# # +# # asyncio reader +# # +# +# +# class AsyncReader(): +# +# def __init__(self, logger, plugin: SmartPlugin, config: dict): +# self.buf = bytes() +# self.logger = logger +# self.lock = config['lock'] +# +# if not ASYNC_IMPORTED: +# raise ImportError('pyserial_asyncio not installed, running asyncio not possible.') +# +# if 'serial_port' not in config: +# raise ValueError(f'configuration {config} is missing serial port config') +# +# self.serial_port = config.get('serial_port') +# self.timeout = config.get('timeout', 2) +# self.baudrate = config.get('baudrate', 300) +# if not config['dlms'].get('only_listen', False): +# self.logger.warning('asyncio operation can only listen, smartmeter will not be triggered!') +# +# self.target = '(not set)' +# self.listening = False +# self.reader = None +# +# self.config = config +# self.transport = None +# self.protocol = DlmsProtocol(logger, config) +# +# # set from plugin +# self.plugin = plugin +# self.data_callback = plugin._update_values +# +# async def listen(self): +# result = self.lock.acquire(blocking=False) +# if not result: +# self.logger.error('couldn\'t acquire lock, polling/manual access active?') +# return +# +# self.logger.debug('acquired lock') +# try: # LOCK +# self.reader, _ = await serial_asyncio.open_serial_connection( +# url=self.serial_port, +# baudrate=self.baudrate, +# bytesize=S_BITS, +# parity=S_PARITY, +# stopbits=S_STOP, +# ) +# self.target = f'async_serial://{self.serial_port}' +# self.logger.debug(f'target is {self.target}') +# +# if self.reader is None and not TESTING: +# self.logger.error('error on setting up async listener, reader is None') +# return +# +# self.plugin.connected = True +# self.listening = True +# self.logger.debug('starting to listen') +# +# buf = bytes() +# +# while self.listening and self.plugin.alive: +# +# if TESTING: +# # make this bytes... +# data = RESULT.encode() +# else: +# data = await self.reader.readuntil(b'!') +# +# # check we got a start byte if buf is empty +# if len(buf) == 0: +# if b'/' not in data: +# self.logger.warning('incomplete data received, no start byte, discarding') +# continue +# else: +# # trim data to start byte +# data = data[data.find(b'/'):] +# +# # add data to buffer +# buf += data +# +# # check if we have an end byte +# if b'!' not in buf: +# if len(buf) > 100000: +# self.logger.warning(f'got {len(buf)} characters without end byte, discarding data') +# buf = bytes() +# continue +# +# # get data from start (b'/') to end (b'!') into data +# # leave the remainder in buf +# data, _, buf = buf.partition(b'!') +# +# # we should have data beginning with b'/' and ending with b'!' +# identification_message = str(data, 'utf-8').splitlines()[0] +# manid = identification_message[1:4] +# manname = manufacturer_ids.get(manid, 'unknown') +# self.logger.debug(f"manufacturer for {manid} is {manname} (out of {len(manufacturer_ids)} given manufacturers)") +# +# response = self.protocol(data.decode()) +# +# # get data from frameparser and call plugin +# if response and self.data_callback: +# self.data_callback(response) +# +# finally: +# # cleanup +# try: +# self.reader.feed_eof() +# except Exception: +# pass +# self.plugin.connected = False +# self.lock.release() +# +# async def stop_on_queue(self): +# """ wait for STOP in queue and signal reader to terminate """ +# self.logger.debug('task waiting for STOP from queue...') +# await self.plugin. wait_for_asyncio_termination() +# self.logger.debug('task received STOP, halting listener') +# self.listening = False +# +# TODO end + +# +# single-shot reader +# + + +class DlmsReader(): + """ + read data from DLMS meter + + open/handle serial connection and provide read/write methods + use DlmsProtocol for parsing data + """ + def __init__(self, logger, config: dict, discover: bool = False): + self.config = config + self.sock = None + self.lock = config['lock'] + self.logger = logger + self.discover = discover + self.protocol = DlmsProtocol(logger, config) + self.target = '(not set)' + + if not ('serial_port' in config or ('host' in config and 'port' in config)): + raise ValueError(f'configuration {config} is missing source config (serialport or host and port)') + + if not config.get('poll') and not ASYNC_IMPORTED: + raise ValueError('async configured but pyserial_asyncio not imported. Aborting.') + + def __call__(self) -> dict: + return self.read() + + def read(self) -> dict: + # + # open the serial communication + # + + starttime = time.time() + + locked = self.lock.acquire(blocking=False) + if not locked: + self.logger.error('could not get lock for serial access. Is another scheduled/manual action still active?') + return {} + + try: # lock release + + self.get_sock() + if not self.sock: + # error already logged, just go + return {} + + if isinstance(self.sock, socket.socket): + self.logger.error(f'network reading not yet implemented for DLMS at {self.target}') + return {} + + self.protocol.set_methods(self.read_data_block_from_serial, self.sock.write, self.target, self.sock) + + runtime = time.time() + self.logger.debug(f"time to open {self.target}: {format_time(time.time() - runtime)}") + + response = self.protocol() + + try: + self.sock.close() + except Exception: + pass + except Exception: + # passthrough, this is only for releasing the lock + raise + finally: + try: + self.sock.close() + self.logger.debug(f'{self.target} closed') + except Exception: + pass + self.lock.release() + + self.logger.debug(f"time for reading OBIS data: {format_time(time.time() - runtime)}") + runtime = time.time() + + # Display performance of the serial communication + self.logger.debug(f"whole communication with smartmeter took {format_time(time.time() - starttime)}") + + return response + + def read_data_block_from_serial(self, end_byte: bytes = b'\n', start_byte: bytes = b'') -> bytes: + """ + This function reads some bytes from serial interface + it returns an array of bytes if a timeout occurs or a given end byte is encountered + and otherwise None if an error occurred + + If global var TESTING is True, only pre-stored data will be returned to test further processing! + + :param the_serial: interface to read from + :param end_byte: the indicator for end of data, this will be included in response + :param start_byte: the indicator for start of data, this will be included in response + :param max_read_time: maximum time after which to stop reading even if data is still sent + :returns the read data or None + """ + if TESTING: + return RESULT.encode() + + # in discover mode, stop trying after 20 secs + # reading SML yields bytes, but doesn't trigger returning data + self.logger.debug(f"start to read data from serial device, start is {start_byte}, end is '{end_byte}, time is 20") + response = bytes() + starttime = time.time() + start_found = False + end_bytes = 0 + ch = bytes() + try: + # try to stop looking if 10 end bytes were found but no start bytes + while not discover or end_bytes < 10: + ch = self.sock.read() + # logger.debug(f"Read {ch}") + runtime = time.time() + if len(ch) == 0: + break + if start_byte != b'': + if ch == start_byte: + self.logger.debug('start byte found') + end_bytes = 0 + response = bytes() + start_found = True + response += ch + if ch == end_byte: + end_bytes += 1 + self.logger.debug(f'end byte found ({end_bytes})') + if start_byte is not None and not start_found: + response = bytes() + continue + else: + break + if (response[-1] == end_byte): + self.logger.debug('end byte at end of response found') + end_bytes = 0 + break + if self.discover: + if runtime - starttime > 20: + logger.debug('max read time reached') + break + except Exception as e: + self.logger.debug(f"error occurred while reading data block from serial: {e} ") + return b'' + self.logger.debug(f"finished reading data from serial device after {len(response)} bytes") + return response + + def get_sock(self): + """ open serial or network socket """ + self.sock = None + self.target = '(not set)' + serial_port = self.config.get('serial_port') + host = self.config.get('host') + port = self.config.get('port') + timeout = self.config.get('timeout', 2) + baudrate = self.config.get('DLMS', {'baudate_min': 300}).get('baudrate_min', 300) + + if TESTING: + self.target = '(test input)' + return + + if serial_port: + # + # open the serial communication + # + try: # open serial + self.sock = serial.Serial( + serial_port, + baudrate, + S_BITS, + S_PARITY, + S_STOP, + timeout=timeout + ) + if not serial_port == self.sock.name: + logger.debug(f"Asked for {serial_port} as serial port, but really using now {sock.name}") + self.target = f'serial://{self.sock.name}' + + except FileNotFoundError: + self.logger.error(f"Serial port '{serial_port}' does not exist, please check your port") + self.sock = None + return + except serial.SerialException: + if self.sock is None: + self.logger.error(f"Serial port '{serial_port}' could not be opened") + else: + self.logger.error(f"Serial port '{serial_port}' could be opened but somehow not accessed") + self.sock = None + return + except OSError: + self.logger.error(f"Serial port '{serial_port}' does not exist, please check the spelling") + self.sock = None + return + except Exception as e: + self.logger.error(f"unforeseen error occurred: '{e}'") + self.sock = None + return + + if self.sock is None: + # this should not happen... + logger.error("unforeseen error occurred, serial object was not initialized.") + return + + if not self.sock.is_open: + logger.error(f"serial port '{serial_port}' could not be opened with given parameters, maybe wrong baudrate?") + self.sock = None + return + + elif host: + # + # open network connection + # + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(2) + self.sock.connect((host, port)) + self.sock.setblocking(False) + self.target = f'tcp://{host}:{port}' + + else: + self.logger.error('neither serialport nor host/port was given, no action possible.') + self.sock = None + return + + +class DlmsProtocol(): + """ read and parse DLMS readout, if necessary, trigger meter to send data """ + + def __init__(self, logger, config): + self.logger = logger + self._read = self.__read + self._write = self.__write + self.sock = None + self.target = '' + + try: + self.device = config['dlms']['device'] + self.initial_baudrate = config['dlms']['baudrate_min'] + self.query_code = config['dlms']['querycode'] + self.use_checksum = config['dlms']['use_checksum'] + self.only_listen = config['dlms'].get('only_listen', False) # just for the case that smartmeter transmits data without a query first + self.normalize = config['dlms'].get('normalize', True) + except (KeyError, AttributeError) as e: + self.logger.warning(f'configuration {config} is missing elements: {e}') + + def __read(self, end_byte: bytes = b'\n', start_byte: bytes = b'') -> bytes: + """ dummy stub to prevent errors """ + self.logger.warning('self._read called without setting method - please check!') + return b'' + + def __write(self, data): + """ dummy stub to prevent errors """ + self.logger.warning('self._write called without setting method - please check!') + + def set_methods(self, read, write, target: str = '', sock=None): + self.logger.debug(f'setting methods {read} / {write} with sock {sock} for {target}') + self._read = read + self._write = write + self.target = target + self.sock = sock + + def __call__(self, data: str = '') -> dict: + if not data: + r_bytes = self.read_data() + if not r_bytes: + return {} + data = self.check_protocol(r_bytes) + if not data: + return {} + r_dict = self.parse(data) + return r_dict + + def read_data(self) -> bytes: + # + # read data from device + # + + runtime = time.time() + + # about timeout: time tr between sending a request and an answer needs to be + # 200ms < tr < 1500ms for protocol mode A or B + # inter character time must be smaller than 1500 ms + # The time between the reception of a message and the transmission of an answer is: + # (20 ms) 200 ms = tr = 1 500 ms (see item 12) of 6.3.14). + # If a response has not been received, the waiting time of the transmitting equipment after + # transmission of the identification message, before it continues with the transmission, is: + # 1 500 ms < tt = 2 200 ms + # The time between two characters in a character sequence is: + # ta < 1 500 ms + wait_before_acknowledge = 0.4 # wait for 400 ms before sending the request to change baudrate + wait_after_acknowledge = 0.4 # wait for 400 ms after sending acknowledge + start_char = b'/' + request_message = b"/" + self.query_code.encode('ascii') + self.device.encode('ascii') + b"!\r\n" + + if not self.only_listen: + response = b'' + + # start a dialog with smartmeter + try: + self.logger.debug(f"writing request message {request_message} to serial port '{self.target}'") + self._write(request_message) + except Exception as e: + self.logger.warning(f"error on serial write: {e}") + return b'' + + logger.debug(f"time to send first request to smartmeter: {format_time(time.time() - runtime)}") + + # now get first response + response = self._read() + if not response: + self.logger.debug("no response received upon first request") + return b'' + + self.logger.debug(f"time to receive an answer: {format_time(time.time() - runtime)}") + runtime = time.time() + + # We need to examine the read response here for an echo of the _Request_Message + # some meters answer with an echo of the request Message + if response == request_message: + self.logger.debug("request message was echoed, need to read the identification message") + # now read the capabilities and type/brand line from Smartmeter + # e.g. b'/LGZ5\\2ZMD3104407.B32\r\n' + response = self._read() + else: + self.logger.debug("request message was not equal to response, treating as identification message") + + self.logger.debug(f"time to get first identification message from smartmeter: {format_time(time.time() - runtime)}") + runtime = time.time() + + identification_message = response + self.logger.debug(f"identification message is {identification_message}") + + # need at least 7 bytes: + # 1 byte "/" + # 3 bytes short Identification + # 1 byte speed indication + # 2 bytes CR LF + if len(identification_message) < 7: + self.logger.warning(f"malformed identification message: '{identification_message}', abort query") + return b'' + + if (identification_message[0] != start_char): + self.logger.warning(f"identification message '{identification_message}' does not start with '/', abort query") + return b'' + + manid = str(identification_message[1:4], 'utf-8') + manname = manufacturer_ids.get(manid, 'unknown') + self.logger.debug(f"manufacturer for {manid} is {manname} ({len(manufacturer_ids)} manufacturers known)") + + # Different smartmeters allow for different protocol modes. + # The protocol mode decides whether the communication is fixed to a certain baudrate or might be speed up. + # Some meters do initiate a protocol by themselves with a fixed speed of 2400 baud e.g. Mode D + # However some meters specify a speed of 9600 Baud although they use protocol mode D (readonly) + # + # protocol_mode = 'A' + # + # The communication of the plugin always stays at the same speed, + # Protocol indicator can be anything except for A-I, 0-9, /, ? + # + baudrates = { + # mode A + '': (300, 'A'), + # mode B + 'A': (600, 'B'), + 'B': (1200, 'B'), + 'C': (2400, 'B'), + 'D': (4800, 'B'), + 'E': (9600, 'B'), + 'F': (19200, 'B'), + # mode C & E + '0': (300, 'C'), + '1': (600, 'C'), + '2': (1200, 'C'), + '3': (2400, 'C'), + '4': (4800, 'C'), + '5': (9600, 'C'), + '6': (19200, 'C'), + } + + baudrate_id = chr(identification_message[4]) + if baudrate_id not in baudrates: + baudrate_id = '' + new_baudrate, protocol_mode = baudrates[baudrate_id] + + logger.debug(f"baudrate id is '{baudrate_id}' thus protocol mode is {protocol_mode} and suggested Baudrate is {new_baudrate} Bd") + + if chr(identification_message[5]) == '\\': + if chr(identification_message[6]) == '2': + self.logger.debug("HDLC protocol could be used if it was implemented") + else: + self.logger.debug(f"another protocol could probably be used if it was implemented, id is {identification_message[6]}") + + # for protocol C or E we now send an acknowledge and include the new baudrate parameter + # maybe todo + # we could implement here a baudrate that is fixed to somewhat lower speed if we need to + # read out a smartmeter with broken communication + action = b'0' # Data readout, possible are also b'1' for programming mode or some manufacturer specific + acknowledge = b'\x060' + baudrate_id.encode() + action + b'\r\n' + + if protocol_mode == 'C': + # the speed change in communication is initiated from the reading device + time.sleep(wait_before_acknowledge) + self.logger.debug(f"using protocol mode C, send acknowledge {acknowledge} and tell smartmeter to switch to {new_baudrate} baud") + try: + self._write(acknowledge) + except Exception as e: + self.logger.warning(f"error on sending baudrate change: {e}") + return b'' + time.sleep(wait_after_acknowledge) + # dlms_serial.flush() + # dlms_serial.reset_input_buffer() + if (new_baudrate != self.initial_baudrate): + # change request to set higher baudrate + self.sock.baudrate = new_baudrate + + elif protocol_mode == 'B': + # the speed change in communication is initiated from the smartmeter device + time.sleep(wait_before_acknowledge) + self.logger.debug(f"using protocol mode B, smartmeter and reader will switch to {new_baudrate} baud") + time.sleep(wait_after_acknowledge) + # dlms_serial.flush() + # dlms_serial.reset_input_buffer() + if (new_baudrate != self.initial_baudrate): + # change request to set higher baudrate + self.sock.baudrate = new_baudrate + else: + self.logger.debug(f"no change of readout baudrate, smartmeter and reader will stay at {new_baudrate} baud") + + # now read the huge data block with all the OBIS codes + self.logger.debug("Reading OBIS data from smartmeter") + response = self._read() + else: + # only listen mode, starts with / and last char is ! + # data will be in between those two + response = self._read(b'!', b'/') + + try: + identification_message = str(response, 'utf-8').splitlines()[0] + manid = identification_message[1:4] + manname = manufacturer_ids.get(manid, 'unknown') + self.logger.debug(f"manufacturer for {manid} is {manname} (out of {len(manufacturer_ids)} given manufacturers)") + except Exception as e: + self.logger.info(f'error while extracting manufacturer: {e}') + + return response + + def split_header(self, readout: str, break_at_eod: bool = True) -> list: + """if there is an empty line at second position within readout then seperate this""" + has_header = False + obis = [] + endofdata_count = 0 + for linecount, line in enumerate(readout.splitlines()): + if linecount == 0 and line.startswith("/"): + has_header = True + continue + + # an empty line separates the header from the codes, it must be suppressed here + if len(line) == 0 and linecount == 1 and has_header: + continue + + # if there is an empty line other than directly after the header + # it is very likely that there is a faulty obis readout. + # It might be that checksum is disabled an thus no error could be catched + if len(line) == 0: + self.logger.error("incorrect format: empty line was encountered unexpectedly, aborting!") + break + + # '!' as single OBIS code line means 'end of data' + if line.startswith("!"): + self.logger.debug("end of data reached") + if endofdata_count: + self.logger.debug(f"found {endofdata_count} end of data marker '!' in readout") + if break_at_eod: # omit the rest of data here + break + endofdata_count += 1 + else: + obis.append(line) + return obis + + def check_protocol(self, data: bytes) -> Union[str, None]: + """ check for proper protocol handling """ + acknowledge = b'' # preset empty answer + + if data.startswith(acknowledge): + if not self.only_listen: + self.logger.debug("acknowledge echoed from smartmeter") + data = data[len(acknowledge):] + + if self.use_checksum: + # data block in response may be capsuled within STX and ETX to provide error checking + # thus the response will contain a sequence of + # STX Datablock ! CR LF ETX BCC + # which means we need at least 6 characters in response where Datablock is empty + self.logger.debug("trying now to calculate a checksum") + + if data[0] == STX: + self.logger.debug("STX found") + else: + self.logger.warning(f"STX not found in response='{' '.join(hex(i) for i in data[:10])}...'") + + if data[-2] == ETX: + self.logger.debug("ETX found") + else: + self.logger.warning(f"ETX not found in response='...{' '.join(hex(i) for i in data[-11:])}'") + + if (len(data) > 5) and (data[0] == STX) and (data[-2] == ETX): + # perform checks (start with char after STX, end with ETX including, checksum matches last byte (BCC)) + BCC = data[-1] + self.logger.debug(f"block check character BCC is {BCC}") + checksum = 0 + for i in data[1:-1]: + checksum ^= i + if checksum != BCC: + self.logger.warning(f"checksum/protocol error: response={' '.join(hex(i) for i in data[1:-1])}, checksum={checksum}") + return + else: + self.logger.debug("checksum over data response was ok, data is valid") + else: + self.logger.warning("STX - ETX not found") + else: + self.logger.debug("checksum calculation skipped") + + if not self.only_listen: + if len(data) > 5: + res = str(data[1:-4], 'ascii') + else: + self.logger.debug("response did not contain enough data for OBIS decode") + return + else: + res = str(data, 'ascii') + + return res + + def parse(self, data: str) -> dict: + """ parse data returned from device read """ + + runtime = time.time() + result = {} + obis = self.split_header(data) + + try: + for line in obis: + # Now check if we can split between values and OBIS code + arguments = line.split('(') + if len(arguments) == 1: + # no values found at all; that seems to be a wrong OBIS code line then + arguments = arguments[0] + values = "" + self.logger.warning(f"OBIS code line without data item: {line}") + else: + # ok, found some values to the right, lets isolate them + values = arguments[1:] + code = arguments[0] + name = OBIS_NAMES.get(hex_obis(code)) + content = [] + for s in values: + s = s.replace(')', '') + if len(s) > 0: + # we now should have a list with values that may contain a number + # separated from a unit by a '*' or a date + # so see, if there is an '*' within + vu = s.split('*') + if len(vu) > 2: + self.logger.error(f"too many '*' found in '{s}' of '{line}'") + elif len(vu) == 2: + # just a value and a unit + v = vu[0] + u = vu[1] + + # normalize SI units if possible to return values analogue to SML (e.g. Wh instead of kWh) + v, u, uc = get_unit_code(v, u, self.normalize) + + values = { + 'value': v, + 'valueRaw': v, + 'obis': code, + 'unit': u + } + if uc: + values['unitCode'] = uc + if name: + values['name'] = name + content.append(values) + else: + # just a value, no unit + v = vu[0] + values = { + 'value': v, + 'valueRaw': v, + 'obis': code + } + if name: + values['name'] = name + content.append(values) + # uncomment the following line to check the generation of the values dictionary + # logger.dbghigh(f"{line:40} ---> {content}") + result[code] = content + self.logger.debug(f"found {code} with {content}") + self.logger.debug("finished processing lines") + except Exception as e: + self.logger.debug(f"error while extracting data: '{e}'") + + self.logger.debug(f"parsing OBIS codes took {format_time(time.time() - runtime)}") + return result + + +def query(config, discover: bool = False) -> Union[dict, None]: + """ + This function will + 1. open a serial communication line to the smartmeter + 2. sends a request for info + 3. parses the devices first (and maybe second) answer for capabilities of the device + 4. adjusts the speed of the communication accordingly + 5. reads out the block of OBIS information + 6. closes the serial communication + 7. strip header lines from returned data + 8. extract obis data and format return dict + + config contains a dict with entries for + 'serial_port', 'device' and a sub-dict 'dlms' with entries for + 'querycode', 'baudrate', 'baudrate_fix', 'timeout', 'only_listen', 'use_checksum' + + return: a dict with the response data formatted as follows: + { + 'readout': , + '': [{'value': , (optional) 'unit': ''}, {'value': ', 'unit': ''}, ...], + '': [...], + ... + } + + The obis lines contain at least one value (index 0), possibly with a unit, and possibly more values in analogous format + """ + + # for the performance of the serial read we need to save the current time + starttime = time.time() + + reader = DlmsReader(logger, config, discover) + response = reader() + + if not response: + return + + suggested_cycle = (time.time() - starttime) + 10.0 + config['suggested_cycle'] = suggested_cycle + logger.debug(f"the whole query took {format_time(time.time() - starttime)}, suggested cycle thus is at least {format_time(suggested_cycle)}") + + return response + + +def discover(config: dict) -> bool: + """ try to autodiscover DLMS protocol """ + + # as of now, this simply tries to query the meter + # called from within the plugin, the parameters are either manually set by + # the user, or preset by the plugin.yaml defaults. + # If really necessary, the query could be called multiple times with + # reduced baud rates or changed parameters, but there would need to be + # the need for this. + # For now, let's see how well this works... + result = query(config, discover=True) + + # result should have one key 'readout' with the full answer and a separate + # key for every read OBIS code. If no OBIS codes are read/converted, we can + # not be sure this is really DLMS, so we check for at least one OBIS code. + if result: + return len(result) > 1 + else: + return False + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description='Query a smartmeter at a given port for DLMS output', + usage='use "%(prog)s --help" for more information', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('port', help='specify the port to use for the smartmeter query, e.g. /dev/ttyUSB0 or /dev/dlms0') + parser.add_argument('-v', '--verbose', help='print verbose information', action='store_true') + parser.add_argument('-t', '--timeout', help='maximum time to wait for a message from the smartmeter', type=float, default=3.0) + parser.add_argument('-b', '--baudrate', help='initial baudrate to start the communication with the smartmeter', type=int, default=300) + parser.add_argument('-d', '--device', help='give a device address to include in the query', default='') + parser.add_argument('-q', '--querycode', help='define alternative query code\ndefault query code is ?\nsome smartmeters provide additional information when sending\nan alternative query code, e.g. 2 instead of ?', default='?') + parser.add_argument('-l', '--onlylisten', help='only listen to serial, no active query', action='store_true') + parser.add_argument('-f', '--baudrate_fix', help='keep baudrate speed fixed', action='store_false') + parser.add_argument('-c', '--nochecksum', help='don\'t use a checksum', action='store_false') + parser.add_argument('-n', '--normalize', help='convert units to base units and recalculate value', action='store_true') + + args = parser.parse_args() + + # complete default dict + config = { + 'lock': Lock(), + 'serial_port': '', + 'host': '', + 'port': 0, + 'connection': '', + 'timeout': 2, + 'baudrate': 9600, + 'dlms': { + 'device': '', + 'querycode': '?', + 'baudrate_min': 300, + 'use_checksum': True, + 'onlylisten': False, + 'normalize': True + }, + 'sml': { + 'buffersize': 1024 + } + } + + config['serial_port'] = args.port + config['timeout'] = args.timeout + config['dlms']['querycode'] = args.querycode + config['dlms']['baudrate_min'] = args.baudrate + config['dlms']['baudrate_fix'] = args.baudrate_fix + config['dlms']['only_listen'] = args.onlylisten + config['dlms']['use_checksum'] = args.nochecksum + config['dlms']['device'] = args.device + config['dlms']['normalize'] = args.normalize + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + # create formatter and add it to the handlers + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s @ %(lineno)d') + # formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + # add the handlers to the logger + logging.getLogger().addHandler(ch) + else: + logging.getLogger().setLevel(logging.INFO) + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) + # just like print + formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + # add the handlers to the logger + logging.getLogger().addHandler(ch) + + logger.info("This is Smartmeter Plugin, DLMS module, running in standalone mode") + logger.info("==================================================================") + + result = query(config) + + if not result: + logger.info(f"No results from query, maybe a problem with the serial port '{config['serial_port']}' given.") + elif len(result) > 1: + logger.info("These are the processed results of the query:") + try: + del result['readout'] + except KeyError: + pass + try: + import pprint + except ImportError: + txt = str(result) + else: + txt = pprint.pformat(result, indent=4) + logger.info(txt) + elif len(result) == 1: + logger.info("The results of the query could not be processed; raw result is:") + logger.info(result) + else: + logger.info("The query did not get any results. Maybe the serial port was occupied or there was an error.") diff --git a/smartmeter/dlms_test.py b/smartmeter/dlms_test.py new file mode 100755 index 000000000..03e8d89ae --- /dev/null +++ b/smartmeter/dlms_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +RESULT = """/ +1-1:F.F(00000000) +1-1:0.0.0(97734234) +1-1:0.0.1(97734234) +1-1:0.9.1(145051) +1-1:0.9.2(241129) +1-1:0.1.2(0000) +1-1:0.1.3(241101) +1-1:0.1.0(30) +1-1:1.2.1(0501.70*kW) +1-1:1.2.2(0501.70*kW) +1-1:2.2.1(0123.46*kW) +1-1:2.2.2(0123.46*kW) +1-1:1.6.1(07.98*kW)(2411061415) +1-1:1.6.2(07.98*kW)(2411061415) +1-1:2.6.1(01.84*kW)(2411021345) +1-1:2.6.2(01.84*kW)(2411021345) +1-1:1.8.0(00043802*kWh) +1-1:2.8.0(00005983*kWh) +1-1:3.8.0(00021203*kvarh) +1-1:4.8.0(00006222*kvarh) +1-1:1.8.1(00035256*kWh) +1-1:1.8.2(00008545*kWh) +1-1:2.8.1(00005983*kWh) +1-1:2.8.2(00000000*kWh) +1-1:3.8.1(00021081*kvarh) +1-1:3.8.2(00000122*kvarh) +1-1:4.8.1(00003666*kvarh) +1-1:4.8.2(00002555*kvarh) +1-1:C.3.1(___-----) +1-1:C.3.2(__------) +1-1:C.3.3(__------) +1-1:C.3.4(--__5_--) +1-1:C.4.0(60C00183) +1-1:C.5.0(0020E0F0) +1-1:C.7.0(00000041) +1-1:0.2.0(B31) +1-1:0.2.1(005) +! +""" diff --git a/smartmeter/get_manufacturer_ids.py b/smartmeter/get_manufacturer_ids.py new file mode 100755 index 000000000..f5a37b04c --- /dev/null +++ b/smartmeter/get_manufacturer_ids.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2016 - 2021 Bernd Meiners Bernd.Meiners@mail.de +######################################################################### +# +# DLMS plugin for SmartHomeNG +# +# This file is part of SmartHomeNG.py. +# Visit: https://github.com/smarthomeNG/ +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# https://smarthomeng.de +# +# SmartHomeNG.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG.py. If not, see . +######################################################################### + +""" +On standalone mode this Python program will visit +https://www.dlms.com/srv/lib/Export_Flagids.php +to download the list of manufacturer ids for smartmeter as xlsx +The columns therein are labeled as "FLAG ID","Manufacturer", "Country", and "World Region" +``FLAG ID`` is exactly 3 characters long + +The result will be stored locally as ``manufacturer.yaml`` +to serve as information database for the identification of smartmeters + +""" + +import logging +import requests +import sys + +from ruamel.yaml import YAML +from io import BytesIO + +try: + import openpyxl +except ImportError: + sys.exit("Package 'openpyxl' was not found.") + +if __name__ == '__main__': + logger = logging.getLogger(__name__) + logger.debug(f"init standalone {__name__}") +else: + logger = logging.getLogger() + logger.debug(f"init plugin component {__name__}") + + +def get_manufacturer(from_url: str, exportfile: str, verbose: bool = False) -> dict: + """ + Read XLSX from given url and write a yaml containing id and manufacturer + """ + # result + r = {} + y = YAML() + + logger.debug(f"Read manufacturer IDs from URL: '{from_url}'") + logger.debug(f"Using openpyxl version '{openpyxl.__version__}'") + + headers = {'User-agent': 'Mozilla/5.0'} + + try: + reque = requests.get(url, headers=headers) + except ConnectionError as e: + logger.debug(f"An error {e} occurred fetching {url}\n") + raise + + try: + wb = openpyxl.load_workbook(filename=BytesIO(reque.content), data_only=True) + + logger.debug('sheetnames {}'.format(wb.sheetnames)) + + sheet = wb.active + logger.debug(f"sheet {sheet}") + logger.debug(f"rows [{sheet.min_row} .. {sheet.max_row}]") + logger.debug(f"columns [{sheet.min_column} .. {sheet.max_column}]") + + if sheet.min_row+1 <= sheet.max_row and sheet.min_column == 1 and sheet.max_column == 4: + # Get data from rows """ + for row in range(sheet.min_row + 1,sheet.max_row): + id = str(sheet.cell(row, 1).value).strip() + if len(id) == 3: + # there are entries like > 'ITRON ...' < that need special cleaning: + man = str(sheet.cell(row, 2).value).strip() + man = man.strip('\'').strip() + r[id] = man + if verbose: + logger.debug(f"{id}->{man}") + else: + logger.debug(f">id< is '{id}' has more than 3 characters and will not be considered") + with open(exportfile, 'w') as f: + y.dump(r, f) + + logger.debug(f"{len(r)} distinct manufacturers were found and written to {exportfile}") + + except Exception as e: + logger.debug(f"Error {e} occurred") + + return r + + +if __name__ == '__main__': + verbose = True + + logging.getLogger().setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + # create formatter and add it to the handlers + if verbose: + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s @ %(lineno)d') + else: + formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + # add the handlers to the logger + logging.getLogger().addHandler(ch) + logger = logging.getLogger(__name__) + + exportfile = 'manufacturer.yaml' + url = 'https://www.dlms.com/srv/lib/Export_Flagids.php' + get_manufacturer(url, exportfile, verbose) diff --git a/smartmeter/plugin.yaml b/smartmeter/plugin.yaml new file mode 100644 index 000000000..71abf71f5 --- /dev/null +++ b/smartmeter/plugin.yaml @@ -0,0 +1,275 @@ +%YAML 1.1 +--- +plugin: + classname: Smartmeter + version: '0.9.0' # Plugin version + sh_minversion: '1.10.0.3' # minimum shNG version to use this plugin + py_minversion: '3.9' # minimum Python version to use for this plugin, due to f-strings + type: interface # plugin type (gateway, interface, protocol, system, web) + description: + de: 'Unterstützung für Smartmeter, die DLMS (Device Language Message Specification, IEC 62056-21) oder SML (Smart Message Language) nutzen und OBIS Codes liefern' + en: 'Support for smartmeter using DLMS (Device Language Message Specification, IEC 62056-21) or SML (Smart Message Language) and delivering OBIS codes' + maintainer: Morg + tester: bmxp, onkelandy, sisamiwe + state: develop + keywords: smartmeter ehz dlms sml obis smartmeter + multi_instance: true # plugin supports multi instance + restartable: true + # support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1002464-support-thread-für-dlms-plugin + +parameters: + protocol: + type: str + valid_list: + - DLMS + - SML + - dlms + - sml + - '' + default: '' + description: + de: 'Protokoll zur Kommunikation mit dem Smartmeter. Leerer Wert bedeutet automatische Erkennung (soweit erfolgreich).' + en: 'protocol for communicating with the smartmeter. Empty value means try to detect protocol automagically.' + serialport: + type: str + description: + de: 'Serieller Port, an dem das Smartmeter angeschlossen ist' + en: 'serial port at which the smartmeter is attached' + host: + type: str + description: + de: 'Host der eine IP Schnittstelle bereitstellt (nur SML)' + en: 'Host that provides an IP interface (SML only)' + port: + type: int + description: + de: 'Port für die Kommunikation (nur SML)' + en: 'Port for communication (SML only)' + autoreconnect: + type: bool + default: true + description: + de: 'Bei Beenden der Verbindung automatisch erneut verbinden (nur bei ständigem Empfang)' + en: 'automatically reconnect on disconnect (only with continuous listening)' + timeout: + type: int + default: 2 + description: + de: 'Timeout in Sekunden nach dem der Lesevorgang automatisch beendet wird' + en: 'timeout in seconds for automatic abortion of readout' + baudrate: + type: int + valid_min: 50 + default: 9600 + description: + de: 'Baudrate, bei der die Kommunikation erfolgen soll' + en: 'Baudrate at which the communikation should take place' + cycle: + type: int + valid_min: 0 + default: 60 + description: + de: 'Zeitlicher Abstand zwischen zwei Abfragen des Smartmeters in Sekunden, setzen auf 0 schaltet cycle aus' + en: 'Time between two queries of smartmeter in seconds, set to zero will switch off usage of cycle' + crontab: + type: str + description: + de: 'Abfragen des Smartmeters mit Festlegung via Crontab' + en: 'Queries of smartmeter by means of a crontab' + poll: + type: bool + default: true + description: + de: 'Gerät regelmäßig abfragen (cycle/crontab) statt ständigem Empfang (nur SML)' + en: 'periodically query device (cycle/crontab) instead of continuous listen (SML only)' + time_filter: + type: int + default: 0 + description: + de: 'Im "ständig empfangen"-Modus Item-Updates nur alle x Sekunden senden.' + en: 'In continuous listen mode only update items every x seconds.' + description_long: + de: > + Im "ständig empfangen"-Modus Item-Updates nur alle x Sekunden senden. + x = 0: alle Updates senden. + x = -1: den Wert von "cycle" verwenden. + en: > + In continuous listen mode only update items every x seconds. + x = 0: send all updates + x = -1: use "cycle" parameter for x + # DLMS parameters + baudrate_min: + type: int + valid_min: 50 + default: 300 + description: + de: 'Baudrate, bei der die Kommunikation zuerst erfolgen soll (nur DLMS)' + en: 'Baudrate at which the communication should be initiated (DLMS only)' + device_address: + type: str + default: '' + description: + de: 'Interne Unteradresse des Smartmeters (nur DLMS)' + en: 'Internal subadress of smartmeter (DLMS only)' + querycode: + type: str + default: '?' + description: + de: 'Abfragecode für den Smartmeter, default ist `?`, einige Smartmeter akzeptieren z.B. `2` (nur DLMS)' + en: 'querycode of smartmeter, defaults to `?`, some smartmeter accept e.g. `2` (DLMS only)' + use_checksum: + type: bool + default: true + description: + de: 'Wenn wahr, wird eine Prüfsumme über die ausgelesenen Daten gebildet (nur DLMS)' + en: 'if true then a checksum will be calculated of the readout result' + only_listen: + type: bool + default: false + description: + de: 'Manche Smartmeter können nicht abgefragt werden sondern senden von sich aus Informationen. Für diese Smartmeter auf True setzen und die Baudrate anpassen (nur DLMS)' + en: 'Some smartmeter can not be queried, they send information without request. For those devices set to True and adjust baudrate' + normalize: + type: bool + default: true + description: + de: 'Wenn Einheiten mit Zehnerpräfix geliefert werden, werden Einheit und Wert in die entsprechende Basiseinheit konvertiert (z.B. 12.7 kWh -> 12700 Wh)' + en: 'if units are read with power of ten prefix, unit and value will be converted to the corresponding base unit (e.g. 12.7 kWh -> 12700 Wh)' + + # SML parameters + buffersize: + type: int + default: 1024 + description: + de: 'Größe des Lesepuffers. Mindestens doppelte Größe der maximalen Nachrichtenlänge in Bytes (nur SML)' + en: 'Size of read buffer. At least twice the size of maximum message length (SML only)' + device_type: + type: str + default: '' + valid_list: + - '' # needs to be edited if new corrections are added + description: + de: 'Typ des Gerätes, ggf. notwendig für spezielle Behandlung (nur SML)' + en: 'type of smartmeter, possibly necessary for quirks (SML only)' + date_offset: + type: int + default: 0 + description: + de: 'Unix timestamp der Smartmeter Inbetriebnahme (nur SML)' + en: 'Unix timestamp of Smartmeter start-up after installation (SML only)' + +item_attributes: + obis_code: + type: str + description: + de: OBIS-Code, dessen Wert gelesen werden soll + en: obis code to be read + obis_index: + type: int + default: 0 + valid_min: 0 + valid_max: 10 + description: + de: Index des OBIS-Werts, der gelesen werden soll + en: index of the obis value to be read + obis_property: + type: str + default: value + valid_list: + - value # value, possibly corrected (scaler, normalized unit) + - valueRaw # value as sent from meter + - unit # unit as str + - unitCode # unit code from smllib.const.UNITS + - name # + - obis # obis code + - valTime # time as sent from meter + - actTime # time corrected for meter time base + - scaler # 10**x scaler for valueRaw + - signature # meter signature + - status # status bit field + - statRun # meter is counting + - statFraudMagnet # magnetic manipulation detected + - statFraudCover # cover manipulation detected + - statEnergyTotal # Current flow total. True: -A, False: +A + - statEnergyL1 # Current flow L1. True: -A, False: +A + - statEnergyL2 # Current flow L2. True: -A, False: +A + - statEnergyL3 # Current flow L3. True: -A, False: +A + - statRotaryField # rotary field faulty = not L1->L2->L3 + - statBackstop # backstop active + - statCalFault # calibration relevant fatal fault + - statVoltageL1 # Voltage L1 present + - statVoltageL2 # Voltage L2 present + - statVoltageL3 # Voltage L3 present + description: + de: > + Eigenschaft des gelesenen Wertes: + * value: korrigierter Wert (Skalierungsfaktor, SI-Basiseinheit) + * valueRaw: vom Gerät gelieferter (Roh-)Wert + * unit: zurückgegebene Einheit des Wertes + * unitCode: Code der Einheit gem. smllib.const.UNITS + * scaler: Multiplikationsfaktor für valueRaw + * status: Status-Bitfeld + * valTime: Zeitstempel des Wertes + * actTime: korrigierter Zeitstempel des Wertes + * signature: tbd + Nicht alle Eigenschaften sind in jedem Datenpunkt vorhanden. + en: > + property of the read data: + * value: corrected value (scaler, SI base units) + * valueRaw: value as read from meter + * unit: read unit of value + * unitCode: code for unit as given in smllib.const.UNITS + * scaler: multiplicative factor for valueRaw + * status: meter status bitfield + * valTime: timestamp for value + * actTime: corrected timestamp for value + * signature: tbd + Not all properties are present for all data points. + obis_vtype: + type: str + default: '' + valid_list: + - str + - num + - int + - float + - ZST12 + - ZST10 + - D6 + - Z6 + - Z4 + - '' + description: + de: > + Konvertierungsart des gelesenen Wertes: + * str: eine Zeichenkette + * num: numerisch, entweder Ganzzahl oder Fließkommazahl + * int: Ganzzahl + * float: Fließkommazahl + * ZST12: Datum und Zeit codiert als YYMMDDhhmmss + * ZST10: Datum und Zeit codiert als YYMMDDhhmm + * D6: Datum codiert als YYMMDD + * Z4: Zeit codiert als hhmm + * Z6: Zeit codiert als hhmmss + en: > + type conversion of read value: + * str: a string + * num: a number, integer or floating point number + * int: an integer number + * float: a floating point number + * ZST12: date and time coded as YYMMDDhhmmss + * ZST10: date and time coded as YYMMDDhhmm + * D6: date coded as YYMMDD + * Z4: time coded as hhmm + * Z6: time coded as hhmmss + obis_readout: + type: str + description: + de: 'Der komplette Auslesepuffer wird für eigene Untersuchungen gespeichert (nur DLMS)' + en: 'the complete readout will be saved for own examinations (DLMS only)' + +logic_parameters: NONE + +plugin_functions: NONE + +item_structs: NONE diff --git a/smartmeter/requirements.txt b/smartmeter/requirements.txt new file mode 100644 index 000000000..0514e3e12 --- /dev/null +++ b/smartmeter/requirements.txt @@ -0,0 +1,2 @@ +pyserial>=3.2.1 +SmlLib>=1.3 diff --git a/smartmeter/sml.py b/smartmeter/sml.py new file mode 100755 index 000000000..450137d10 --- /dev/null +++ b/smartmeter/sml.py @@ -0,0 +1,742 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2013 - 2015 KNX-User-Forum e.V. http://knx-user-forum.de/ +# Copyright 2022 Julian Scholle julian.scholle@googlemail.com +# Copyright 2024 - Sebastian Helms morg @ knx-user-forum.de +######################################################################### +# +# SML module for SmartMeter plugin for SmartHomeNG +# +# This file is part of SmartHomeNG.py. +# Visit: https://github.com/smarthomeNG/ +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# https://smarthomeng.de +# +# SmartHomeNG.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG.py. If not, see . +######################################################################### + +import asyncio +import errno +import logging +import serial +try: + import serial_asyncio + ASYNC_IMPORTED = True +except ImportError: + ASYNC_IMPORTED = False +import socket +import time +import traceback + +from smllib.reader import SmlStreamReader +from smllib import const as smlConst +from threading import Lock +from typing import Union + +# only for syntax/type checking +try: + from lib.model.smartplugin import SmartPlugin +except ImportError: + class SmartPlugin(): + pass + + class SmartPluginWebIf(): + pass + +""" +This module implements the query of a smartmeter using the SML protocol. +The smartmeter needs to have an infrared interface and an IR-Adapter is needed for USB. + +Abbreviations +------------- +OBIS + OBject Identification System (see iec62056-61{ed1.0}en_obis_protocol.pdf) +""" + + +OBIS_NAMES = { + **smlConst.OBIS_NAMES, + '010000020000': 'Firmware Version, Firmware Prüfsumme CRC, Datum', + '0100010800ff': 'Bezug Zählerstand Total', + '0100010801ff': 'Bezug Zählerstand Tarif 1', + '0100010802ff': 'Bezug Zählerstand Tarif 2', + '0100011100ff': 'Total-Zählerstand', + '0100020800ff': 'Einspeisung Zählerstand Total', + '0100020801ff': 'Einspeisung Zählerstand Tarif 1', + '0100020802ff': 'Einspeisung Zählerstand Tarif 2', + '0100600100ff': 'Server-ID', + '010060320101': 'Hersteller-Identifikation', + '0100605a0201': 'Prüfsumme', +} + +# serial config +S_BITS = serial.EIGHTBITS +S_PARITY = serial.PARITY_NONE +S_STOP = serial.STOPBITS_ONE + + +if __name__ == '__main__': + logger = logging.getLogger(__name__) + logger.debug(f"init standalone {__name__}") +else: + logger = logging.getLogger(__name__) + logger.debug(f"init plugin component {__name__}") + + +# +# internal testing +# +TESTING = False +# TESTING = True + +if TESTING: + if __name__ == '__main__': + from sml_test import RESULT + else: + from .sml_test import RESULT + logger.error(f'SML testing mode enabled, no serial communication, no real results! Dataset is {len(RESULT)} bytes long.') +else: + RESULT = b'' + + +# +# start module code +# + + +def to_hex(data: Union[int, str, bytes, bytearray], space: bool = True) -> str: + """ + Returns the hex representation of the given data + """ + if isinstance(data, int): + return hex(data) + + if isinstance(data, str): + if space: + return " ".join([data[i:i + 2] for i in range(0, len(data), 2)]) + else: + return data + + templ = "%02x" + if space: + templ += " " + return "".join(templ % b for b in data).rstrip() + + +def format_time(timedelta): + """ + returns a pretty formatted string according to the size of the timedelta + :param timediff: time delta given in seconds + :return: returns a string + """ + if timedelta > 1000.0: + return f"{timedelta:.2f} s" + elif timedelta > 1.0: + return f"{timedelta:.2f} s" + elif timedelta > 0.001: + return f"{timedelta*1000.0:.2f} ms" + elif timedelta > 0.000001: + return f"{timedelta*1000000.0:.2f} µs" + elif timedelta > 0.000000001: + return f"{timedelta * 1000000000.0:.2f} ns" + +# +# asyncio reader +# + + +class AsyncReader(): + + def __init__(self, logger, plugin: SmartPlugin, config: dict): + self.buf = bytes() + self.logger = logger + self.lock = config['lock'] + + if not ASYNC_IMPORTED: + raise ImportError('pyserial_asyncio not installed, running asyncio not possible.') + self.config = config + self.transport = None + self.stream = SmlStreamReader() + self.fp = SmlFrameParser(config) + self.frame_lock = Lock() + + # set from plugin + self.plugin = plugin + self.data_callback = plugin._update_values + + if not ('serial_port' in config or ('host' in config and 'port' in config)): + raise ValueError(f'configuration {config} is missing source config (serialport or host and port)') + + self.serial_port = config.get('serial_port') + self.host = config.get('host') + self.port = config.get('port') + self.timeout = config.get('timeout', 2) + self.baudrate = config.get('baudrate', 9600) + self.target = '(not set)' + self.buffersize = config.get('sml', {'buffersize': 1024}).get('buffersize', 1024) + self.listening = False + self.reader = None + + async def listen(self): + result = self.lock.acquire(blocking=False) + if not result: + self.logger.error('couldn\'t acquire lock, polling/manual access active?') + return + + self.logger.debug('acquired lock') + try: # LOCK + if self.serial_port: + self.reader, _ = await serial_asyncio.open_serial_connection( + url=self.serial_port, + baudrate=self.baudrate, + bytesize=S_BITS, + parity=S_PARITY, + stopbits=S_STOP, + ) + self.target = f'async_serial://{self.serial_port}' + else: + self.reader, _ = await asyncio.open_connection(self.host, self.port) + self.target = f'async_tcp://{self.host}:{self.port}' + + self.logger.debug(f'target is {self.target}') + if self.reader is None and not TESTING: + self.logger.error('error on setting up async listener, reader is None') + return + + self.plugin.connected = True + self.listening = True + self.logger.debug('starting to listen') + while self.listening and self.plugin.alive: + if TESTING: + chunk = RESULT + else: + try: + chunk = await self.reader.read(self.buffersize) + except serial.serialutil.SerialException: + # possibly port closed from remote site? happens with socat... + chunk = b'' + if chunk == b'': + self.logger.debug('read reached EOF, quitting') + break + # self.logger.debug(f'read {chunk} ({len(chunk)} bytes), buf is {self.buf}') + self.buf += chunk + + if len(self.buf) < 100: + continue + try: + # self.logger.debug(f'adding {len(self.buf)} bytes to stream') + self.stream.add(self.buf) + except Exception as e: + self.logger.error(f'Writing data to SmlStreamReader failed with exception {e}') + else: + self.buf = bytes() + # get frames as long as frames are detected + while True: + try: + frame = self.stream.get_frame() + if frame is None: + # self.logger.debug('didn\'t get frame') + break + + # self.logger.debug('got frame') + self.fp(frame) + except Exception as e: + detail = traceback.format_exc() + self.logger.warning(f'Preparing and parsing data failed with exception {e}: and detail: {detail}') + + # get data from frameparser and call plugin + if self.data_callback: + self.data_callback(self.fp()) + + # just in case of many errors, reset buffer + # with SmlStreamParser, this should not happen anymore, but who knows... + if len(self.buf) > 100000: + self.logger.error("Buffer got to large, doing buffer reset") + self.buf = bytes() + finally: + # cleanup + try: + self.reader.feed_eof() + except Exception: + pass + self.plugin.connected = False + self.lock.release() + + async def stop_on_queue(self): + """ wait for STOP in queue and signal reader to terminate """ + self.logger.debug('task waiting for STOP from queue...') + await self.plugin. wait_for_asyncio_termination() + self.logger.debug('task received STOP, halting listener') + self.listening = False + + +# +# single-shot reader +# + + +class SmlReader(): + def __init__(self, logger, config: dict): + self.config = config + self.sock = None + self.lock = config['lock'] + self.logger = logger + + if not ('serial_port' in config or ('host' in config and 'port' in config)): + raise ValueError(f'configuration {config} is missing source config (serialport or host and port)') + + if not config.get('poll') and not ASYNC_IMPORTED: + raise ValueError('async configured but pyserial_asyncio not imported. Aborting.') + self.serial_port = config.get('serial_port') + self.host = config.get('host') + self.port = config.get('port') + self.timeout = config.get('timeout', 2) + self.baudrate = config.get('baudrate', 9600) + self.target = '(not set)' + self.buffersize = config.get('sml', {'buffersize': 1024}).get('buffersize', 1024) + + logger.debug(f"config='{config}'") + + def __call__(self) -> bytes: + + # + # open the serial communication + # + locked = self.lock.acquire(blocking=False) + if not locked: + logger.error('could not get lock for serial/network access. Is another scheduled/manual action still active?') + return b'' + + try: # lock release + runtime = time.time() + self.get_sock() + if not self.sock: + # error already logged, just go + return b'' + logger.debug(f"time to open {self.target}: {format_time(time.time() - runtime)}") + + # + # read data from device + # + response = bytes() + try: + response = self.read() + if len(response) == 0: + logger.error('reading data from device returned 0 bytes!') + return b'' + else: + logger.debug(f'read {len(response)} bytes') + + except Exception as e: + logger.error(f'reading data from {self.target} failed with error: {e}') + + except Exception: + # passthrough, this is only for releasing the lock + raise + finally: + # + # clean up connection + # + try: + self.sock.close() + except Exception: + pass + self.sock = None + self.lock.release() + return response + + def _read(self) -> bytes: + """ isolate the read method from the connection object """ + if isinstance(self.sock, serial.Serial): + return self.sock.read() + elif isinstance(self.sock, socket.socket): + return self.sock.recv(self.buffersize) + else: + return b'' + + def read(self) -> bytes: + """ + This function reads some bytes from serial or network interface + it returns an array of bytes if a timeout occurs or a given end byte is encountered + and otherwise b'' if an error occurred + :returns the read data + """ + if TESTING: + return RESULT + + self.logger.debug("start to read data from serial/network device") + response = bytes() + while True: + try: + # on serial, length is ignored + data = self._read() + if data: + response += data + if len(response) >= self.buffersize: + self.logger.debug('read end, length reached') + break + else: + if isinstance(self.sock, serial.Serial): + self.logger.debug('read end, end of data reached') + break + except socket.error as e: + if e.args[0] == errno.EAGAIN or e.args[0] == errno.EWOULDBLOCK: + self.logger.debug(f'read end, error: {e}') + break + else: + raise + except Exception as e: + self.logger.debug(f"error while reading from serial/network: {e}") + return b'' + + self.logger.debug(f"finished reading data from serial/network {len(response)} bytes") + return response + + def get_sock(self): + """ open serial or network socket """ + if TESTING: + self.sock = 1 + self.target = '(test input)' + return + + if self.serial_port: + # + # open the serial communication + # + count = 0 + while count < 3: + try: # open serial + count += 1 + self.sock = serial.Serial( + self.serial_port, + self.baudrate, + S_BITS, + S_PARITY, + S_STOP, + timeout=self.timeout + ) + if not self.serial_port == self.sock.name: + logger.debug(f"Asked for {self.serial_port} as serial port, but really using now {self.sock.name}") + self.target = f'serial://{self.sock.name}' + + except FileNotFoundError: + logger.error(f"Serial port '{self.serial_port}' does not exist, please check your port") + return None, '' + except serial.SerialException: + if self.sock is None: + if count < 3: + # count += 1 + logger.error(f"Serial port '{self.serial_port}' could not be opened, retrying {count}/3...") + time.sleep(3) + continue + else: + logger.error(f"Serial port '{self.serial_port}' could not be opened") + else: + logger.error(f"Serial port '{self.serial_port}' could be opened but somehow not accessed") + return None, '' + except OSError: + logger.error(f"Serial port '{self.serial_port}' does not exist, please check the spelling") + return None, '' + except Exception as e: + logger.error(f"unforeseen error occurred: '{e}'") + return None, '' + + if self.sock is None: + if count == 3: + logger.error("retries unsuccessful, serial port could not be opened, giving up.") + else: + # this should not happen... + logger.error("retries unsuccessful or unforeseen error occurred, serial object was not initialized.") + return None, '' + + if not self.sock.is_open: + logger.error(f"serial port '{self.serial_port}' could not be opened with given parameters, maybe wrong baudrate?") + return None, '' + + elif self.host: + # + # open network connection + # + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + sock.connect((self.host, self.port)) + sock.setblocking(False) + self.target = f'tcp://{self.host}:{self.port}' + + else: + logger.error('neither serialport nor host/port was given, no action possible.') + return None, '' + + +# +# frame parser +# + + +class SmlFrameParser(): + def __init__(self, config: dict): + self.config = config + self.result = {} + + def __call__(self, frame=None): + if frame is None: + res = self.result + self.result = {} + return res + else: + self.parse_frame(frame) + return self.result + + def parse_frame(self, frame): + """ parse single SML frame and add data to result dict """ + obis_values = frame.get_obis() + for entry in obis_values: + code = entry.obis.obis_code + if code not in self.result: + self.result[code] = [] + content = { + 'obis': code, + 'value': entry.get_value(), + 'valueRaw': entry.get_value(), + 'name': OBIS_NAMES.get(entry.obis) + } + # skip scaler calculation as the smllib has done this already + # if entry.scaler: + # content['scaler'] = entry.scaler + # content['value'] = round(content['value'] * 10 ** content['scaler'], 1) + if entry.unit: + content['unit'] = smlConst.UNITS.get(entry.unit) + content['unitCode'] = entry.unit + if entry.val_time: + content['valTime'] = entry.val_time + content['actTime'] = time.ctime(self.config.get('date_offset', 0) + entry.val_time) + if entry.value_signature: + content['signature'] = entry.value_signature + if entry.status: + content['status'] = entry.status + # Decoding status information if present + # for bitwise operation, true-ish result means bit is set + try: + content['statRun'] = bool((content['status'] >> 8) & 1) # True: meter is counting, False: standstill + content['statFraudMagnet'] = bool((content['status'] >> 8) & 2) # True: magnetic manipulation detected, False: ok + content['statFraudCover'] = bool((content['status'] >> 8) & 4) # True: cover manipulation detected, False: ok + content['statEnergyTotal'] = bool((content['status'] >> 8) & 8) # Current flow total. True: -A, False: +A + content['statEnergyL1'] = bool((content['status'] >> 8) & 16) # Current flow L1. True: -A, False: +A + content['statEnergyL2'] = bool((content['status'] >> 8) & 32) # Current flow L2. True: -A, False: +A + content['statEnergyL3'] = bool((content['status'] >> 8) & 64) # Current flow L3. True: -A, False: +A + content['statRotaryField'] = bool((content['status'] >> 8) & 128) # True: rotary field not L1->L2->L3, False: ok + content['statBackstop'] = bool((content['status'] >> 8) & 256) # True: backstop active, False: backstop not active + content['statCalFault'] = bool((content['status'] >> 8) & 512) # True: calibration relevant fatal fault, False: ok + content['statVoltageL1'] = bool((content['status'] >> 8) & 1024) # True: Voltage L1 present, False: not present + content['statVoltageL2'] = bool((content['status'] >> 8) & 2048) # True: Voltage L2 present, False: not present + content['statVoltageL3'] = bool((content['status'] >> 8) & 4096) # True: Voltage L3 present, False: not present + except Exception: + pass + + # Convert some special OBIS values into nicer format + # EMH ED300L: add additional OBIS codes + if code == '1-0:96.5.0*255': + val = int(content['valueRaw'], 16) + content['value'] = bin(val >> 8) # Status as binary string, so not decoded into status bits as above + # end TODO + + # don't return multiple code, only the last one -> overwrite earlier data + # self.result[code].append(content) + self.result[code] = [content] + logger.debug(f"found {code} with {content}") + + +# +# cyclic parser +# + + +class SmlParser(): + def __init__(self, config: dict): + self.fp = SmlFrameParser(config) + + def __call__(self, data: bytes) -> dict: + return self.parse(data) + + def parse(self, data: bytes) -> dict: + """ parse data returned from device read """ + stream = SmlStreamReader() + stream.add(data) + + while True: + try: + frame = stream.get_frame() + if frame is None: + break + + self.fp(frame) + + except Exception as e: + detail = traceback.format_exc() + logger.warning(f'parsing data failed with error: {e}; details are {detail}') + # at least return what was decoded up to now + return self.fp() + + return self.fp() + + +def query(config) -> dict: + """ + This function will + 1. open a serial communication line to the smartmeter + 2. reads out the block of OBIS information + 3. closes the serial communication + 4. extract obis data and format return dict + + config contains a dict with entries for + 'serial_port', 'device' and a sub-dict 'sml' with entries for + 'device', 'buffersize', 'date_offset' and additional entries for + calculating crc ('poly', 'reflect_in', 'xor_in', 'reflect_out', 'xor_out', 'swap_crc_bytes') + + return: a dict with the response data formatted as follows: + { + 'readout': , + '': [{'value': , (optional) 'unit': ''}, {'value': ', 'unit': ''}, ...], + '': [...], + ... + } + """ + + # + # initialize module + # + + # for the performance of the serial read we need to save the current time + starttime = time.time() + runtime = starttime + + try: + reader = SmlReader(logger, config) + except ValueError as e: + logger.error(f'error on opening connection: {e}') + return {} + result = reader() + + logger.debug(f"time for reading OBIS data: {format_time(time.time() - runtime)}") + runtime = time.time() + + # Display performance of the serial communication + logger.debug(f"whole communication with smartmeter took {format_time(time.time() - starttime)}") + + # + # parse data + # + + parser = SmlParser(config) + return parser(result) + + +def discover(config: dict) -> bool: + """ try to autodiscover SML protocol """ + + # as of now, this simply tries to listen to the meter + # called from within the plugin, the parameters are either manually set by + # the user, or preset by the plugin.yaml defaults. + # If really necessary, the query could be called multiple times with + # reduced baud rates or changed parameters, but there would need to be + # the need for this. + # For now, let's see how well this works... + return bool(query(config)) + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description='Query a smartmeter at a given port for SML output', + usage='use "%(prog)s --help" for more information', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('port', help='specify the port to use for the smartmeter query, e.g. /dev/ttyUSB0 or /dev/sml0') + parser.add_argument('-v', '--verbose', help='print verbose information', action='store_true') + parser.add_argument('-t', '--timeout', help='maximum time to wait for a message from the smartmeter', type=float, default=3.0) + parser.add_argument('-b', '--buffersize', help='maximum size of message buffer for the reply', type=int, default=1024) + + args = parser.parse_args() + + # complete default dict + config = { + 'lock': Lock(), + 'serial_port': '', + 'host': '', + 'port': 0, + 'connection': '', + 'timeout': 2, + 'baudrate': 9600, + 'dlms': { + 'device': '', + 'querycode': '?', + 'baudrate_min': 300, + 'use_checksum': True, + 'onlylisten': False, + 'normalize': True + }, + 'sml': { + 'buffersize': 1024 + } + } + + config['serial_port'] = args.port + config['timeout'] = args.timeout + config['sml']['buffersize'] = args.buffersize + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + # create formatter and add it to the handlers + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s @ %(lineno)d') + # formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + # add the handlers to the logger + logging.getLogger().addHandler(ch) + else: + logging.getLogger().setLevel(logging.INFO) + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) + # just like print + formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + # add the handlers to the logger + logging.getLogger().addHandler(ch) + + logger.info("This is Smartmeter Plugin, SML module, running in standalone mode") + logger.info("==================================================================") + + result = query(config) + + if not result: + logger.info(f"No results from query, maybe a problem with the serial port '{config['serial_port']}' given.") + elif len(result) > 1: + logger.info("These are the processed results of the query:") + try: + del result['readout'] + except KeyError: + pass + try: + import pprint + except ImportError: + txt = str(result) + else: + txt = pprint.pformat(result, indent=4) + logger.info(txt) + elif len(result) == 1: + logger.info("The results of the query could not be processed; raw result is:") + logger.info(result) + else: + logger.info("The query did not get any results. Maybe the serial port was occupied or there was an error.") diff --git a/smartmeter/sml_test.py b/smartmeter/sml_test.py new file mode 100755 index 000000000..15d365fc6 --- /dev/null +++ b/smartmeter/sml_test.py @@ -0,0 +1,363 @@ +HEIZUNG = [ + b'\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x0050b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcd\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xa4p\x00v\x05\x1a\x0051b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xd7zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01', + b'\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3i\xeb\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3i\xeb\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00', + b'\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16\xe1\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07(\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07I\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08p\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?', + b"\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01ch\xe1\x00v\x05\x1a\x0052b\x00b\x00rc\x02\x01q\x01c\xddZ\x00\x1b\x1b\x1b\x1b\x1a\x00]x\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x0053b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbce\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c[o\x00v\x05\x1a\x0054b\x00b\x00", + b'rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xd8zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3i', + b'\xfc\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3i\xfc\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16\xa3\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\r\x01w\x07\x01\x008\x07\x00\xff\x01\x01', + b"b\x1bR\x00U\x00\x00\x07?\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08W\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x0e\xf0\x00v", + b'\x05\x1a\x0055b\x00b\x00rc\x02\x01q\x01c;\xfa\x00\x1b\x1b\x1b\x1b\x1a\x00t\xce\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x0056b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcf\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cZO\x00v\x05\x1a\x0057b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!', + b'\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xd9zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\r\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xff', + b'Y\x00\x00\x00\x00\x17\xb3j\r\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16\x7f\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\t\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x079\x01w\x07\x01\x00L\x07', + b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08<\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x17J\x00v\x05\x1a\x0058b\x00b\x00rc\x02\x01q\x01c", + b'UM\x00\x1b\x1b\x1b\x1b\x1a\x00v\x84\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x0059b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcg\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xe0\xdc\x00v\x05\x1a\x005:b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xda', + b'zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x1d\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x1d\x01w\x07\x01\x00\x01\x08', + b'\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16\x97\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x11\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x074\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08O\x01w\x07', + b"\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x98\xfd\x00v\x05\x1a\x005;b\x00b\x00rc\x02\x01q\x01c\xe6\xb3\x00\x1b\x1b\x1b\x1b\x1a\x00\xd4\xb5\x1b\x1b\x1b\x1b\x01", + b'\x01\x01\x01v\x05\x1a\x005A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xbd\xd5\x00v\x05\x1a\x005>b\x00b\x00rc\x02\x01q\x01c"\xb8\x00\x1b\x1b\x1b\x1b\x1a\x00\xd7\xb5\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005?b\x00b\x00rc\x01', + b'\x01v\x01\x01\x05\x08\xaa\xbci\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xc4\xff\x00v\x05\x1a\x005@b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xdczw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t', + b'\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j>\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j>\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00', + b"U\x00\x00\x16K\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x02\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x1e\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x087\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf", + b'!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xf1\xf8\x00v\x05\x1a\x005Ab\x00b\x00rc\x02\x01q\x01c\xa4\xfa\x00\x1b\x1b\x1b\x1b\x1a\x00\t\xc4\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Bb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcj\x0b\t\x01ISK\x00', + b'\x03\xcf!\x02\x01\x01c2\x8d\x00v\x05\x1a\x005Cb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xddzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08', + b'\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3jO\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3jO\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16e\x01w\x07\x01\x00$\x07\x00\xff\x01\x01', + b"b\x1bR\x00U\x00\x00\x07\x07\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07'\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x086\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89", + b'\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01ccK\x00v\x05\x1a\x005Db\x00b\x00rc\x02\x01q\x01c`\xf1\x00\x1b\x1b\x1b\x1b\x1a\x00\xf1r\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Eb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbck\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x01\x11\x00v\x05\x1a\x005F', + b'b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xdezw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00', + b'\x00\x17\xb3j_\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j_\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16#\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xdd\x01w\x07\x01\x008\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x07\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08>\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c", + # b'\xae\x96\x00v\x05\x1a\x005Gb\x00b\x00rc\x02\x01q\x01c\xd3\x0f\x00\x1b\x1b\x1b\x1b\x1a\x00\xbdb\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Hb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcl\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c0\xcd\x00v\x05\x1a\x005Ib\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK', + # b'\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xdfzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3jo\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01', + # b'b\x1eR\xffY\x00\x00\x00\x00\x17\xb3jo\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16O\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x19\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\n\x01w\x07', + # b"\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08+\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c&]\x00v\x05\x1a\x005Jb\x00b\x00rc\x02", + # b'\x01q\x01c\xbd\xb8\x00\x1b\x1b\x1b\x1b\x1a\x00N\xea\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Kb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcm\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xcf\xd2\x00v\x05\x1a\x005Lb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e', + # b'\x12\xda\x9b\xe0zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x80\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x80\x01w\x07', + # b'\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16{\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x1a\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x1e\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08', + # b"A\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01ca\x88\x00v\x05\x1a\x005Mb\x00b\x00rc\x02\x01q\x01c[\x18\x00\x1b\x1b\x1b\x1b\x1a\x00\xc4U\x1b", + # b'\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Nb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcn\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xce\xf2\x00v\x05\x1a\x005Ob\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe1zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01', + # b'\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x90\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\x90\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00', + # b'\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16\x81\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x1b\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x077\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08-\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02', + # b"?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xdb\x1b\x00v\x05\x1a\x005Pb\x00b\x00rc\x02\x01q\x01cp\xde\x00\x1b\x1b\x1b\x1b\x1a\x00k\xa3\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Qb\x00b", + # b'\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbco\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cf~\x00v\x05\x1a\x005Rb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe2zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01', + # b'\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xa0\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xa0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01', + # b"b\x1bR\x00U\x00\x00\x16R\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xef\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07$\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08>\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5", + # b'\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xacL\x00v\x05\x1a\x005Sb\x00b\x00rc\x02\x01q\x01c\xc3 \x00\x1b\x1b\x1b\x1b\x1a\x00p\xed\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Tb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcp\x0b\t\x01', + # b'ISK\x00\x03\xcf!\x02\x01\x01cj\x94\x00v\x05\x1a\x005Ub\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe3zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07', + # b'\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xb0\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xb0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16?\x01w\x07\x01\x00$\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xe4\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07%\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x085\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A", + # b'\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cM\x04\x00v\x05\x1a\x005Vb\x00b\x00rc\x02\x01q\x01c\x07+\x00\x1b\x1b\x1b\x1b\x1a\x00q,\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Wb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcq\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x95\x8b\x00v\x05', + # b'\x1a\x005Xb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe4zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xff', + # b'Y\x00\x00\x00\x00\x17\xb3j\xc0\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xc0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16Q\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xe9\x01w\x07', + # b"\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07&\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08B\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi", + # b'\x01\x01\x01c\x82\xf1\x00v\x05\x1a\x005Yb\x00b\x00rc\x02\x01q\x01cK7\x00\x1b\x1b\x1b\x1b\x1a\x003^\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005Zb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcr\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x1d\xa4\x00v\x05\x1a\x005[b\x00b\x00rc\x07\x01w\x01\x0b\t', + # b'\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe5zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xd0\x01w\x07\x01\x00\x01\x08', + # b'\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xd0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x16%\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xd7\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07', + # b"!\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08,\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xf3\x97\x00v\x05\x1a\x005\\b\x00b", + # b'\x00rc\x02\x01q\x01c\x8f<\x00\x1b\x1b\x1b\x1b\x1a\x00\xfc\xab\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005]b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcs\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c.8\x00v\x05\x1a\x005^b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xff', + # b'rb\x01e\x12\xda\x9b\xe6zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xe0\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j', + # b'\xe0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x15\xb4\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xa7\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xf5\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00', + # b"U\x00\x00\x08\x17\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cJ\x84\x00v\x05\x1a\x005_b\x00b\x00rc\x02\x01q\x01c<\xc2\x00\x1b\x1b\x1b\x1b\x1a", + # b'\x00\xe7\xf4\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005`b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbct\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c)\xc5\x00v\x05\x1a\x005ab\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe7zw\x07\x81\x81\xc7\x82\x03', + # b'\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xf0\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3j\xf0\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xff', + # b'Y\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x15\x85\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\x9c\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xde\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x08\x0e\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01', + # b"\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xaa\x91\x00v\x05\x1a\x005bb\x00b\x00rc\x02\x01q\x01c\x9d\xe6\x00\x1b\x1b\x1b\x1b\x1a\x00\xb11\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005", + # b'cb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcu\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xd6\xda\x00v\x05\x1a\x005db\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe8zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00', + # b'\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x00\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x00\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x15O\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\x8e\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xd6\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\xea\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n", + # b'\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x12{\x00v\x05\x1a\x005eb\x00b\x00rc\x02\x01q\x01c{F\x00\x1b\x1b\x1b\x1b\x1a\x00\x8e\xa7\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005fb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc', + # b'v\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xd7\xfa\x00v\x05\x1a\x005gb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xe9zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!', + # b'\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x10\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x10\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x14\xf8\x01w\x07', + # b"\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06c\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xc1\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\xd3\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6", + # b'TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x81e\x00v\x05\x1a\x005hb\x00b\x00rc\x02\x01q\x01c\x15\xf1\x00\x1b\x1b\x1b\x1b\x1a\x00\xe1p\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005ib\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcw\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cm', + # b'i\x00v\x05\x1a\x005jb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xeazw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01', + # b'b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x1f\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x1f\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x14\xff\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06', + # b"\x8b\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xd0\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\xc0\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7", + # b'\xaaixi\x01\x01\x01c8Y\x00v\x05\x1a\x005kb\x00b\x00rc\x02\x01q\x01c\xa6\x0f\x00\x1b\x1b\x1b\x1b\x1a\x00\xc86\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005lb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcx\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xb6U\x00v\x05\x1a\x005mb\x00b\x00rc\x07\x01', + # b'w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xebzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k.\x01w\x07', + # b'\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k.\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x14D\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x062\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00', + # b"U\x00\x00\x06\x87\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x8f\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c 2\x00v\x05\x1a\x005", + # b'nb\x00b\x00rc\x02\x01q\x01cb\x04\x00\x1b\x1b\x1b\x1b\x1a\x00>\x81\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005ob\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcy\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cIJ\x00v\x05\x1a\x005pb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00', + # b'b\n\xff\xffrb\x01e\x12\xda\x9b\xeczw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k=\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00', + # b'\x00\x17\xb3k=\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x13\xf1\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\n\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06r\x01w\x07\x01\x00L\x07\x00\xff\x01\x01', + # b"b\x1bR\x00U\x00\x00\x07p\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\\D\x00v\x05\x1a\x005qb\x00b\x00rc\x02\x01q\x01cki\x00\x1b", + # b'\x1b\x1b\x1b\x1a\x00\xb6\xff\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005rb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbcz\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xd3z\x00v\x05\x1a\x005sb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xedzw\x07\x81', + # b'\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kL\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kL\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01', + # b'b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x13\x8a\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xef\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06Y\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07@\x01w\x07\x81\x81\xc7\x82', + # b"\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cz]\x00v\x05\x1a\x005tb\x00b\x00rc\x02\x01q\x01c\xafb\x00\x1b\x1b\x1b\x1b\x1a\x00\xe2\xf8\x1b\x1b\x1b\x1b\x01\x01\x01\x01v", + # b'\x05\x1a\x005ub\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc{\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xe0\xe6\x00v\x05\x1a\x005vb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xeezw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07', + # b'\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kZ\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kZ\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07', + # b"\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x12\xde\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xab\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06!\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x11\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81", + # b'\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xed\xa7\x00v\x05\x1a\x005wb\x00b\x00rc\x02\x01q\x01c\x1c\x9c\x00\x1b\x1b\x1b\x1b\x1a\x00<\xc2\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005xb\x00b\x00rc\x01\x01v\x01\x01', + # b'\x05\x08\xaa\xbc|\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xd1:\x00v\x05\x1a\x005yb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xefzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK', + # b'\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kh\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kh\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x12', + # b"\xce\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xbc\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\x0b\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x07\x06\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02", + # b'\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x89\x9d\x00v\x05\x1a\x005zb\x00b\x00rc\x02\x01q\x01cr+\x00\x1b\x1b\x1b\x1b\x1a\x00m\x1b\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005{b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc}\x0b\t\x01ISK\x00\x03\xcf!\x02', + # b'\x01\x01c.%\x00v\x05\x1a\x005|b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf1zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00', + # b'\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kv\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3kv\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x12\x08\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00', + # b"U\x00\x00\x05j\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xd6\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xc6\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1", + # b'\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xb3\xb3\x00v\x05\x1a\x005}b\x00b\x00rc\x02\x01q\x01c\x94\x8b\x00\x1b\x1b\x1b\x1b\x1a\x00\xd2\x98\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005~b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc~\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c/\x05\x00v\x05\x1a\x005\x7fb\x00b\x00', + # b'rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf2zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k', + # b'\x83\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x83\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x11\xc1\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05V\x01w\x07\x01\x008\x07\x00\xff\x01\x01', + # b"b\x1bR\x00U\x00\x00\x05\xb2\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\xb8\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xa6\xab\x00v", + # b'\x05\x1a\x005\x80b\x00b\x00rc\x02\x01q\x01c+\xf0\x00\x1b\x1b\x1b\x1b\x1a\x00\xa1\xcc\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x81b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x7f\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c{3\x00v\x05\x1a\x005\x82b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!', + # b'\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf3zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x90\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xff', + # b'Y\x00\x00\x00\x00\x17\xb3k\x90\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x116\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05#\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x88\x01w\x07\x01\x00L\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\x8a\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cR\xa4\x00v\x05\x1a\x005\x83b\x00b\x00rc\x02\x01q\x01c", + # b'\x98\x0e\x00\x1b\x1b\x1b\x1b\x1a\x00\xbe\xfe\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x84b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x80\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cy\xb9\x00v\x05\x1a\x005\x85b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf4', + # b'zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x9d\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\x9d\x01w\x07\x01\x00\x01\x08', + # b'\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x10\xd2\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x03\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05c\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06j\x01w\x07', + # b"\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c}u\x00v\x05\x1a\x005\x86b\x00b\x00rc\x02\x01q\x01c\\\x05\x00\x1b\x1b\x1b\x1b\x1a\x00*f\x1b\x1b\x1b\x1b\x01", + # b'\x01\x01\x01v\x05\x1a\x005\x87b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x81\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x86\xa6\x00v\x05\x1a\x005\x88b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf5zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04IS', + # b'K\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xa9\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xa9\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00', + # b"\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x10S\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xde\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x055\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06?\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'", + # b'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xaf\xc0\x00v\x05\x1a\x005\x89b\x00b\x00rc\x02\x01q\x01c\x10\x19\x00\x1b\x1b\x1b\x1b\x1a\x00\x07\x1b\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x8ab\x00b\x00rc\x01', + # b'\x01v\x01\x01\x05\x08\xaa\xbc\x82\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x0e\x89\x00v\x05\x1a\x005\x8bb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf6zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t', + # b'\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xb5\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xb5\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00', + # b"U\x00\x00\x0f\xdc\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\x9e\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x15\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06(\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf", + # b'!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x8a\x16\x00v\x05\x1a\x005\x8cb\x00b\x00rc\x02\x01q\x01c\xd4\x12\x00\x1b\x1b\x1b\x1b\x1a\x00\xa7_\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x8db\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x83\x0b\t\x01ISK\x00', + # b'\x03\xcf!\x02\x01\x01c=\x15\x00v\x05\x1a\x005\x8eb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf7zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08', + # b'\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xc1\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xc1\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0f\xa9\x01w\x07\x01\x00$\x07\x00\xff\x01\x01', + # b"b\x1bR\x00U\x00\x00\x04\xa2\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xee\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x06\x19\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89", + # b'\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xb6?\x00v\x05\x1a\x005\x8fb\x00b\x00rc\x02\x01q\x01cg\xec\x00\x1b\x1b\x1b\x1b\x1a\x00\xa8Y\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x90b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x84\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x1e\xd6\x00v\x05\x1a\x005\x91', + # b'b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf8zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00', + # b'\x00\x17\xb3k\xcd\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xcd\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0fN\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\x8b\x01w\x07\x01\x008\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xce\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xf4\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c", + # b'\xfc\x0c\x00v\x05\x1a\x005\x92b\x00b\x00rc\x02\x01q\x01cL*\x00\x1b\x1b\x1b\x1b\x1a\x00\xf3\xd6\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x93b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x85\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xe1\xc9\x00v\x05\x1a\x005\x94b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK', + # b'\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xf9zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xd8\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01', + # b'b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xd8\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0f"\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\x83\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xc5\x01w\x07', + # b"\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\xd9\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cOS\x00v\x05\x1a\x005\x95b\x00b\x00rc\x02", + # b'\x01q\x01c\xaa\x8a\x00\x1b\x1b\x1b\x1b\x1a\x00\x10\xc6\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x96b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x86\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xe0\xe9\x00v\x05\x1a\x005\x97b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e', + # b'\x12\xda\x9b\xfazw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xe3\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xe3\x01w\x07', + # b'\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0e\xd7\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04o\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xa8\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05', + # b"\xbf\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cv\xcd\x00v\x05\x1a\x005\x98b\x00b\x00rc\x02\x01q\x01c\xc4=\x00\x1b\x1b\x1b\x1b\x1a\x00\xc3d\x1b", + # b'\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x99b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x87\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cZz\x00v\x05\x1a\x005\x9ab\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xfbzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01', + # b'\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xee\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xee\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00', + # b'\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0e;\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04:\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04k\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x95\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02', + # b"?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xda \x00v\x05\x1a\x005\x9bb\x00b\x00rc\x02\x01q\x01cw\xc3\x00\x1b\x1b\x1b\x1b\x1a\x004\xc0\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x9cb\x00b", + # b'\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x88\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x81F\x00v\x05\x1a\x005\x9db\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xfczw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01', + # b'\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xf9\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3k\xf9\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01', + # b"b\x1bR\x00U\x00\x00\x0e\x1f\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x046\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04]\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x8b\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5", + # b'\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xcdp\x00v\x05\x1a\x005\x9eb\x00b\x00rc\x02\x01q\x01c\xb3\xc8\x00\x1b\x1b\x1b\x1b\x1a\x00\x85\xc3\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\x9fb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x89\x0b\t\x01', + # b'ISK\x00\x03\xcf!\x02\x01\x01c~Y\x00v\x05\x1a\x005\xa0b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xfdzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07', + # b'\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x03\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x03\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\r\xd1\x01w\x07\x01\x00$\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\x10\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04O\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05q\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A", + # b'\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xb0\xbf\x00v\x05\x1a\x005\xa1b\x00b\x00rc\x02\x01q\x01c0G\x00\x1b\x1b\x1b\x1b\x1a\x00j\xfd\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xa2b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x8a\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xc0W\x00v\x05', + # b'\x1a\x005\xa3b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xfezw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xff', + # b'Y\x00\x00\x00\x00\x17\xb3l\r\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\r\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\r\x1a\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\xd5\x01w\x07', + # b"\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\x0f\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x055\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi", + # b'\x01\x01\x01c/\x99\x00v\x05\x1a\x005\xa4b\x00b\x00rc\x02\x01q\x01c\xf4L\x00\x1b\x1b\x1b\x1b\x1a\x00\x91$\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xa5b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x8b\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xf3\xcb\x00v\x05\x1a\x005\xa6b\x00b\x00rc\x07\x01w\x01\x0b\t', + # b'\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9b\xffzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x17\x01w\x07\x01\x00\x01\x08', + # b'\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x17\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0c\xd2\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\xb1\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04', + # b"\x05\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x05\x1b\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c=\xa3\x00v\x05\x1a\x005\xa7b\x00b", + # b'\x00rc\x02\x01q\x01cG\xb2\x00\x1b\x1b\x1b\x1b\x1a\x00\x1f\xca\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xa8b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x8c\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xc2\x17\x00v\x05\x1a\x005\xa9b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xff', + # b'rb\x01e\x12\xda\x9c\x00zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l!\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l', + # b'!\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0c\x9b\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\xa4\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\xec\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00', + # b"U\x00\x00\x05\n\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xb8\xa0\x00v\x05\x1a\x005\xaab\x00b\x00rc\x02\x01q\x01c)\x05\x00\x1b\x1b\x1b\x1b\x1a", + # b'\x00A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xcc\xb3\x00v\x05\x1a\x005\xadb\x00b\x00rc\x02\x01q\x01c\xcf\xa5\x00\x1b\x1b\x1b\x1b\x1a\x00#\xd1\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005", + # b'\xaeb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x8e\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c<(\x00v\x05\x1a\x005\xafb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x02zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00', + # b'\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l3\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l3\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07', + # b"\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0b\xe3\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x81\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\xa6\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xbb\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n", + # b'\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c3\x1a\x00v\x05\x1a\x005\xb0b\x00b\x00rc\x02\x01q\x01c\xe4c\x00\x1b\x1b\x1b\x1b\x1a\x00\xc0*\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xb1b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x8f\x0b\t\x01ISK\x00', + # b'\x03\xcf!\x02\x01\x01c\x94\xa4\x00v\x05\x1a\x005\xb2b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x03zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xff', + # b'e\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l<\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l<\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0b\x8d\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03^\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00', + # b"\x03\x8c\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04\xa3\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\x85.\x00v\x05\x1a\x005\xb3b\x00", + # b'b\x00rc\x02\x01q\x01cW\x9d\x00\x1b\x1b\x1b\x1b\x1a\x00\xab\xc6\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xb4b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x90\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x98N\x00v\x05\x1a\x005\xb5b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff', + # b'\xffrb\x01e\x12\xda\x9c\x04zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lE\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3', + # b'lE\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x0b\x07\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x036\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03`\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR', + # b"\x00U\x00\x00\x04p\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xad\xaa\x00v\x05\x1a\x005\xb6b\x00b\x00rc\x02\x01q\x01c\x93\x96\x00\x1b\x1b\x1b\x1b", + # b'\x1a\x00\xa5\xf4\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xb7b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x91\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cgQ\x00v\x05\x1a\x005\xb8b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x06zw\x07\x81\x81\xc7\x82', + # b'\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lM\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lM\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR', + # b'\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n\xda\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x037\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03H\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04Z\x01w\x07\x81\x81\xc7\x82\x05\xff\x01', + # b"\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xe5\xf1\x00v\x05\x1a\x005\xb9b\x00b\x00rc\x02\x01q\x01c\xdf\x8a\x00\x1b\x1b\x1b\x1b\x1a\x002\x9c\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x00", + # b'5\xbab\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x92\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xef~\x00v\x05\x1a\x005\xbbb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x07zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00', + # b'\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lU\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lU\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10', + # b"\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n\xcf\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x034\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03I\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04S\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c", + # b'\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xe3"\x00v\x05\x1a\x005\xbcb\x00b\x00rc\x02\x01q\x01c\x1b\x81\x00\x1b\x1b\x1b\x1b\x1a\x00\xbd\xb0\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xbdb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa', + # b'\xbc\x93\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xdc\xe2\x00v\x05\x1a\x005\xbeb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x08zw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf', + # b'!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l]\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l]\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n\x93\x01w', + # b'\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03"\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03-\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04D\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef\'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01', + # b'\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xedQ\x00v\x05\x1a\x005\xbfb\x00b\x00rc\x02\x01q\x01c\xa8\x7f\x00\x1b\x1b\x1b\x1b\x1a\x00\xd8\x0e\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xc0b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x94\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c', + # b'\x93c\x00v\x05\x1a\x005\xc1b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\tzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82', + # b'\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3le\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3le\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n\x91\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00', + # b"\x03\x1a\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x037\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04?\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb", + # b'\xf7\xaaixi\x01\x01\x01c\xdc;\x00v\x05\x1a\x005\xc2b\x00b\x00rc\x02\x01q\x01c\x0c\x96\x00\x1b\x1b\x1b\x1b\x1a\x00\xe9\xb7\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xc3b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x95\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cl|\x00v\x05\x1a\x005\xc4b\x00b\x00rc\x07', + # b'\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\nzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lm\x01w', + # b'\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lm\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n^\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x0f\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR', + # b"\x00U\x00\x00\x03 \x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04-\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01cm\xf9\x00v\x05\x1a\x00", + # b'5\xc5b\x00b\x00rc\x02\x01q\x01c\xea6\x00\x1b\x1b\x1b\x1b\x1a\x00\x8db\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xc6b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x96\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01cm\\\x00v\x05\x1a\x005\xc7b\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01', + # b'\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x0bzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3lu\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00', + # b'\x00\x00\x17\xb3lu\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\nN\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x08\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x15\x01w\x07\x01\x00L\x07\x00\xff\x01', + # b"\x01b\x1bR\x00U\x00\x00\x04/\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xcbt\x00v\x05\x1a\x005\xc8b\x00b\x00rc\x02\x01q\x01c\x84\x81\x00", + # b'\x1b\x1b\x1b\x1b\x1a\x00\x94{\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x1a\x005\xc9b\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x97\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\xd7\xcf\x00v\x05\x1a\x005\xcab\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\x0czw\x07', + # b'\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l}\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l}\x01w\x07\x01\x00\x01\x08\x02\xff\x01', + # b'\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\nH\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x00\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x1a\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x04(\x01w\x07\x81\x81\xc7', + # b"\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc\x81\xbc\x0b\x8c\n\xfd\x99\xb8\xf5\x9dX\xddf!d.\x02\xdd\xb6\x01\xc6TF>A\x0e\xc7\xef\x89\xa5\x8b\xf3\xd1\x93-\xbb\xf7\xaaixi\x01\x01\x01c\xb7\x13\x00v\x05\x1a\x005\xcbb\x00b\x00rc\x02\x01q\x01c7\x7f\x00\x1b\x1b\x1b\x1b\x1a\x00\xda\xee\x1b\x1b\x1b\x1b\x01\x01\x01\x01", + # b'v\x05\x1a\x005\xccb\x00b\x00rc\x01\x01v\x01\x01\x05\x08\xaa\xbc\x98\x0b\t\x01ISK\x00\x03\xcf!\x02\x01\x01c\x0c\xf3\x00v\x05\x1a\x005\xcdb\x00b\x00rc\x07\x01w\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x07\x01\x00b\n\xff\xffrb\x01e\x12\xda\x9c\rzw\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w', + # b'\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\x0b\t\x01ISK\x00\x03\xcf!\x02\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x85\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x17\xb3l\x85\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w', + # b"\x07\x01\x00\x10\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\n6\x01w\x07\x01\x00$\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x02\xf1\x01w\x07\x01\x008\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x03\x12\x01w\x07\x01\x00L\x07\x00\xff\x01\x01b\x1bR\x00U\x00\x00\x042\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02?\n\xef'(\x18\xbc", +] + +HAUS = [ + b'\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xbab\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x97\xfa\x01c\x05g\x00v\x05\x00\xe2\xc5\xbbb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n', + b'\xff\xffrb\x01e\x00K\x97\xfavw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xae\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00', + b'W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xed!\x00v\x05\x00\xe2\xc5\xbcb\x00b\x00re\x00\x00\x02\x01q\x01c\xd3\x1d\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x17\xeb\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xbdb\x00b\x00re\x00\x00\x01\x01', + b'v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x97\xfb\x01c\x0fy\x00v\x05\x00\xe2\xc5\xbeb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x97\xfbvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZP', + b'A\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xaf\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z', + b'\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cv\xcd\x00v\x05\x00\xe2\xc5\xbfb\x00b\x00re\x00\x00\x02\x01q\x01c$\x13\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xdcT\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xc0b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01', + b'\x92\x0c\xd1rb\x01e\x00K\x97\xfc\x01c"+\x00v\x05\x00\xe2\xc5\xc1b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x97\xfcvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c', + b'\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xb1\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xfes\x00v\x05\x00\xe2\xc5\xc2', + b'b\x00b\x00re\x00\x00\x02\x01q\x01ct\xa1\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02r?\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xc3b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x97\xfd\x01c[_\x00v\x05\x00\xe2\xc5\xc4b\x00', + b'b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x97\xfdvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00', + b'\x00\x00J\xb6\xb3\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xd5\x1f\x00v\x05\x00\xe2\xc5\xc5b\x00b\x00re\x00\x00\x02\x01q\x01c7\xb9\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02', + b'r\xa9\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xc6b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x97\xfe\x01c\xd0\xc3\x00v\x05\x00\xe2\xc5\xc7b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00', + b'b\n\xff\xffrb\x01e\x00K\x97\xfevw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xb4\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00', + b'\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x84O\x00v\x05\x00\xe2\xc5\xc8b\x00b\x00re\x00\x00\x02\x01q\x01cF\x87\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x90~\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xc9b\x00b\x00re\x00\x00', + b'\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x97\xff\x01c<\t\x00v\x05\x00\xe2\xc5\xcab\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x97\xffvw\x07\x01\x00`2\x01\x01\x01\x01\x01', + b'\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xb6\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07', + b'\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x886\x00v\x05\x00\xe2\xc5\xcbb\x00b\x00re\x00\x00\x02\x01q\x01c\xb1\x89\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02(\xb4\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xccb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01Z', + b'PA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x00\x01c\xd8\n\x00v\x05\x00\xe2\xc5\xcdb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x00vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA', + b'\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xb8\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xb5s\x00v\x05', + b'\x00\xe2\xc5\xceb\x00b\x00re\x00\x00\x02\x01q\x01c\xa8\x9a\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02y|\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xcfb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x01\x01c\xa1~\x00v\x05\x00\xe2', + b'\xc5\xd0b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x01vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xff', + b'i\x00\x00\x00\x00\x00J\xb6\xb9\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cG\xa0\x00v\x05\x00\xe2\xc5\xd1b\x00b\x00re\x00\x00\x02\x01q\x01cS\xf5\x00\x00\x00\x1b\x1b', + b'\x1b\x1b\x1a\x02\xa5S\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xd2b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x02\x01c\x11\x97\x00v\x05\x00\xe2\xc5\xd3b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c', + b'\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x02vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xbb\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xff', + b'i\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xd7y\x00v\x05\x00\xe2\xc5\xd4b\x00b\x00re\x00\x00\x02\x01q\x01cJ\xe6\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x1b\x8b\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xd5b\x00b\x00', + b're\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x03\x01c\x1b\x89\x00v\x05\x00\xe2\xc5\xd6b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x03vw\x07\x01\x00`2\x01', + b'\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xbc\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x030', + b'1\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c=\x9c\x00v\x05\x00\xe2\xc5\xd7b\x00b\x00re\x00\x00\x02\x01q\x01c\xbd\xe8\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xa1\xd9\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xd8b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04', + b'\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x04\x01c\x16\xa6\x00v\x05\x00\xe2\xc5\xd9b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x04vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n', + b'\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xbe\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xca', + b'&\x00v\x05\x00\xe2\xc5\xdab\x00b\x00re\x00\x00\x02\x01q\x01c\xcc\xd6\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x1a\xad\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xdbb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x05\x01co\xd2\x00', + b'v\x05\x00\xe2\xc5\xdcb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x05vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01', + b'b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc0\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cd\x0e\x00v\x05\x00\xe2\xc5\xddb\x00b\x00re\x00\x00\x02\x01q\x01c\x8f\xce\x00', + b'\x00\x00\x1b\x1b\x1b\x1b\x1a\x02M\x87\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xdeb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x06\x01c\xe4N\x00v\x05\x00\xe2\xc5\xdfb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA', + b'\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x06vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc1\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01', + # b'b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cDW\x00v\x05\x00\xe2\xc5\xe0b\x00b\x00re\x00\x00\x02\x01q\x01c\x8e\x1f\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02 \xd9\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xe1', + # b'b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x07\x01c~n\x00v\x05\x00\xe2\xc5\xe2b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x07vw\x07\x01', + # b'\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc3\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01', + # b'\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x9aP\x00v\x05\x00\xe2\xc5\xe3b\x00b\x00re\x00\x00\x02\x01q\x01cy\x11\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02!\x1b\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xe4b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05', + # b'\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x08\x01cU[\x00v\x05\x00\xe2\xc5\xe5b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x08vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01', + # b'\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc5\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01', + # b'\x01\x01c\xbc\xf0\x00v\x05\x00\xe2\xc5\xe6b\x00b\x00re\x00\x00\x02\x01q\x01c`\x02\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x95\x11\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xe7b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\t\x01', + # b'c,/\x00v\x05\x00\xe2\xc5\xe8b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\tvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00', + # b'\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc6\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c \xf6\x00v\x05\x00\xe2\xc5\xe9b\x00b\x00re\x00\x00\x02\x01q\x01', + # b'cK7\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02.\x17\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xeab\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\n\x01cAg\x00v\x05\x00\xe2\xc5\xebb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n', + # b'\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\nvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xc8\x01w\x07\x01\x00\x02\x08\x00\xff\x01', + # b'\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cR=\x00v\x05\x00\xe2\xc5\xecb\x00b\x00re\x00\x00\x02\x01q\x01cR$\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x19\xb3\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5', + # b'\xedb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x0b\x01cKy\x00v\x05\x00\xe2\xc5\xeeb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x0bvw\x07', + # b'\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xca\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01', + # b'\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cyQ\x00v\x05\x00\xe2\xc5\xefb\x00b\x00re\x00\x00\x02\x01q\x01c\xa5*\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02b\xfe\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xf0b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1', + # b'\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x0c\x01c\x9b\xf7\x00v\x05\x00\xe2\xc5\xf1b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x0cvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01', + # b'\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xcb\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d', + # b'\x01\x01\x01cpA\x00v\x05\x00\xe2\xc5\xf2b\x00b\x00re\x00\x00\x02\x01q\x01c\x04N\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02[\xa4\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xf3b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\r', + # b'\x01c\xe2\x83\x00v\x05\x00\xe2\xc5\xf4b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\rvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe', + # b'\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xcd\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\n\xdb\x00v\x05\x00\xe2\xc5\xf5b\x00b\x00re\x00\x00\x02\x01q', + # b'\x01cGV\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x05\x84\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xf6b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x0e\x01ci\x1f\x00v\x05\x00\xe2\xc5\xf7b\x00b\x00re\x00\x00\x07\x01w\x01\x0b', + # b'\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x0evw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xce\x01w\x07\x01\x00\x02', + # b'\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\n}\x00v\x05\x00\xe2\xc5\xf8b\x00b\x00re\x00\x00\x02\x01q\x01c6h\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xb9\xe5\x1b\x1b\x1b\x1b\x01\x01\x01\x01v', + # b'\x05\x00\xe2\xc5\xf9b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x0f\x01c\x85\xd5\x00v\x05\x00\xe2\xc5\xfab\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98', + # b'\x0fvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd0\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00', + # b'\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x93\xd7\x00v\x05\x00\xe2\xc5\xfbb\x00b\x00re\x00\x00\x02\x01q\x01c\xc1f\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xa84\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xfcb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00', + # b'\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x10\x01c?u\x00v\x05\x00\xe2\xc5\xfdb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x10vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`', + # b'\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd2\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05', + # b'rI\xa0\x1d\x01\x01\x01c\xbbz\x00v\x05\x00\xe2\xc5\xfeb\x00b\x00re\x00\x00\x02\x01q\x01c\xd8u\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xc3\xcc\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc5\xffb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e', + # b'\x00K\x98\x11\x01cF\x01\x00v\x05\x00\xe2\xc6\x00b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x11vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01', + # b'\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd3\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cS\x11\x00v\x05\x00\xe2\xc6\x01b\x00b\x00re\x00', + # b'\x00\x02\x01q\x01cN\x89\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xebx\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x02b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x12\x01cX\xa6\x00v\x05\x00\xe2\xc6\x03b\x00b\x00re\x00\x00\x07', + # b'\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x12vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd5\x01w', + # b'\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x92>\x00v\x05\x00\xe2\xc6\x04b\x00b\x00re\x00\x00\x02\x01q\x01cW\x9a\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02m\xf4\x1b\x1b\x1b\x1b\x01', + # b'\x01\x01\x01v\x05\x00\xe2\xc6\x05b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x13\x01cR\xb8\x00v\x05\x00\xe2\xc6\x06b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01', + # b'e\x00K\x98\x13vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd7\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w', + # b'\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xb9R\x00v\x05\x00\xe2\xc6\x07b\x00b\x00re\x00\x00\x02\x01q\x01c\xa0\x94\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x16\xb9\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x08b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01', + # b'ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x14\x01c_\x97\x00v\x05\x00\xe2\xc6\tb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x14vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w', + # b'\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xd8\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01', + # b'\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c<\x85\x00v\x05\x00\xe2\xc6\nb\x00b\x00re\x00\x00\x02\x01q\x01c\xd1\xaa\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xc1\xb6\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x0bb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1', + # b'rb\x01e\x00K\x98\x15\x01c&\xe3\x00v\x05\x00\xe2\xc6\x0cb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x15vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w', + # b'\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xda\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x17\xe9\x00v\x05\x00\xe2\xc6\rb\x00b', + # b'\x00re\x00\x00\x02\x01q\x01c\x92\xb2\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xc1 \x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x0eb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x16\x01c\xad\x7f\x00v\x05\x00\xe2\xc6\x0fb\x00b\x00r', + # b'e\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x16vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J', + # b'\xb6\xdc\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xd6\xc6\x00v\x05\x00\xe2\xc6\x10b\x00b\x00re\x00\x00\x02\x01q\x01c3\xd6\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x19\x85\x1b', + # b'\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x11b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x17\x01c\x9c\x14\x00v\x05\x00\xe2\xc6\x12b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff', + # b'\xffrb\x01e\x00K\x98\x17vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xdd\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W', + # b'\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c$\x15\x00v\x05\x00\xe2\xc6\x13b\x00b\x00re\x00\x00\x02\x01q\x01c\xc4\xd8\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x86\xba\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x14b\x00b\x00re\x00\x00\x01\x01v', + # b'\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x18\x01c\xb7!\x00v\x05\x00\xe2\xc6\x15b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x18vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04Z', + # b'PA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xdf\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`', + # b'Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cSC\x00v\x05\x00\xe2\xc6\x16b\x00b\x00re\x00\x00\x02\x01q\x01c\xdd\xcb\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02l\x06\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x17b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00', + # b'\x01\x92\x0c\xd1rb\x01e\x00K\x98\x19\x01c\xceU\x00v\x05\x00\xe2\xc6\x18b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x19vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92', + # b'\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xe0\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xa5\x1c\x00v\x05\x00\xe2\xc6', + # b'\x19b\x00b\x00re\x00\x00\x02\x01q\x01c\xf6\xfe\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xdb\x81\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x1ab\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1a\x01c\xa3\x1d\x00v\x05\x00\xe2\xc6\x1bb', + # b'\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x1avw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00', + # b'\x00\x00\x00J\xb6\xe2\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c5\xc5\x00v\x05\x00\xe2\xc6\x1cb\x00b\x00re\x00\x00\x02\x01q\x01c\xef\xed\x00\x00\x00\x1b\x1b\x1b\x1b\x1a', + # b'\x02\x1f\xf7\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\x1db\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1b\x01c\xa9\x03\x00v\x05\x00\xe2\xc6\x1eb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01', + # b'\x00b\n\xff\xffrb\x01e\x00K\x98\x1bvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xe4\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00', + # b'\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cO_\x00v\x05\x00\xe2\xc6\x1fb\x00b\x00re\x00\x00\x02\x01q\x01c\x18\xe3\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02:\x0c\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6 b\x00b\x00re\x00\x00', + # b'\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1c\x01c\xd2\xc6\x00v\x05\x00\xe2\xc6!b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x1cvw\x07\x01\x00`2\x01\x01\x01\x01\x01', + # b'\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xe5\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07', + # b'\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xda\x1b\x00v\x05\x00\xe2\xc6"b\x00b\x00re\x00\x00\x02\x01q\x01c\x192\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02v\xe6\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6#b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01Z', + # b'PA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1d\x01c\xab\xb2\x00v\x05\x00\xe2\xc6$b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x1dvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA', + # b'\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xe7\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xf1w\x00v\x05', + # b'\x00\xe2\xc6%b\x00b\x00re\x00\x00\x02\x01q\x01cZ*\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02vp\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6&b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1e\x01c .\x00v\x05\x00\xe2', + # b"\xc6'b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x1evw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xff", + # b'i\x00\x00\x00\x00\x00J\xb6\xe9\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x83\xbc\x00v\x05\x00\xe2\xc6(b\x00b\x00re\x00\x00\x02\x01q\x01c+\x14\x00\x00\x00\x1b\x1b', + # b'\x1b\x1b\x1a\x02\xa6j\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6)b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98\x1f\x01c\xcc\xe4\x00v\x05\x00\xe2\xc6*b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c', + # b'\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98\x1fvw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xea\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xff', + # b'i\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x1f\xba\x00v\x05\x00\xe2\xc6+b\x00b\x00re\x00\x00\x02\x01q\x01c\xdc\x1a\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x81\t\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6,b\x00b\x00', + # b're\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98 \x01cEg\x00v\x05\x00\xe2\xc6-b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98 vw\x07\x01\x00`2\x01', + # b'\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xec\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x030', + # b'1\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xc9\x1f\x00v\x05\x00\xe2\xc6.b\x00b\x00re\x00\x00\x02\x01q\x01c\xc5\t\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xb6\xce\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6/b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04', + # b'\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98!\x01c<\x13\x00v\x05\x00\xe2\xc60b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98!vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n', + # b'\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xee\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x8b', + # b'L\x00v\x05\x00\xe2\xc61b\x00b\x00re\x00\x00\x02\x01q\x01c>f\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xda\x13\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc62b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98"\x01c\x8c\xfa\x00', + # b'v\x05\x00\xe2\xc63b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98"vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01', + # b"b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xef\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xab\x15\x00v\x05\x00\xe2\xc64b\x00b\x00re\x00\x00\x02\x01q\x01c'u\x00", + # b'\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xd49\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc65b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98#\x01c\x86\xe4\x00v\x05\x00\xe2\xc66b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA', + # b'\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98#vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xf1\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01', + # b'b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x15\xaa\x00v\x05\x00\xe2\xc67b\x00b\x00re\x00\x00\x02\x01q\x01c\xd0{\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x06o\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc68', + # b'b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98$\x01c\x8b\xcb\x00v\x05\x00\xe2\xc69b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98$vw\x07\x01', + # b'\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xf2\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01', + # b'\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01cro\x00v\x05\x00\xe2\xc6:b\x00b\x00re\x00\x00\x02\x01q\x01c\xa1E\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02"\xb2\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6;b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05', + # b'\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98%\x01c\xf2\xbf\x00v\x05\x00\xe2\xc6b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98&\x01', + # b'cy#\x00v\x05\x00\xe2\xc6?b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98&vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00', + # b'\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xf6\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x98,\x00v\x05\x00\xe2\xc6@b\x00b\x00re\x00\x00\x02\x01q\x01', + # b"c\xb2\xef\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x06H\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Ab\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98'\x01c\xb5\x94\x00v\x05\x00\xe2\xc6Bb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n", + # b"\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98'vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xf7\x01w\x07\x01\x00\x02\x08", + # b'\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xce\x02\x00v\x05\x00\xe2\xc6Cb\x00b\x00re\x00\x00\x02\x01q\x01cE\xe1\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xebf\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05', + # b'\x00\xe2\xc6Db\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98(\x01c\x9e\xa1\x00v\x05\x00\xe2\xc6Eb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98(', + # b'vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xf9\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02', + # b'\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c[F\x00v\x05\x00\xe2\xc6Fb\x00b\x00re\x00\x00\x02\x01q\x01c\\\xf2\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xf2\x08\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Gb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01', + # b'\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98)\x01c\xe7\xd5\x00v\x05\x00\xe2\xc6Hb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98)vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01', + # b'\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xfb\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05r', + # b'I\xa0\x1d\x01\x01\x01cW?\x00v\x05\x00\xe2\xc6Ib\x00b\x00re\x00\x00\x02\x01q\x01cw\xc7\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xd6\xa7\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Jb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00', + # b'K\x98*\x01c\x8a\x9d\x00v\x05\x00\xe2\xc6Kb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98*vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08', + # b'\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xfc\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\x06o\x00v\x05\x00\xe2\xc6Lb\x00b\x00re\x00\x00', + # b'\x02\x01q\x01cn\xd4\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xd3\xce\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Mb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98+\x01c\x80\x83\x00v\x05\x00\xe2\xc6Nb\x00b\x00re\x00\x00\x07\x01', + # b'w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98+vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb6\xfe\x01w\x07', + # b'\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c-\x03\x00v\x05\x00\xe2\xc6Ob\x00b\x00re\x00\x00\x02\x01q\x01c\x99\xda\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xa8\x83\x1b\x1b\x1b\x1b\x01\x01', + # b'\x01\x01v\x05\x00\xe2\xc6Pb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98,\x01cP\r\x00v\x05\x00\xe2\xc6Qb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e', + # b'\x00K\x98,vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb7\x00\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07', + # b'\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c9x\x00v\x05\x00\xe2\xc6Rb\x00b\x00re\x00\x00\x02\x01q\x01c8\xbe\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x17\x81\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Sb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01Z', + # b'PA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98-\x01c)y\x00v\x05\x00\xe2\xc6Tb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98-vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07', + # b'\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb7\x01\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01', + # b'\x01\x01\x05rI\xa0\x1d\x01\x01\x01c\xa2\x94\x00v\x05\x00\xe2\xc6Ub\x00b\x00re\x00\x00\x02\x01q\x01c{\xa6\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\xa7\xe5\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Vb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1r', + # b'b\x01e\x00K\x98.\x01c\xa2\xe5\x00v\x05\x00\xe2\xc6Wb\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98.vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07', + # b'\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb7\x03\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01c2M\x00v\x05\x00\xe2\xc6Xb\x00b\x00', + # b're\x00\x00\x02\x01q\x01c\n\x98\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02\x84-\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6Yb\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x98/\x01cN/\x00v\x05\x00\xe2\xc6Zb\x00b\x00re', + # b'\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xffrb\x01e\x00K\x98/vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb7', + # b'\x05\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91\xe1\x01w\x07\x01\x00\x00\x02\x00\x00\x01\x01\x01\x01\x0301\x01w\x07\x01\x00`Z\x02\x01\x01\x01\x01\x01\x05rI\xa0\x1d\x01\x01\x01co\xc2\x00v\x05\x00\xe2\xc6[b\x00b\x00re\x00\x00\x02\x01q\x01c\xfd\x96\x00\x00\x00\x1b\x1b\x1b\x1b\x1a\x02bQ\x1b\x1b', + # b'\x1b\x1b\x01\x01\x01\x01v\x05\x00\xe2\xc6\\b\x00b\x00re\x00\x00\x01\x01v\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x05\x01\x02\x03\x04\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1rb\x01e\x00K\x980\x01c\xf4\x8f\x00v\x05\x00\xe2\xc6]b\x00b\x00re\x00\x00\x07\x01w\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x07\x01\x00b\n\xff\xff', + # b'rb\x01e\x00K\x980vw\x07\x01\x00`2\x01\x01\x01\x01\x01\x01\x04ZPA\x01w\x07\x01\x00`\x01\x00\xff\x01\x01\x01\x01\x0b\n\x01ZPA\x00\x01\x92\x0c\xd1\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x1c\x01\x04\x01b\x1eR\xffi\x00\x00\x00\x00\x00J\xb7\x06\x01w\x07\x01\x00\x02\x08\x00\xff\x01\x01b\x1eR\xffi\x00\x00\x00\x00\x00W\x91' +] + +RHa = b''.join(HAUS) +RHe = b''.join(HEIZUNG) + +RESULT = bytes(RHa) diff --git a/smartmeter/webif/__init__.py b/smartmeter/webif/__init__.py new file mode 100644 index 000000000..9c005b6d1 --- /dev/null +++ b/smartmeter/webif/__init__.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2024- Michael Wenzel wenzel_michael@web.de +######################################################################### +# This file is part of SmartHomeNG. +# https://www.smarthomeNG.de +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# +# This file implements the web interface for the Sample plugin. +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +# +######################################################################### + +import datetime +import time +import os +import json +import pprint + +from lib.item import Items +from lib.model.smartplugin import SmartPluginWebIf + + +# ------------------------------------------ +# Webinterface of the plugin +# ------------------------------------------ + +import cherrypy +import csv +from jinja2 import Environment, FileSystemLoader + + +class WebInterface(SmartPluginWebIf): + + def __init__(self, webif_dir, plugin): + """ + Initialization of instance of class WebInterface + + :param webif_dir: directory where the webinterface of the plugin resides + :param plugin: instance of the plugin + :type webif_dir: str + :type plugin: object + """ + self.logger = plugin.logger + self.webif_dir = webif_dir + self.plugin = plugin + self.items = Items.get_instance() + + self.tplenv = self.init_template_environment() + + @cherrypy.expose + def index(self, reload=None): + """ + Build index.html for cherrypy + + Render the template and return the html file to be delivered to the browser + + :return: contents of the template after being rendered + """ + pagelength = self.plugin.get_parameter_value('webif_pagelength') + tmpl = self.tplenv.get_template('index.html') + return tmpl.render(p=self.plugin, + webif_pagelength=pagelength, + item_count=len(self.plugin.get_item_list()), + ) + + @cherrypy.expose + def get_data_html(self, dataSet=None): + """ + Return data to update the webpage + + For the standard update mechanism of the web interface, the dataSet to return the data for is None + + :param dataSet: Dataset for which the data should be returned (standard: None) + :return: dict with the data needed to update the web page. + """ + + # if dataSets are used, define them here + if dataSet == 'overview': + try: + data = json.dumps(self.plugin.obis_results) + return data + except Exception as e: + self.logger.error(f"get_data_html overview exception: {e}") + + elif dataSet == 'devices_info': + data = {'items': {}} + + # add item data + for item in self.plugin.get_item_list(): + item_dict = {'typ': item.property.type, + 'obis_code': self.plugin.get_iattr_value(item.conf, 'obis_code', ''), + 'obis_index': self.plugin.get_iattr_value(item.conf, 'obis_index', '0'), + 'obis_property': self.plugin.get_iattr_value(item.conf, 'obis_property', 'value'), + 'obis_vtype': self.plugin.get_iattr_value(item.conf, 'obis_vtype', '-'), + 'value': item.property.value, + 'last_update': item.property.last_update.strftime('%d.%m.%Y %H:%M:%S'), + 'last_change': item.property.last_change.strftime('%d.%m.%Y %H:%M:%S'), + } + + data['items'][item.property.path] = item_dict + + # add obis result + data['obis_results'] = self.plugin.obis_results + + try: + return json.dumps(data, default=str) + except Exception as e: + self.logger.error(f"get_data_html devices_info exception: {e}") + + if dataSet is None: + return + + @cherrypy.expose + def submit(self, cmd=None): + + self.logger.debug(f"submit: {cmd=}") + result = None + + if cmd == "detect": + result = {'discovery_successful': self.plugin.discover(), 'protocol': self.plugin.protocol} + + elif cmd == 'query': + result = self.plugin.query(assign_values=False) + + elif cmd == 'create_items': + result = self.plugin.create_items() + + if result is not None: + # JSON zurücksenden + cherrypy.response.headers['Content-Type'] = 'application/json' + self.logger.debug(f"Result for web interface: {result}") + return json.dumps(result).encode('utf-8') + diff --git a/smartmeter/webif/templates/index.html b/smartmeter/webif/templates/index.html new file mode 100644 index 000000000..c374c4684 --- /dev/null +++ b/smartmeter/webif/templates/index.html @@ -0,0 +1,496 @@ +{% extends "base_plugin.html" %} + +{% set logo_frame = false %} + + +{% set update_interval = 10000 %} + + +{% set dataSet = 'devices_info' %} + + +{% set update_params = item_id %} + + +{% set buttons = true %} + + +{% set autorefresh_buttons = true %} + + +{% set reload_button = true %} + + +{% set close_button = true %} + + +{% set row_count = true %} + + +{% set initial_update = true %} + + +{% block pluginstyles %} + +{% endblock pluginstyles %} + + +{% block pluginscripts %} + + + + + + + + + + + + + + + + + + + + + + + +{% endblock pluginscripts %} + + +{% block headtable %} + + + + + + + + + + + + + + + + + + + + + +
Connection{% if p._config['host'] %}{{ p._config['host'] }}{{ _(':') }}{{ p._config['port'] }}{% else %}{{ p._config['serial_port'] }}{% endif %}Timeout{{ p._config['timeout'] }}s
Protokoll{{ p.protocol }}Baudrate{{ p._config['baudrate'] }}
{% if p.use_asyncio %}Verbunden{% else %}{{ '' }}{% endif %}{% if p.use_asyncio %}{% if p.connected %}{{ _('Ja') }}{% else %}{{ _('Nein') }}{% endif %}{% endif %}Abfrage{% if p.cycle %} {{ p.cycle }}s {% elif p.crontab %} {{ p.crontab }} {% elif p.use_asyncio %} {{ _('kontinuierlich') }} {% endif %}
+{% endblock headtable %} + + +{% block buttons %} +
+ {% if not p.protocol %} + + {% endif %} + + +
+
+ + +
+{% endblock %} + + +{% set tabcount = 3 %} + +{% set start_tab = 1 %} +{% if item_count==0 %} + {% set start_tab = 2 %} +{% endif %} + + +{% set tab1title = "" ~ p.get_shortname() ~ " Items (" ~ item_count ~ ")" %} +{% block bodytab1 %} + +
+{% endblock bodytab1 %} + + + +{% set tab2title = "" "OBIS Data (" ~ len(p.obis_results) ~ ")" %} +{% block bodytab2 %} + +
+{% endblock bodytab2 %} + + + +{% if p.protocol in ['SML', 'sml'] %} + {% set tab3title = "" "Zählerstatus" %} +{% else %} + {% set tab3title = "hidden" %} +{% endif %} +{% block bodytab3 %} + +
+{% endblock bodytab3 %}