diff --git a/.gitignore b/.gitignore index 6a46b1f3c..c664aba43 100755 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,8 @@ nosetests.xml ehthumbs.db Thumbs.db -# don't upload private plugins -/priv_*/ +# don't upload private plugins or symlinked dirs +/priv_* # don't upload plugins loaded from develop to a release installation /*_dev/ diff --git a/beolink/__init__.py b/beolink/__init__.py index 3d5f3796d..77f9c6893 100755 --- a/beolink/__init__.py +++ b/beolink/__init__.py @@ -42,7 +42,7 @@ class BeoNetlink(SmartPlugin): the update functions for the items """ - PLUGIN_VERSION = '0.8.0' + PLUGIN_VERSION = '0.8.1' def __init__(self, sh): """ @@ -267,7 +267,7 @@ def poll_device(self): item = self.beo_items[beo_itemkey] beo_id = item.conf['beo_id'] beo_status = item.conf['beo_status'] - if beo_status: + if beo_status and beo_id != '': # set items according to beo_status #if beo_status == 'beoname': # deviceinfo = self.beodevices.beodeviceinfo[beo_id].get('FriendlyName', None) @@ -275,43 +275,47 @@ def poll_device(self): # deviceinfo = self.beodevices.beodeviceinfo[beo_id].get('productType', None) #else: # deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None) - if beo_status == 'audiomode': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('audiomode'[1], False) - elif beo_status == 'videomode': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('videomode'[1], False) - elif beo_status == 'powerstate': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('powerstate', False) - elif beo_status == 'stand': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('stand'[1], False) - elif beo_status == 'source': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['source'].get('source', '-') - elif beo_status == 'volume': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('level', 0) - elif beo_status == 'muted': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('muted', False) - elif beo_status == 'FriendlyName': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('FriendlyName', False) - elif beo_status == 'productType': - deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('productType', False) - + beo_device = self.beodevices.beodeviceinfo.get(beo_id, None) + if beo_device is None: + self.logger.warning(f"poll_device: No deviceinfo found for device-id '{beo_id}'") else: - deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None) - #self.logger.info(f"poll_device: item={item.property.path}, beo_id={beo_id}, beo_status={beo_status}, self.beodevices.beodeviceinfo[beo_id]={self.beodevices.beodeviceinfo[beo_id]}") - #self.logger.info(f"poll_device: item={item.property.path}, deviceinfo={deviceinfo}") - if isinstance(deviceinfo, tuple): - if item._type == 'num': - beo_value = deviceinfo[1] + if beo_status == 'audiomode': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('audiomode'[1], False) + elif beo_status == 'videomode': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('videomode'[1], False) + elif beo_status == 'powerstate': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('powerstate', False) + elif beo_status == 'stand': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('stand'[1], False) + elif beo_status == 'source': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['source'].get('source', '-') + elif beo_status == 'volume': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('level', 0) + elif beo_status == 'muted': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['volume'].get('muted', False) + elif beo_status == 'FriendlyName': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('FriendlyName', False) + elif beo_status == 'productType': + deviceinfo = self.beodevices.beodeviceinfo[beo_id]['device'].get('productType', False) + else: - beo_value = deviceinfo[0] - else: - beo_value = deviceinfo + deviceinfo = self.beodevices.beodeviceinfo[beo_id].get(beo_status, None) + #self.logger.info(f"poll_device: item={item.property.path}, beo_id={beo_id}, beo_status={beo_status}, self.beodevices.beodeviceinfo[beo_id]={self.beodevices.beodeviceinfo[beo_id]}") + #self.logger.info(f"poll_device: item={item.property.path}, deviceinfo={deviceinfo}") + if isinstance(deviceinfo, tuple): + if item._type == 'num': + beo_value = deviceinfo[1] + else: + beo_value = deviceinfo[0] + else: + beo_value = deviceinfo - if item() == beo_value: - self.logger.debug("update_deviceinfo: Updated item {} with beo-{} {}".format(item.property.path, beo_status, beo_value)) - else: - self.logger.info("update_deviceinfo: Changed item {} with beo-{} {}".format(item.property.path, beo_status, beo_value)) - item(beo_value, self.get_shortname()) - self._update_item_values(item, beo_value) + if item() == beo_value: + self.logger.debug("update_deviceinfo: Updated item {} with beo-{} {}".format(item.property.path, beo_status, beo_value)) + else: + self.logger.info("update_deviceinfo: Changed item {} with beo-{} {}".format(item.property.path, beo_status, beo_value)) + item(beo_value, self.get_shortname()) + self._update_item_values(item, beo_value) else: self.logger.info(f"poll_device: No beo_status") return diff --git a/beolink/beodevices.py b/beolink/beodevices.py index b9bacfcbc..92e538c8b 100644 --- a/beolink/beodevices.py +++ b/beolink/beodevices.py @@ -234,20 +234,22 @@ def update_deviceinfo(self, beo_id): self.beodeviceinfo[beo_id]['device']['audiomode'] = ['-', -1] self.beodeviceinfo[beo_id]['device']['stand'] = ['-', -1] else: - self.beodeviceinfo[beo_id]['source']['source'] = self.get_beo_api(ip, '/BeoZone/Zone/ActiveSources', ['primaryExperience','source','friendlyName']) - - # get picture-mode of the B&O device - self.beodeviceinfo[beo_id]['device']['videomode'] = list(self.read_list_value(ip, '/Picture/Mode', 'mode')) - self.logger.debug("update_deviceinfo: ip: {} videomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['videomode'])) + try: + self.beodeviceinfo[beo_id]['source']['source'] = self.get_beo_api(ip, '/BeoZone/Zone/ActiveSources', ['primaryExperience','source','friendlyName']) - # get sound-mode of the B&O device - self.beodeviceinfo[beo_id]['device']['audiomode'] = list(self.read_list_value(ip, '/Sound/Mode', 'mode')) - self.logger.debug("update_deviceinfo: ip: {} audiomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['audiomode'])) + # get picture-mode of the B&O device + self.beodeviceinfo[beo_id]['device']['videomode'] = list(self.read_list_value(ip, '/Picture/Mode', 'mode')) + self.logger.debug("update_deviceinfo: ip: {} videomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['videomode'])) - # get stand position of the B&O device - self.beodeviceinfo[beo_id]['device']['stand'] = list(self.read_list_value(ip, '/Stand', 'stand')) - self.logger.debug("update_deviceinfo: ip: {} stand-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['stand'])) + # get sound-mode of the B&O device + self.beodeviceinfo[beo_id]['device']['audiomode'] = list(self.read_list_value(ip, '/Sound/Mode', 'mode')) + self.logger.debug("update_deviceinfo: ip: {} audiomode-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['audiomode'])) + # get stand position of the B&O device + self.beodeviceinfo[beo_id]['device']['stand'] = list(self.read_list_value(ip, '/Stand', 'stand')) + self.logger.debug("update_deviceinfo: ip: {} stand-friendly = {}".format(ip, self.beodeviceinfo[beo_id]['device']['stand'])) + except Exception as ex: + self.logger.warning(f"beodevices/update_deviceinfo: {beo_id} - Exception '{ex}'") # get possible sources of the B&O device #raw_sources = self.get_beo_api(ip, '/BeoZone/Zone/Sources', []) #self.beodeviceinfo[beo_id]['sources'] = [] diff --git a/beolink/plugin.yaml b/beolink/plugin.yaml index 9ddb079b3..764f0e229 100755 --- a/beolink/plugin.yaml +++ b/beolink/plugin.yaml @@ -12,7 +12,7 @@ plugin: # documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page # support: https://knx-user-forum.de/forum/supportforen/smarthome-py - version: 0.8.0 # Plugin version + version: 0.8.1 # Plugin version sh_minversion: '1.9' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: False # plugin supports multi instance diff --git a/database/__init__.py b/database/__init__.py index 1a02a6475..2f2fd9eee 100755 --- a/database/__init__.py +++ b/database/__init__.py @@ -51,7 +51,7 @@ class Database(SmartPlugin): """ ALLOW_MULTIINSTANCE = True - PLUGIN_VERSION = '1.6.12' + PLUGIN_VERSION = '1.6.13' # SQL queries: {item} = item table name, {log} = log table name # time, item_id, val_str, val_num, val_bool, changed @@ -104,6 +104,7 @@ def __init__(self, sh, *args, **kwargs): self._precision = self.get_parameter_value('precision') self.count_logentries = self.get_parameter_value('count_logentries') self.max_delete_logentries = self.get_parameter_value('max_delete_logentries') + self.max_reassign_logentries = self.get_parameter_value('max_reassign_logentries') self._default_maxage = float(self.get_parameter_value('default_maxage')) self._copy_database = self.get_parameter_value('copy_database') @@ -967,6 +968,38 @@ def _count_orphanlogentries(self): return + def reassign_orphaned_id(self, orphan_id, to): + """ + Reassign values from orphaned item ID to given item ID + + :param orphan_id: item id of the orphaned item + :param to: item id of the target item + :type orphan_id: int + :type to: int + """ + log_info = self.logger.warning # info + log_debug = self.logger.error # debug + try: + log_info(f'reassigning orphaned data from (old) id {orphan_id} to (new) id {to}') + cur = self._db_maint.cursor() + count = self.readLogCount(orphan_id, cur=cur) + log_debug(f'found {count} entries to reassign, reassigning {self.max_reassign_logentries} at once') + + while count > 0: + log_debug(f'reassigning {min(count, self.max_reassign_logentries)} log entries') + self._execute(self._prepare("UPDATE {log} SET item_id = :newid WHERE item_id = :orphanid LIMIT :limit;"), {'newid': to, 'orphanid': orphan_id, 'limit': self.max_reassign_logentries}, cur=cur) + count -= self.max_reassign_logentries + + self._execute(self._prepare("DELETE FROM {item} WHERE id = :orphanid LIMIT 1;"), {'orphanid': orphan_id}, cur=cur) + log_info(f'reassigned orphaned id {orphan_id} to new id {to}') + cur.close() + self._db_maint.commit() + log_debug('rebuilding orphan list') + self.build_orphanlist() + except Exception as e: + self.logger.error(f'error on reassigning id {orphan_id} to {to}: {e}') + return e + def _delete_orphan(self, item_path): """ Delete orphan item or logentries it @@ -988,7 +1021,7 @@ def _delete_orphan(self, item_path): return True cur = self._db_maint.cursor() - self._execute(self._prepare("DELETE FROM {log} WHERE item_id = :id ORDER BY time ASC LIMIT :maxrecords;"), {'id': item_id, 'maxrecords': self.delete_orphan_chunk_size}, cur=cur) + self._execute(self._prepare("DELETE FROM {log} WHERE item_id = :id LIMIT :maxrecords;"), {'id': item_id, 'maxrecords': self.delete_orphan_chunk_size}, cur=cur) delete_orphan_chunk_size_str = f"{self.delete_orphan_chunk_size:,}".replace(',', '.') self.logger.info(f"_delete_orphan: Deleted (up to) {delete_orphan_chunk_size_str} log entries for Item {item_path}") cur.close() @@ -1137,6 +1170,7 @@ def _series(self, func, start, end='now', count=100, ratio=1, update=False, step 'step': logs['step'], 'sid': sid}, 'update': self.shtime.now() + datetime.timedelta(seconds=int(logs['step'] / 1000)) } + self.logger.dbgmed(f"_series: {sid=}, {step=}, update={result['update']}, delta={int(logs['step'] / 1000)}, now={self.shtime.now()}") #self.logger.debug("_series: result={}".format(result)) return result @@ -1183,7 +1217,7 @@ def _expression(self, func): expression['finalizer'] = func[:func.index(":")] func = func[func.index(":") + 1:] if func == 'count' or func.startswith('count'): - parts = re.match('(count)((<>|!=|<|=|>)(\d+))?', func) + parts = re.match(r'(count)((<>|!=|<|=|>)(\d+))?', func) func = 'count' if parts and parts.group(3) is not None: expression['params']['op'] = parts.group(3) diff --git a/database/plugin.yaml b/database/plugin.yaml index 3f4cbbafe..50f3e59fb 100755 --- a/database/plugin.yaml +++ b/database/plugin.yaml @@ -11,7 +11,7 @@ plugin: keywords: database support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1021844-neues-database-plugin - version: 1.6.12 # Plugin version + version: 1.6.13 # Plugin version sh_minversion: '1.9.3.2' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: True # plugin supports multi instance @@ -72,6 +72,14 @@ parameters: de: "Maximal auf einmal zu löschende Anzahl an Log Einträgen mit dem database_maxage Attribut, reduziert die Belastung der Datenbank bei alten Datenbeständen" en: "Maximum number of Logentries to delete at once with database_maxage attribute, reduces load on database with old datasets" + max_reassign_logentries: + type: int + default: 20 # 000 + valid_min: 10 # 00 + description: + de: "Maximal auf einmal neu zuzuweisende Anzahl an Log Einträgen, reduziert die Belastung der Datenbank bei großen Datenbeständen" + en: "Maximum number of Logentries to reassign at once, reduces load on database with large datasets" + default_maxage: type: int default: 0 diff --git a/database/webif/__init__.py b/database/webif/__init__.py index 9c9178391..1fef30513 100755 --- a/database/webif/__init__.py +++ b/database/webif/__init__.py @@ -130,6 +130,28 @@ def index(self, reload=None, action=None, item_id=None, item_path=None, time_end tabcount=2, action=action, item_id=item_id, delete_triggered=delete_triggered, language=self.plugin.get_sh().get_defaultlanguage()) + @cherrypy.expose + def reassign(self): + cl = cherrypy.request.headers['Content-Length'] + if not cl: + return + try: + rawbody = cherrypy.request.body.read(int(cl)) + data = json.loads(rawbody) + except Exception: + return + orphan_id = data.get("orphan_id") + new_id = data.get("new_id") + result = {"operation": "request", "result": "success"} + if orphan_id is not None and new_id is not None and orphan_id != new_id: + self.logger.info(f'reassigning orphaned id {orphan_id} to new id {new_id}') + err = self.plugin.reassign_orphaned_id(orphan_id, to=new_id) + if err: + return + return json.dumps(result) + else: + self.logger.warning(f'reassigning orphaned id {orphan_id} to new id {new_id} failed') + @cherrypy.expose def get_data_html(self, dataSet=None, params=None): """ @@ -271,7 +293,6 @@ def db_sqldump(self): return - @cherrypy.expose def cleanup(self): self.plugin.cleanup() diff --git a/database/webif/static/style.css b/database/webif/static/style.css new file mode 100644 index 000000000..94a8b7bf9 --- /dev/null +++ b/database/webif/static/style.css @@ -0,0 +1,37 @@ +/* The Modal (background) */ +.or-modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 999; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +/* Modal Content/Box */ +.or-modal-content { + background-color: #fefefe; + margin: 15% auto; /* 15% from the top and centered */ + padding: 20px; + border: 1px solid #888; + width: 80%; /* Could be more or less, depending on screen size */ +} + +/* The Close Button */ +.or-close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; +} + +.or-close:hover, +.or-close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} \ No newline at end of file diff --git a/database/webif/templates/base_database.html b/database/webif/templates/base_database.html index 8a305091a..0bbea1793 100755 --- a/database/webif/templates/base_database.html +++ b/database/webif/templates/base_database.html @@ -31,7 +31,8 @@ { className: "time", targets: 2 }, { className: "type", targets: 3 }, { className: "id", targets: 4, render: $.fn.dataTable.render.number('.', ',', 0, '') }, - { className: "logcount", targets: 5, render: $.fn.dataTable.render.number('.', ',', 0, '') }, + { className: "reassign", targets: 5 }, + { className: "logcount", targets: 6, render: $.fn.dataTable.render.number('.', ',', 0, '') }, ].concat($.fn.dataTable.defaults.columnDefs)}); {% else %} orphantable = $('#orphantable').DataTable( { @@ -42,6 +43,7 @@ { className: "time", targets: 2 }, { className: "type", targets: 3 }, { className: "id", targets: 4, render: $.fn.dataTable.render.number('.', ',', 0, '') }, + { className: "reassign", targets: 5 }, ].concat($.fn.dataTable.defaults.columnDefs)}); {% endif %} diff --git a/database/webif/templates/index.html b/database/webif/templates/index.html index 6a52ed64b..173072106 100755 --- a/database/webif/templates/index.html +++ b/database/webif/templates/index.html @@ -4,6 +4,10 @@ {% set dataSet = 'overview' %} {% set tab1title = _('Database Items') %} +{% block pluginstyles %} + +{% endblock pluginstyles %} + {%- block pluginscripts %} {{ super() }} + +
+
+ × +
+ Neuzuweisen von Itemreihe (ID ) +
+
+ Bitte wählen Sie die Itemreihe aus, der die verwaisten Daten zugewiesen werden sollen: + + + +
+
+
+
{% if p.remove_orphan or len(p.orphanlist) == 0 %} @@ -174,6 +235,7 @@ {{ _('Letzte Änderung') }} {{ _('Typ') }} {{ _('DB-ID') }} + {{ _('Neuzuweisung') }} {% if p.count_logentries %} {{ _('Anzahl Einträge') }} {% endif %} @@ -181,6 +243,7 @@ {% for item in p.orphanlist %} + {% set itemid = p.id(item, create=False) %} {{ item }} @@ -199,7 +262,10 @@ {% endif %} {% endif %} {{ _(p.db_itemtype(item)) }} - {{ p.id(item, create=False) }} + {{ itemid }} + + + {% if p.count_logentries %} {{ p._orphan_logcount[p.id(item, create=False)] }} {% endif %} diff --git a/denon/__init__.py b/denon/__init__.py index 56b0f7818..275efa482 100755 --- a/denon/__init__.py +++ b/denon/__init__.py @@ -24,6 +24,7 @@ import builtins import os import sys +import time if __name__ == '__main__': builtins.SDP_standalone = True @@ -40,8 +41,9 @@ class SmartPluginWebIf(): else: builtins.SDP_standalone = False -from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_SERIAL_PORT, PLUGIN_ATTR_CONN_TERMINATOR, CONN_NULL, CONN_NET_TCP_CLI, CONN_SER_ASYNC) +from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_SERIAL_PORT, PLUGIN_ATTR_CONN_TERMINATOR, PLUGIN_ATTR_CMD_CLASS, CONN_NULL, CONN_NET_TCP_CLI, CONN_SER_ASYNC) from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone +from lib.model.sdp.command import SDPCommandParseStr # from .webif import WebInterface @@ -55,12 +57,8 @@ class denon(SmartDevicePlugin): PLUGIN_VERSION = '1.0.1' - def on_connect(self, by=None): - self.logger.debug("Checking for custom input names.") - self.send_command('general.custom_inputnames') - def _set_device_defaults(self): - + self._use_callbacks = True self._custom_inputnames = {} # set our own preferences concerning connections @@ -72,77 +70,37 @@ def _set_device_defaults(self): self.logger.error('Neither host nor serialport set, connection not possible. Using dummy connection, plugin will not work') self._parameters[PLUGIN_ATTR_CONNECTION] = CONN_NULL + self._parameters[PLUGIN_ATTR_CMD_CLASS] = SDPCommandParseStr + b = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR].encode() b = b.decode('unicode-escape').encode() self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] = b - # we need to receive data via callback, as the "reply" can be unrelated to - # the sent command. Getting it as return value would assign it to the wrong - # command and discard it... so break the "return result"-chain and don't - # return anything - def _send(self, data_dict): - self._connection.send(data_dict) - def _transform_send_data(self, data=None, **kwargs): if isinstance(data, dict): data['limit_response'] = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] data['payload'] = f'{data.get("payload", "")}{data["limit_response"].decode("unicode-escape")}' return data - def on_data_received(self, by, data, command=None): + def _process_additional_data(self, command, data, value, custom, by): + zone = 0 + if command == 'zone1.control.power': + zone = 1 + elif command == 'zone2.control.power': + zone = 2 + elif command == 'zone3.control.power': + zone = 3 + if zone > 0 and value is True: + self.logger.debug(f"Device is turned on by command {command}. Requesting current state of zone {zone}.") + time.sleep(1) + self.send_command(f'zone{zone}.control.mute') + self.send_command(f'zone{zone}.control.sleep') + self.send_command(f'zone{zone}.control.standby') + if zone == 1 and value is True: + self.send_command(f'zone{zone}.control.input') + self.send_command(f'zone{zone}.control.volume') + self.send_command(f'zone{zone}.control.listeningmode') - commands = None - if command is not None: - self.logger.debug(f'received data "{data}" from {by} for command {command}') - commands = [command] - else: - # command == None means that we got raw data from a callback and - # don't know yet to which command this belongs to. So find out... - self.logger.debug(f'received data "{data}" from {by} without command specification') - - # command can be a string (classic single command) or - # - new - a list of strings if multiple commands are identified - # in that case, work on all strings - commands = self._commands.get_commands_from_reply(data) - if not commands: - if self._discard_unknown_command: - self.logger.debug(f'data "{data}" did not identify a known command, ignoring it') - return - else: - self.logger.debug(f'data "{data}" did not identify a known command, forwarding it anyway for {self._unknown_command}') - self._dispatch_callback(self._unknown_command, data, by) - - # TODO: remove later? - assert(isinstance(commands, list)) - - # process all commands - for command in commands: - self._check_for_custominputs(command, data) - custom = None - if self.custom_commands: - custom = self._get_custom_value(command, data) - - base_command = command - value = None - try: - if CUSTOM_INPUT_NAME_COMMAND in command: - value = self._custom_inputnames - else: - value = self._commands.get_shng_data(command, data) - except OSError as e: - self.logger.warning(f'received data "{data}" for command {command}, error {e} occurred while converting. Discarding data.') - else: - self.logger.debug(f'received data "{data}" for command {command} converted to value {value}') - self._dispatch_callback(command, value, by) - - self._process_additional_data(base_command, data, value, custom, by) - - def _check_for_custominputs(self, command, data): - if CUSTOM_INPUT_NAME_COMMAND in command and isinstance(data, str): - tmp = data.split(' ', 1) - src = tmp[0][5:] - name = tmp[1] - self._custom_inputnames[src] = name if __name__ == '__main__': - s = Standalone(lms, sys.argv[0]) + s = Standalone(denon, sys.argv[0]) diff --git a/denon/commands.py b/denon/commands.py index 27afbd0fa..041c178c1 100755 --- a/denon/commands.py +++ b/denon/commands.py @@ -36,32 +36,32 @@ 'info': { 'fullmodel': {'read': True, 'write': False, 'read_cmd': 'NSFRN ?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'NSFRN\s(.*)', 'item_attrs': {'initial': True}}, 'model': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': 'VIALL(AVR.*)', 'item_attrs': {'initial': True}}, - 'serialnumber': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLS/N\.(.*)'}, - 'main': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLMAIN:(.*)'}, - 'mainfbl': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLMAINFBL:(.*)'}, - 'dsp1': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP1:(.*)'}, - 'dsp2': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP2:(.*)'}, - 'dsp3': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP3:(.*)'}, - 'dsp4': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP4:(.*)'}, - 'apld': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLAPLD:(.*)'}, - 'vpld': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLVPLD:(.*)'}, - 'guidat': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLGUIDAT:(.*)'}, - 'heosversion': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSVER:(.*)'}, - 'heosbuild': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSBLD:(.*)'}, - 'heosmod': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSMOD:(.*)'}, - 'heoscnf': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLHEOSCNF:(.*)'}, - 'heoslanguage': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLHEOSLCL:(.*)'}, - 'mac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLMAC:(.*)'}, - 'wifimac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLWIFIMAC:(.*)'}, - 'btmac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLBTMAC:(.*)'}, - 'audyif': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLAUDYIF:(.*)'}, - 'productid': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLPRODUCTID:(.*)'}, - 'packageid': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLPACKAGEID:(.*)'}, - 'cmp': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLCMP:(.*)'}, + 'serialnumber': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLS/N\.(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'main': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLMAIN:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'mainfbl': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLMAINFBL:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'dsp1': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP1:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'dsp2': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP2:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'dsp3': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP3:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'dsp4': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLDSP4:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'apld': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLAPLD:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'vpld': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLVPLD:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'guidat': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLGUIDAT:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'heosversion': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSVER:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'heosbuild': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSBLD:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'heosmod': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLHEOSMOD:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'heoscnf': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLHEOSCNF:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'heoslanguage': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLHEOSLCL:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'mac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLMAC:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'wifimac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLWIFIMAC:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'btmac': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLBTMAC:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'audyif': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLAUDYIF:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'productid': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLPRODUCTID:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'packageid': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'VIALLPACKAGEID:(.*)', 'item_attrs': {'read_group_levels': 0}}, + 'cmp': {'read': True, 'write': False, 'read_cmd': 'VIALL?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'VIALLCMP:(.*)', 'item_attrs': {'read_group_levels': 0}}, 'region': {'read': True, 'write': False, 'read_cmd': 'SYMODTUN ?', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'SYMODTUN\s(.*)', 'item_attrs': {'initial': True}}, }, 'general': { - 'custom_inputnames': {'read': True, 'write': False, 'read_cmd': 'SSFUN ?', 'item_type': 'dict', 'dev_datatype': 'str', 'reply_pattern': 'SSFUN(.*)', 'item_attrs': {'item_template': 'custom_inputnames'}}, + 'custom_inputnames': {'read': True, 'write': False, 'read_cmd': 'SSFUN ?', 'item_type': 'dict', 'dev_datatype': 'DenonCustominput', 'reply_pattern': 'SSFUN(.*)', 'item_attrs': {'read_groups': [{'name': 'custom_inputnames', 'trigger': 'update'}], 'initial': True, 'item_template': 'custom_inputnames'}}, 'power': {'read': True, 'write': True, 'read_cmd': 'PW?', 'write_cmd': 'PW{VALUE}', 'item_type': 'bool', 'dev_datatype': 'str', 'reply_pattern': 'PW{LOOKUP}', 'lookup': 'POWER'}, 'setupmenu': {'read': True, 'write': True, 'read_cmd': 'MNMEN?', 'write_cmd': 'MNMEN {VALUE}', 'item_type': 'bool', 'dev_datatype': 'onoff', 'reply_pattern': 'MNMEN (ON|OFF)'}, 'display': {'read': True, 'write': False, 'read_cmd': 'NSE', 'item_type': 'str', 'dev_datatype': 'DenonDisplay', 'reply_pattern': 'NSE(.*)'}, @@ -106,9 +106,9 @@ 'volume': {'read': True, 'write': True, 'read_cmd': 'MV?', 'write_cmd': 'MV{VALUE}', 'item_type': 'num', 'dev_datatype': 'DenonVol', 'reply_pattern': r'MV(\d{2,3})', 'cmd_settings': {'force_min': 0.0, 'valid_max': 98.0}, 'item_attrs': {'initial': True}}, 'volumeup': {'read': False, 'write': True, 'item_type': 'bool', 'write_cmd': 'MVUP', 'dev_datatype': 'raw'}, 'volumedown': {'read': False, 'write': True, 'write_cmd': 'MVDOWN', 'item_type': 'bool', 'dev_datatype': 'raw'}, - 'volumemax': {'opcode': '{VALUE}', 'read': True, 'write': False, 'item_type': 'num', 'dev_datatype': 'str', 'reply_pattern': r'MVMAX (\d{2,3})', 'item_attrs': {'initial': True}}, + 'volumemax': {'opcode': '{VALUE}', 'read': True, 'write': False, 'item_type': 'num', 'dev_datatype': 'str', 'reply_pattern': r'MVMAX (\d{2,3})', 'item_attrs': {'read_group_levels': 0}}, 'input': {'read': True, 'write': True, 'read_cmd': 'SI?', 'write_cmd': 'SI{VALUE}', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': 'SI{LOOKUP}', 'lookup': 'INPUT', 'item_attrs': {'item_template': 'input', 'initial': True}}, - 'listeningmode': {'read': True, 'write': True, 'cmd_settings': {'valid_list_ci': ['MOVIE', 'MUSIC', 'GAME', 'DIRECT', 'PURE DIRECT', 'STEREO', 'AUTO', 'DOLBY DIGITAL', 'DOLBY SURROUND', 'DTS SURROUND', 'NEURAL:X', 'AURO3D', 'AURO2DSURR', 'MCH STEREO', 'ROCK ARENA', 'JAZZ CLUB', 'MONO MOVIE', 'MATRIX', 'VIDEO GAME', 'VIRTUAL', 'LEFT', 'RIGHT']}, 'read_cmd': 'MS?', 'write_cmd': 'MS{RAW_VALUE_UPPER}', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'\s?MS(.*)', 'item_attrs': {'initial': True}}, + 'listeningmode': {'read': True, 'write': True, 'cmd_settings': {'valid_list_ci': ['MOVIE', 'MUSIC', 'GAME', 'DIRECT', 'PURE DIRECT', 'STEREO', 'AUTO', 'DOLBY DIGITAL', 'DOLBY SURROUND', 'DTS SURROUND', 'NEURAL:X', 'AURO3D', 'AURO2DSURR', 'MCH STEREO', 'ROCK ARENA', 'JAZZ CLUB', 'MONO MOVIE', 'MATRIX', 'VIDEO GAME', 'VIRTUAL', 'LEFT', 'RIGHT']}, 'read_cmd': 'MS?', 'write_cmd': 'MS{RAW_VALUE_UPPER}', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'\x00?MS(.*)', 'item_attrs': {'initial': True}}, 'sleep': {'read': True, 'write': True, 'item_type': 'num', 'read_cmd': 'SLP?', 'write_cmd': 'SLP{VALUE}', 'dev_datatype': 'convert0', 'reply_pattern': r'SLP(\d{3}|OFF)', 'cmd_settings': {'force_min': 0, 'force_max': 120}, 'item_attrs': {'initial': True}}, 'standby': {'read': True, 'write': True, 'item_type': 'num', 'read_cmd': 'STBY?', 'write_cmd': 'STBY{VALUE}', 'dev_datatype': 'DenonStandby1', 'reply_pattern': r'STBY(\d{2}M|OFF)', 'cmd_settings': {'valid_list_ci': [0, 15, 30, 60]}, 'item_attrs': {'initial': True}}, }, @@ -421,10 +421,10 @@ } }, 'input': { - 'on_change': [".custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value]",], + 'on_change': [".custom_name = sh.....general.custom_inputnames().get(value, '')"], 'custom_name': { 'type': 'str', - 'on_change': ".. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value]" + 'on_change': "sh...(sh......general.custom_inputnames.reverse().get(value, ''))" } } } diff --git a/denon/datatypes.py b/denon/datatypes.py index 11dbe3490..f56d5a228 100755 --- a/denon/datatypes.py +++ b/denon/datatypes.py @@ -20,6 +20,17 @@ def get_shng_data(self, data, type=None, **kwargs): return None +# read only. Creating dict with custom inputnames +class DT_DenonCustominput(DT.Datatype): + def __init__(self, fail_silent=False): + super().__init__(fail_silent) + self._custom_inputnames = {} + + def get_shng_data(self, data, type=None, **kwargs): + tmp = data.split(' ', 1) + self._custom_inputnames[tmp[0]] = tmp[1] + return self._custom_inputnames + # handle pseudo-decimal values without decimal point class DT_DenonVol(DT.Datatype): def get_send_data(self, data, **kwargs): diff --git a/denon/plugin.yaml b/denon/plugin.yaml index e03f03321..0e79b628d 100755 --- a/denon/plugin.yaml +++ b/denon/plugin.yaml @@ -1,14 +1,18 @@ plugin: type: interface - description: Denon AV-Receiver + + description: + de: Denon AV-Receiver + en: Denon AV-Receiver maintainer: OnkelAndy tester: Morg state: develop keywords: iot device av denon sdp - version: 1.0.1 + version: '1.0.1' sh_minversion: '1.9.5' py_minversion: '3.7' + sdp_minversion: '1.0.3' multi_instance: false restartable: true classname: denon @@ -128,17 +132,6 @@ parameters: de: Verbindungstyp en: connection type - command_class: - type: str - default: SDPCommandParseStr - valid_list: - - SDPCommand - - SDPCommandParseStr - - description: - de: Klasse für Verarbeitung von Kommandos - en: class for command processing - autoreconnect: type: bool default: true @@ -163,6 +156,63 @@ parameters: de: Anzahl Verbindungsversuche en: number of connect retries + send_retries: + type: num + default: 0 + + description: + de: Anzahl Sendeversuche + en: number of sending retries + + description_long: + de: |4 + Anzahl Sendeversuche\n + Kommt keine (passende) Antwort nach dem Senden + eines Commands zurück, wird das Kommando nochmals + gesendet, sofern der Wert über 0 liegt. + en: |4 + number of sending retries\n + If no (suiting) answer is received after sending + a command the command is resent as long as this + value is more than 0. + + sendretry_cycle: + type: num + valid_min: 1 + default: 1 + + description: + de: Pause zwischen Durchgängen von Sendeversuchen + en: wait time between sending retry rounds + + description_long: + de: |4 + Pause zwischen Durchgängen von Sendeversuchen\n + Sind Send Retries aktiv, wird ein Scheduler erstellt, + der im angegebenen Sekundentakt Kommandos erneut sendet, + zu denen keine (passenden) Antworten erhalten wurden. + en: |4 + wait time between sending retry rounds\n + If send retries are active, a scheduler gets added + that resends commands in the given cycle value (in seconds) + where no (suiting) answer got received. + + delay_initial_read: + type: num + default: 0 + + description: + de: Warte nach Verbindungsaufbau mit dem Abfragen von Werten + en: Wait after connection with querying values + + resume_initial_read: + type: bool + defaul: false + + description: + de: Bei resume vom Plugin erstmaliges Lesen erneut durchführen + en: Repeat initial read on resume + connect_cycle: type: num default: 3 @@ -195,7 +245,6 @@ parameters: de: Item-Pfad für das Standby-Item en: item path for standby switch item - item_attributes: denon_command: @@ -226,6 +275,13 @@ item_attributes: de: Konfiguriert ein Intervall in Sekunden für regelmäßiges Lesen en: Configures a interval in seconds for cyclic read actions + denon_readafterwrite: + type: num + + description: + de: Konfiguriert eine Verzögerung, nach der der Lesebefehl im Anschluss an einen Schreibbefehl gesendet werden soll. + en: Configures a delay after that the read command related to a send command will get sent. + denon_read_initial: type: bool @@ -255,8 +311,26 @@ item_attributes: en: The lookup table with the given name will be assigned to the item in dict or list format once on startup. description_long: - de: "Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim\nStart einmalig als dict oder list in das Item geschrieben.\n\n\nDurch Anhängen von \"#\" an den Namen der Tabelle kann die Art\nder Tabelle ausgewählt werden:\n- fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard)\n- rev liefert die Tabelle SmartHomeNG -> Gerät\n- rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben\n- list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu)" - en: "The lookup table with the given name will be assigned to the item\nin dict or list format once on startup.\n\n\nBy appending \"#\" to the tables name the type of table can\nbe selected:\n- fwd returns the table device -> SmartHomeNG (default)\n- rev returns the table SmartHomeNG -> device\n- rci returns the table SmartHomeNG -> device in lower case\n- list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications)" + de: |4- + Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim + Start einmalig als dict oder list in das Item geschrieben. + + Durch Anhängen von "#" an den Namen der Tabelle kann die Art + der Tabelle ausgewählt werden: + - fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard) + - rev liefert die Tabelle SmartHomeNG -> Gerät + - rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben + - list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu) + en: |4- + The lookup table with the given name will be assigned to the item + in dict or list format once on startup. + + By appending "#" to the tables name the type of table can + be selected: + - fwd returns the table device -> SmartHomeNG (default) + - rev returns the table SmartHomeNG -> device + - rci returns the table SmartHomeNG -> device in lower case + - list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications) item_structs: @@ -265,225 +339,183 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: info + denon_read_group_trigger@instance: info fullmodel: type: str - denon_command: info.fullmodel - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.fullmodel + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - info - denon_read_initial: true + denon_read_initial@instance: true model: type: str - denon_command: info.model - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.model + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - info - denon_read_initial: true + denon_read_initial@instance: true serialnumber: type: num - denon_command: info.serialnumber - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.serialnumber + denon_read@instance: true + denon_write@instance: false main: type: str - denon_command: info.main - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.main + denon_read@instance: true + denon_write@instance: false mainfbl: type: num - denon_command: info.mainfbl - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.mainfbl + denon_read@instance: true + denon_write@instance: false dsp1: type: num - denon_command: info.dsp1 - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.dsp1 + denon_read@instance: true + denon_write@instance: false dsp2: type: num - denon_command: info.dsp2 - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.dsp2 + denon_read@instance: true + denon_write@instance: false dsp3: type: num - denon_command: info.dsp3 - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.dsp3 + denon_read@instance: true + denon_write@instance: false dsp4: type: num - denon_command: info.dsp4 - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.dsp4 + denon_read@instance: true + denon_write@instance: false apld: type: num - denon_command: info.apld - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.apld + denon_read@instance: true + denon_write@instance: false vpld: type: num - denon_command: info.vpld - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.vpld + denon_read@instance: true + denon_write@instance: false guidat: type: num - denon_command: info.guidat - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.guidat + denon_read@instance: true + denon_write@instance: false heosversion: type: str - denon_command: info.heosversion - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.heosversion + denon_read@instance: true + denon_write@instance: false heosbuild: type: num - denon_command: info.heosbuild - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.heosbuild + denon_read@instance: true + denon_write@instance: false heosmod: type: num - denon_command: info.heosmod - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.heosmod + denon_read@instance: true + denon_write@instance: false heoscnf: type: str - denon_command: info.heoscnf - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.heoscnf + denon_read@instance: true + denon_write@instance: false heoslanguage: type: str - denon_command: info.heoslanguage - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.heoslanguage + denon_read@instance: true + denon_write@instance: false mac: type: str - denon_command: info.mac - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.mac + denon_read@instance: true + denon_write@instance: false wifimac: type: str - denon_command: info.wifimac - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.wifimac + denon_read@instance: true + denon_write@instance: false btmac: type: str - denon_command: info.btmac - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.btmac + denon_read@instance: true + denon_write@instance: false audyif: type: num - denon_command: info.audyif - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.audyif + denon_read@instance: true + denon_write@instance: false productid: type: num - denon_command: info.productid - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.productid + denon_read@instance: true + denon_write@instance: false packageid: type: num - denon_command: info.packageid - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.packageid + denon_read@instance: true + denon_write@instance: false cmp: type: str - denon_command: info.cmp - denon_read: true - denon_write: false - denon_read_group: - - info + denon_command@instance: info.cmp + denon_read@instance: true + denon_write@instance: false region: type: str - denon_command: info.region - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.region + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - info - denon_read_initial: true + denon_read_initial@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: general + denon_read_group_trigger@instance: general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -495,96 +527,101 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - general display: type: str - denon_command: general.display - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.display + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true allzonestereo: type: bool - denon_command: general.allzonestereo - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.allzonestereo + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - general tuner: @@ -592,86 +629,86 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: tuner + denon_read_group_trigger@instance: tuner title: type: str - denon_command: tuner.title - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: tuner.title + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - tuner - denon_read_initial: true + denon_read_initial@instance: true album: type: str - denon_command: tuner.album - denon_read: true - denon_write: false + denon_command@instance: tuner.album + denon_read@instance: true + denon_write@instance: false artist: type: str - denon_command: tuner.artist - denon_read: true - denon_write: false + denon_command@instance: tuner.artist + denon_read@instance: true + denon_write@instance: false preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner hd: @@ -679,215 +716,211 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: tuner.hd + denon_read_group_trigger@instance: tuner.hd channel: type: num - denon_command: tuner.hd.channel - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.channel + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - tuner.hd - denon_read_initial: true + denon_read_initial@instance: true channelup: type: bool - denon_command: tuner.hd.channelup - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.channelup + denon_read@instance: false + denon_write@instance: true channeldown: type: bool - denon_command: tuner.hd.channeldown - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.channeldown + denon_read@instance: false + denon_write@instance: true multicastchannel: type: num - denon_command: tuner.hd.multicastchannel - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.multicastchannel + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - tuner.hd presetmemory: type: num - denon_command: tuner.hd.presetmemory - denon_read: true - denon_write: true + denon_command@instance: tuner.hd.presetmemory + denon_read@instance: true + denon_write@instance: true preset: type: num - denon_command: tuner.hd.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - tuner.hd presetup: type: bool - denon_command: tuner.hd.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.hd.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.presetdown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.hd.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - tuner - tuner.hd - denon_read_initial: true + denon_read_initial@instance: true zone1: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1 + denon_read_group_trigger@instance: zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.control + denon_read_group_trigger@instance: zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - zone1 - - zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings + denon_read_group_trigger@instance: zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings.sound + denon_read_group_trigger@instance: zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings.sound.channel_level + denon_read_group_trigger@instance: zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -895,10 +928,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -906,10 +939,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -917,10 +950,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -928,10 +961,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -939,10 +972,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -950,10 +983,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -961,10 +994,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -972,10 +1005,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -983,10 +1016,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -994,10 +1027,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1005,10 +1038,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1016,10 +1049,10 @@ item_structs: subwoofer2: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer2 - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer2 + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1030,14 +1063,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings.sound.tone_control + denon_read_group_trigger@instance: zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1045,10 +1078,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1056,22 +1089,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1079,29 +1112,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings.sound.general + denon_read_group_trigger@instance: zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1109,10 +1142,10 @@ item_structs: dynamic_eq: type: bool - denon_command: zone1.settings.sound.general.dynamic_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamic_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1120,10 +1153,10 @@ item_structs: multeq: type: bool - denon_command: zone1.settings.sound.general.multeq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.multeq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1131,10 +1164,10 @@ item_structs: dynamic_vol: type: bool - denon_command: zone1.settings.sound.general.dynamic_vol - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamic_vol + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1142,10 +1175,10 @@ item_structs: speakersetup: type: str - denon_command: zone1.settings.sound.general.speakersetup - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.speakersetup + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1153,10 +1186,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1164,10 +1197,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1175,10 +1208,10 @@ item_structs: dialogtoggle: type: bool - denon_command: zone1.settings.sound.general.dialogtoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogtoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1186,10 +1219,10 @@ item_structs: dialog: type: num - denon_command: zone1.settings.sound.general.dialog - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialog + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1197,22 +1230,22 @@ item_structs: dialogup: type: bool - denon_command: zone1.settings.sound.general.dialogup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogup + denon_read@instance: false + denon_write@instance: true dialogdown: type: bool - denon_command: zone1.settings.sound.general.dialogdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogdown + denon_read@instance: false + denon_write@instance: true dialogenhance: type: num - denon_command: zone1.settings.sound.general.dialogenhance - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogenhance + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1220,10 +1253,10 @@ item_structs: subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1231,10 +1264,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1242,22 +1275,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1265,22 +1298,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true digitalinput: type: str - denon_command: zone1.settings.sound.general.digitalinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.digitalinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1288,10 +1321,10 @@ item_structs: audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -1302,74 +1335,74 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone1.settings.video + denon_read_group_trigger@instance: zone1.settings.video aspectratio: type: str - denon_command: zone1.settings.video.aspectratio - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.aspectratio + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video hdmimonitor: type: num - denon_command: zone1.settings.video.hdmimonitor - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmimonitor + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video hdmiresolution: type: str - denon_command: zone1.settings.video.hdmiresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmiresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video videoprocessingmode: type: str - denon_command: zone1.settings.video.videoprocessingmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoprocessingmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video videoresolution: type: str - denon_command: zone1.settings.video.videoresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video pictureenhancer: type: num - denon_command: zone1.settings.video.pictureenhancer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.pictureenhancer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video videoinput: type: str - denon_command: zone1.settings.video.videoinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone1 - zone1.settings - zone1.settings.video @@ -1379,84 +1412,84 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2 + denon_read_group_trigger@instance: zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.control + denon_read_group_trigger@instance: zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.control @@ -1465,28 +1498,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.settings + denon_read_group_trigger@instance: zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.settings.sound + denon_read_group_trigger@instance: zone2.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.settings.sound.channel_level + denon_read_group_trigger@instance: zone2.settings.sound.channel_level front_left: type: num - denon_command: zone2.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1494,10 +1527,10 @@ item_structs: front_right: type: num - denon_command: zone2.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1508,14 +1541,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.settings.sound.tone_control + denon_read_group_trigger@instance: zone2.settings.sound.tone_control treble: type: num - denon_command: zone2.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1523,22 +1556,22 @@ item_structs: trebleup: type: bool - denon_command: zone2.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone2.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone2.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1546,29 +1579,29 @@ item_structs: bassup: type: bool - denon_command: zone2.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone2.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: zone2.settings.sound.general + denon_read_group_trigger@instance: zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1576,10 +1609,10 @@ item_structs: HPF: type: bool - denon_command: zone2.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1590,114 +1623,114 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3 + denon_read_group_trigger@instance: zone3 control: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.control + denon_read_group_trigger@instance: zone3.control power: type: bool - denon_command: zone3.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control mute: type: bool - denon_command: zone3.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control volume: type: num - denon_command: zone3.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control volumeup: type: bool - denon_command: zone3.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone3.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumedown + denon_read@instance: false + denon_write@instance: true sleep: type: num - denon_command: zone3.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control standby: type: num - denon_command: zone3.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control input: type: str - denon_command: zone3.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) settings: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.settings + denon_read_group_trigger@instance: zone3.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.settings.sound + denon_read_group_trigger@instance: zone3.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.settings.sound.channel_level + denon_read_group_trigger@instance: zone3.settings.sound.channel_level front_left: type: num - denon_command: zone3.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1705,10 +1738,10 @@ item_structs: front_right: type: num - denon_command: zone3.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1719,14 +1752,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.settings.sound.tone_control + denon_read_group_trigger@instance: zone3.settings.sound.tone_control treble: type: num - denon_command: zone3.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1734,22 +1767,22 @@ item_structs: trebleup: type: bool - denon_command: zone3.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone3.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone3.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1757,29 +1790,29 @@ item_structs: bassup: type: bool - denon_command: zone3.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone3.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: zone3.settings.sound.general + denon_read_group_trigger@instance: zone3.settings.sound.general HPF: type: bool - denon_command: zone3.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1790,23 +1823,25 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL + denon_read_group_trigger@instance: ALL general: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.general + denon_read_group_trigger@instance: ALL.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -1818,86 +1853,91 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - ALL - ALL.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.general @@ -1906,68 +1946,68 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.tuner + denon_read_group_trigger@instance: ALL.tuner preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.tuner @@ -1976,148 +2016,143 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1 + denon_read_group_trigger@instance: ALL.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.control + denon_read_group_trigger@instance: ALL.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - ALL - - ALL.zone1 - - ALL.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.settings + denon_read_group_trigger@instance: ALL.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.settings.sound + denon_read_group_trigger@instance: ALL.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: ALL.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2126,10 +2161,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2138,10 +2173,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2150,10 +2185,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2162,10 +2197,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2174,10 +2209,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2186,10 +2221,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2198,10 +2233,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2210,10 +2245,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2222,10 +2257,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2234,10 +2269,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2246,10 +2281,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2261,14 +2296,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: ALL.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2277,10 +2312,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2289,22 +2324,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2313,29 +2348,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone1.settings.sound.general + denon_read_group_trigger@instance: ALL.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2344,10 +2379,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2356,10 +2391,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2368,10 +2403,10 @@ item_structs: subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2380,10 +2415,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2392,22 +2427,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2416,22 +2451,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -2443,89 +2478,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone2 + denon_read_group_trigger@instance: ALL.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone2.control + denon_read_group_trigger@instance: ALL.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control @@ -2535,28 +2570,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone2.settings + denon_read_group_trigger@instance: ALL.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone2.settings.sound + denon_read_group_trigger@instance: ALL.zone2.settings.sound general: read: type: bool enforce_updates: true - denon_read_group_trigger: ALL.zone2.settings.sound.general + denon_read_group_trigger@instance: ALL.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.settings @@ -2568,258 +2603,194 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H + denon_read_group_trigger@instance: AVR-X6300H info: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.info + denon_read_group_trigger@instance: AVR-X6300H.info fullmodel: type: str - denon_command: info.fullmodel - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.fullmodel + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.info - denon_read_initial: true + denon_read_initial@instance: true model: type: str - denon_command: info.model - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.model + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.info - denon_read_initial: true + denon_read_initial@instance: true serialnumber: type: num - denon_command: info.serialnumber - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.serialnumber + denon_read@instance: true + denon_write@instance: false main: type: str - denon_command: info.main - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.main + denon_read@instance: true + denon_write@instance: false mainfbl: type: num - denon_command: info.mainfbl - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.mainfbl + denon_read@instance: true + denon_write@instance: false dsp1: type: num - denon_command: info.dsp1 - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.dsp1 + denon_read@instance: true + denon_write@instance: false dsp2: type: num - denon_command: info.dsp2 - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.dsp2 + denon_read@instance: true + denon_write@instance: false dsp3: type: num - denon_command: info.dsp3 - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.dsp3 + denon_read@instance: true + denon_write@instance: false dsp4: type: num - denon_command: info.dsp4 - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.dsp4 + denon_read@instance: true + denon_write@instance: false apld: type: num - denon_command: info.apld - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.apld + denon_read@instance: true + denon_write@instance: false vpld: type: num - denon_command: info.vpld - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.vpld + denon_read@instance: true + denon_write@instance: false guidat: type: num - denon_command: info.guidat - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.guidat + denon_read@instance: true + denon_write@instance: false heosversion: type: str - denon_command: info.heosversion - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.heosversion + denon_read@instance: true + denon_write@instance: false heosbuild: type: num - denon_command: info.heosbuild - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.heosbuild + denon_read@instance: true + denon_write@instance: false heosmod: type: num - denon_command: info.heosmod - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.heosmod + denon_read@instance: true + denon_write@instance: false heoscnf: type: str - denon_command: info.heoscnf - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.heoscnf + denon_read@instance: true + denon_write@instance: false heoslanguage: type: str - denon_command: info.heoslanguage - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.heoslanguage + denon_read@instance: true + denon_write@instance: false mac: type: str - denon_command: info.mac - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.mac + denon_read@instance: true + denon_write@instance: false wifimac: type: str - denon_command: info.wifimac - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.wifimac + denon_read@instance: true + denon_write@instance: false btmac: type: str - denon_command: info.btmac - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.btmac + denon_read@instance: true + denon_write@instance: false audyif: type: num - denon_command: info.audyif - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.audyif + denon_read@instance: true + denon_write@instance: false productid: type: num - denon_command: info.productid - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.productid + denon_read@instance: true + denon_write@instance: false packageid: type: num - denon_command: info.packageid - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.packageid + denon_read@instance: true + denon_write@instance: false cmp: type: str - denon_command: info.cmp - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.info + denon_command@instance: info.cmp + denon_read@instance: true + denon_write@instance: false region: type: str - denon_command: info.region - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: info.region + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.info - denon_read_initial: true + denon_read_initial@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.general + denon_read_group_trigger@instance: AVR-X6300H.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -2831,86 +2802,91 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.general @@ -2919,68 +2895,68 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.tuner + denon_read_group_trigger@instance: AVR-X6300H.tuner preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner @@ -2989,227 +2965,222 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.tuner.hd + denon_read_group_trigger@instance: AVR-X6300H.tuner.hd channel: type: num - denon_command: tuner.hd.channel - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.channel + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - AVR-X6300H.tuner.hd - denon_read_initial: true + denon_read_initial@instance: true channelup: type: bool - denon_command: tuner.hd.channelup - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.channelup + denon_read@instance: false + denon_write@instance: true channeldown: type: bool - denon_command: tuner.hd.channeldown - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.channeldown + denon_read@instance: false + denon_write@instance: true multicastchannel: type: num - denon_command: tuner.hd.multicastchannel - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.multicastchannel + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - AVR-X6300H.tuner.hd presetmemory: type: num - denon_command: tuner.hd.presetmemory - denon_read: true - denon_write: true + denon_command@instance: tuner.hd.presetmemory + denon_read@instance: true + denon_write@instance: true preset: type: num - denon_command: tuner.hd.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - AVR-X6300H.tuner.hd presetup: type: bool - denon_command: tuner.hd.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.hd.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.hd.presetdown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.hd.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.hd.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.tuner - AVR-X6300H.tuner.hd - denon_read_initial: true + denon_read_initial@instance: true zone1: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1 + denon_read_group_trigger@instance: AVR-X6300H.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.control + denon_read_group_trigger@instance: AVR-X6300H.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - AVR-X6300H - - AVR-X6300H.zone1 - - AVR-X6300H.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings.sound + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3218,10 +3189,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3230,10 +3201,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3242,10 +3213,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3254,10 +3225,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3266,10 +3237,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3278,10 +3249,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3290,10 +3261,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3302,10 +3273,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3314,10 +3285,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3326,10 +3297,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3338,10 +3309,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3350,10 +3321,10 @@ item_structs: subwoofer2: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer2 - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer2 + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3365,14 +3336,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3381,10 +3352,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3393,22 +3364,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3417,29 +3388,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings.sound.general + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3448,10 +3419,10 @@ item_structs: speakersetup: type: str - denon_command: zone1.settings.sound.general.speakersetup - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.speakersetup + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3460,10 +3431,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3472,10 +3443,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3484,10 +3455,10 @@ item_structs: dialogenhance: type: num - denon_command: zone1.settings.sound.general.dialogenhance - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogenhance + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3496,10 +3467,10 @@ item_structs: subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3508,10 +3479,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3520,22 +3491,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3544,22 +3515,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3571,14 +3542,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone1.settings.video + denon_read_group_trigger@instance: AVR-X6300H.zone1.settings.video aspectratio: type: str - denon_command: zone1.settings.video.aspectratio - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.aspectratio + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3586,10 +3557,10 @@ item_structs: hdmimonitor: type: num - denon_command: zone1.settings.video.hdmimonitor - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmimonitor + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3597,10 +3568,10 @@ item_structs: hdmiresolution: type: str - denon_command: zone1.settings.video.hdmiresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmiresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3608,10 +3579,10 @@ item_structs: videoprocessingmode: type: str - denon_command: zone1.settings.video.videoprocessingmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoprocessingmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3619,10 +3590,10 @@ item_structs: videoresolution: type: str - denon_command: zone1.settings.video.videoresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3630,10 +3601,10 @@ item_structs: pictureenhancer: type: num - denon_command: zone1.settings.video.pictureenhancer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.pictureenhancer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3641,10 +3612,10 @@ item_structs: videoinput: type: str - denon_command: zone1.settings.video.videoinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone1 - AVR-X6300H.zone1.settings @@ -3655,89 +3626,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2 + denon_read_group_trigger@instance: AVR-X6300H.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.control + denon_read_group_trigger@instance: AVR-X6300H.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.control @@ -3747,28 +3718,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.settings + denon_read_group_trigger@instance: AVR-X6300H.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.settings.sound + denon_read_group_trigger@instance: AVR-X6300H.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X6300H.zone2.settings.sound.channel_level front_left: type: num - denon_command: zone2.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3777,10 +3748,10 @@ item_structs: front_right: type: num - denon_command: zone2.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3792,14 +3763,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X6300H.zone2.settings.sound.tone_control treble: type: num - denon_command: zone2.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3808,22 +3779,22 @@ item_structs: trebleup: type: bool - denon_command: zone2.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone2.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone2.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3832,29 +3803,29 @@ item_structs: bassup: type: bool - denon_command: zone2.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone2.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone2.settings.sound.general + denon_read_group_trigger@instance: AVR-X6300H.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3863,10 +3834,10 @@ item_structs: HPF: type: bool - denon_command: zone2.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone2 - AVR-X6300H.zone2.settings @@ -3878,120 +3849,120 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3 + denon_read_group_trigger@instance: AVR-X6300H.zone3 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.control + denon_read_group_trigger@instance: AVR-X6300H.zone3.control power: type: bool - denon_command: zone3.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control mute: type: bool - denon_command: zone3.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control volume: type: num - denon_command: zone3.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control volumeup: type: bool - denon_command: zone3.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone3.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumedown + denon_read@instance: false + denon_write@instance: true sleep: type: num - denon_command: zone3.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control standby: type: num - denon_command: zone3.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control input: type: str - denon_command: zone3.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.settings + denon_read_group_trigger@instance: AVR-X6300H.zone3.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.settings.sound + denon_read_group_trigger@instance: AVR-X6300H.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X6300H.zone3.settings.sound.channel_level front_left: type: num - denon_command: zone3.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.settings @@ -4000,10 +3971,10 @@ item_structs: front_right: type: num - denon_command: zone3.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.settings @@ -4015,14 +3986,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X6300H.zone3.settings.sound.tone_control treble: type: num - denon_command: zone3.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.settings @@ -4031,22 +4002,22 @@ item_structs: trebleup: type: bool - denon_command: zone3.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone3.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone3.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.settings @@ -4055,29 +4026,29 @@ item_structs: bassup: type: bool - denon_command: zone3.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone3.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X6300H.zone3.settings.sound.general + denon_read_group_trigger@instance: AVR-X6300H.zone3.settings.sound.general HPF: type: bool - denon_command: zone3.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X6300H - AVR-X6300H.zone3 - AVR-X6300H.zone3.settings @@ -4089,23 +4060,25 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H + denon_read_group_trigger@instance: AVR-X4300H general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.general + denon_read_group_trigger@instance: AVR-X4300H.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -4117,86 +4090,91 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.general @@ -4205,68 +4183,68 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.tuner + denon_read_group_trigger@instance: AVR-X4300H.tuner preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.tuner @@ -4275,148 +4253,143 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1 + denon_read_group_trigger@instance: AVR-X4300H.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.control + denon_read_group_trigger@instance: AVR-X4300H.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - AVR-X4300H - - AVR-X4300H.zone1 - - AVR-X4300H.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings.sound + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4425,10 +4398,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4437,10 +4410,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4449,10 +4422,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4461,10 +4434,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4473,10 +4446,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4485,10 +4458,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4497,10 +4470,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4509,10 +4482,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4521,10 +4494,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4533,10 +4506,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4545,10 +4518,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4557,10 +4530,10 @@ item_structs: subwoofer2: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer2 - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer2 + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4572,14 +4545,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4588,10 +4561,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4600,22 +4573,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4624,29 +4597,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings.sound.general + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4655,10 +4628,10 @@ item_structs: speakersetup: type: str - denon_command: zone1.settings.sound.general.speakersetup - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.speakersetup + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4667,10 +4640,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4679,10 +4652,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4691,10 +4664,10 @@ item_structs: dialogtoggle: type: bool - denon_command: zone1.settings.sound.general.dialogtoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogtoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4703,10 +4676,10 @@ item_structs: dialog: type: num - denon_command: zone1.settings.sound.general.dialog - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialog + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4715,22 +4688,22 @@ item_structs: dialogup: type: bool - denon_command: zone1.settings.sound.general.dialogup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogup + denon_read@instance: false + denon_write@instance: true dialogdown: type: bool - denon_command: zone1.settings.sound.general.dialogdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogdown + denon_read@instance: false + denon_write@instance: true subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4739,10 +4712,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4751,22 +4724,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4775,22 +4748,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4802,14 +4775,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone1.settings.video + denon_read_group_trigger@instance: AVR-X4300H.zone1.settings.video aspectratio: type: str - denon_command: zone1.settings.video.aspectratio - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.aspectratio + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4817,10 +4790,10 @@ item_structs: hdmimonitor: type: num - denon_command: zone1.settings.video.hdmimonitor - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmimonitor + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4828,10 +4801,10 @@ item_structs: hdmiresolution: type: str - denon_command: zone1.settings.video.hdmiresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmiresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4839,10 +4812,10 @@ item_structs: videoprocessingmode: type: str - denon_command: zone1.settings.video.videoprocessingmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoprocessingmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4850,10 +4823,10 @@ item_structs: videoresolution: type: str - denon_command: zone1.settings.video.videoresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4861,10 +4834,10 @@ item_structs: pictureenhancer: type: num - denon_command: zone1.settings.video.pictureenhancer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.pictureenhancer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4872,10 +4845,10 @@ item_structs: videoinput: type: str - denon_command: zone1.settings.video.videoinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone1 - AVR-X4300H.zone1.settings @@ -4886,89 +4859,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2 + denon_read_group_trigger@instance: AVR-X4300H.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.control + denon_read_group_trigger@instance: AVR-X4300H.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.control @@ -4978,28 +4951,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.settings + denon_read_group_trigger@instance: AVR-X4300H.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.settings.sound + denon_read_group_trigger@instance: AVR-X4300H.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X4300H.zone2.settings.sound.channel_level front_left: type: num - denon_command: zone2.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5008,10 +4981,10 @@ item_structs: front_right: type: num - denon_command: zone2.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5023,14 +4996,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X4300H.zone2.settings.sound.tone_control treble: type: num - denon_command: zone2.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5039,22 +5012,22 @@ item_structs: trebleup: type: bool - denon_command: zone2.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone2.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone2.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5063,29 +5036,29 @@ item_structs: bassup: type: bool - denon_command: zone2.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone2.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone2.settings.sound.general + denon_read_group_trigger@instance: AVR-X4300H.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5094,10 +5067,10 @@ item_structs: HPF: type: bool - denon_command: zone2.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone2 - AVR-X4300H.zone2.settings @@ -5109,120 +5082,120 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3 + denon_read_group_trigger@instance: AVR-X4300H.zone3 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.control + denon_read_group_trigger@instance: AVR-X4300H.zone3.control power: type: bool - denon_command: zone3.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control mute: type: bool - denon_command: zone3.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control volume: type: num - denon_command: zone3.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control volumeup: type: bool - denon_command: zone3.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone3.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone3.control.volumedown + denon_read@instance: false + denon_write@instance: true sleep: type: num - denon_command: zone3.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control standby: type: num - denon_command: zone3.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control input: type: str - denon_command: zone3.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.settings + denon_read_group_trigger@instance: AVR-X4300H.zone3.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.settings.sound + denon_read_group_trigger@instance: AVR-X4300H.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X4300H.zone3.settings.sound.channel_level front_left: type: num - denon_command: zone3.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.settings @@ -5231,10 +5204,10 @@ item_structs: front_right: type: num - denon_command: zone3.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.settings @@ -5246,14 +5219,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X4300H.zone3.settings.sound.tone_control treble: type: num - denon_command: zone3.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.settings @@ -5262,22 +5235,22 @@ item_structs: trebleup: type: bool - denon_command: zone3.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone3.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone3.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.settings @@ -5286,29 +5259,29 @@ item_structs: bassup: type: bool - denon_command: zone3.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone3.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone3.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X4300H.zone3.settings.sound.general + denon_read_group_trigger@instance: AVR-X4300H.zone3.settings.sound.general HPF: type: bool - denon_command: zone3.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone3.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X4300H - AVR-X4300H.zone3 - AVR-X4300H.zone3.settings @@ -5320,23 +5293,25 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W + denon_read_group_trigger@instance: AVR-X3300W general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.general + denon_read_group_trigger@instance: AVR-X3300W.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -5348,95 +5323,100 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general display: type: str - denon_command: general.display - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.display + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.general @@ -5445,90 +5425,90 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.tuner + denon_read_group_trigger@instance: AVR-X3300W.tuner title: type: str - denon_command: tuner.title - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: tuner.title + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.tuner - denon_read_initial: true + denon_read_initial@instance: true album: type: str - denon_command: tuner.album - denon_read: true - denon_write: false + denon_command@instance: tuner.album + denon_read@instance: true + denon_write@instance: false artist: type: str - denon_command: tuner.artist - denon_read: true - denon_write: false + denon_command@instance: tuner.artist + denon_read@instance: true + denon_write@instance: false preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.tuner @@ -5537,148 +5517,143 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1 + denon_read_group_trigger@instance: AVR-X3300W.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.control + denon_read_group_trigger@instance: AVR-X3300W.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - AVR-X3300W - - AVR-X3300W.zone1 - - AVR-X3300W.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings.sound + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5687,10 +5662,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5699,10 +5674,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5711,10 +5686,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5723,10 +5698,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5735,10 +5710,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5747,10 +5722,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5759,10 +5734,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5771,10 +5746,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5783,10 +5758,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5795,10 +5770,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5807,10 +5782,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5819,10 +5794,10 @@ item_structs: subwoofer2: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer2 - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer2 + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5834,14 +5809,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5850,10 +5825,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5862,22 +5837,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5886,29 +5861,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings.sound.general + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5917,10 +5892,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5929,10 +5904,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5941,10 +5916,10 @@ item_structs: dialogtoggle: type: bool - denon_command: zone1.settings.sound.general.dialogtoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogtoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5953,10 +5928,10 @@ item_structs: dialog: type: num - denon_command: zone1.settings.sound.general.dialog - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialog + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5965,22 +5940,22 @@ item_structs: dialogup: type: bool - denon_command: zone1.settings.sound.general.dialogup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogup + denon_read@instance: false + denon_write@instance: true dialogdown: type: bool - denon_command: zone1.settings.sound.general.dialogdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogdown + denon_read@instance: false + denon_write@instance: true subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -5989,10 +5964,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6001,22 +5976,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6025,22 +6000,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6052,14 +6027,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone1.settings.video + denon_read_group_trigger@instance: AVR-X3300W.zone1.settings.video aspectratio: type: str - denon_command: zone1.settings.video.aspectratio - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.aspectratio + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6067,10 +6042,10 @@ item_structs: hdmiresolution: type: str - denon_command: zone1.settings.video.hdmiresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmiresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6078,10 +6053,10 @@ item_structs: videoprocessingmode: type: str - denon_command: zone1.settings.video.videoprocessingmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoprocessingmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6089,10 +6064,10 @@ item_structs: videoresolution: type: str - denon_command: zone1.settings.video.videoresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6100,10 +6075,10 @@ item_structs: pictureenhancer: type: num - denon_command: zone1.settings.video.pictureenhancer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.pictureenhancer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6111,10 +6086,10 @@ item_structs: videoinput: type: str - denon_command: zone1.settings.video.videoinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone1 - AVR-X3300W.zone1.settings @@ -6125,89 +6100,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2 + denon_read_group_trigger@instance: AVR-X3300W.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.control + denon_read_group_trigger@instance: AVR-X3300W.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.control @@ -6217,28 +6192,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.settings + denon_read_group_trigger@instance: AVR-X3300W.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.settings.sound + denon_read_group_trigger@instance: AVR-X3300W.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X3300W.zone2.settings.sound.channel_level front_left: type: num - denon_command: zone2.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6247,10 +6222,10 @@ item_structs: front_right: type: num - denon_command: zone2.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6262,14 +6237,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X3300W.zone2.settings.sound.tone_control treble: type: num - denon_command: zone2.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6278,22 +6253,22 @@ item_structs: trebleup: type: bool - denon_command: zone2.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone2.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone2.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6302,29 +6277,29 @@ item_structs: bassup: type: bool - denon_command: zone2.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone2.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone2.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X3300W.zone2.settings.sound.general + denon_read_group_trigger@instance: AVR-X3300W.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6333,10 +6308,10 @@ item_structs: HPF: type: bool - denon_command: zone2.settings.sound.general.HPF - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.HPF + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X3300W - AVR-X3300W.zone2 - AVR-X3300W.zone2.settings @@ -6348,23 +6323,25 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W + denon_read_group_trigger@instance: AVR-X2300W general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.general + denon_read_group_trigger@instance: AVR-X2300W.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -6376,95 +6353,100 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general display: type: str - denon_command: general.display - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.display + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.general @@ -6473,90 +6455,90 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.tuner + denon_read_group_trigger@instance: AVR-X2300W.tuner title: type: str - denon_command: tuner.title - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: tuner.title + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.tuner - denon_read_initial: true + denon_read_initial@instance: true album: type: str - denon_command: tuner.album - denon_read: true - denon_write: false + denon_command@instance: tuner.album + denon_read@instance: true + denon_write@instance: false artist: type: str - denon_command: tuner.artist - denon_read: true - denon_write: false + denon_command@instance: tuner.artist + denon_read@instance: true + denon_write@instance: false preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.tuner @@ -6565,148 +6547,143 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1 + denon_read_group_trigger@instance: AVR-X2300W.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.control + denon_read_group_trigger@instance: AVR-X2300W.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - AVR-X2300W - - AVR-X2300W.zone1 - - AVR-X2300W.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings.sound + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6715,10 +6692,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6727,10 +6704,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6739,10 +6716,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6751,10 +6728,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6763,10 +6740,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6775,10 +6752,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6787,10 +6764,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6799,10 +6776,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6811,10 +6788,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6823,10 +6800,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6835,10 +6812,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6850,14 +6827,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6866,10 +6843,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6878,22 +6855,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6902,29 +6879,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings.sound.general + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6933,10 +6910,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6945,10 +6922,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6957,10 +6934,10 @@ item_structs: dialogtoggle: type: bool - denon_command: zone1.settings.sound.general.dialogtoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogtoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6969,10 +6946,10 @@ item_structs: dialog: type: num - denon_command: zone1.settings.sound.general.dialog - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialog + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -6981,22 +6958,22 @@ item_structs: dialogup: type: bool - denon_command: zone1.settings.sound.general.dialogup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogup + denon_read@instance: false + denon_write@instance: true dialogdown: type: bool - denon_command: zone1.settings.sound.general.dialogdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogdown + denon_read@instance: false + denon_write@instance: true subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7005,10 +6982,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7017,22 +6994,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7041,22 +7018,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7068,14 +7045,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone1.settings.video + denon_read_group_trigger@instance: AVR-X2300W.zone1.settings.video aspectratio: type: str - denon_command: zone1.settings.video.aspectratio - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.aspectratio + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7083,10 +7060,10 @@ item_structs: hdmimonitor: type: num - denon_command: zone1.settings.video.hdmimonitor - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmimonitor + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7094,10 +7071,10 @@ item_structs: hdmiresolution: type: str - denon_command: zone1.settings.video.hdmiresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.hdmiresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7105,10 +7082,10 @@ item_structs: videoprocessingmode: type: str - denon_command: zone1.settings.video.videoprocessingmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoprocessingmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7116,10 +7093,10 @@ item_structs: videoresolution: type: str - denon_command: zone1.settings.video.videoresolution - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoresolution + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7127,10 +7104,10 @@ item_structs: pictureenhancer: type: num - denon_command: zone1.settings.video.pictureenhancer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.pictureenhancer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7138,10 +7115,10 @@ item_structs: videoinput: type: str - denon_command: zone1.settings.video.videoinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.video.videoinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone1 - AVR-X2300W.zone1.settings @@ -7152,89 +7129,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2 + denon_read_group_trigger@instance: AVR-X2300W.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2.control + denon_read_group_trigger@instance: AVR-X2300W.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.control @@ -7244,28 +7221,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2.settings + denon_read_group_trigger@instance: AVR-X2300W.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2.settings.sound + denon_read_group_trigger@instance: AVR-X2300W.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X2300W.zone2.settings.sound.channel_level front_left: type: num - denon_command: zone2.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.settings @@ -7274,10 +7251,10 @@ item_structs: front_right: type: num - denon_command: zone2.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.settings @@ -7289,14 +7266,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X2300W.zone2.settings.sound.general + denon_read_group_trigger@instance: AVR-X2300W.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X2300W - AVR-X2300W.zone2 - AVR-X2300W.zone2.settings @@ -7308,23 +7285,25 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W + denon_read_group_trigger@instance: AVR-X1300W general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.general + denon_read_group_trigger@instance: AVR-X1300W.general custom_inputnames: type: dict - denon_command: general.custom_inputnames - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.custom_inputnames + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general + - custom_inputnames + denon_read_initial@instance: true cache: true reverse: @@ -7336,95 +7315,100 @@ item_structs: eval: sh...timer(2, {}) eval_trigger: '...' + update: + type: bool + enforce_updates: 'true' + denon_read_group_trigger: custom_inputnames + power: type: bool - denon_command: general.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general setupmenu: type: bool - denon_command: general.setupmenu - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.setupmenu + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general display: type: str - denon_command: general.display - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.display + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general soundmode: type: str - denon_command: general.soundmode - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.soundmode + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general - denon_read_initial: true + denon_read_initial@instance: true inputsignal: type: str - denon_command: general.inputsignal - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputsignal + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general - denon_read_initial: true + denon_read_initial@instance: true inputrate: type: num - denon_command: general.inputrate - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputrate + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general - denon_read_initial: true + denon_read_initial@instance: true inputformat: type: str - denon_command: general.inputformat - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputformat + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general - denon_read_initial: true + denon_read_initial@instance: true inputresolution: type: str - denon_command: general.inputresolution - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: general.inputresolution + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general - denon_read_initial: true + denon_read_initial@instance: true outputresolution: type: str - denon_command: general.outputresolution - denon_read: true - denon_write: false + denon_command@instance: general.outputresolution + denon_read@instance: true + denon_write@instance: false ecomode: type: str - denon_command: general.ecomode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: general.ecomode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.general @@ -7433,90 +7417,90 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.tuner + denon_read_group_trigger@instance: AVR-X1300W.tuner title: type: str - denon_command: tuner.title - denon_read: true - denon_write: false - denon_read_group: + denon_command@instance: tuner.title + denon_read@instance: true + denon_write@instance: false + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.tuner - denon_read_initial: true + denon_read_initial@instance: true album: type: str - denon_command: tuner.album - denon_read: true - denon_write: false + denon_command@instance: tuner.album + denon_read@instance: true + denon_write@instance: false artist: type: str - denon_command: tuner.artist - denon_read: true - denon_write: false + denon_command@instance: tuner.artist + denon_read@instance: true + denon_write@instance: false preset: type: num - denon_command: tuner.preset - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.preset + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.tuner - denon_read_initial: true + denon_read_initial@instance: true presetup: type: bool - denon_command: tuner.presetup - denon_read: false - denon_write: true + denon_command@instance: tuner.presetup + denon_read@instance: false + denon_write@instance: true presetdown: type: bool - denon_command: tuner.presetdown - denon_read: false - denon_write: true + denon_command@instance: tuner.presetdown + denon_read@instance: false + denon_write@instance: true frequency: type: num - denon_command: tuner.frequency - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.frequency + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.tuner - denon_read_initial: true + denon_read_initial@instance: true frequencyup: type: bool - denon_command: tuner.frequencyup - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencyup + denon_read@instance: false + denon_write@instance: true frequencydown: type: bool - denon_command: tuner.frequencydown - denon_read: false - denon_write: true + denon_command@instance: tuner.frequencydown + denon_read@instance: false + denon_write@instance: true band: type: str - denon_command: tuner.band - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.band + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.tuner - denon_read_initial: true + denon_read_initial@instance: true tuningmode: type: str - denon_command: tuner.tuningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: tuner.tuningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.tuner @@ -7525,148 +7509,143 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1 + denon_read_group_trigger@instance: AVR-X1300W.zone1 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.control + denon_read_group_trigger@instance: AVR-X1300W.zone1.control power: type: bool - denon_command: zone1.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true mute: type: bool - denon_command: zone1.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volume: type: num - denon_command: zone1.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true volumeup: type: bool - denon_command: zone1.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone1.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone1.control.volumedown + denon_read@instance: false + denon_write@instance: true volumemax: type: num - denon_command: zone1.control.volumemax - denon_read: true - denon_write: false - denon_read_group: - - AVR-X1300W - - AVR-X1300W.zone1 - - AVR-X1300W.zone1.control - denon_read_initial: true + denon_command@instance: zone1.control.volumemax + denon_read@instance: true + denon_write@instance: false input: type: str - denon_command: zone1.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) listeningmode: type: str - denon_command: zone1.control.listeningmode - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.listeningmode + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true sleep: type: num - denon_command: zone1.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true standby: type: num - denon_command: zone1.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.control - denon_read_initial: true + denon_read_initial@instance: true settings: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.settings + denon_read_group_trigger@instance: AVR-X1300W.zone1.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.settings.sound + denon_read_group_trigger@instance: AVR-X1300W.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.settings.sound.channel_level + denon_read_group_trigger@instance: AVR-X1300W.zone1.settings.sound.channel_level front_left: type: num - denon_command: zone1.settings.sound.channel_level.front_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7675,10 +7654,10 @@ item_structs: front_right: type: num - denon_command: zone1.settings.sound.channel_level.front_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7687,10 +7666,10 @@ item_structs: front_height_left: type: num - denon_command: zone1.settings.sound.channel_level.front_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7699,10 +7678,10 @@ item_structs: front_height_right: type: num - denon_command: zone1.settings.sound.channel_level.front_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7711,10 +7690,10 @@ item_structs: front_center: type: num - denon_command: zone1.settings.sound.channel_level.front_center - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.front_center + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7723,10 +7702,10 @@ item_structs: surround_left: type: num - denon_command: zone1.settings.sound.channel_level.surround_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7735,10 +7714,10 @@ item_structs: surround_right: type: num - denon_command: zone1.settings.sound.channel_level.surround_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surround_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7747,10 +7726,10 @@ item_structs: surroundback_left: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7759,10 +7738,10 @@ item_structs: surroundback_right: type: num - denon_command: zone1.settings.sound.channel_level.surroundback_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.surroundback_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7771,10 +7750,10 @@ item_structs: rear_height_left: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_left - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_left + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7783,10 +7762,10 @@ item_structs: rear_height_right: type: num - denon_command: zone1.settings.sound.channel_level.rear_height_right - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.rear_height_right + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7795,10 +7774,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.channel_level.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.channel_level.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7810,14 +7789,14 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.settings.sound.tone_control + denon_read_group_trigger@instance: AVR-X1300W.zone1.settings.sound.tone_control tone: type: bool - denon_command: zone1.settings.sound.tone_control.tone - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.tone + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7826,10 +7805,10 @@ item_structs: treble: type: num - denon_command: zone1.settings.sound.tone_control.treble - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.treble + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7838,22 +7817,22 @@ item_structs: trebleup: type: bool - denon_command: zone1.settings.sound.tone_control.trebleup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebleup + denon_read@instance: false + denon_write@instance: true trebledown: type: bool - denon_command: zone1.settings.sound.tone_control.trebledown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.trebledown + denon_read@instance: false + denon_write@instance: true bass: type: num - denon_command: zone1.settings.sound.tone_control.bass - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.tone_control.bass + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7862,29 +7841,29 @@ item_structs: bassup: type: bool - denon_command: zone1.settings.sound.tone_control.bassup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassup + denon_read@instance: false + denon_write@instance: true bassdown: type: bool - denon_command: zone1.settings.sound.tone_control.bassdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.tone_control.bassdown + denon_read@instance: false + denon_write@instance: true general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone1.settings.sound.general + denon_read_group_trigger@instance: AVR-X1300W.zone1.settings.sound.general cinema_eq: type: bool - denon_command: zone1.settings.sound.general.cinema_eq - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.cinema_eq + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7893,10 +7872,10 @@ item_structs: hdmiaudioout: type: str - denon_command: zone1.settings.sound.general.hdmiaudioout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.hdmiaudioout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7905,10 +7884,10 @@ item_structs: dynamicrange: type: num - denon_command: zone1.settings.sound.general.dynamicrange - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dynamicrange + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7917,10 +7896,10 @@ item_structs: dialogtoggle: type: bool - denon_command: zone1.settings.sound.general.dialogtoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialogtoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7929,10 +7908,10 @@ item_structs: dialog: type: num - denon_command: zone1.settings.sound.general.dialog - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.dialog + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7941,22 +7920,22 @@ item_structs: dialogup: type: bool - denon_command: zone1.settings.sound.general.dialogup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogup + denon_read@instance: false + denon_write@instance: true dialogdown: type: bool - denon_command: zone1.settings.sound.general.dialogdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.dialogdown + denon_read@instance: false + denon_write@instance: true subwoofertoggle: type: bool - denon_command: zone1.settings.sound.general.subwoofertoggle - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofertoggle + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7965,10 +7944,10 @@ item_structs: subwoofer: type: num - denon_command: zone1.settings.sound.general.subwoofer - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.subwoofer + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -7977,22 +7956,22 @@ item_structs: subwooferup: type: bool - denon_command: zone1.settings.sound.general.subwooferup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferup + denon_read@instance: false + denon_write@instance: true subwooferdown: type: bool - denon_command: zone1.settings.sound.general.subwooferdown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.subwooferdown + denon_read@instance: false + denon_write@instance: true lfe: type: num - denon_command: zone1.settings.sound.general.lfe - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.lfe + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -8001,22 +7980,22 @@ item_structs: lfeup: type: bool - denon_command: zone1.settings.sound.general.lfeup - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfeup + denon_read@instance: false + denon_write@instance: true lfedown: type: bool - denon_command: zone1.settings.sound.general.lfedown - denon_read: false - denon_write: true + denon_command@instance: zone1.settings.sound.general.lfedown + denon_read@instance: false + denon_write@instance: true audioinput: type: str - denon_command: zone1.settings.sound.general.audioinput - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone1.settings.sound.general.audioinput + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone1 - AVR-X1300W.zone1.settings @@ -8028,89 +8007,89 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone2 + denon_read_group_trigger@instance: AVR-X1300W.zone2 control: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone2.control + denon_read_group_trigger@instance: AVR-X1300W.zone2.control power: type: bool - denon_command: zone2.control.power - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.power + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control mute: type: bool - denon_command: zone2.control.mute - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.mute + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control volume: type: num - denon_command: zone2.control.volume - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.volume + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control volumeup: type: bool - denon_command: zone2.control.volumeup - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumeup + denon_read@instance: false + denon_write@instance: true volumedown: type: bool - denon_command: zone2.control.volumedown - denon_read: false - denon_write: true + denon_command@instance: zone2.control.volumedown + denon_read@instance: false + denon_write@instance: true input: type: str - denon_command: zone2.control.input - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.input + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control on_change: - - .custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[value] + - .custom_name = sh.....general.custom_inputnames().get(value, '') custom_name: type: str - on_change: .. = '' if sh......general.custom_inputnames.reverse() == {} else sh......general.custom_inputnames.reverse()[value] + on_change: sh...(sh......general.custom_inputnames.reverse().get(value, '')) sleep: type: num - denon_command: zone2.control.sleep - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.sleep + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control standby: type: num - denon_command: zone2.control.standby - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.control.standby + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.control @@ -8120,28 +8099,28 @@ item_structs: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone2.settings + denon_read_group_trigger@instance: AVR-X1300W.zone2.settings sound: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone2.settings.sound + denon_read_group_trigger@instance: AVR-X1300W.zone2.settings.sound general: read: type: bool enforce_updates: true - denon_read_group_trigger: AVR-X1300W.zone2.settings.sound.general + denon_read_group_trigger@instance: AVR-X1300W.zone2.settings.sound.general hdmiout: type: str - denon_command: zone2.settings.sound.general.hdmiout - denon_read: true - denon_write: true - denon_read_group: + denon_command@instance: zone2.settings.sound.general.hdmiout + denon_read@instance: true + denon_write@instance: true + denon_read_group@instance: - AVR-X1300W - AVR-X1300W.zone2 - AVR-X1300W.zone2.settings diff --git a/husky2/__init__.py b/husky2/__init__.py index 5b5330b69..007273101 100755 --- a/husky2/__init__.py +++ b/husky2/__init__.py @@ -71,7 +71,7 @@ class properties and methods (class variables and class functions) are already available! """ - PLUGIN_VERSION = '2.1.1' + PLUGIN_VERSION = '2.1.2' ITEM_INFO = "husky_info" ITEM_CONTROL = "husky_control" @@ -440,7 +440,7 @@ def update_item(self, item, caller=None, source=None, dest=None): :param dest: if given it represents the dest """ - if self.alive and caller != self.get_shortname(): + if self.alive and not self.get_fullname() in caller: # code to execute if the plugin is not stopped # and only, if the item has not been changed by this this plugin: item_value = "{0}".format(item()) @@ -459,7 +459,7 @@ def update_item(self, item, caller=None, source=None, dest=None): # if control item is a bool type reset it to False after sending cmd if item.property.type == 'bool': - item(0, self.get_shortname()) + item(0, self.get_fullname()) def sendCmd(self, cmd, value=-1): self.writeToStatusItem(self.translate("Sending command") + ": " + cmd) @@ -469,7 +469,7 @@ def writeToStatusItem(self, txt): self.message = txt if 'message' in self._items_state: for item in self._items_state['message']: - item(txt, self.get_shortname()) + item(txt, self.get_fullname()) def poll_device(self): self.logger.debug("Poll new status") @@ -517,19 +517,19 @@ def data_callback(self, status): data['attributes']['positions'][i]['longitude']]) if 'gpspoints' in self._items_state: for item in self._items_state['gpspoints']: - item(self.mowerGpspoints.get_list()[::-1], self.get_shortname()) + item(self.mowerGpspoints.get_list()[::-1], self.get_fullname()) self.mowerLongitude.push(data['attributes']['positions'][0]['longitude']) if 'longitude' in self._items_state: for item in self._items_state['longitude']: - item(self.mowerLongitude.get_last(), self.get_shortname()) + item(self.mowerLongitude.get_last(), self.get_fullname()) self.mowerLatitude.push(data['attributes']['positions'][0]['latitude']) if 'latitude' in self._items_state: for item in self._items_state['latitude']: - item(self.mowerLatitude.get_last(), self.get_shortname()) + item(self.mowerLatitude.get_last(), self.get_fullname()) self.mowerBatterypercent.push(data['attributes']['battery']['batteryPercent']) if 'batterypercent' in self._items_state: for item in self._items_state['batterypercent']: - item(self.mowerBatterypercent.get_last(), self.get_shortname()) + item(self.mowerBatterypercent.get_last(), self.get_fullname()) errorcode = data['attributes']['mower']['errorCode'] if errorcode in self.MOWERERROR: self.mowerErrormsg.push(self.translate(self.MOWERERROR[errorcode]['msg'])) @@ -537,39 +537,39 @@ def data_callback(self, status): self.mowerErrormsg.push(self.translate('Unknown error code') + ": " + str(errorcode)) if 'errormessage' in self._items_state: for item in self._items_state['errormessage']: - item(self.mowerErrormsg.get_last(), self.get_shortname()) + item(self.mowerErrormsg.get_last(), self.get_fullname()) self.mowerActivity.push(data['attributes']['mower']['activity']) if 'activity' in self._items_state: for item in self._items_state['activity']: - item(self.translate(self.mowerActivity.get_last()), self.get_shortname()) + item(self.translate(self.mowerActivity.get_last()), self.get_fullname()) if 'state' in self._items_state: for item in self._items_state['state']: - item(self.translate(data['attributes']['mower']['state']), self.get_shortname()) + item(self.translate(data['attributes']['mower']['state']), self.get_fullname()) if 'mode' in self._items_state: for item in self._items_state['mode']: - item(self.translate(data['attributes']['mower']['mode']), self.get_shortname()) + item(self.translate(data['attributes']['mower']['mode']), self.get_fullname()) self.mowerConnection.push(data['attributes']['metadata']['connected']) if 'connection' in self._items_state: for item in self._items_state['connection']: - item(self.mowerConnection.get_last(), self.get_shortname()) + item(self.mowerConnection.get_last(), self.get_fullname()) # settings if 'cuttingHeight' in data['attributes']: if 'cuttingheight' in self._items_control: for item in self._items_control['cuttingheight']: - item(data['attributes']['cuttingHeight'], self.get_shortname()) + item(data['attributes']['cuttingHeight'], self.get_fullname()) else: if 'cuttingheight' in self._items_control: for item in self._items_control['cuttingheight']: - item(data['attributes']['settings']['cuttingHeight'], self.get_shortname()) + item(data['attributes']['settings']['cuttingHeight'], self.get_fullname()) if 'headlight' in data['attributes']: if 'headlight' in self._items_control: for item in self._items_control['headlight']: - item(data['attributes']['headlight']['mode'], self.get_shortname()) + item(data['attributes']['headlight']['mode'], self.get_fullname()) else: if 'headlight' in self._items_control: for item in self._items_control['headlight']: - item(data['attributes']['settings']['headlight']['mode'], self.get_shortname()) + item(data['attributes']['settings']['headlight']['mode'], self.get_fullname()) def token_callback(self, token): """ @@ -622,16 +622,16 @@ async def initWorker(self): if 'name' in self._items_info: for item in self._items_info['name']: - item(self.mowerName, self.get_shortname()) + item(self.mowerName, self.get_fullname()) if 'id' in self._items_info: for item in self._items_info['id']: - item(self.mowerId, self.get_shortname()) + item(self.mowerId, self.get_fullname()) if 'serial' in self._items_info: for item in self._items_info['serial']: - item(self.mowerSerial, self.get_shortname()) + item(self.mowerSerial, self.get_fullname()) if 'model' in self._items_info: for item in self._items_info['model']: - item(self.mowerModel, self.get_shortname()) + item(self.mowerModel, self.get_fullname()) self.data_callback(status_all) return @@ -801,7 +801,7 @@ def init_webinterface(self): # Register the web interface as a cherrypy app self.mod_http.register_webif(WebInterface(webif_dir, self), - self.get_shortname(), + self.get_fullname(), config, self.get_classname(), self.get_instance_name(), description='') diff --git a/kodi/__init__.py b/kodi/__init__.py index 70a3489b9..a5f3159d5 100644 --- a/kodi/__init__.py +++ b/kodi/__init__.py @@ -40,7 +40,9 @@ class SmartPluginWebIf(): else: builtins.SDP_standalone = False -from lib.model.sdp.globals import JSON_MOVE_KEYS +from lib.model.sdp.globals import (JSON_MOVE_KEYS, PLUGIN_ATTR_CMD_CLASS, PLUGIN_ATTR_PROTOCOL, + PROTO_JSONRPC, PLUGIN_ATTR_CONNECTION, CONN_NET_TCP_CLI) + from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone # from .webif import WebInterface @@ -81,11 +83,16 @@ class kodi(SmartDevicePlugin): another place, in ``commands.py`` and/or the item configuration. """ - PLUGIN_VERSION = '1.7.2' + PLUGIN_VERSION = '1.7.3' def _set_device_defaults(self): self._use_callbacks = True - self._parameters[JSON_MOVE_KEYS] = ['playerid', 'properties'] + self._parameters.update({ + JSON_MOVE_KEYS: ['playerid', 'properties'], + PLUGIN_ATTR_CONNECTION: CONN_NET_TCP_CLI, + PLUGIN_ATTR_PROTOCOL: PROTO_JSONRPC, + PLUGIN_ATTR_CMD_CLASS: 'SDPCommandJSON' + }) def _post_init(self): self._activeplayers = [] diff --git a/kodi/plugin.yaml b/kodi/plugin.yaml index 48bcc26e5..af8eaa34f 100644 --- a/kodi/plugin.yaml +++ b/kodi/plugin.yaml @@ -6,9 +6,10 @@ plugin: tester: OnkelAndy state: develop keywords: iot device mediacenter kodi xmbc sdp - version: 1.7.2 + version: 1.7.3 sh_minversion: '1.9.5' py_minversion: '3.7' + sdp_minversion: '1.0.3' multi_instance: true restartable: true classname: kodi @@ -31,6 +32,20 @@ parameters: de: Item-Pfad für das Suspend-Item en: item path for suspend switch item + delay_initial_read: + type: num + default: 10 + description: + de: 'Verzögerung für das erstmalige Lesen beim Start (in Sekunden)' + en: 'delay for initial command read on start (in seconds)' + + resume_initial_read: + type: bool + defaul: true + description: + de: 'Bei resume vom Plugin erstmaliges Lesen erneut durchführen' + en: 'Repeat initial read on resume' + timeout: type: num default: 3 @@ -87,7 +102,7 @@ parameters: de: Anzahl von Durchgängen vor Verbindungsabbruch oder Suspend-Modus en: number of connect rounds before giving up / entering suspend mode - message_timeout: + send_timeout: type: num default: 5 @@ -95,7 +110,7 @@ parameters: de: Timeout für Antwort auf Protokollebene en: timeout for reply at protocol level - message_repeat: + send_retries: type: num default: 3 @@ -111,39 +126,6 @@ parameters: de: Port für Netzwerkverbindung en: network port - conn_type: - type: str - default: net_tcp_client - valid_list: - - '' - - net_tcp_client - - description: - de: Verbindungstyp - en: connection type - - protocol: - type: str - default: jsonrpc - valid_list: - - '' - - jsonrpc - - description: - de: Protokolltyp für Verbindung - en: protocol type for connection - - command_class: - type: str - default: SDPCommandJSON - valid_list: - - SDPCommand - - SDPCommandJSON - - description: - de: Klasse für Verarbeitung von Kommandos - en: class for command processing - item_attributes: kodi_command: diff --git a/lms/__init__.py b/lms/__init__.py index df32aad30..8cdc17b36 100755 --- a/lms/__init__.py +++ b/lms/__init__.py @@ -40,8 +40,9 @@ class SmartPluginWebIf(): else: builtins.SDP_standalone = False -from lib.model.sdp.globals import (CUSTOM_SEP, PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_RECURSIVE, PLUGIN_ATTR_CONN_TERMINATOR) +from lib.model.sdp.globals import (CUSTOM_SEP, PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_RECURSIVE, PLUGIN_ATTR_CMD_CLASS, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_CONN_TERMINATOR) from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone +from lib.model.sdp.command import SDPCommandParseStr import urllib.parse @@ -49,7 +50,7 @@ class SmartPluginWebIf(): class lms(SmartDevicePlugin): """ Device class for Logitech Mediaserver/Squeezebox function. """ - PLUGIN_VERSION = '1.5.2' + PLUGIN_VERSION = '1.5.3' def _set_device_defaults(self): self.custom_commands = 1 @@ -58,6 +59,9 @@ def _set_device_defaults(self): self._custom_patterns = {1: '(?:[0-9a-fA-F]{2}[-:]){5}[0-9a-fA-F]{2}', 2: '', 3: ''} self._use_callbacks = True self._parameters[PLUGIN_ATTR_RECURSIVE] = 1 + self._parameters[PLUGIN_ATTR_CMD_CLASS] = SDPCommandParseStr + self._parameters[PLUGIN_ATTR_CONNECTION] = 'net_tcp_client' + self._parameters['web_port'] = self.get_parameter_value('web_port') if self.get_parameter_value('web_host') == '': host = self._parameters.get(PLUGIN_ATTR_NET_HOST) diff --git a/lms/commands.py b/lms/commands.py index 2b29e4308..c1401eb30 100755 --- a/lms/commands.py +++ b/lms/commands.py @@ -6,7 +6,7 @@ commands = { 'server': { - 'listenmode': {'read': True, 'write': True, 'write_cmd': 'listen {RAW_VALUE:01}', 'item_type': 'bool', 'dev_datatype': 'str', 'reply_pattern': r'listen (\d)', 'item_attrs': {'custom1': ''}}, + 'listenmode': {'read': True, 'write': True, 'write_cmd': 'listen {RAW_VALUE:01}', 'item_type': 'bool', 'dev_datatype': 'LMSonoff', 'reply_pattern': r'listen (\d)', 'item_attrs': {'custom1': ''}}, 'playercount': {'read': True, 'write': False, 'read_cmd': 'player count ?', 'item_type': 'num', 'dev_datatype': 'str', 'reply_pattern': r'player count (\d+)', 'item_attrs': {'initial': True, 'custom1': ''}}, 'favoritescount': {'read': True, 'write': False, 'read_cmd': 'favorites items', 'item_type': 'num', 'dev_datatype': 'str', 'reply_pattern': r'favorites items\s+ count:(\d+)', 'item_attrs': {'initial': True, 'custom1': ''}}, }, @@ -28,11 +28,11 @@ }, 'player': { 'control': { - 'power': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} power ?', 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} power {RAW_VALUE:01}', 'dev_datatype': 'str', 'reply_pattern': [r'{CUSTOM_PATTERN1} (?:prefset server\s)?power (\d)', '{CUSTOM_PATTERN1} status(?:.*)power:([^\s]+)'], 'item_attrs': {'enforce': True}}, + 'power': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} power ?', 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} power {RAW_VALUE:01}', 'dev_datatype': 'LMSonoff', 'reply_pattern': [r'{CUSTOM_PATTERN1} (?:prefset server\s)?power (\d)', '{CUSTOM_PATTERN1} status(?:.*)power:([^\s]+)'], 'item_attrs': {'enforce': True}}, 'playmode': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} mode ?', 'item_type': 'str', 'write_cmd': '{CUSTOM_ATTR1} mode {VALUE}', 'dev_datatype': 'LMSPlayMode', 'cmd_settings': {'valid_list_ci': ['PLAY', 'PAUSE', 'STOP']}, 'reply_pattern': [r'{CUSTOM_PATTERN1} mode {VALID_LIST_CI}', r'{CUSTOM_PATTERN1} playlist (pause \d|stop)', '{CUSTOM_PATTERN1} status(?:.*)mode:([^\s]+)'], 'item_attrs': {'enforce': True}}, 'playpause': {'read': True, 'write': True, 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} {VALUE}', 'dev_datatype': 'LMSPlay', 'reply_pattern': [r'{CUSTOM_PATTERN1} (?:playlist\s)?(play|pause)(?:\s3)?$', '{CUSTOM_PATTERN1} pause (0|1)'], 'item_attrs': {'enforce': True}}, 'stop': {'read': True, 'write': True, 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} {VALUE}', 'dev_datatype': 'LMSStop', 'reply_pattern': r'{CUSTOM_PATTERN1} (?:playlist\s)?(stop)$', 'item_attrs': {'enforce': True}}, - 'mute': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} mixer muting ?', 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} mixer muting {RAW_VALUE:01}', 'dev_datatype': 'str', 'reply_pattern': r'{CUSTOM_PATTERN1} (?:mixer muting|prefset server mute) (\d)', 'item_attrs': {'initial': True, 'enforce': True}}, + 'mute': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} mixer muting ?', 'item_type': 'bool', 'write_cmd': '{CUSTOM_ATTR1} mixer muting {RAW_VALUE:01}', 'dev_datatype': 'LMSonoff', 'reply_pattern': r'{CUSTOM_PATTERN1} (?:mixer muting|prefset server mute) (\d)', 'item_attrs': {'initial': True, 'enforce': True}}, 'volume': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} mixer volume ?', 'item_type': 'num', 'write_cmd': '{CUSTOM_ATTR1} mixer volume {VALUE}', 'dev_datatype': 'str', 'reply_pattern': [r'{CUSTOM_PATTERN1} (?:mixer volume \-?|prefset server volume \-?)(\d{1,3})', '{CUSTOM_PATTERN1} status(?:.*)mixer volume:([^\s]+)']}, 'volume_fading': {'read': False, 'write': True, 'item_type': 'num', 'write_cmd': '{CUSTOM_ATTR1} mixer volume {VALUE}', 'dev_datatype': 'str', 'item_attrs': {'item_template': 'volume_fading'}}, 'volume_low': {'read': False, 'write': True, 'item_type': 'num', 'write_cmd': '{CUSTOM_ATTR1} mixer volume {VALUE}', 'dev_datatype': 'str', 'item_attrs': {'attributes': {'cache': True, 'enforce_updates': True, 'initial_value': 60}}}, @@ -42,7 +42,7 @@ 'set_alarm': {'read': True, 'write': True, 'item_type': 'str', 'write_cmd': '{CUSTOM_ATTR1} alarm {VALUE}', 'dev_datatype': 'str', 'reply_pattern': '{CUSTOM_PATTERN1} alarm (.*)'}, 'alarms': {'read': True, 'write': False, 'item_type': 'dict', 'read_cmd': '{CUSTOM_ATTR1} alarms 0 100 all', 'dev_datatype': 'LMSAlarms', 'reply_pattern': r'{CUSTOM_PATTERN1} alarms 0 100 all fade:\d+ count:\d+ (.*)', 'item_attrs': {'initial': True, 'read_groups': [{'name': 'player.control.alarms', 'trigger': 'query'}]}}, 'sync': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} sync ?', 'write_cmd': '{CUSTOM_ATTR1} sync {VALUE}', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': '{CUSTOM_PATTERN1} sync (.*)', 'item_attrs': {'initial': True}}, - 'unsync': {'read': False, 'write': True, 'write_cmd': '{CUSTOM_ATTR1} sync -', 'item_type': 'bool', 'dev_datatype': 'str', 'item_attrs': {'attributes': {'autotimer': '1s = 0'}}}, + 'unsync': {'read': False, 'write': True, 'write_cmd': '{CUSTOM_ATTR1} sync -', 'item_type': 'bool', 'dev_datatype': 'LMSonoff', 'item_attrs': {'attributes': {'autotimer': '1s = 0'}}}, 'display': {'read': True, 'write': True, 'read_cmd': '{CUSTOM_ATTR1} display ? ?', 'item_type': 'str', 'write_cmd': '{CUSTOM_ATTR1} display {VALUE}', 'dev_datatype': 'str', 'reply_pattern': r'{CUSTOM_PATTERN1} display\s?(.*)', 'item_attrs': {'initial': True}}, 'connect': {'read': True, 'write': True, 'item_type': 'str', 'write_cmd': '{CUSTOM_ATTR1} connect {VALUE}', 'dev_datatype': 'str', 'reply_pattern': '{CUSTOM_PATTERN1} connect (.*)', 'item_attrs': {'attributes': {'remark': 'ip|www.mysqueezebox.com|www.test.mysqueezebox.com'}}}, 'disconnect': {'read': True, 'write': True, 'item_type': 'str', 'write_cmd': 'disconnect {CUSTOM_ATTR1} {VALUE}', 'dev_datatype': 'str', 'reply_pattern': 'disconnect {CUSTOM_PATTERN1} (.*)', 'item_attrs': {'attributes': {'remark': 'ip|www.mysqueezebox.com|www.test.mysqueezebox.com'}}}, diff --git a/lms/datatypes.py b/lms/datatypes.py index c60618a47..42c10b6a6 100755 --- a/lms/datatypes.py +++ b/lms/datatypes.py @@ -70,3 +70,7 @@ def get_send_data(self, data, type=None, **kwargs): def get_shng_data(self, data, type=None, **kwargs): return True if data == "stop" else False + +class DT_LMSonoff(DT.Datatype): + def get_shng_data(self, data, type=None, **kwargs): + return True if data == "1" else False diff --git a/lms/plugin.yaml b/lms/plugin.yaml index ae6004f07..ae83616c7 100755 --- a/lms/plugin.yaml +++ b/lms/plugin.yaml @@ -1,14 +1,17 @@ plugin: type: interface - description: Logitech Mediaserver + description: + de: Logitech Mediaserver + en: Logitech Mediaserver maintainer: OnkelAndy tester: Morg state: develop keywords: iot device logitechmediaserver lms sdp av - version: 1.5.2 + version: '1.5.3' sh_minversion: '1.9.5' py_minversion: '3.7' + sdp_minversion: '1.0.3' multi_instance: false restartable: true classname: lms @@ -71,28 +74,6 @@ parameters: de: Port für CLI Netzwerkverbindung en: CLI network port - conn_type: - type: str - default: net_tcp_client - valid_list: - - '' - - net_tcp_client - - description: - de: Verbindungstyp - en: connection type - - command_class: - type: str - default: SDPCommandParseStr - valid_list: - - SDPCommand - - SDPCommandParseStr - - description: - de: Klasse für Verarbeitung von Kommandos - en: class for command processing - autoreconnect: type: bool default: true @@ -149,6 +130,63 @@ parameters: de: Item-Pfad für das Standby-Item en: item path for standby switch item + send_retries: + type: num + default: 0 + + description: + de: Anzahl Sendeversuche + en: number of sending retries + + description_long: + de: | + Anzahl Sendeversuche\n + Kommt keine (passende) Antwort nach dem Senden + eines Commands zurück, wird das Kommando nochmals + gesendet, sofern der Wert über 0 liegt. + en: | + number of sending retries\n + If no (suiting) answer is received after sending + a command the command is resent as long as this + value is more than 0. + + sendretry_cycle: + type: num + valid_min: 1 + default: 1 + + description: + de: Pause zwischen Durchgängen von Sendeversuchen + en: wait time between sending retry rounds + + description_long: + de: | + Pause zwischen Durchgängen von Sendeversuchen\n + Sind Send Retries aktiv, wird ein Scheduler erstellt, + der im angegebenen Sekundentakt Kommandos erneut sendet, + zu denen keine (passenden) Antworten erhalten wurden. + en: | + wait time between sending retry rounds\n + If send retries are active, a scheduler gets added + that resends commands in the given cycle value (in seconds) + where no (suiting) answer got received. + + delay_initial_read: + type: num + default: 0 + + description: + de: Warte nach Verbindungsaufbau mit dem Abfragen von Werten + en: Wait after connection with querying values + + resume_initial_read: + type: bool + defaul: false + + description: + de: Bei resume vom Plugin erstmaliges Lesen erneut durchführen + en: Repeat initial read on resume + item_attributes: sqb_command: @@ -208,8 +246,26 @@ item_attributes: en: The lookup table with the given name will be assigned to the item in dict or list format once on startup. description_long: - de: "Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim\nStart einmalig als dict oder list in das Item geschrieben.\n\n\nDurch Anhängen von \"#\" an den Namen der Tabelle kann die Art\nder Tabelle ausgewählt werden:\n- fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard)\n- rev liefert die Tabelle SmartHomeNG -> Gerät\n- rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben\n- list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu)" - en: "The lookup table with the given name will be assigned to the item\nin dict or list format once on startup.\n\n\nBy appending \"#\" to the tables name the type of table can\nbe selected:\n- fwd returns the table device -> SmartHomeNG (default)\n- rev returns the table SmartHomeNG -> device\n- rci returns the table SmartHomeNG -> device in lower case\n- list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications)" + de: |- + Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim + Start einmalig als dict oder list in das Item geschrieben. + + Durch Anhängen von "#" an den Namen der Tabelle kann die Art + der Tabelle ausgewählt werden: + - fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard) + - rev liefert die Tabelle SmartHomeNG -> Gerät + - rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben + - list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu) + en: |- + The lookup table with the given name will be assigned to the item + in dict or list format once on startup. + + By appending "#" to the tables name the type of table can + be selected: + - fwd returns the table device -> SmartHomeNG (default) + - rev returns the table SmartHomeNG -> device + - rci returns the table SmartHomeNG -> device in lower case + - list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications) sqb_custom1: type: str @@ -349,7 +405,7 @@ item_structs: duration_format: type: str eval: "'{}d {}h {}i {}s'.format(int(sh...()//86400), int((sh...()%86400)//3600), int((sh...()%3600)//60), round((sh...()%3600)%60))" - eval_trigger: .. + eval_trigger: '..' totalartists: type: num @@ -1113,7 +1169,7 @@ item_structs: duration_format: type: str eval: "'{}d {}h {}i {}s'.format(int(sh...()//86400), int((sh...()%86400)//3600), int((sh...()%3600)//60), round((sh...()%3600)%60))" - eval_trigger: .. + eval_trigger: '..' totalartists: type: num diff --git a/mvg_live/__init__.py b/mvg_live/__init__.py index 6ac5c092c..c78560788 100755 --- a/mvg_live/__init__.py +++ b/mvg_live/__init__.py @@ -27,7 +27,7 @@ class MVG_Live(SmartPlugin): ALLOW_MULTIINSTANCE = False - PLUGIN_VERSION = "1.6.0" + PLUGIN_VERSION = "1.6.1" def __init__(self, sh, *args, **kwargs): """ @@ -42,14 +42,19 @@ def stop(self): self.alive = False def get_station(self, station): - mvg_station = MvgApi.station(station) - if mvg_station: - return mvg_station + try: + mvg_station = MvgApi.station(station) + if mvg_station: + return mvg_station + except MvgApiError as e: + self.logger.error("MVGLive: Could not find %s: %s" % (ort, e)) def get_station_departures(self, station): mvg_station = self.get_station(station) + self.logger.error(mvg_station) if mvg_station: mvgapi = MvgApi(mvg_station['id']) + mvgapi.station_id = mvg_station['id'] return mvgapi.departures() else: - logger.error("Station %s does not exist."%station) \ No newline at end of file + self.logger.error("Station %s does not exist."%station) \ No newline at end of file diff --git a/mvg_live/plugin.yaml b/mvg_live/plugin.yaml index 65b810d52..260a57f28 100755 --- a/mvg_live/plugin.yaml +++ b/mvg_live/plugin.yaml @@ -12,8 +12,8 @@ plugin: documentation: http://smarthomeng.de/user/plugins_doc/config/mvg_live.html support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1108867-neues-plugin-mvg_live - version: 1.6.0 # Plugin version - sh_minversion: '1.5' # minimum shNG version to use this plugin + version: 1.6.1 # Plugin version + sh_minversion: '1.5' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: False # plugin supports multi instance restartable: unknown diff --git a/onewire/__init__.py b/onewire/__init__.py index 8d1a493fb..9065fc0d7 100755 --- a/onewire/__init__.py +++ b/onewire/__init__.py @@ -43,7 +43,7 @@ class OneWire(SmartPlugin): the update functions for the items """ - PLUGIN_VERSION = '1.9.4' + PLUGIN_VERSION = '1.9.5' _flip = {0: '1', False: '1', 1: '0', True: '0', '0': True, '1': False} @@ -448,7 +448,7 @@ def _sensor_cycle(self): def _discovery_process_bus(self, path): bus = path.split("/")[-2] - self.logger.info(f"- Processing of data for bus {bus} started") + self.logger.dbghigh(f"Discovery: Processing of data for bus {bus} started") if bus not in self._buses: self._buses[bus] = [] self._webif_buses[bus] = {} @@ -461,7 +461,7 @@ def _discovery_process_bus(self, path): self.logger.info(f"_discovery_process_bus: Problem reading {bus}, error: {e}") return - self.logger.info(f"- On bus {bus} found sensors: {sensors}") + self.logger.info(f"Discovery: On bus {bus} {len(sensors)} sensors found: {sensors}") for sensor in sensors: # skip subdirectories alarm, interface and simultaneous @@ -547,7 +547,7 @@ def _discovery_process_bus(self, path): else: self.logger.debug(f"_discovery_process_bus: Sensor {sensor} was already found in bus {bus}") - self.logger.info(f"- Processing of data for bus {bus} finished") + self.logger.dbghigh(f"Discovery: Processing of data for bus {bus} finished") return def _discovery(self): @@ -558,14 +558,14 @@ def _discovery(self): If the next call takes places it will be checked if there is something changed in top level directory. The rest of the discovery will be skipped if now changes are found. """ - self.logger.info("discovery started") + self.logger.dbghigh("Discovery started") self._intruders = [] # reset intrusion detection try: listing = self.owbase.dir('/') except Exception as e: self.logger.error(f"_discovery: listing '/' failed with error '{e}'") return - self.logger.info(f"_discovery: got listing for '/' = '{listing}' self.alive: {self.alive}") + self.logger.dbghigh(f"Discovery: Got listing for '/' = '{listing}' self.alive: {self.alive}") if type(listing) != list: self.logger.warning(f"_discovery: listing '{listing}' is not a list.") return @@ -588,7 +588,7 @@ def _discovery(self): else: # for did not end prematurely with break or something else self._discovered = True - self.logger.info("discovery finished") + self.logger.dbghigh("Discovery finished") # get a list of all directory entries from owserver # self.devices = self.tree() diff --git a/onewire/plugin.yaml b/onewire/plugin.yaml index b880b7521..a7a839b7d 100755 --- a/onewire/plugin.yaml +++ b/onewire/plugin.yaml @@ -11,7 +11,7 @@ plugin: keywords: 1wire onewire dallas ibutton sensor temperature humidity documentation: '' support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1493319-support-thread-zum-onewire-plugin - version: 1.9.4 # Plugin version + version: 1.9.5 # Plugin version sh_minversion: '1.9.3.5' # minimum shNG version to use this plugin multi_instance: True restartable: True diff --git a/oppo/__init__.py b/oppo/__init__.py index f09637415..ee269375d 100755 --- a/oppo/__init__.py +++ b/oppo/__init__.py @@ -39,8 +39,9 @@ class SmartPluginWebIf(): else: builtins.SDP_standalone = False -from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_SERIAL_PORT, PLUGIN_ATTR_CONN_TERMINATOR, CONN_NET_TCP_CLI, CONN_SER_ASYNC) +from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_SERIAL_PORT, PLUGIN_ATTR_CONN_TERMINATOR, CONN_NET_TCP_CLI, CONN_SER_ASYNC, PLUGIN_ATTR_CMD_CLASS) from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone +from lib.model.sdp.command import SDPCommandParseStr CUSTOM_INPUT_NAME_COMMAND = 'custom_inputnames' @@ -54,7 +55,7 @@ class oppo(SmartDevicePlugin): The know-how is in the commands.py (and some DT_ classes...) """ - PLUGIN_VERSION = '1.0.0' + PLUGIN_VERSION = '1.0.1' def _set_device_defaults(self): @@ -64,6 +65,8 @@ def _set_device_defaults(self): elif PLUGIN_ATTR_SERIAL_PORT in self._parameters and self._parameters[PLUGIN_ATTR_SERIAL_PORT]: self._parameters[PLUGIN_ATTR_CONNECTION] = CONN_SER_ASYNC + self._parameters[PLUGIN_ATTR_CMD_CLASS] = SDPCommandParseStr + b = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR].encode() b = b.decode('unicode-escape').encode() self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] = b @@ -75,10 +78,6 @@ def on_connect(self, by=None): self.logger.debug(f"Activating verbose mode {verbose} after connection.") self.send_command('general.verbose', verbose) - def _send(self, data_dict): - self.logger.debug(f"Sending data_dict {data_dict}") - self._connection.send(data_dict) - def _transform_send_data(self, data=None, **kwargs): if isinstance(data, dict): data['limit_response'] = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] diff --git a/oppo/commands.py b/oppo/commands.py index 1c303d97f..c11c7029d 100755 --- a/oppo/commands.py +++ b/oppo/commands.py @@ -29,7 +29,7 @@ 'language': {'read': True, 'write': False, 'item_type': 'str', 'dev_datatype': 'raw', 'reply_pattern': [r'@QAT OK (?:[A-Z]*) (?:\d{1,2})/(?:\d{1,2}) ([A-Za-z]*)', r'@UAT (?:[A-Z]{2}) (?:\d{2})/(?:\d{2}) ([A-Z]{3}) (?:[0-7.]*)']}, }, 'subtitle': { - 'current': {'read': True, 'write': False, 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'@UST (\d{2})/(?:\d{2}) ([A-Z]{3})'}, + 'current': {'read': True, 'write': False, 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'@UST (\d{2})/(?:\d{2}) (?:[A-Z]{3})'}, 'available': {'read': True, 'write': False, 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': r'@UST (?:\d{2})/(\d{2}) (?:[A-Z]{3})'}, 'language': {'read': True, 'write': False, 'item_type': 'str', 'dev_datatype': 'raw', 'reply_pattern': r'@UST (?:\d{2})/(?:\d{2}) ([A-Z]{3})'}, }, @@ -58,35 +58,35 @@ 'eject': {'read': True, 'write': True, 'write_cmd': '#EJT', 'item_type': 'bool', 'dev_datatype': 'openclose', 'reply_pattern': ['@UPL (OPEN|CLOS)', '@EJT OK (OPEN|CLOSE)'], 'item_attrs': {'enforce': True}}, 'chapter': {'read': True, 'write': True, 'read_cmd': '#QCH', 'write_cmd': '#SRH C{RAW_VALUE:03}', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': ['@SRH (OK|ER INVALID)', r'@QCH OK (\d{2})/(?:\d{2})']}, 'title': {'read': True, 'write': True, 'read_cmd': '#QTK', 'write_cmd': '#SRH T{RAW_VALUE:03}', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': [r'@QTK OK (\d{2})/(?:\d{2})', '@SRH (OK|ER INVALID)', r'@UAT (?:[A-Z]{2}) (\d{2})/(?:\d{2}) (?:[A-Z]{3}) (?:[0-7.]{3})']}, - 'next': {'read': True, 'write': True, 'write_cmd': '#NXT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@NXT (.*)'], 'item_attrs': {'enforce': True}}, - 'previous': {'read': True, 'write': True, 'write_cmd': '#PRE', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@PRE (.*)'], 'item_attrs': {'enforce': True}}, - 'forward': {'read': True, 'write': True, 'write_cmd': '#FWD', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@FWD (.*) 1x'], 'item_attrs': {'enforce': True}}, - 'reverse': {'read': True, 'write': True, 'write_cmd': '#REV', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@REV (.*) 1x'], 'item_attrs': {'enforce': True}}, + 'next': {'read': True, 'write': True, 'write_cmd': '#NXT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@NXT (.*)', 'item_attrs': {'enforce': True}}, + 'previous': {'read': True, 'write': True, 'write_cmd': '#PRE', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@PRE (.*)', 'item_attrs': {'enforce': True}}, + 'forward': {'read': True, 'write': True, 'write_cmd': '#FWD', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@FWD (.*) 1x', 'item_attrs': {'enforce': True}}, + 'reverse': {'read': True, 'write': True, 'write_cmd': '#REV', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@REV (.*) 1x', 'item_attrs': {'enforce': True}}, 'audio': {'read': True, 'write': True, 'write_cmd': '#AUD', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@AUD (.*)', 'item_attrs': {'enforce': True}}, 'subtitle': {'read': True, 'write': True, 'write_cmd': '#SUB', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@SUB (.*)', 'item_attrs': {'enforce': True}}, - 'repeat': {'read': True, 'write': True, 'write_cmd': '#RPT', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': ['@RPT OK {LOOKUP}$'], 'lookup': 'REPEAT'}, - 'input': {'read': True, 'write': True, 'write_cmd': '#SRC\r#NU{VALUE}', 'item_type': 'num', 'dev_datatype': 'ok', 'reply_pattern': ['@SRC (.*)']}, + 'repeat': {'read': True, 'write': True, 'write_cmd': '#RPT', 'item_type': 'num', 'dev_datatype': 'raw', 'reply_pattern': '@RPT OK {LOOKUP}$', 'lookup': 'REPEAT'}, + 'input': {'read': True, 'write': True, 'write_cmd': '#SRC\r#NU{VALUE}', 'item_type': 'num', 'dev_datatype': 'ok', 'reply_pattern': '@SRC (.*)'}, }, 'menu': { 'home': {'read': True, 'write': True, 'write_cmd': '#HOM', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@HOM (.*)', 'item_attrs': {'enforce': True}}, - 'setup': {'read': True, 'write': True, 'write_cmd': '#SET', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@SET (.*)'], 'item_attrs': {'enforce': True}}, - 'option': {'read': True, 'write': True, 'write_cmd': '#OPT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@OPT (.*)'], 'item_attrs': {'enforce': True}}, - 'info': {'read': True, 'write': True, 'write_cmd': '#INH', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@INH (.*)'], 'item_attrs': {'enforce': True}}, - 'popup': {'read': True, 'write': True, 'write_cmd': '#MNU', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@MNU (.*)'], 'item_attrs': {'enforce': True}}, - 'top': {'read': True, 'write': True, 'write_cmd': '#TTL', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@TTL (.*)'], 'item_attrs': {'enforce': True}}, - 'osd': {'read': True, 'write': True, 'write_cmd': '#OSD', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@OSD (.*)'], 'item_attrs': {'enforce': True}}, - 'pageup': {'read': True, 'write': True, 'write_cmd': '#PUP', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@PUP (.*)'], 'item_attrs': {'enforce': True}}, - 'pagedown': {'read': True, 'write': True, 'write_cmd': '#PDN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@PDN (.*)'], 'item_attrs': {'enforce': True}}, - 'up': {'read': True, 'write': True, 'write_cmd': '#NUP', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@NUP (.*)'], 'item_attrs': {'enforce': True}}, - 'down': {'read': True, 'write': True, 'write_cmd': '#NDN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@NDN (.*)'], 'item_attrs': {'enforce': True}}, - 'left': {'read': True, 'write': True, 'write_cmd': '#NLT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@NLT (.*)'], 'item_attrs': {'enforce': True}}, - 'right': {'read': True, 'write': True, 'write_cmd': '#NRT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@NRT (.*)'], 'item_attrs': {'enforce': True}}, - 'select': {'read': True, 'write': True, 'write_cmd': '#SEL', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@SEL (.*)'], 'item_attrs': {'enforce': True}}, - 'back': {'read': True, 'write': True, 'write_cmd': '#RET', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@RET (.*)'], 'item_attrs': {'enforce': True}}, - 'red': {'read': True, 'write': True, 'write_cmd': '#RED', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@RED (.*)'], 'item_attrs': {'enforce': True}}, - 'green': {'read': True, 'write': True, 'write_cmd': '#GRN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@GRN (.*)'], 'item_attrs': {'enforce': True}}, - 'blue': {'read': True, 'write': True, 'write_cmd': '#BLU', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@BLU (.*)'], 'item_attrs': {'enforce': True}}, - 'yellow': {'read': True, 'write': True, 'write_cmd': '#YLW', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': ['@YLW (.*)'], 'item_attrs': {'enforce': True}}, + 'setup': {'read': True, 'write': True, 'write_cmd': '#SET', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@SET (.*)', 'item_attrs': {'enforce': True}}, + 'option': {'read': True, 'write': True, 'write_cmd': '#OPT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@OPT (.*)', 'item_attrs': {'enforce': True}}, + 'info': {'read': True, 'write': True, 'write_cmd': '#INH', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@INH (.*)', 'item_attrs': {'enforce': True}}, + 'popup': {'read': True, 'write': True, 'write_cmd': '#MNU', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@MNU (.*)', 'item_attrs': {'enforce': True}}, + 'top': {'read': True, 'write': True, 'write_cmd': '#TTL', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@TTL (.*)', 'item_attrs': {'enforce': True}}, + 'osd': {'read': True, 'write': True, 'write_cmd': '#OSD', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@OSD (.*)', 'item_attrs': {'enforce': True}}, + 'pageup': {'read': True, 'write': True, 'write_cmd': '#PUP', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@PUP (.*)', 'item_attrs': {'enforce': True}}, + 'pagedown': {'read': True, 'write': True, 'write_cmd': '#PDN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@PDN (.*)', 'item_attrs': {'enforce': True}}, + 'up': {'read': True, 'write': True, 'write_cmd': '#NUP', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@NUP (.*)', 'item_attrs': {'enforce': True}}, + 'down': {'read': True, 'write': True, 'write_cmd': '#NDN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@NDN (.*)', 'item_attrs': {'enforce': True}}, + 'left': {'read': True, 'write': True, 'write_cmd': '#NLT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@NLT (.*)', 'item_attrs': {'enforce': True}}, + 'right': {'read': True, 'write': True, 'write_cmd': '#NRT', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@NRT (.*)', 'item_attrs': {'enforce': True}}, + 'select': {'read': True, 'write': True, 'write_cmd': '#SEL', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@SEL (.*)', 'item_attrs': {'enforce': True}}, + 'back': {'read': True, 'write': True, 'write_cmd': '#RET', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@RET (.*)', 'item_attrs': {'enforce': True}}, + 'red': {'read': True, 'write': True, 'write_cmd': '#RED', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@RED (.*)', 'item_attrs': {'enforce': True}}, + 'green': {'read': True, 'write': True, 'write_cmd': '#GRN', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@GRN (.*)', 'item_attrs': {'enforce': True}}, + 'blue': {'read': True, 'write': True, 'write_cmd': '#BLU', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@BLU (.*)', 'item_attrs': {'enforce': True}}, + 'yellow': {'read': True, 'write': True, 'write_cmd': '#YLW', 'item_type': 'bool', 'dev_datatype': 'ok', 'reply_pattern': '@YLW (.*)', 'item_attrs': {'enforce': True}}, } } diff --git a/oppo/plugin.yaml b/oppo/plugin.yaml index 1210475d2..9eb08a1cc 100755 --- a/oppo/plugin.yaml +++ b/oppo/plugin.yaml @@ -1,14 +1,17 @@ plugin: type: interface - description: Oppo UHD Player + description: + de: Oppo UHD Player + en: Oppo UHD Player maintainer: OnkelAndy tester: Morg state: develop keywords: iot device - version: 1.0.0 + version: '1.0.1' sh_minversion: '1.9.5.1' py_minversion: '3.6' + sdp_minversion: '1.0.3' multi_instance: false restartable: true classname: oppo @@ -39,36 +42,6 @@ parameters: de: Serieller Anschluss (z.B. /dev/ttyUSB0 oder COM1) en: serial port (e.g. /dev/ttyUSB0 or COM1) - conn_type: - type: str - mandatory: false - valid_list: - - '' - - net_tcp_request - - net_tcp_client - - net_tcp_jsonrpc - - net_udp_server - - serial - - serial_async - - description: - de: Verbindungstyp - en: connection type - - command_class: - type: str - default: SDPCommandParseStr - valid_list: - - SDPCommand - - SDPCommandStr - - SDPCommandParseStr - - SDPCommandJSON - - SDPCommandViessmann - - description: - de: Klasse für Verarbeitung von Kommandos - en: class for command processing - model: type: str mandatory: false @@ -198,6 +171,62 @@ parameters: de: Item-Pfad für das Standby-Item en: item path for standby switch item + send_retries: + type: num + default: 0 + + description: + de: Anzahl Sendeversuche + en: number of sending retries + + description_long: + de: | + Anzahl Sendeversuche\n + Kommt keine (passende) Antwort nach dem Senden + eines Commands zurück, wird das Kommando nochmals + gesendet, sofern der Wert über 0 liegt. + en: | + number of sending retries\n + If no (suiting) answer is received after sending + a command the command is resent as long as this + value is more than 0. + + sendretry_cycle: + type: num + valid_min: 1 + default: 1 + + description: + de: Pause zwischen Durchgängen von Sendeversuchen + en: wait time between sending retry rounds + + description_long: + de: | + Pause zwischen Durchgängen von Sendeversuchen\n + Sind Send Retries aktiv, wird ein Scheduler erstellt, + der im angegebenen Sekundentakt Kommandos erneut sendet, + zu denen keine (passenden) Antworten erhalten wurden. + en: | + wait time between sending retry rounds\n + If send retries are active, a scheduler gets added + that resends commands in the given cycle value (in seconds) + where no (suiting) answer got received. + + delay_initial_read: + type: num + default: 0 + + description: + de: Warte nach Verbindungsaufbau mit dem Abfragen von Werten + en: Wait after connection with querying values + + resume_initial_read: + type: bool + defaul: false + + description: + de: Bei resume vom Plugin erstmaliges Lesen erneut durchführen + en: Repeat initial read on resume item_attributes: @@ -258,8 +287,26 @@ item_attributes: en: The lookup table with the given name will be assigned to the item in dict or list format once on startup. description_long: - de: "Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim\nStart einmalig als dict oder list in das Item geschrieben.\n\n\nDurch Anhängen von \"#\" an den Namen der Tabelle kann die Art\nder Tabelle ausgewählt werden:\n- fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard)\n- rev liefert die Tabelle SmartHomeNG -> Gerät\n- rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben\n- list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu)" - en: "The lookup table with the given name will be assigned to the item\nin dict or list format once on startup.\n\n\nBy appending \"#\" to the tables name the type of table can\nbe selected:\n- fwd returns the table device -> SmartHomeNG (default)\n- rev returns the table SmartHomeNG -> device\n- rci returns the table SmartHomeNG -> device in lower case\n- list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications)" + de: |- + Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim + Start einmalig als dict oder list in das Item geschrieben. + + Durch Anhängen von "#" an den Namen der Tabelle kann die Art + der Tabelle ausgewählt werden: + - fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard) + - rev liefert die Tabelle SmartHomeNG -> Gerät + - rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben + - list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu) + en: |- + The lookup table with the given name will be assigned to the item + in dict or list format once on startup. + + By appending "#" to the tables name the type of table can + be selected: + - fwd returns the table device -> SmartHomeNG (default) + - rev returns the table SmartHomeNG -> device + - rci returns the table SmartHomeNG -> device in lower case + - list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications) item_structs: @@ -268,66 +315,66 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: info + oppo_read_group_trigger@instance: info time: read: type: bool enforce_updates: true - oppo_read_group_trigger: info.time + oppo_read_group_trigger@instance: info.time totalelapsed: type: str - oppo_command: info.time.totalelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time totalremaining: type: str - oppo_command: info.time.totalremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time chapterelapsed: type: str - oppo_command: info.time.chapterelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time chapterremaining: type: str - oppo_command: info.time.chapterremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time titleelapsed: type: str - oppo_command: info.time.titleelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time titleremaining: type: str - oppo_command: info.time.titleremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - info.time @@ -336,421 +383,421 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: info.audio + oppo_read_group_trigger@instance: info.audio current: type: num - oppo_command: info.audio.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.audio.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.audio.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.language + oppo_read@instance: true + oppo_write@instance: false subtitle: read: type: bool enforce_updates: true - oppo_read_group_trigger: info.subtitle + oppo_read_group_trigger@instance: info.subtitle current: type: num - oppo_command: info.subtitle.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.subtitle.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.subtitle.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.language + oppo_read@instance: true + oppo_write@instance: false firmware: type: str - oppo_command: info.firmware - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.firmware + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - oppo_read_initial: true + oppo_read_initial@instance: true status: type: str - oppo_command: info.status - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.status + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - oppo_read_initial: true + oppo_read_initial@instance: true disctype: type: str - oppo_command: info.disctype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.disctype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - oppo_read_initial: true + oppo_read_initial@instance: true totaltracks: type: num - oppo_command: info.totaltracks - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.totaltracks + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - oppo_read_initial: true + oppo_read_initial@instance: true displaytype: type: str - oppo_command: info.displaytype - oppo_read: true - oppo_write: true + oppo_command@instance: info.displaytype + oppo_read@instance: true + oppo_write@instance: true audiotype: type: str - oppo_command: info.audiotype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.audiotype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info - oppo_read_initial: true + oppo_read_initial@instance: true channels: type: str - oppo_command: info.channels - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.channels + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info trackinfo: type: num - oppo_command: info.trackinfo - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.trackinfo + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - info inputresolution: type: str - oppo_command: info.inputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.inputresolution + oppo_read@instance: true + oppo_write@instance: false outputresolution: type: str - oppo_command: info.outputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.outputresolution + oppo_read@instance: true + oppo_write@instance: false aspectratio: type: str - oppo_command: info.aspectratio - oppo_read: true - oppo_write: false + oppo_command@instance: info.aspectratio + oppo_read@instance: true + oppo_write@instance: false U3D: type: str - oppo_command: info.U3D - oppo_read: true - oppo_write: false + oppo_command@instance: info.U3D + oppo_read@instance: true + oppo_write@instance: false general: read: type: bool enforce_updates: true - oppo_read_group_trigger: general + oppo_read_group_trigger@instance: general verbose: type: num - oppo_command: general.verbose - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.verbose + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - general cache: true initial_value: 2 hdmiresolution: type: str - oppo_command: general.hdmiresolution - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.hdmiresolution + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - general - oppo_read_initial: true + oppo_read_initial@instance: true control: read: type: bool enforce_updates: true - oppo_read_group_trigger: control + oppo_read_group_trigger@instance: control power: type: bool - oppo_command: control.power - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.power + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - control - oppo_read_initial: true + oppo_read_initial@instance: true pureaudio: type: bool - oppo_command: control.pureaudio - oppo_read: true - oppo_write: true + oppo_command@instance: control.pureaudio + oppo_read@instance: true + oppo_write@instance: true playpause: type: bool - oppo_command: control.playpause - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.playpause + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - control stop: type: bool - oppo_command: control.stop - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.stop + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - control eject: type: bool - oppo_command: control.eject - oppo_read: true - oppo_write: true + oppo_command@instance: control.eject + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true chapter: type: num - oppo_command: control.chapter - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.chapter + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - control title: type: num - oppo_command: control.title - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.title + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - control next: type: bool - oppo_command: control.next - oppo_read: true - oppo_write: true + oppo_command@instance: control.next + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true previous: type: bool - oppo_command: control.previous - oppo_read: true - oppo_write: true + oppo_command@instance: control.previous + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true forward: type: bool - oppo_command: control.forward - oppo_read: true - oppo_write: true + oppo_command@instance: control.forward + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true reverse: type: bool - oppo_command: control.reverse - oppo_read: true - oppo_write: true + oppo_command@instance: control.reverse + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true audio: type: bool - oppo_command: control.audio - oppo_read: true - oppo_write: true + oppo_command@instance: control.audio + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true subtitle: type: bool - oppo_command: control.subtitle - oppo_read: true - oppo_write: true + oppo_command@instance: control.subtitle + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true repeat: type: num - oppo_command: control.repeat - oppo_read: true - oppo_write: true + oppo_command@instance: control.repeat + oppo_read@instance: true + oppo_write@instance: true input: type: num - oppo_command: control.input - oppo_read: true - oppo_write: true + oppo_command@instance: control.input + oppo_read@instance: true + oppo_write@instance: true menu: read: type: bool enforce_updates: true - oppo_read_group_trigger: menu + oppo_read_group_trigger@instance: menu home: type: bool - oppo_command: menu.home - oppo_read: true - oppo_write: true + oppo_command@instance: menu.home + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true setup: type: bool - oppo_command: menu.setup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.setup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true option: type: bool - oppo_command: menu.option - oppo_read: true - oppo_write: true + oppo_command@instance: menu.option + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true info: type: bool - oppo_command: menu.info - oppo_read: true - oppo_write: true + oppo_command@instance: menu.info + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true popup: type: bool - oppo_command: menu.popup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.popup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true top: type: bool - oppo_command: menu.top - oppo_read: true - oppo_write: true + oppo_command@instance: menu.top + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true osd: type: bool - oppo_command: menu.osd - oppo_read: true - oppo_write: true + oppo_command@instance: menu.osd + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pageup: type: bool - oppo_command: menu.pageup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pageup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pagedown: type: bool - oppo_command: menu.pagedown - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pagedown + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true up: type: bool - oppo_command: menu.up - oppo_read: true - oppo_write: true + oppo_command@instance: menu.up + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true down: type: bool - oppo_command: menu.down - oppo_read: true - oppo_write: true + oppo_command@instance: menu.down + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true left: type: bool - oppo_command: menu.left - oppo_read: true - oppo_write: true + oppo_command@instance: menu.left + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true right: type: bool - oppo_command: menu.right - oppo_read: true - oppo_write: true + oppo_command@instance: menu.right + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true select: type: bool - oppo_command: menu.select - oppo_read: true - oppo_write: true + oppo_command@instance: menu.select + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true back: type: bool - oppo_command: menu.back - oppo_read: true - oppo_write: true + oppo_command@instance: menu.back + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true red: type: bool - oppo_command: menu.red - oppo_read: true - oppo_write: true + oppo_command@instance: menu.red + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true green: type: bool - oppo_command: menu.green - oppo_read: true - oppo_write: true + oppo_command@instance: menu.green + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true blue: type: bool - oppo_command: menu.blue - oppo_read: true - oppo_write: true + oppo_command@instance: menu.blue + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true yellow: type: bool - oppo_command: menu.yellow - oppo_read: true - oppo_write: true + oppo_command@instance: menu.yellow + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true ALL: @@ -758,78 +805,78 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL + oppo_read_group_trigger@instance: ALL info: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.info + oppo_read_group_trigger@instance: ALL.info time: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.info.time + oppo_read_group_trigger@instance: ALL.info.time totalelapsed: type: str - oppo_command: info.time.totalelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time totalremaining: type: str - oppo_command: info.time.totalremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time chapterelapsed: type: str - oppo_command: info.time.chapterelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time chapterremaining: type: str - oppo_command: info.time.chapterremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time titleelapsed: type: str - oppo_command: info.time.titleelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time titleremaining: type: str - oppo_command: info.time.titleremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - ALL.info.time @@ -839,162 +886,162 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.info.audio + oppo_read_group_trigger@instance: ALL.info.audio current: type: num - oppo_command: info.audio.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.audio.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.audio.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.language + oppo_read@instance: true + oppo_write@instance: false subtitle: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.info.subtitle + oppo_read_group_trigger@instance: ALL.info.subtitle current: type: num - oppo_command: info.subtitle.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.subtitle.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.subtitle.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.language + oppo_read@instance: true + oppo_write@instance: false firmware: type: str - oppo_command: info.firmware - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.firmware + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - oppo_read_initial: true + oppo_read_initial@instance: true status: type: str - oppo_command: info.status - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.status + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - oppo_read_initial: true + oppo_read_initial@instance: true disctype: type: str - oppo_command: info.disctype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.disctype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - oppo_read_initial: true + oppo_read_initial@instance: true totaltracks: type: num - oppo_command: info.totaltracks - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.totaltracks + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - oppo_read_initial: true + oppo_read_initial@instance: true displaytype: type: str - oppo_command: info.displaytype - oppo_read: true - oppo_write: true + oppo_command@instance: info.displaytype + oppo_read@instance: true + oppo_write@instance: true audiotype: type: str - oppo_command: info.audiotype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.audiotype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info - oppo_read_initial: true + oppo_read_initial@instance: true channels: type: str - oppo_command: info.channels - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.channels + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info trackinfo: type: num - oppo_command: info.trackinfo - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.trackinfo + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - ALL - ALL.info inputresolution: type: str - oppo_command: info.inputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.inputresolution + oppo_read@instance: true + oppo_write@instance: false outputresolution: type: str - oppo_command: info.outputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.outputresolution + oppo_read@instance: true + oppo_write@instance: false aspectratio: type: str - oppo_command: info.aspectratio - oppo_read: true - oppo_write: false + oppo_command@instance: info.aspectratio + oppo_read@instance: true + oppo_write@instance: false U3D: type: str - oppo_command: info.U3D - oppo_read: true - oppo_write: false + oppo_command@instance: info.U3D + oppo_read@instance: true + oppo_write@instance: false general: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.general + oppo_read_group_trigger@instance: ALL.general verbose: type: num - oppo_command: general.verbose - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.verbose + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.general cache: true @@ -1002,272 +1049,272 @@ item_structs: hdmiresolution: type: str - oppo_command: general.hdmiresolution - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.hdmiresolution + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.general - oppo_read_initial: true + oppo_read_initial@instance: true control: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.control + oppo_read_group_trigger@instance: ALL.control power: type: bool - oppo_command: control.power - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.power + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.control - oppo_read_initial: true + oppo_read_initial@instance: true pureaudio: type: bool - oppo_command: control.pureaudio - oppo_read: true - oppo_write: true + oppo_command@instance: control.pureaudio + oppo_read@instance: true + oppo_write@instance: true playpause: type: bool - oppo_command: control.playpause - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.playpause + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.control stop: type: bool - oppo_command: control.stop - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.stop + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.control eject: type: bool - oppo_command: control.eject - oppo_read: true - oppo_write: true + oppo_command@instance: control.eject + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true chapter: type: num - oppo_command: control.chapter - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.chapter + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.control title: type: num - oppo_command: control.title - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.title + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - ALL - ALL.control next: type: bool - oppo_command: control.next - oppo_read: true - oppo_write: true + oppo_command@instance: control.next + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true previous: type: bool - oppo_command: control.previous - oppo_read: true - oppo_write: true + oppo_command@instance: control.previous + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true forward: type: bool - oppo_command: control.forward - oppo_read: true - oppo_write: true + oppo_command@instance: control.forward + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true reverse: type: bool - oppo_command: control.reverse - oppo_read: true - oppo_write: true + oppo_command@instance: control.reverse + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true audio: type: bool - oppo_command: control.audio - oppo_read: true - oppo_write: true + oppo_command@instance: control.audio + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true subtitle: type: bool - oppo_command: control.subtitle - oppo_read: true - oppo_write: true + oppo_command@instance: control.subtitle + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true repeat: type: num - oppo_command: control.repeat - oppo_read: true - oppo_write: true + oppo_command@instance: control.repeat + oppo_read@instance: true + oppo_write@instance: true input: type: num - oppo_command: control.input - oppo_read: true - oppo_write: true + oppo_command@instance: control.input + oppo_read@instance: true + oppo_write@instance: true menu: read: type: bool enforce_updates: true - oppo_read_group_trigger: ALL.menu + oppo_read_group_trigger@instance: ALL.menu home: type: bool - oppo_command: menu.home - oppo_read: true - oppo_write: true + oppo_command@instance: menu.home + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true setup: type: bool - oppo_command: menu.setup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.setup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true option: type: bool - oppo_command: menu.option - oppo_read: true - oppo_write: true + oppo_command@instance: menu.option + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true info: type: bool - oppo_command: menu.info - oppo_read: true - oppo_write: true + oppo_command@instance: menu.info + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true popup: type: bool - oppo_command: menu.popup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.popup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true top: type: bool - oppo_command: menu.top - oppo_read: true - oppo_write: true + oppo_command@instance: menu.top + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true osd: type: bool - oppo_command: menu.osd - oppo_read: true - oppo_write: true + oppo_command@instance: menu.osd + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pageup: type: bool - oppo_command: menu.pageup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pageup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pagedown: type: bool - oppo_command: menu.pagedown - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pagedown + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true up: type: bool - oppo_command: menu.up - oppo_read: true - oppo_write: true + oppo_command@instance: menu.up + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true down: type: bool - oppo_command: menu.down - oppo_read: true - oppo_write: true + oppo_command@instance: menu.down + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true left: type: bool - oppo_command: menu.left - oppo_read: true - oppo_write: true + oppo_command@instance: menu.left + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true right: type: bool - oppo_command: menu.right - oppo_read: true - oppo_write: true + oppo_command@instance: menu.right + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true select: type: bool - oppo_command: menu.select - oppo_read: true - oppo_write: true + oppo_command@instance: menu.select + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true back: type: bool - oppo_command: menu.back - oppo_read: true - oppo_write: true + oppo_command@instance: menu.back + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true red: type: bool - oppo_command: menu.red - oppo_read: true - oppo_write: true + oppo_command@instance: menu.red + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true green: type: bool - oppo_command: menu.green - oppo_read: true - oppo_write: true + oppo_command@instance: menu.green + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true blue: type: bool - oppo_command: menu.blue - oppo_read: true - oppo_write: true + oppo_command@instance: menu.blue + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true yellow: type: bool - oppo_command: menu.yellow - oppo_read: true - oppo_write: true + oppo_command@instance: menu.yellow + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true UDP-203: @@ -1275,78 +1322,78 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203 + oppo_read_group_trigger@instance: UDP-203 info: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.info + oppo_read_group_trigger@instance: UDP-203.info time: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.info.time + oppo_read_group_trigger@instance: UDP-203.info.time totalelapsed: type: str - oppo_command: info.time.totalelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time totalremaining: type: str - oppo_command: info.time.totalremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.totalremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time chapterelapsed: type: str - oppo_command: info.time.chapterelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time chapterremaining: type: str - oppo_command: info.time.chapterremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.chapterremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time titleelapsed: type: str - oppo_command: info.time.titleelapsed - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleelapsed + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time titleremaining: type: str - oppo_command: info.time.titleremaining - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.time.titleremaining + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - UDP-203.info.time @@ -1356,162 +1403,162 @@ item_structs: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.info.audio + oppo_read_group_trigger@instance: UDP-203.info.audio current: type: num - oppo_command: info.audio.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.audio.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.audio.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.audio.language + oppo_read@instance: true + oppo_write@instance: false subtitle: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.info.subtitle + oppo_read_group_trigger@instance: UDP-203.info.subtitle current: type: num - oppo_command: info.subtitle.current - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.current + oppo_read@instance: true + oppo_write@instance: false available: type: num - oppo_command: info.subtitle.available - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.available + oppo_read@instance: true + oppo_write@instance: false language: type: str - oppo_command: info.subtitle.language - oppo_read: true - oppo_write: false + oppo_command@instance: info.subtitle.language + oppo_read@instance: true + oppo_write@instance: false firmware: type: str - oppo_command: info.firmware - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.firmware + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - oppo_read_initial: true + oppo_read_initial@instance: true status: type: str - oppo_command: info.status - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.status + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - oppo_read_initial: true + oppo_read_initial@instance: true disctype: type: str - oppo_command: info.disctype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.disctype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - oppo_read_initial: true + oppo_read_initial@instance: true totaltracks: type: num - oppo_command: info.totaltracks - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.totaltracks + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - oppo_read_initial: true + oppo_read_initial@instance: true displaytype: type: str - oppo_command: info.displaytype - oppo_read: true - oppo_write: true + oppo_command@instance: info.displaytype + oppo_read@instance: true + oppo_write@instance: true audiotype: type: str - oppo_command: info.audiotype - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.audiotype + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info - oppo_read_initial: true + oppo_read_initial@instance: true channels: type: str - oppo_command: info.channels - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.channels + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info trackinfo: type: num - oppo_command: info.trackinfo - oppo_read: true - oppo_write: false - oppo_read_group: + oppo_command@instance: info.trackinfo + oppo_read@instance: true + oppo_write@instance: false + oppo_read_group@instance: - UDP-203 - UDP-203.info inputresolution: type: str - oppo_command: info.inputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.inputresolution + oppo_read@instance: true + oppo_write@instance: false outputresolution: type: str - oppo_command: info.outputresolution - oppo_read: true - oppo_write: false + oppo_command@instance: info.outputresolution + oppo_read@instance: true + oppo_write@instance: false aspectratio: type: str - oppo_command: info.aspectratio - oppo_read: true - oppo_write: false + oppo_command@instance: info.aspectratio + oppo_read@instance: true + oppo_write@instance: false U3D: type: str - oppo_command: info.U3D - oppo_read: true - oppo_write: false + oppo_command@instance: info.U3D + oppo_read@instance: true + oppo_write@instance: false general: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.general + oppo_read_group_trigger@instance: UDP-203.general verbose: type: num - oppo_command: general.verbose - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.verbose + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.general cache: true @@ -1519,272 +1566,272 @@ item_structs: hdmiresolution: type: str - oppo_command: general.hdmiresolution - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: general.hdmiresolution + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.general - oppo_read_initial: true + oppo_read_initial@instance: true control: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.control + oppo_read_group_trigger@instance: UDP-203.control power: type: bool - oppo_command: control.power - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.power + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.control - oppo_read_initial: true + oppo_read_initial@instance: true pureaudio: type: bool - oppo_command: control.pureaudio - oppo_read: true - oppo_write: true + oppo_command@instance: control.pureaudio + oppo_read@instance: true + oppo_write@instance: true playpause: type: bool - oppo_command: control.playpause - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.playpause + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.control stop: type: bool - oppo_command: control.stop - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.stop + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.control eject: type: bool - oppo_command: control.eject - oppo_read: true - oppo_write: true + oppo_command@instance: control.eject + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true chapter: type: num - oppo_command: control.chapter - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.chapter + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.control title: type: num - oppo_command: control.title - oppo_read: true - oppo_write: true - oppo_read_group: + oppo_command@instance: control.title + oppo_read@instance: true + oppo_write@instance: true + oppo_read_group@instance: - UDP-203 - UDP-203.control next: type: bool - oppo_command: control.next - oppo_read: true - oppo_write: true + oppo_command@instance: control.next + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true previous: type: bool - oppo_command: control.previous - oppo_read: true - oppo_write: true + oppo_command@instance: control.previous + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true forward: type: bool - oppo_command: control.forward - oppo_read: true - oppo_write: true + oppo_command@instance: control.forward + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true reverse: type: bool - oppo_command: control.reverse - oppo_read: true - oppo_write: true + oppo_command@instance: control.reverse + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true audio: type: bool - oppo_command: control.audio - oppo_read: true - oppo_write: true + oppo_command@instance: control.audio + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true subtitle: type: bool - oppo_command: control.subtitle - oppo_read: true - oppo_write: true + oppo_command@instance: control.subtitle + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true repeat: type: num - oppo_command: control.repeat - oppo_read: true - oppo_write: true + oppo_command@instance: control.repeat + oppo_read@instance: true + oppo_write@instance: true input: type: num - oppo_command: control.input - oppo_read: true - oppo_write: true + oppo_command@instance: control.input + oppo_read@instance: true + oppo_write@instance: true menu: read: type: bool enforce_updates: true - oppo_read_group_trigger: UDP-203.menu + oppo_read_group_trigger@instance: UDP-203.menu home: type: bool - oppo_command: menu.home - oppo_read: true - oppo_write: true + oppo_command@instance: menu.home + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true setup: type: bool - oppo_command: menu.setup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.setup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true option: type: bool - oppo_command: menu.option - oppo_read: true - oppo_write: true + oppo_command@instance: menu.option + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true info: type: bool - oppo_command: menu.info - oppo_read: true - oppo_write: true + oppo_command@instance: menu.info + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true popup: type: bool - oppo_command: menu.popup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.popup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true top: type: bool - oppo_command: menu.top - oppo_read: true - oppo_write: true + oppo_command@instance: menu.top + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true osd: type: bool - oppo_command: menu.osd - oppo_read: true - oppo_write: true + oppo_command@instance: menu.osd + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pageup: type: bool - oppo_command: menu.pageup - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pageup + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true pagedown: type: bool - oppo_command: menu.pagedown - oppo_read: true - oppo_write: true + oppo_command@instance: menu.pagedown + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true up: type: bool - oppo_command: menu.up - oppo_read: true - oppo_write: true + oppo_command@instance: menu.up + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true down: type: bool - oppo_command: menu.down - oppo_read: true - oppo_write: true + oppo_command@instance: menu.down + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true left: type: bool - oppo_command: menu.left - oppo_read: true - oppo_write: true + oppo_command@instance: menu.left + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true right: type: bool - oppo_command: menu.right - oppo_read: true - oppo_write: true + oppo_command@instance: menu.right + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true select: type: bool - oppo_command: menu.select - oppo_read: true - oppo_write: true + oppo_command@instance: menu.select + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true back: type: bool - oppo_command: menu.back - oppo_read: true - oppo_write: true + oppo_command@instance: menu.back + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true red: type: bool - oppo_command: menu.red - oppo_read: true - oppo_write: true + oppo_command@instance: menu.red + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true green: type: bool - oppo_command: menu.green - oppo_read: true - oppo_write: true + oppo_command@instance: menu.green + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true blue: type: bool - oppo_command: menu.blue - oppo_read: true - oppo_write: true + oppo_command@instance: menu.blue + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true yellow: type: bool - oppo_command: menu.yellow - oppo_read: true - oppo_write: true + oppo_command@instance: menu.yellow + oppo_read@instance: true + oppo_write@instance: true enforce_updates: true plugin_functions: NONE logic_parameters: NONE diff --git a/panasonic_ac/__init__.py b/panasonic_ac/__init__.py new file mode 100644 index 000000000..ecf29536a --- /dev/null +++ b/panasonic_ac/__init__.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2024- Martin Sinn m.sinn@gmx.de +######################################################################### +# This file is part of SmartHomeNG. +# https://www.smarthomeNG.de +# https://knx-user-forum.de/forum/supportforen/smarthome-py +# +# Plugin to control Panasonic air conditioning systems via the +# Panasonic comfort cloud +# +# 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 os +import json + +from lib.model.smartplugin import SmartPlugin +from lib.item import Items + + +mapping_delimiter = '|' + +# optionally import package 'pcomfcloud' from local plugin folder +LOCAL_PACKAGE_IMPORTED = False +EXCEPTION_TEXT = '' +if os.path.isfile(os.path.join('plugins', 'panasonic_ac', 'packages', 'pcomfortcloud', '__init__.py')): + try: + import plugins.panasonic_ac.packages.pcomfortcloud as pcomfortcloud + LOCAL_PACKAGE_IMPORTED = True + except Exception as ex: + EXCEPTION_TEXT = str(ex) +else: + import pcomfortcloud + +from .webif import WebInterface + + +# If a needed package is imported, which might be not installed in the Python environment, +# add it to a requirements.txt file within the plugin's directory + + +class PanComfortCloud(SmartPlugin): + """ + Main class of the Plugin. Does all plugin specific stuff and provides + the update functions for the items + + HINT: Please have a look at the SmartPlugin class to see which + class properties and methods (class variables and class functions) + are already available! + """ + + PLUGIN_VERSION = '0.3.1' # (must match the version specified in plugin.yaml), use '1.0.0' for your initial plugin Release + + def __init__(self, sh): + """ + Initalizes the plugin. + + If you need the sh object at all, use the method self.get_sh() to get it. There should be almost no need for + a reference to the sh object any more. + + Plugins have to use the new way of getting parameter values: + use the SmartPlugin method get_parameter_value(parameter_name). Anywhere within the Plugin you can get + the configured (and checked) value for a parameter by calling self.get_parameter_value(parameter_name). It + returns the value in the datatype that is defined in the metadata. + """ + + # Call init code of parent class (SmartPlugin) + super().__init__() + + # cycle time in seconds, only needed, if hardware/interface needs to be + # polled for value changes by adding a scheduler entry in the run method of this plugin + # (maybe you want to make it a plugin parameter?) + self._cycle = 30 + + # if you want to use an item to toggle plugin execution, enable the + # definition in plugin.yaml and uncomment the following line + #self._pause_item_path = self.get_parameter_value('pause_item') + + # create the var directory for this plugin + var_plugin_dir = os.path.join(self.get_sh()._var_dir, self.get_shortname()) + try: + os.mkdir(var_plugin_dir, 0o775 ) + except FileExistsError: + pass + + # Initialization code goes here + self._username = self.get_parameter_value('username') + self._password = self.get_parameter_value('password') + + self._raw = False + self._token = os.path.join(var_plugin_dir, 'token.json') + self._skipVerify = False + self.session = None + + if LOCAL_PACKAGE_IMPORTED: + self.logger.notice("Using local installation of 'pcomfortcloud' package") + if EXCEPTION_TEXT != "": + self.logger.error(f"Exception while importing LOCAL package: {EXCEPTION_TEXT}") + self._init_complete = False + return + + if not self.pcc_login(): + # Do not load Plugin, if login fails due to wrong 'pcomfortcloud' package + self._init_complete = False + return + + self._devices = {} # devices reported by comfort cloud + + self.pcc_readdevicelist() + for idx, device in enumerate(self._devices): + index = idx + 1 + self.poll_device() + #self.logger.notice(f"- Device {index} status: {self._devices[str(index)]['parameters']}") + + + self.init_webinterface(WebInterface) + return + + + def _check_plugin_var_dir(self): + + var_dir = os.path.join(self.get_sh()._var_dir, self.get_shortname()) + try: + os.mkdir(var_dir, 0o775 ) + except FileExistsError: + pass + + + def pcc_getdevicestatus(self, index): + # index: 1, ... + if int(index) <= 0 or int(index) > len(self._devices): + self.logger.error(f"Device with index={index} not found in device list") + + id = self._devices[str(index)]['id'] + try: + device_status = self.session.get_device(id)['parameters'] + except Exception as ex: + device_status = {} + self.logger.dbghigh(f"- Status of device (index={index}) cannot be read - Exception {ex}") + return device_status + + + def pcc_readdevicelist(self): + + for idx, device in enumerate(self.session.get_devices()): + self._devices[str(idx+1)] = device + + + def pcc_login(self): + + if os.path.isfile(self._token): + with open(self._token, "r") as token_file: + json_token = json.load(token_file) + else: + json_token = None + + try: + auth = pcomfortcloud.Authentication( + self._username, self._password, + json_token, + self._raw + ) + except: + self.logger.error("The installed verson of 'pcomfortcloud' package is not compatible with this plugin") + self._init_complete = False + return + + try: + auth.login() + except Exception as ex: + self.logger.notice(f"Exception on auth.login: {ex}") + self.logger.notice(f"Retrying login without old token") + json_token = None + auth = pcomfortcloud.Authentication( + self._username, self._password, + json_token, + self._raw + ) + auth.login() + + json_token = auth.get_token() + self.session = pcomfortcloud.ApiClient( + auth, + self._raw + ) + with open(self._token, "w") as token_file: + json.dump(json_token, token_file, indent=4) + return True + + + def run(self): + """ + Run method for the plugin + """ + self.logger.dbghigh(self.translate("Methode '{method}' aufgerufen", {'method': 'run()'})) + + # connect to network / web / serial device + # (enable the following lines if you want to open a connection + # don't forget to implement a connect (and disconnect) method.. :) ) + #self.connect() + + # setup scheduler for device poll loop + # (enable the following line, if you need to poll the device. + # Rember to un-comment the self._cycle statement in __init__ as well) + self.scheduler_add(self.get_fullname() + '_poll', self.poll_device, cycle=self._cycle) + + # Start the asyncio eventloop in it's own thread + # and set self.alive to True when the eventloop is running + # (enable the following line, if you need to use asyncio in the plugin) + #self.start_asyncio(self.plugin_coro()) + + self.alive = True # if using asyncio, do not set self.alive here. Set it in the session coroutine + + # let the plugin change the state of pause_item + if self._pause_item: + self._pause_item(False, self.get_fullname()) + + # if you need to create child threads, do not make them daemon = True! + # They will not shutdown properly. (It's a python bug) + # Also, don't create the thread in __init__() and start them here, but + # create and start them here. Threads can not be restarted after they + # have been stopped... + + def stop(self): + """ + Stop method for the plugin + """ + self.logger.dbghigh(self.translate("Methode '{method}' aufgerufen", {'method': 'stop()'})) + self.alive = False # if using asyncio, do not set self.alive here. Set it in the session coroutine + + # let the plugin change the state of pause_item + if self._pause_item: + self._pause_item(True, self.get_fullname()) + + # this stops all schedulers the plugin has started. + # you can disable/delete the line if you don't use schedulers + self.scheduler_remove_all() + + # stop the asyncio eventloop and it's thread + # If you use asyncio, enable the following line + #self.stop_asyncio() + + # If you called connect() on run(), disconnect here + # (remember to write a disconnect() method!) + #self.disconnect() + + # also, clean up anything you set up in run(), so the plugin can be + # cleanly stopped and started again + + def parse_item(self, item): + """ + Default plugin parse_item method. Is called when the plugin is initialized. + The plugin can, corresponding to its attribute keywords, decide what to do with + the item in future, like adding it to an internal array for future reference + :param item: The item to process. + :return: If the plugin needs to be informed of an items change you should return a call back function + like the function update_item down below. An example when this is needed is the knx plugin + where parse_item returns the update_item function when the attribute knx_send is found. + This means that when the items value is about to be updated, the call back function is called + with the item, caller, source and dest as arguments and in case of the knx plugin the value + can be sent to the knx with a knx write function within the knx plugin. + """ + # check for pause item + if item.property.path == self._pause_item_path: + self.logger.debug(f'pause item {item.property.path} registered') + self._pause_item = item + self.add_item(item, updating=True) + return self.update_item + + if self.has_iattr(item.conf, 'pcc_index') and self.has_iattr(item.conf, 'pcc_parameter'): + index = str(self.get_iattr_value(item.conf, 'pcc_index')) + parameter = self.get_iattr_value(item.conf, 'pcc_parameter') + config_data = {} + config_data['index'] = index + config_data['parameter'] = parameter + config_data['item'] = item + mapping = config_data['index'] + mapping_delimiter + config_data['parameter'] + self.add_item(item, mapping=mapping, config_data_dict=config_data, updating=True) + return self.update_item + + def parse_logic(self, logic): + """ + Default plugin parse_logic method + """ + if 'xxx' in logic.conf: + # self.function(logic['name']) + pass + + def update_item(self, item, caller=None, source=None, dest=None): + """ + Item has been updated + + This method is called, if the value of an item has been updated by SmartHomeNG. + It should write the changed value out to the device (hardware/interface) that + is managed by this plugin. + + To prevent a loop, the changed value should only be written to the device, if the plugin is running and + the value was changed outside of this plugin(-instance). That is checked by comparing the caller parameter + with the fullname (plugin name & instance) of the plugin. + + :param item: item to be updated towards the plugin + :param caller: if given it represents the callers name + :param source: if given it represents the source + :param dest: if given it represents the dest + """ + # check for pause item + if item is self._pause_item: + if caller != self.get_shortname(): + self.logger.debug(f'pause item changed to {item()}') + if item() and self.alive: + self.stop() + elif not item() and not self.alive: + self.run() + return + + if self.alive and caller != self.get_fullname(): + # code to execute if the plugin is not stopped + # and only, if the item has not been changed by this plugin: + self.logger.dbghigh(f"update_item: '{item.property.path}' has been changed outside this plugin by caller '{self.callerinfo(caller, source)}'") + + config_data = self.get_item_config(item) + #self.logger.notice(f"update_item: Sending '{item()}' of '{config_data['item']}' to comfort cloud -> {config_data=}") + + self.update_pcc_from_item(config_data, item) + + return + + + def update_pcc_from_item(self, config_data, item): + value = item() + #self.logger.notice(f"update_pcc_from_item: config_data = {config_data}") + + try: + index = config_data['index'] + kwargs = {} + if config_data['parameter'] == 'temperature': + kwargs['temperature'] = float(value) + self.logger.info(f" -> Device {index}: Setting '{config_data['parameter']}' to {value}°C") + else: + if config_data['parameter'] == 'power': + kwargs['power'] = pcomfortcloud.constants.Power(int(value)) + elif config_data['parameter'] == 'mode': + kwargs['mode'] = pcomfortcloud.constants.OperationMode(int(value)) + elif config_data['parameter'] == 'fanSpeed': + kwargs['fanSpeed'] = pcomfortcloud.constants.FanSpeed(int(value)) + elif config_data['parameter'] == 'airSwingHorizontal': + kwargs['airSwingHorizontal'] = pcomfortcloud.constants.AirSwingLR(int(value)) + elif config_data['parameter'] == 'airSwingVertical': + kwargs['airSwingVertical'] = pcomfortcloud.constants.AirSwingUD(int(value)) + elif config_data['parameter'] == 'eco': + kwargs['eco'] = pcomfortcloud.constants.EcoMode(int(value)) + elif config_data['parameter'] == 'nanoe': + kwargs['nanoe'] = pcomfortcloud.constants.NanoeMode(int(value)) + else: + self.logger.notice(f"update_pcc_from_item: Updating the parameter {config_data['parameter']} is not supported/implemented") + if kwargs != {}: + self.logger.info(f" -> Device {index=}: Setting '{config_data['parameter']}' to {value} ({kwargs[config_data['parameter']].name})") + if kwargs != {}: + self.session.set_device(self._devices[config_data['index']]['id'], **kwargs) + except Exception as ex: + self.logger.warning(f"Cannot set parameter '{config_data['parameter']}' of device index={config_data['index']} - Exception: {ex}") + + return + + + def update_items_with_mapping(self, mapping_root, mapping_key, value=None, value_enum=None): + + update_items = self.get_items_for_mapping(mapping_root + mapping_key) + if value_enum is None: + for item in update_items: + item(value, self.get_fullname()) + else: + val_num = int(value_enum.value) + val_str = value_enum.name + for item in update_items: + if item.property.type == 'num': + item(val_num, self.get_fullname()) + else: + item(val_str, self.get_fullname()) + + + def poll_device(self): + """ + Polls for updates of the device + + This method is only needed, if the device (hardware/interface) does not propagate + changes on it's own, but has to be polled to get the actual status. + It is called by the scheduler which is set within run() method. + """ + for idx, device in enumerate(self._devices): + index = idx + 1 + self._devices[str(index)]['parameters'] = self.pcc_getdevicestatus(index) + + # Items updaten + mapping_root = str(index) + mapping_delimiter + self.update_items_with_mapping(mapping_root, 'name', self._devices[str(index)]['name']) + if self._devices[str(index)]['parameters'] != {}: + self.update_items_with_mapping(mapping_root, 'connected', True) + for key in self._devices[str(index)]['parameters'].keys(): + try: + value = int(self._devices[str(index)]['parameters'][key].value) + self.update_items_with_mapping(mapping_root, key, value_enum=self._devices[str(index)]['parameters'][key]) + except: + value = self._devices[str(index)]['parameters'][key] + self.update_items_with_mapping(mapping_root, key, value) + else: + self.update_items_with_mapping(mapping_root, 'connected', False) + + async def plugin_coro(self): + """ + Coroutine for the plugin session (only needed, if using asyncio) + + This coroutine is run as the PluginTask and should + only terminate, when the plugin is stopped + """ + self.logger.notice("plugin_coro started") + + self.alive = True + + # ... + + self.alive = False + + self.logger.notice("plugin_coro finished") + return diff --git a/panasonic_ac/assets/webif_tab1.jpg b/panasonic_ac/assets/webif_tab1.jpg new file mode 100644 index 000000000..68dc0ea23 Binary files /dev/null and b/panasonic_ac/assets/webif_tab1.jpg differ diff --git a/panasonic_ac/assets/webif_tab2.jpg b/panasonic_ac/assets/webif_tab2.jpg new file mode 100644 index 000000000..89790fb92 Binary files /dev/null and b/panasonic_ac/assets/webif_tab2.jpg differ diff --git a/panasonic_ac/assets/webif_tab3.jpg b/panasonic_ac/assets/webif_tab3.jpg new file mode 100644 index 000000000..90cbbd700 Binary files /dev/null and b/panasonic_ac/assets/webif_tab3.jpg differ diff --git a/panasonic_ac/packages/pcomfortcloud/__init__.py b/panasonic_ac/packages/pcomfortcloud/__init__.py new file mode 100644 index 000000000..6d3dc9992 --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/__init__.py @@ -0,0 +1,32 @@ +""" +A python module for reading and changing status of panasonic climate devices through Panasonic Comfort Cloud app api +""" + +__all__ = [ + 'ApiClient', + 'Error', + 'LoginError', + 'RequestError', + 'ResponseError' +] + +from .apiclient import ( + ApiClient +) + +from .session import ( + Session +) + +from .authentication import ( + Authentication +) + +from .exceptions import ( + Error, + LoginError, + RequestError, + ResponseError +) + +from . import constants diff --git a/panasonic_ac/packages/pcomfortcloud/__main__.py b/panasonic_ac/packages/pcomfortcloud/__main__.py new file mode 100644 index 000000000..984020ddc --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/__main__.py @@ -0,0 +1,299 @@ +import argparse +import os +import json +import pcomfortcloud + +from enum import Enum + +def print_result(obj, indent=0): + for key in obj: + value = obj[key] + + if isinstance(value, dict): + print(" "*indent + key) + print_result(value, indent + 4) + elif isinstance(value, Enum): + print( + " "*indent + "{0: <{width}}: {1}".format(key, value.name, width=25-indent)) + elif isinstance(value, list): + print(" "*indent + "{0: <{width}}:".format(key, width=25-indent)) + for elt in value: + print_result(elt, indent + 4) + print("") + else: + print(" "*indent + + "{0: <{width}}: {1}".format(key, value, width=25-indent)) + + +def str2bool(boolean_string_value): + if boolean_string_value.lower() in ('yes', 'true', 't', 'y', '1'): + return True + if boolean_string_value.lower() in ('no', 'false', 'f', 'n', '0'): + return False + raise argparse.ArgumentTypeError('Boolean value expected.') + + +def main(): + """ Start pcomfortcloud Comfort Cloud command line """ + + parser = argparse.ArgumentParser( + description='Read or change status of pcomfortcloud Climate devices') + + parser.add_argument( + 'username', + help='Username for pcomfortcloud Comfort Cloud') + + parser.add_argument( + 'password', + help='Password for pcomfortcloud Comfort Cloud') + + parser.add_argument( + '-t', '--token', + help='File to store token in', + default='token.json') + + parser.add_argument( + '-r', '--raw', + help='Raw dump of response', + type=str2bool, nargs='?', const=True, + default=False) + + commandparser = parser.add_subparsers( + help='commands', + dest='command') + + commandparser.add_parser( + 'list', + help="Get a list of all devices") + + get_parser = commandparser.add_parser( + 'get', + help="Get status of a device") + + get_parser.add_argument( + dest='device', + type=int, + help='Device number #') + + set_parser = commandparser.add_parser( + 'set', + help="Set status of a device") + + set_parser.add_argument( + dest='device', + type=int, + help='Device number #' + ) + + set_parser.add_argument( + '-p', '--power', + choices=[ + pcomfortcloud.constants.Power.On.name, + pcomfortcloud.constants.Power.Off.name], + help='Power mode') + + set_parser.add_argument( + '-t', '--temperature', + type=float, + help="Temperature") + + set_parser.add_argument( + '-f', '--fanSpeed', + choices=[ + pcomfortcloud.constants.FanSpeed.Auto.name, + pcomfortcloud.constants.FanSpeed.Low.name, + pcomfortcloud.constants.FanSpeed.LowMid.name, + pcomfortcloud.constants.FanSpeed.Mid.name, + pcomfortcloud.constants.FanSpeed.HighMid.name, + pcomfortcloud.constants.FanSpeed.High.name], + help='Fan speed') + + set_parser.add_argument( + '-m', '--mode', + choices=[ + pcomfortcloud.constants.OperationMode.Auto.name, + pcomfortcloud.constants.OperationMode.Cool.name, + pcomfortcloud.constants.OperationMode.Dry.name, + pcomfortcloud.constants.OperationMode.Heat.name, + pcomfortcloud.constants.OperationMode.Fan.name], + help='Operation mode') + + set_parser.add_argument( + '-e', '--eco', + choices=[ + pcomfortcloud.constants.EcoMode.Auto.name, + pcomfortcloud.constants.EcoMode.Quiet.name, + pcomfortcloud.constants.EcoMode.Powerful.name], + help='Eco mode') + + set_parser.add_argument( + '-n', '--nanoe', + choices=[ + pcomfortcloud.constants.NanoeMode.On.name, + pcomfortcloud.constants.NanoeMode.Off.name, + pcomfortcloud.constants.NanoeMode.ModeG.name, + pcomfortcloud.constants.NanoeMode.All.name], + help='Nanoe mode') + + # set_parser.add_argument( + # '--airswingauto', + # choices=[ + # pcomfortcloud.constants.AirSwingAutoMode.Disabled.name, + # pcomfortcloud.constants.AirSwingAutoMode.AirSwingLR.name, + # pcomfortcloud.constants.AirSwingAutoMode.AirSwingUD.name, + # pcomfortcloud.constants.AirSwingAutoMode.Both.name], + # help='Automation of air swing') + + set_parser.add_argument( + '-y', '--airSwingVertical', + choices=[ + pcomfortcloud.constants.AirSwingUD.Auto.name, + pcomfortcloud.constants.AirSwingUD.Down.name, + pcomfortcloud.constants.AirSwingUD.DownMid.name, + pcomfortcloud.constants.AirSwingUD.Mid.name, + pcomfortcloud.constants.AirSwingUD.UpMid.name, + pcomfortcloud.constants.AirSwingUD.Up.name], + help='Vertical position of the air swing') + + set_parser.add_argument( + '-x', '--airSwingHorizontal', + choices=[ + pcomfortcloud.constants.AirSwingLR.Auto.name, + pcomfortcloud.constants.AirSwingLR.Left.name, + pcomfortcloud.constants.AirSwingLR.LeftMid.name, + pcomfortcloud.constants.AirSwingLR.Mid.name, + pcomfortcloud.constants.AirSwingLR.RightMid.name, + pcomfortcloud.constants.AirSwingLR.Right.name], + help='Horizontal position of the air swing') + + dump_parser = commandparser.add_parser( + 'dump', + help="Dump data of a device") + + dump_parser.add_argument( + dest='device', + type=int, + help='Device number 1-x') + + history_parser = commandparser.add_parser( + 'history', + help="Dump history of a device") + + history_parser.add_argument( + dest='device', + type=int, + help='Device number 1-x') + + history_parser.add_argument( + dest='mode', + type=str, + help='mode (Day, Week, Month, Year)') + + history_parser.add_argument( + dest='date', + type=str, + help='date of day like 20190807') + + args = parser.parse_args() + + if os.path.isfile(args.token): + with open(args.token, "r") as token_file: + json_token = json.load(token_file) + else: + json_token = None + + auth = pcomfortcloud.Authentication( + args.username, + args.password, + json_token, + args.raw + ) + auth.login() + + json_token = auth.get_token() + + session = pcomfortcloud.ApiClient( + auth, + args.raw + ) + + with open(args.token, "w") as token_file: + json.dump(json_token, token_file, indent=4) + + try: + if args.command == 'list': + print("list of devices and its device id (1-x)") + for idx, device in enumerate(session.get_devices()): + if idx > 0: + print('') + + print("device #{}".format(idx + 1)) + print_result(device, 4) + + if args.command == 'get': + if int(args.device) <= 0 or int(args.device) > len(session.get_devices()): + raise Exception("device not found, acceptable device id is from {} to {}".format(1, len(session.get_devices()))) + + device = session.get_devices()[int(args.device) - 1] + print("reading from device '{}' ({})".format( + device['name'], device['id'])) + + print_result(session.get_device(device['id'])) + + if args.command == 'set': + if int(args.device) <= 0 or int(args.device) > len(session.get_devices()): + raise Exception("device not found, acceptable device id is from {} to {}".format(1, len(session.get_devices()))) + + device = session.get_devices()[int(args.device) - 1] + print("writing to device '{}' ({})".format(device['name'], device['id'])) + + kwargs = {} + + if args.power is not None: + kwargs['power'] = pcomfortcloud.constants.Power[args.power] + + if args.temperature is not None: + kwargs['temperature'] = args.temperature + + if args.fanSpeed is not None: + kwargs['fanSpeed'] = pcomfortcloud.constants.FanSpeed[args.fanSpeed] + + if args.mode is not None: + kwargs['mode'] = pcomfortcloud.constants.OperationMode[args.mode] + + if args.eco is not None: + kwargs['eco'] = pcomfortcloud.constants.EcoMode[args.eco] + + if args.nanoe is not None: + kwargs['nanoe'] = pcomfortcloud.constants.NanoeMode[args.nanoe] + + if args.airSwingHorizontal is not None: + kwargs['airSwingHorizontal'] = pcomfortcloud.constants.AirSwingLR[args.airSwingHorizontal] + + if args.airSwingVertical is not None: + kwargs['airSwingVertical'] = pcomfortcloud.constants.AirSwingUD[args.airSwingVertical] + + session.set_device(device['id'], **kwargs) + + if args.command == 'dump': + if int(args.device) <= 0 or int(args.device) > len(session.get_devices()): + raise Exception("device not found, acceptable device id is from {} to {}".format(1, len(session.get_devices()))) + + device = session.get_devices()[int(args.device) - 1] + + print_result(session.dump(device['id'])) + + if args.command == 'history': + if int(args.device) <= 0 or int(args.device) > len(session.get_devices()): + raise Exception("device not found, acceptable device id is from {} to {}".format(1, len(session.get_devices()))) + + device = session.get_devices()[int(args.device) - 1] + + print_result(session.history(device['id'], args.mode, args.date)) + + except pcomfortcloud.ResponseError as ex: + print(ex) + + +if __name__ == "__main__": + main() diff --git a/panasonic_ac/packages/pcomfortcloud/apiclient.py b/panasonic_ac/packages/pcomfortcloud/apiclient.py new file mode 100644 index 000000000..31ff6a619 --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/apiclient.py @@ -0,0 +1,276 @@ +''' +Panasonic session, using Panasonic Comfort Cloud app api +''' + +import hashlib +import re +from urllib.parse import quote_plus + +from . import authentication +from . import constants + + +class ApiClient(): + def __init__(self, auth: authentication.Authentication, raw=False): + self._auth = auth + + self._groups = None + self._devices = None + self._device_indexer = {} + self._raw = raw + self._acc_client_id = None + + def _ensure_logged_in(self): + self._auth.login() + + def _get_groups(self): + self._ensure_logged_in() + self._groups = self._auth.execute_get( + self._get_group_url(), + "get_groups", + 200 + ) + self._devices = None + + def get_devices(self): + if self._devices is None: + if self._groups is None: + self._get_groups() + + self._devices = [] + + for group in self._groups['groupList']: + if 'deviceList' in group: + device_list = group.get('deviceList', []) + else: + device_list = group.get('deviceIdList', []) + + for device in device_list: + if device: + if 'deviceHashGuid' in device: + device_id = device['deviceHashGuid'] + else: + device_id = hashlib.md5( + device['deviceGuid'].encode('utf-8')).hexdigest() + + self._device_indexer[device_id] = device['deviceGuid'] + self._devices.append({ + 'id': device_id, + 'name': device['deviceName'], + 'group': group['groupName'], + 'model': device['deviceModuleNumber'] if 'deviceModuleNumber' in device else '' + }) + return self._devices + + def dump(self, device_id): + device_guid = self._device_indexer.get(device_id) + if device_guid: + return self._auth.execute_get(self._get_device_status_url(device_guid), "dump", 200) + return None + + def history(self, device_id, mode, date, time_zone="+01:00"): + self._ensure_logged_in() + + device_guid = self._device_indexer.get(device_id) + + if device_guid: + try: + data_mode = constants.DataMode[mode].value + except KeyError: + raise Exception("Wrong mode parameter") + + payload = { + "deviceGuid": device_guid, + "dataMode": data_mode, + "date": date, + "osTimezone": time_zone + } + + json_response = self._auth.execute_post( + self._get_device_history_url(), payload, "history", 200) + + return { + 'id': device_id, + 'parameters': self._read_parameters(json_response) + } + return None + + def get_device(self, device_id): + self._ensure_logged_in() + + device_guid = self._device_indexer.get(device_id) + + if device_guid: + json_response = self._auth.execute_get( + self._get_device_status_url(device_guid), "get_device", 200) + return { + 'id': device_id, + 'parameters': self._read_parameters(json_response['parameters']) + } + return None + + def set_device(self, device_id, **kwargs): + """ Set parameters of device + + Args: + device_id (str): Id of the device + kwargs : {temperature=float}, {mode=OperationMode}, {fanSpeed=FanSpeed}, {power=Power}, + {airSwingHorizontal=}, {airSwingVertical=}, {eco=EcoMode} + """ + + parameters = {} + air_x = None + air_y = None + + if kwargs is not None: + for key, value in kwargs.items(): + if key == 'power' and isinstance(value, constants.Power): + parameters['operate'] = value.value + + if key == 'temperature': + parameters['temperatureSet'] = value + + if key == 'mode' and isinstance(value, constants.OperationMode): + parameters['operationMode'] = value.value + + if key == 'fanSpeed' and isinstance(value, constants.FanSpeed): + parameters['fanSpeed'] = value.value + + if key == 'airSwingHorizontal' and isinstance(value, constants.AirSwingLR): + air_x = value + + if key == 'airSwingVertical' and isinstance(value, constants.AirSwingUD): + air_y = value + + if key == 'eco' and isinstance(value, constants.EcoMode): + parameters['ecoMode'] = value.value + + if key == 'nanoe' and \ + isinstance(value, constants.NanoeMode) and \ + value != constants.NanoeMode.Unavailable: + parameters['nanoe'] = value.value + + # routine to set the auto mode of fan + # (either horizontal, vertical, both or disabled) + if air_x is not None or air_y is not None: + fan_auto = 0 + device = self.get_device(device_id) + + if device and device['parameters']['airSwingHorizontal'].value == -1: + fan_auto = fan_auto | 1 + + if device and device['parameters']['airSwingVertical'].value == -1: + fan_auto = fan_auto | 2 + + if air_x is not None: + if air_x.value == -1: + fan_auto = fan_auto | 1 + else: + fan_auto = fan_auto & ~1 + parameters['airSwingLR'] = air_x.value + + if air_y is not None: + if air_y.value == -1: + fan_auto = fan_auto | 2 + else: + fan_auto = fan_auto & ~2 + print(air_y.name) + parameters['airSwingUD'] = air_y.value + + if fan_auto == 3: + parameters['fanAutoMode'] = constants.AirSwingAutoMode.Both.value + elif fan_auto == 1: + parameters['fanAutoMode'] = constants.AirSwingAutoMode.AirSwingLR.value + elif fan_auto == 2: + parameters['fanAutoMode'] = constants.AirSwingAutoMode.AirSwingUD.value + else: + parameters['fanAutoMode'] = constants.AirSwingAutoMode.Disabled.value + + device_guid = self._device_indexer.get(device_id) + if device_guid: + payload = { + "deviceGuid": device_guid, + "parameters": parameters + } + _ = self._auth.execute_post( + self._get_device_status_control_url(), payload, "set_device", 200) + return True + return False + + def _read_parameters(self, parameters=dict()): + value = dict() + + _convert = { + 'insideTemperature': 'temperatureInside', + 'outTemperature': 'temperatureOutside', + 'temperatureSet': 'temperature', + 'currencyUnit': 'currencyUnit', + 'energyConsumption': 'energyConsumption', + 'estimatedCost': 'estimatedCost', + 'historyDataList': 'historyDataList', + } + for key in _convert: + if key in parameters: + value[_convert[key]] = parameters[key] + + if 'operate' in parameters: + value['power'] = constants.Power(parameters['operate']) + + if 'operationMode' in parameters: + value['mode'] = constants.OperationMode( + parameters['operationMode']) + + if 'fanSpeed' in parameters: + value['fanSpeed'] = constants.FanSpeed(parameters['fanSpeed']) + + if 'airSwingLR' in parameters: + value['airSwingHorizontal'] = constants.AirSwingLR( + parameters['airSwingLR']) + + if 'airSwingUD' in parameters: + value['airSwingVertical'] = constants.AirSwingUD( + parameters['airSwingUD']) + + if 'ecoMode' in parameters: + value['eco'] = constants.EcoMode(parameters['ecoMode']) + + if 'nanoe' in parameters: + value['nanoe'] = constants.NanoeMode(parameters['nanoe']) + + if 'fanAutoMode' in parameters: + if parameters['fanAutoMode'] == constants.AirSwingAutoMode.Both.value: + value['airSwingHorizontal'] = constants.AirSwingLR.Auto + value['airSwingVertical'] = constants.AirSwingUD.Auto + elif parameters['fanAutoMode'] == constants.AirSwingAutoMode.AirSwingLR.value: + value['airSwingHorizontal'] = constants.AirSwingLR.Auto + elif parameters['fanAutoMode'] == constants.AirSwingAutoMode.AirSwingUD.value: + value['airSwingVertical'] = constants.AirSwingUD.Auto + + return value + + def _get_group_url(self): + return '{base_url}/device/group'.format( + base_url=constants.BASE_PATH_ACC + ) + + def _get_device_status_url(self, guid): + return '{base_url}/deviceStatus/{guid}'.format( + base_url=constants.BASE_PATH_ACC, + guid=re.sub(r'(?i)\%2f', 'f', quote_plus(guid)) + ) + + def _get_device_status_now_url(self, guid): + return '{base_url}/deviceStatus/now/{guid}'.format( + base_url=constants.BASE_PATH_ACC, + guid=re.sub(r'(?i)\%2f', 'f', quote_plus(guid)) + ) + + def _get_device_status_control_url(self): + return '{base_url}/deviceStatus/control'.format( + base_url=constants.BASE_PATH_ACC + ) + + def _get_device_history_url(self): + return '{base_url}/deviceHistoryData'.format( + base_url=constants.BASE_PATH_ACC, + ) diff --git a/panasonic_ac/packages/pcomfortcloud/authentication.py b/panasonic_ac/packages/pcomfortcloud/authentication.py new file mode 100644 index 000000000..1d5e47d8f --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/authentication.py @@ -0,0 +1,419 @@ +import base64 +import datetime +import hashlib +import json +import random +import string +import time +import urllib +import requests + +from bs4 import BeautifulSoup +from . import exceptions +from . import constants + +def generate_random_string(length): + return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length)) + + +def generate_random_string_hex(length): + return ''.join(random.choice(string.hexdigits) for _ in range(length)) + + +def check_response(response, function_description, expected_status): + if response.status_code != expected_status: + raise exceptions.ResponseError( + f"({function_description}: Expected status code {expected_status}, received: {response.status_code}: " + + f"{response.text}" + ) + + +def get_querystring_parameter_from_header_entry_url(response, header_entry, querystring_parameter): + header_entry_value = response.headers[header_entry] + parsed_url = urllib.parse.urlparse(header_entry_value) + params = urllib.parse.parse_qs(parsed_url.query) + return params.get(querystring_parameter, [None])[0] + + +class Authentication(): + # token: + # - access_token + # - refresh_token + # - id_token + # - unix_timestamp_token_received + # - expires_in_sec + # - acc_client_id + # - scope + + def __init__(self, username, password, token, raw=False): + self._username = username + self._password = password + self._token = token + self._raw = raw + self._app_version = constants.X_APP_VERSION + self._update_app_version() + + def _check_token_is_valid(self): + if self._raw: + print("--- Checking token is valid") + if self._token is not None: + now = datetime.datetime.now() + now_unix = time.mktime(now.timetuple()) + + # multiple parts in access_token which are separated by . + part_of_token_b64 = str(self._token["access_token"].split(".")[1]) + # as seen here: https://stackoverflow.com/questions/3302946/how-to-decode-base64-url-in-python + part_of_token = base64.urlsafe_b64decode( + part_of_token_b64 + '=' * (4 - len(part_of_token_b64) % 4)) + token_info_json = json.loads(part_of_token) + + if self._raw: + print(json.dumps(token_info_json, indent=4)) + + expiry_in_token = token_info_json["exp"] + + if (now_unix > expiry_in_token) or \ + (now_unix > self._token["unix_timestamp_token_received"] + self._token["expires_in_sec"]): + return False + return True + else: + return False + + def _get_new_token(self): + requests_session = requests.Session() + + # generate initial state and code_challenge + state = generate_random_string(20) + code_verifier = generate_random_string(43) + + code_challenge = base64.urlsafe_b64encode( + hashlib.sha256( + code_verifier.encode('utf-8') + ).digest()).split('='.encode('utf-8'))[0].decode('utf-8') + + # -------------------------------------------------------------------- + # AUTHORIZE + # -------------------------------------------------------------------- + + response = requests_session.get( + f'{constants.BASE_PATH_AUTH}/authorize', + headers={ + "user-agent": "okhttp/4.10.0", + }, + params={ + "scope": "openid offline_access comfortcloud.control a2w.control", + "audience": f"https://digital.panasonic.com/{constants.APP_CLIENT_ID}/api/v1/", + "protocol": "oauth2", + "response_type": "code", + "code_challenge": code_challenge, + "code_challenge_method": "S256", + "auth0Client": constants.AUTH_0_CLIENT, + "client_id": constants.APP_CLIENT_ID, + "redirect_uri": constants.REDIRECT_URI, + "state": state, + }, + allow_redirects=False) + check_response(response, 'authorize', 302) + + # ------------------------------------------------------------------- + # FOLLOW REDIRECT + # ------------------------------------------------------------------- + + location = response.headers['Location'] + state = get_querystring_parameter_from_header_entry_url( + response, 'Location', 'state') + + if not location.startswith(constants.REDIRECT_URI): + response = requests_session.get( + f"{constants.BASE_PATH_AUTH}/{location}", + allow_redirects=False) + check_response(response, 'authorize_redirect', 200) + + # get the "_csrf" cookie + csrf = response.cookies['_csrf'] + + # ------------------------------------------------------------------- + # LOGIN + # ------------------------------------------------------------------- + + response = requests_session.post( + f'{constants.BASE_PATH_AUTH}/usernamepassword/login', + headers={ + "Auth0-Client": constants.AUTH_0_CLIENT, + "user-agent": "okhttp/4.10.0", + }, + json={ + "client_id": constants.APP_CLIENT_ID, + "redirect_uri": constants.REDIRECT_URI, + "tenant": "pdpauthglb-a1", + "response_type": "code", + "scope": "openid offline_access comfortcloud.control a2w.control", + "audience": f"https://digital.panasonic.com/{constants.APP_CLIENT_ID}/api/v1/", + "_csrf": csrf, + "state": state, + "_intstate": "deprecated", + "username": self._username, + "password": self._password, + "lang": "en", + "connection": "PanasonicID-Authentication" + }, + allow_redirects=False) + check_response(response, 'login', 200) + + # ------------------------------------------------------------------- + # CALLBACK + # ------------------------------------------------------------------- + + # get wa, wresult, wctx from body + soup = BeautifulSoup(response.content, "html.parser") + input_lines = soup.find_all("input", {"type": "hidden"}) + parameters = dict() + for input_line in input_lines: + parameters[input_line.get("name")] = input_line.get("value") + + user_agent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 " + user_agent += "(KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36" + + response = requests_session.post( + url=f"{constants.BASE_PATH_AUTH}/login/callback", + data=parameters, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": user_agent, + }, + allow_redirects=False) + check_response(response, 'login_callback', 302) + + # ------------------------------------------------------------------ + # FOLLOW REDIRECT + # ------------------------------------------------------------------ + + location = response.headers['Location'] + + response = requests_session.get( + f"{constants.BASE_PATH_AUTH}/{location}", + allow_redirects=False) + check_response(response, 'login_redirect', 302) + + # ------------------------------------------------------------------ + # GET TOKEN + # ------------------------------------------------------------------ + + code = get_querystring_parameter_from_header_entry_url( + response, 'Location', 'code') + + # do before, so that timestamp is older rather than newer + now = datetime.datetime.now() + unix_time_token_received = time.mktime(now.timetuple()) + + response = requests_session.post( + f'{constants.BASE_PATH_AUTH}/oauth/token', + headers={ + "Auth0-Client": constants.AUTH_0_CLIENT, + "user-agent": "okhttp/4.10.0", + }, + json={ + "scope": "openid", + "client_id": constants.APP_CLIENT_ID, + "grant_type": "authorization_code", + "code": code, + "redirect_uri": constants.REDIRECT_URI, + "code_verifier": code_verifier + }, + allow_redirects=False) + check_response(response, 'get_token', 200) + + token_response = json.loads(response.text) + + # ------------------------------------------------------------------ + # RETRIEVE ACC_CLIENT_ID + # ------------------------------------------------------------------ + now = datetime.datetime.now() + timestamp = now.strftime("%Y-%m-%d %H:%M:%S") + response = requests.post( + f'{constants.BASE_PATH_ACC}/auth/v2/login', + headers={ + "Content-Type": "application/json;charset=utf-8", + "User-Agent": "G-RAC", + "x-app-name": "Comfort Cloud", + "x-app-timestamp": timestamp, + "x-app-type": "1", + "x-app-version": self._app_version, + "x-cfc-api-key": generate_random_string_hex(128), + "x-user-authorization-v2": "Bearer " + token_response["access_token"] + }, + json={ + "language": 0 + }) + check_response(response, 'get_acc_client_id', 200) + + json_body = json.loads(response.text) + acc_client_id = json_body["clientId"] + + self._token = { + "access_token": token_response["access_token"], + "refresh_token": token_response["refresh_token"], + "id_token": token_response["id_token"], + "unix_timestamp_token_received": unix_time_token_received, + "expires_in_sec": token_response["expires_in"], + "acc_client_id": acc_client_id, + "scope": token_response["scope"] + } + + def get_token(self): + return self._token + + def login(self): + if self._token is not None: + if not self._check_token_is_valid(): + self._refresh_token() + else: + self._get_new_token() + + def logout(self): + response = requests.post( + f"{constants.BASE_PATH_ACC}/auth/v2/logout", + headers=self._get_header_for_api_calls() + ) + check_response(response, "logout", 200) + if json.loads(response.text)["result"] != 0: + # issue during logout, but do we really care? + pass + + def _refresh_token(self): + # do before, so that timestamp is older rather than newer + now = datetime.datetime.now() + unix_time_token_received = time.mktime(now.timetuple()) + + response = requests.post( + f'{constants.BASE_PATH_AUTH}/oauth/token', + headers={ + "Auth0-Client": constants.AUTH_0_CLIENT, + "user-agent": "okhttp/4.10.0", + }, + json={ + "scope": self._token["scope"], + "client_id": constants.APP_CLIENT_ID, + "refresh_token": self._token["refresh_token"], + "grant_type": "refresh_token" + }, + allow_redirects=False) + check_response(response, 'refresh_token', 200) + token_response = json.loads(response.text) + + self._token = { + "access_token": token_response["access_token"], + "refresh_token": token_response["refresh_token"], + "id_token": token_response["id_token"], + "unix_timestamp_token_received": unix_time_token_received, + "expires_in_sec": token_response["expires_in"], + "acc_client_id": self._token["acc_client_id"], + "scope": token_response["scope"] + } + + def _get_header_for_api_calls(self, no_client_id=False): + now = datetime.datetime.now() + timestamp = now.strftime("%Y-%m-%d %H:%M:%S") + return { + "Content-Type": "application/json;charset=utf-8", + "x-app-name": "Comfort Cloud", + "user-agent": "G-RAC", + "x-app-timestamp": timestamp, + "x-app-type": "1", + "x-app-version": self._app_version, + # Seems to work by either setting X-CFC-API-KEY to 0 or to a 128-long hex string + # "X-CFC-API-KEY": "0", + "x-cfc-api-key": generate_random_string_hex(128), + "x-client-id": self._token["acc_client_id"], + "x-user-authorization-v2": "Bearer " + self._token["access_token"] + } + + def _get_user_info(self): + response = requests.get( + f'{constants.BASE_PATH_AUTH}/userinfo', + headers={ + "Auth0-Client": self.AUTH_0_CLIENT, + "Authorization": "Bearer " + self._token["access_token"] + }) + check_response(response, 'userinfo', 200) + + def execute_post(self, + url, + json_data, + function_description, + expected_status_code): + self._ensure_valid_token() + + try: + response = requests.post( + url, + json=json_data, + headers=self._get_header_for_api_calls() + ) + except requests.exceptions.RequestException as ex: + raise exceptions.RequestError(ex) + + self._print_response_if_raw_is_set(response, function_description) + check_response(response, function_description, expected_status_code) + return json.loads(response.text) + + def execute_get(self, url, function_description, expected_status_code): + self._ensure_valid_token() + + try: + response = requests.get( + url, + headers=self._get_header_for_api_calls() + ) + except requests.exceptions.RequestException as ex: + raise exceptions.RequestError(ex) + + self._print_response_if_raw_is_set(response, function_description) + check_response(response, function_description, expected_status_code) + return json.loads(response.text) + + def _print_response_if_raw_is_set(self, response, function_description): + if self._raw: + print("=" * 79) + print(f"Response: {function_description}") + print("=" * 79) + print(f"Status: {response.status_code}") + print("-" * 79) + print("Headers:") + for header in response.headers: + print(f'{header}: {response.headers[header]}') + print("-" * 79) + print("Response body:") + print(response.text) + print("-" * 79) + + def _ensure_valid_token(self): + if self._check_token_is_valid(): + return + self._refresh_token() + + def _update_app_version(self): + if self._raw: + print("--- auto detecting latest app version") + try: + response = requests.get(constants.APPBRAIN_URL) + responseContent = response.content + soup = BeautifulSoup(responseContent, "html.parser") + meta_tag = soup.find("meta", itemprop="softwareVersion") + if meta_tag is not None: + version = meta_tag['content'] + self._app_version = version + if self._raw: + print("--- found version: {}".format(self._app_version)) + return + else: + self._app_version = constants.X_APP_VERSION + print("--- Error finding meta_tag") + return + + except Exception: + self._app_version = constants.X_APP_VERSION + if self._raw: + print("--- failed to detect app version using version default") + pass + diff --git a/panasonic_ac/packages/pcomfortcloud/constants.py b/panasonic_ac/packages/pcomfortcloud/constants.py new file mode 100644 index 000000000..9b999d1a7 --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/constants.py @@ -0,0 +1,69 @@ +from enum import Enum + +APP_CLIENT_ID = "Xmy6xIYIitMxngjB2rHvlm6HSDNnaMJx" +AUTH_0_CLIENT = "eyJuYW1lIjoiQXV0aDAuQW5kcm9pZCIsImVudiI6eyJhbmRyb2lkIjoiMzAifSwidmVyc2lvbiI6IjIuOS4zIn0=" +REDIRECT_URI = "panasonic-iot-cfc://authglb.digital.panasonic.com/android/com.panasonic.ACCsmart/callback" +BASE_PATH_AUTH = "https://authglb.digital.panasonic.com" +BASE_PATH_ACC = "https://accsmart.panasonic.com" +X_APP_VERSION = "1.21.0" +APPBRAIN_URL = "https://www.appbrain.com/app/panasonic-comfort-cloud/com.panasonic.ACCsmart" + +class Power(Enum): + Off = 0 + On = 1 + +class OperationMode(Enum): + Auto = 0 + Dry = 1 + Cool = 2 + Heat = 3 + Fan = 4 + +class AirSwingUD(Enum): + Auto = -1 + Up = 0 + UpMid = 3 + Mid = 2 + DownMid = 4 + Down = 1 + Swing = 5 + +class AirSwingLR(Enum): + Auto = -1 + Left = 1 + LeftMid = 5 + Mid = 2 + RightMid = 4 + Right = 0 + +class EcoMode(Enum): + Auto = 0 + Powerful = 1 + Quiet = 2 + +class AirSwingAutoMode(Enum): + Disabled = 1 + Both = 0 + AirSwingLR = 3 + AirSwingUD = 2 + +class FanSpeed(Enum): + Auto = 0 + Low = 1 + LowMid = 2 + Mid = 3 + HighMid = 4 + High = 5 + +class DataMode(Enum): + Day = 0 + Week = 1 + Month = 2 + Year = 4 + +class NanoeMode(Enum): + Unavailable = 0 + Off = 1 + On = 2 + ModeG = 3 + All = 4 diff --git a/panasonic_ac/packages/pcomfortcloud/exceptions.py b/panasonic_ac/packages/pcomfortcloud/exceptions.py new file mode 100644 index 000000000..73e5e5194 --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/exceptions.py @@ -0,0 +1,14 @@ +class Error(Exception): + pass + + +class LoginError(Error): + pass + + +class RequestError(Error): + pass + + +class ResponseError(Error): + pass diff --git a/panasonic_ac/packages/pcomfortcloud/session.py b/panasonic_ac/packages/pcomfortcloud/session.py new file mode 100644 index 000000000..ee6368074 --- /dev/null +++ b/panasonic_ac/packages/pcomfortcloud/session.py @@ -0,0 +1,46 @@ +from .authentication import Authentication +from .apiclient import ApiClient +from . import constants + +class Session(Authentication): + def __init__(self, username, password, token, raw=False): + super().__init__(username, password, token, raw) + + self._app_version = constants.X_APP_VERSION + self._update_app_version() + + self._api = ApiClient(self, raw) + + def get_token(self): + return super().get_token() + + def login(self): + super().login() + + def logout(self): + super().logout() + + def execute_post(self, + url, + json_data, + function_description, + expected_status_code): + return super().execute_post(url, json_data, function_description, expected_status_code) + + def execute_get(self, url, function_description, expected_status_code): + return super().execute_get(url, function_description, expected_status_code) + + def get_devices(self, group=None): + return self._api.get_devices() + + def dump(self, id): + return self._api.dump(id) + + def history(self, id, mode, date, tz="+01:00"): + return self._api.history(id, mode, date, tz) + + def get_device(self, id): + return self._api.get_device(id) + + def set_device(self, id, **kwargs): + return self._api.set_device(id, **kwargs) diff --git a/panasonic_ac/plugin.yaml b/panasonic_ac/plugin.yaml new file mode 100755 index 000000000..fc4df1257 --- /dev/null +++ b/panasonic_ac/plugin.yaml @@ -0,0 +1,200 @@ +# Metadata for the plugin +plugin: + # Global plugin attributes + type: gateway # plugin type (gateway, interface, protocol, system, web) + description: + de: 'Ansteuerung von Panasonic Klimaanlagen über die Panasonic Comfort Cloud' + en: 'Control Panasonic air conditioning systems using the Panasonc comfort cloud' + maintainer: msinn +# tester: # Who tests this plugin? + state: develop # change to ready when done with development + keywords: panasonic cloud air-condition +# documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page + support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1976353-support-thread-für-das-panasonic_ac-plugin + + version: 0.3.1 # Plugin version (must match the version specified in __init__.py) + sh_minversion: '1.10.0' # minimum shNG version to use this plugin +# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) +# py_minversion: 3.6 # minimum Python version to use for this plugin +# py_maxversion: # maximum Python version to use for this plugin (leave empty if latest) + multi_instance: False # plugin supports multi instance + restartable: unknown + configuration_needed: False # False: The plugin will be enabled by the Admin GUI without configuration + classname: PanComfortCloud # class containing the plugin + +parameters: + # Definition of parameters to be configured in etc/plugin.yaml (enter 'parameters: NONE', if section should be empty) + username: + type: str + description: + de: 'Login name für die Panasonic Comfort Cloud' + en: 'Login name for the Panasonic comfort cloud' + + password: + type: str + description: + de: 'Login Password für die Panasonic Comfort Cloud' + en: 'Login password for the Panasonic comfort cloud' + +item_attributes: + # Definition of item attributes defined by this plugin (enter 'item_attributes: NONE', if section should be empty) + + pcc_index: + type: str + description: + de: "Index der anzusteuernden Klimaanlage" + en: "Index of the air conditioning unit" + + pcc_parameter: + type: str + valid_list: + - name + - temperatureInside + - temperatureOutside + - temperature + - power + - mode + - fanSpeed + - airSwingHorizontal + - airSwingVertical + - eco + - nanoe + - connected + description: + de: "Parameter der über pcc_index gewählten Klimaanlage" + en: "Parameter of the air conditioning unit selected by pcc_index" + + +item_structs: + # Definition of item-structure templates for this plugin (enter 'item_structs: NONE', if section should be empty) + air_condition: + type: str + #pcc_index: 1 + pcc_parameter: name + device_connected: + type: bool + enforce_updates: True + pcc_index: ..:. + pcc_parameter: connected + temp_inside: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperatureInside + temp_outside: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperatureOutside + temp: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperature + power: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: power + mode: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: mode + fanspeed: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: fanSpeed + swing_hor: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: airSwingHorizontal + swing_vert: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: airSwingVertical + eco: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: eco + nanoe: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: nanoe + + air_condition_enum: + type: str + #pcc_index: 2 + pcc_parameter: name + device_connected: + type: bool + enforce_updates: True + pcc_index: ..:. + pcc_parameter: connected + temp_inside: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperatureInside + temp_outside: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperatureOutside + temp: + type: num + enforce_updates: True + pcc_index: ..:. + pcc_parameter: temperature + power: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: power + mode: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: mode + fanspeed: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: fanSpeed + swing_hor: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: airSwingHorizontal + swing_vert: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: airSwingVertical + eco: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: eco + nanoe: + type: str + enforce_updates: True + pcc_index: ..:. + pcc_parameter: nanoe + + +#item_attribute_prefixes: + # Definition of item attributes that only have a common prefix (enter 'item_attribute_prefixes: NONE' or ommit this section, if section should be empty) + # NOTE: This section should only be used, if really nessesary (e.g. for the stateengine plugin) + +plugin_functions: NONE + # Definition of plugin functions defined by this plugin (enter 'plugin_functions: NONE', if section should be empty) + +logic_parameters: NONE + # Definition of logic parameters defined by this plugin (enter 'logic_parameters: NONE', if section should be empty) + diff --git a/panasonic_ac/requirements.txt b/panasonic_ac/requirements.txt new file mode 100755 index 000000000..b5c7ab4a6 --- /dev/null +++ b/panasonic_ac/requirements.txt @@ -0,0 +1,2 @@ +pcomfortcloud +bs4 diff --git a/panasonic_ac/user_doc.rst b/panasonic_ac/user_doc.rst new file mode 100755 index 000000000..8acfc6785 --- /dev/null +++ b/panasonic_ac/user_doc.rst @@ -0,0 +1,143 @@ +.. index:: Plugins; panasonic_ac +.. index:: panasonic_ac + +============ +panasonic_ac +============ + +.. image:: webif/static/img/plugin_logo.jpg + :alt: plugin logo + :width: 300px + :height: 300px + :scale: 50 % + :align: left + + +Mit diesem Plugin können Klimaanlagen der Firma Panasonic vom Typ Etherea gesteuert werden. Die Ansteuerung erfolgt +durch das WLAN Interface der Klimaanlagen und einen von Panasonic zur Verfügung gestellen Cloud Service +(Panasonic Comfort Cloud). + +| + +Anforderungen +============= + +Das Plugin benötigt das Python Package **pcomfortcloud**. Allerdings ist die aktuell releaste Version 0.0.27 dieses +Packages nicht kompatibel mit dem aktuelllen (neuen) Anmeldeverfahren der Panasonic Comfort Cloud. Es wird eine +neuere Version des Packages benötigt, die mit dem Plugin im Unterververzeichnis **packages** des Plugins installiert +wird. + +Notwendige Software +------------------- + +Es wird eine aktuelle Develop Version des Packages **pcomfortcloud** benötigt. Diese Version muß Stand 18. August 2024 +oder neuer sein. + +| + +Plugin Instanz hinzufügen +========================= + +Da das Plugin ohne vorherige Konfiguration weiterer Parameter lauffähig ist, wird die Instanz beim Hinzufügen in +der Admin GUI auch gleich aktiviert und beim Neustart von SmartHomeNG geladen. + +| + +Konfiguration +============= + +Optionale Einstellungen können über die Admin GUI vorgenommen werden. Diese Parameter und die Informationen +zur Item-spezifischen Konfiguration des Plugins sind unter :doc:`/plugins_doc/config/panasonic_ac` beschrieben. + +| + +Verwendung von structs +---------------------- + +Mit der Hilfe von Struktur Templates wird die Einrichtung von Items stark vereinfacht. Hierzu wird ein struct Template +vom Plugin mitgeliefert: + +- **panasonic_ac.air_condition** - Standard Definition für Panasonic Klimaanlagen + +Ein Item für eine Panasonic Klimaanlage kann einfach folgendermaßen konfiguriert werden, indem nur der Index der zu +steuernden Anlage als ``pcc_index`` angegeben wird: + +.. code-block:: yaml + + klimaanlage: + pcc_index: 1 + struct: panasonic_ac.air_condition + +Der Index der jeweiligen Klimaanlage kann dem 3. Tab des Webinterfaces entnommen werden. Die Numerierung beginnt bei 1. +Falls also nur eine Klimaanlage mit der Panasonic Comfort Cloud verbunden ist, ist ``pcc_index: 1`` + +Die struct richtet folgende Unteritems ein: ``temp_inside``, ``temp_outside``, ``temp``, ``power``, +``mode``, ``fanspeed``, ``swing_hor``, ``swing_vert``, ``eco`` und ``nanoe``. + +Das Item ``Klimaanlage`` enthält als Wert den Namen der Klimaanlage. + +Die Werte für ``Klimaanlage``, ``temp_inside`` und ``temp_outside`` können nur von der Comfort Cloud gelesen +werden. Eine Veränderung des Item-Wertes hal also keine Auswirkung auf die Klimaanlage. + + +Item Attribute +-------------- + +Die vollständige Übersicht über die unterstützen Attribute und deren zulässige Werte kann auf der +Seite :doc:`/plugins_doc/config/panasonic_ac` nachgelesen werden. + + +| + +Web Interface +============= + +Das panasonic_ac Plugin verfügt über ein Webinterface, mit dessen Hilfe die Items die das Plugin nutzen +übersichtlich dargestellt werden. Außerdem können Informationen zu den Devices angezeigt werden, +die durch die Panasonic Comfort Cloud verwaltet werden. + + +Aufruf des Webinterfaces +------------------------ + +Das Plugin kann aus der Admin GUI (von der Seite Plugins/Plugin Liste aus) aufgerufen werden. Dazu auf der Seite +in der entsprechenden Zeile das Icon in der Spalte **Web Interface** anklicken. + +Außerdem kann das Webinterface direkt über ``http://smarthome.local:8383/plugin/panasonic_ac`` aufgerufen werden. + +| + +Beispiele +--------- + +Folgende Informationen können im Webinterface angezeigt werden: + +Oben rechts werden allgemeine Parameter zum Plugin angezeigt. Die weiteren Informationen werden in den +sechs Tabs des Webinterface angezeigt. + +Im ersten Tab werden die Items angezeigt, die das Plugin nutzen: + +.. image:: assets/webif_tab1.jpg + :class: screenshot + + +| +| + +Im zweiten Tab werden Informationen zur aktuellen Parametrierung des einzelnen Klimaanlagen angezeigt: + +.. image:: assets/webif_tab2.jpg + :class: screenshot + +| +| + +Im dritten Tab werden die Grunddaten der einzelnen Klimaanlagen angezeigt: + +.. image:: assets/webif_tab3.jpg + :class: screenshot + + +| +| + diff --git a/panasonic_ac/webif/__init__.py b/panasonic_ac/webif/__init__.py new file mode 100755 index 000000000..949880e86 --- /dev/null +++ b/panasonic_ac/webif/__init__.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2023- +######################################################################### +# 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 + +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() + + + def get_itemsdata(self): + + result = {} + + for item in self.plugin.get_item_list(): + item_config = self.plugin.get_item_config(item) + value_dict = {} + value_dict['path'] = item.property.path + value_dict['type'] = item.type() + value_dict['value'] = item() + if value_dict['type'] == 'dict': + value_dict['value'] = str(item()) + value_dict['index'] = item_config['index'] + value_dict['parameter'] = item_config['parameter'] + + value_dict['last_update'] = item.property.last_update.strftime('%d.%m.%y %H:%M:%S') + value_dict['last_change'] = item.property.last_change.strftime('%d.%m.%y %H:%M:%S') + + result[value_dict['path']] = value_dict + return result + + + def get_device_parameter(self, device_index, parametername, suffix=''): + result = self.plugin._devices[device_index]['parameters'].get(parametername, '-') + try: + result = str(result.value) + ' (' + result.name + ')' # get name of enum type + except: + pass + if result != '-': + result = str(result) + suffix + return result + + + def get_devicesdata(self): + + result = {} + + for device_index in self.plugin._devices: + value_dict = {} + value_dict['name'] = self.plugin._devices[device_index]['name'] + value_dict['group'] = self.plugin._devices[device_index]['group'] + value_dict['model'] = self.plugin._devices[device_index]['model'] + value_dict['id'] = self.plugin._devices[device_index]['id'] + # + try: + value_dict['parameters'] = {} + value_dict['parameters']['temperatureInside'] = self.get_device_parameter(device_index, 'temperatureInside', '°C') + value_dict['parameters']['temperatureOutside'] = self.get_device_parameter(device_index, 'temperatureOutside', '°C') + if value_dict['parameters']['temperatureOutside'] == '126°C': + value_dict['parameters']['temperatureOutside'] = '-' + value_dict['parameters']['temperature'] = self.get_device_parameter(device_index, 'temperature', '°C') + value_dict['parameters']['power'] = self.get_device_parameter(device_index, 'power') + value_dict['parameters']['mode'] = self.get_device_parameter(device_index, 'mode') + value_dict['parameters']['fanSpeed'] = self.get_device_parameter(device_index, 'fanSpeed') + value_dict['parameters']['airSwingHorizontal'] = self.get_device_parameter(device_index, 'airSwingHorizontal') + value_dict['parameters']['airSwingVertical'] = self.get_device_parameter(device_index, 'airSwingVertical') + value_dict['parameters']['eco'] = self.get_device_parameter(device_index, 'eco') + value_dict['parameters']['nanoe'] = self.get_device_parameter(device_index, 'nanoe') + except Exception as ex: + self.logger.warning(f"WebIf get_devicesdata(): Exception {ex}") + self.logger.warning(f" - Devicedata for index={device_index}: {self.plugin._devices[device_index]}") + self.logger.warning(f" - Devices: {self.plugin._devices}") + result[device_index] = value_dict + return result + + + @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') + # add values to be passed to the Jinja2 template eg: tmpl.render(p=self.plugin, interface=interface, ...) + return tmpl.render(p=self.plugin, + webif_pagelength=pagelength, + items=sorted(self.items.return_items(), key=lambda k: str.lower(k['_path'])), + item_count=0 + ) + + + @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. + """ + # self.plugin.logger.info(f"get_data_html: dataSet={dataSet}") + item_list = [] + if dataSet is None : + result_array = [] + + # callect data for items + items = self.get_itemsdata() + for item in items: + value_dict = {} + for key in items[item]: + value_dict[key] = items[item][key] + + item_list.append(value_dict) + + # collect data for devices + devices_data = self.get_devicesdata() + + # if dataSets are used, define them here + if dataSet == 'overview': + # get the new data from the plugin variable called _webdata + data = self.plugin._webdata + try: + data = json.dumps(data) + return data + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") + + result = {'items': item_list, 'devices': devices_data} + + # send result to wen interface + try: + data = json.dumps(result) + if data: + return data + else: + return None + except Exception as e: + self.logger.error(f"get_data_html exception: {e}") + self.logger.error(f"- {result}") + + return {} + diff --git a/panasonic_ac/webif/static/img/plugin_logo.jpg b/panasonic_ac/webif/static/img/plugin_logo.jpg new file mode 100644 index 000000000..e4ef87105 Binary files /dev/null and b/panasonic_ac/webif/static/img/plugin_logo.jpg differ diff --git a/panasonic_ac/webif/static/img/plugin_logo.jpg.off b/panasonic_ac/webif/static/img/plugin_logo.jpg.off new file mode 100644 index 000000000..00ac1fe5f Binary files /dev/null and b/panasonic_ac/webif/static/img/plugin_logo.jpg.off differ diff --git a/panasonic_ac/webif/static/img/readme.txt b/panasonic_ac/webif/static/img/readme.txt new file mode 100644 index 000000000..1a7c55eef --- /dev/null +++ b/panasonic_ac/webif/static/img/readme.txt @@ -0,0 +1,6 @@ +This directory is for storing images that are used by the web interface. + +If you want to have your own logo on the top of the web interface, store it here and name it plugin_logo.. + +Extension can be png, svg or jpg + diff --git a/panasonic_ac/webif/templates/index.html b/panasonic_ac/webif/templates/index.html new file mode 100755 index 000000000..407489cd2 --- /dev/null +++ b/panasonic_ac/webif/templates/index.html @@ -0,0 +1,488 @@ +{% extends "base_plugin.html" %} + +{% set logo_frame = false %} + + +{% set update_interval = 5000 %} + + + + + + + + + + +{% set buttons = true %} + + +{% set autorefresh_buttons = true %} + + +{% set reload_button = false %} + + +{% set close_button = false %} + + +{% set row_count = true %} + + +{% set initial_update = true %} + + +{% block pluginstyles %} + +{% endblock pluginstyles %} + + +{% block pluginscripts %} + + + + + + + + +{% endblock pluginscripts %} + + +{% block headtable %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 {{ _(' ') }}{{ _('') }}
 {{ _('') }}
 
+{% endblock headtable %} + + + +{% block buttons %} +{% if 1==2 %} +
+ + +
+{% endif %} +{% endblock %} + + +{% set tabcount = 3 %} + + + +{% if p._plg_item_dict|length == 0 %} + {% set start_tab = 2 %} +{% endif %} + + + +{% set tab1title = "Plugin Items (" ~ p._plg_item_dict|length ~ ")" %} +{% block bodytab1 %} +
+
+
+
+ Etwaige Informationen unterhalb der Tabelle (optional) +
+ +{% endblock bodytab1 %} + + + +{% set tab2title = " Geräte Einstellungen (" ~ p._devices | length ~ ")" %} +{% block bodytab2 %} + +
+
+
+ +{% endblock bodytab2 %} + + +{% set tab3title = "Klima-Geräte (" ~ p._devices | length ~ ")" %} +{% block bodytab3 %} + +
+
+
+ +{% endblock bodytab3 %} + + + +{% block bodytab4 %} +{% endblock bodytab4 %} diff --git a/pioneer/__init__.py b/pioneer/__init__.py index ac9d4aaac..7f5f220cf 100755 --- a/pioneer/__init__.py +++ b/pioneer/__init__.py @@ -37,10 +37,11 @@ class SmartPluginWebIf(): BASE = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3]) sys.path.insert(0, BASE) -from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, +from lib.model.sdp.globals import (PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_CONNECTION, PLUGIN_ATTR_CMD_CLASS, PLUGIN_ATTR_SERIAL_PORT, PLUGIN_ATTR_CONN_TERMINATOR, PLUGIN_ATTR_MODEL, CONN_NET_TCP_CLI, CONN_SER_ASYNC, CONN_NULL) from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone +from lib.model.sdp.command import SDPCommandParseStr # from .webif import WebInterface @@ -50,7 +51,7 @@ class SmartPluginWebIf(): class pioneer(SmartDevicePlugin): """ Device class for Pioneer AV function. """ - PLUGIN_VERSION = '1.0.2' + PLUGIN_VERSION = '1.0.3' def _set_device_defaults(self): # set our own preferences concerning connections @@ -62,6 +63,8 @@ def _set_device_defaults(self): self.logger.error('Neither host nor serialport set, connection not possible. Using dummy connection, plugin will not work') self._parameters[PLUGIN_ATTR_CONNECTION] = CONN_NULL + self._parameters[PLUGIN_ATTR_CMD_CLASS] = SDPCommandParseStr + b = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR].encode() b = b.decode('unicode-escape').encode() self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] = b diff --git a/pioneer/commands.py b/pioneer/commands.py index cdac9906d..6938979c7 100755 --- a/pioneer/commands.py +++ b/pioneer/commands.py @@ -21,7 +21,7 @@ 'dimmer': {'read': True, 'write': True, 'write_cmd': '{RAW_VALUE}SAA', 'cmd_settings': {'force_min': 0, 'force_max': 3}, 'item_type': 'num', 'dev_datatype': 'str', 'reply_pattern': r'SAA(\d)', 'item_attrs': {'attributes': {'remark': '0 = very bright, 1 = bright, 2 = dark, 3 = off'}}}, 'sleep': {'read': True, 'write': True, 'read_cmd': '?SAB', 'write_cmd': '{VALUE}SAB', 'item_type': 'num', 'dev_datatype': 'PioSleep', 'reply_pattern': r'SAB(\d{3})', 'item_attrs': {'attributes': {'remark': '0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes'}}}, 'amp': {'read': True, 'write': True, 'read_cmd': '?SAC', 'write_cmd': '{VALUE}SAC', 'item_type': 'str', 'dev_datatype': 'str', 'reply_pattern': r'SAC{LOOKUP}', 'lookup': 'AMP', 'item_attrs': {'attributes': {'remark': '0 = AMP, 1 = THR'}, 'lookup_item': True}}, - 'multizone': {'read': False, 'write': True, 'write_cmd': 'ZZ', 'item_type': 'str', 'dev_datatype': 'str'}, + 'multizone': {'read': True, 'write': True, 'write_cmd': 'ZZ', 'item_type': 'bool', 'dev_datatype': 'onoff', 'reply_pattern': r'(?:APR|BPR|ZEP)(\d{1})'}, 'settings': { 'language': {'read': True, 'write': True, 'read_cmd': '?SSE', 'write_cmd': '{VALUE}SSE', 'item_type': 'str', 'dev_datatype': 'raw', 'reply_pattern': r'SSE{LOOKUP}', 'lookup': 'LANGUAGE', 'item_attrs': {'initial': True}}, 'name': {'read': True, 'write': True, 'read_cmd': '?SSO', 'write_cmd': '{VALUE}SSO', 'item_type': 'str', 'dev_datatype': 'PioName', 'reply_pattern': r'SSO(?:\d{2})(.*)', 'item_attrs': {'initial': True}}, diff --git a/pioneer/plugin.yaml b/pioneer/plugin.yaml index f6904e8ed..d17bb76eb 100755 --- a/pioneer/plugin.yaml +++ b/pioneer/plugin.yaml @@ -1,14 +1,17 @@ plugin: type: interface - description: Pioneer AV-Receiver + description: + de: Pioneer AV-Receiver + en: Pioneer AV-Receiver maintainer: OnkelAndy tester: Morg state: develop keywords: iot device av pioneer sdp - version: 1.0.2 + version: '1.0.3' sh_minversion: '1.9.5' py_minversion: '3.7' + sdp_minversion: '1.0.3' multi_instance: false restartable: true classname: pioneer @@ -118,6 +121,63 @@ parameters: de: Anzahl Stopbits en: number of stop bits + send_retries: + type: num + default: 0 + + description: + de: Anzahl Sendeversuche + en: number of sending retries + + description_long: + de: | + Anzahl Sendeversuche\n + Kommt keine (passende) Antwort nach dem Senden + eines Commands zurück, wird das Kommando nochmals + gesendet, sofern der Wert über 0 liegt. + en: | + number of sending retries\n + If no (suiting) answer is received after sending + a command the command is resent as long as this + value is more than 0. + + sendretry_cycle: + type: num + valid_min: 1 + default: 1 + + description: + de: Pause zwischen Durchgängen von Sendeversuchen + en: wait time between sending retry rounds + + description_long: + de: | + Pause zwischen Durchgängen von Sendeversuchen\n + Sind Send Retries aktiv, wird ein Scheduler erstellt, + der im angegebenen Sekundentakt Kommandos erneut sendet, + zu denen keine (passenden) Antworten erhalten wurden. + en: | + wait time between sending retry rounds\n + If send retries are active, a scheduler gets added + that resends commands in the given cycle value (in seconds) + where no (suiting) answer got received. + + delay_initial_read: + type: num + default: 0 + + description: + de: Warte nach Verbindungsaufbau mit dem Abfragen von Werten + en: Wait after connection with querying values + + resume_initial_read: + type: bool + defaul: false + + description: + de: Bei resume vom Plugin erstmaliges Lesen erneut durchführen + en: Repeat initial read on resume + conn_type: type: str mandatory: false @@ -130,17 +190,6 @@ parameters: de: Verbindungstyp en: connection type - command_class: - type: str - default: SDPCommandParseStr - valid_list: - - SDPCommand - - SDPCommandParseStr - - description: - de: Klasse für Verarbeitung von Kommandos - en: class for command processing - autoreconnect: type: bool default: true @@ -197,7 +246,6 @@ parameters: de: Item-Pfad für das Standby-Item en: item path for standby switch item - item_attributes: pioneer_command: @@ -257,8 +305,26 @@ item_attributes: en: The lookup table with the given name will be assigned to the item in dict or list format once on startup. description_long: - de: "Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim\nStart einmalig als dict oder list in das Item geschrieben.\n\n\nDurch Anhängen von \"#\" an den Namen der Tabelle kann die Art\nder Tabelle ausgewählt werden:\n- fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard)\n- rev liefert die Tabelle SmartHomeNG -> Gerät\n- rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben\n- list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu)" - en: "The lookup table with the given name will be assigned to the item\nin dict or list format once on startup.\n\n\nBy appending \"#\" to the tables name the type of table can\nbe selected:\n- fwd returns the table device -> SmartHomeNG (default)\n- rev returns the table SmartHomeNG -> device\n- rci returns the table SmartHomeNG -> device in lower case\n- list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications)" + de: |- + Der Inhalt der Lookup-Tabelle mit dem angegebenen Namen wird beim + Start einmalig als dict oder list in das Item geschrieben. + + Durch Anhängen von "#" an den Namen der Tabelle kann die Art + der Tabelle ausgewählt werden: + - fwd liefert die Tabelle Gerät -> SmartHomeNG (Standard) + - rev liefert die Tabelle SmartHomeNG -> Gerät + - rci liefert die Tabelle SmarthomeNG -> Gerät in Kleinbuchstaben + - list liefert die Liste der Namen für SmartHomeNG (z.B. für Auswahllisten in der Visu) + en: |- + The lookup table with the given name will be assigned to the item + in dict or list format once on startup. + + By appending "#" to the tables name the type of table can + be selected: + - fwd returns the table device -> SmartHomeNG (default) + - rev returns the table SmartHomeNG -> device + - rci returns the table SmartHomeNG -> device in lower case + - list return the list of names for SmartHomeNG (e.g. for selection dropdowns in visu applications) item_structs: @@ -267,290 +333,290 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: general + pioneer_read_group_trigger@instance: general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes amp: type: str - pioneer_command: general.amp - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.amp + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general remark: 0 = AMP, 1 = THR lookup: type: list - pioneer_lookup: AMP#list + pioneer_lookup@instance: AMP#list multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: general.settings + pioneer_read_group_trigger@instance: general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list surroundposition: type: str - pioneer_command: general.settings.surroundposition - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.surroundposition + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SURROUNDPOSITION#list + pioneer_lookup@instance: SURROUNDPOSITION#list xover: type: str - pioneer_command: general.settings.xover - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xover + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true loudness: type: bool - pioneer_command: general.settings.loudness - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.loudness + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true initialvolume: type: num - pioneer_command: general.settings.initialvolume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.initialvolume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true mutelevel: type: num - pioneer_command: general.settings.mutelevel - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.mutelevel + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: general.settings.hdmi + pioneer_read_group_trigger@instance: general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - general - general.settings - general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: tuner + pioneer_read_group_trigger@instance: tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1 + pioneer_read_group_trigger@instance: zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.control + pioneer_read_group_trigger@instance: zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.control pioneer_read_initial: true @@ -563,75 +629,75 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.settings + pioneer_read_group_trigger@instance: zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -639,21 +705,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.settings.sound + pioneer_read_group_trigger@instance: zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -661,10 +727,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -672,10 +738,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -683,10 +749,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -694,10 +760,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -705,10 +771,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -716,10 +782,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -727,10 +793,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -738,10 +804,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -749,10 +815,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -763,14 +829,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -780,10 +846,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -791,22 +857,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -814,29 +880,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone1.settings.sound.general + pioneer_read_group_trigger@instance: zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -844,14 +910,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -859,14 +925,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -874,22 +940,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -897,14 +963,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -912,14 +978,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone1 - zone1.settings - zone1.settings.sound @@ -941,21 +1007,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2 + pioneer_read_group_trigger@instance: zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2.control + pioneer_read_group_trigger@instance: zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.control pioneer_read_initial: true @@ -968,75 +1034,75 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2.settings + pioneer_read_group_trigger@instance: zone2.settings standby: type: num - pioneer_command: zone2.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -1044,21 +1110,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2.settings.sound + pioneer_read_group_trigger@instance: zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1066,10 +1132,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1080,14 +1146,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1097,10 +1163,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1108,10 +1174,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone2 - zone2.settings - zone2.settings.sound @@ -1122,21 +1188,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone3 + pioneer_read_group_trigger@instance: zone3 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone3.control + pioneer_read_group_trigger@instance: zone3.control power: type: bool - pioneer_command: zone3.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.control pioneer_read_initial: true @@ -1149,75 +1215,75 @@ item_structs: mute: type: bool - pioneer_command: zone3.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.control volume: type: num - pioneer_command: zone3.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.control volumeup: type: bool - pioneer_command: zone3.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone3.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone3.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.control lookup: type: list - pioneer_lookup: INPUT3#list + pioneer_lookup@instance: INPUT3#list inputup: type: bool - pioneer_command: zone3.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone3.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone3.settings + pioneer_read_group_trigger@instance: zone3.settings standby: type: num - pioneer_command: zone3.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -1225,21 +1291,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone3.settings.sound + pioneer_read_group_trigger@instance: zone3.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: zone3.settings.sound.channel_level + pioneer_read_group_trigger@instance: zone3.settings.sound.channel_level front_left: type: num - pioneer_command: zone3.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1247,10 +1313,10 @@ item_structs: front_right: type: num - pioneer_command: zone3.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - zone3 - zone3.settings - zone3.settings.sound @@ -1261,53 +1327,53 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: hdzone + pioneer_read_group_trigger@instance: hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: hdzone.control + pioneer_read_group_trigger@instance: hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - hdzone - hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - hdzone - hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: hdzone.settings + pioneer_read_group_trigger@instance: hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - hdzone - hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours ALL: @@ -1315,242 +1381,242 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL + pioneer_read_group_trigger@instance: ALL general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.general + pioneer_read_group_trigger@instance: ALL.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - ALL - ALL.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.general.settings + pioneer_read_group_trigger@instance: ALL.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.general.settings.hdmi + pioneer_read_group_trigger@instance: ALL.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - ALL.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - ALL.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - ALL.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.general - ALL.general.settings - ALL.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.tuner + pioneer_read_group_trigger@instance: ALL.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1 + pioneer_read_group_trigger@instance: ALL.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.control + pioneer_read_group_trigger@instance: ALL.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control @@ -1564,79 +1630,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.settings + pioneer_read_group_trigger@instance: ALL.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -1644,21 +1710,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.settings.sound + pioneer_read_group_trigger@instance: ALL.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: ALL.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1667,10 +1733,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1679,10 +1745,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1691,10 +1757,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1703,10 +1769,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1715,10 +1781,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1727,10 +1793,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1739,10 +1805,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1751,10 +1817,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1763,10 +1829,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1778,14 +1844,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: ALL.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1796,10 +1862,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1808,22 +1874,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1832,29 +1898,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone1.settings.sound.general + pioneer_read_group_trigger@instance: ALL.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1863,14 +1929,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1879,14 +1945,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1895,22 +1961,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1919,14 +1985,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1935,14 +2001,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone1 - ALL.zone1.settings @@ -1965,21 +2031,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone2 + pioneer_read_group_trigger@instance: ALL.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.zone2.control + pioneer_read_group_trigger@instance: ALL.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control @@ -1993,117 +2059,117 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.zone2 - ALL.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true hdzone: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.hdzone + pioneer_read_group_trigger@instance: ALL.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.hdzone.control + pioneer_read_group_trigger@instance: ALL.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.hdzone - ALL.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.hdzone - ALL.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: ALL.hdzone.settings + pioneer_read_group_trigger@instance: ALL.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - ALL - ALL.hdzone - ALL.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours SC-LX87: @@ -2111,293 +2177,293 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87 + pioneer_read_group_trigger@instance: SC-LX87 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.general + pioneer_read_group_trigger@instance: SC-LX87.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes amp: type: str - pioneer_command: general.amp - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.amp + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general remark: 0 = AMP, 1 = THR lookup: type: list - pioneer_lookup: AMP#list + pioneer_lookup@instance: AMP#list multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.general.settings + pioneer_read_group_trigger@instance: SC-LX87.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list surroundposition: type: str - pioneer_command: general.settings.surroundposition - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.surroundposition + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SURROUNDPOSITION#list + pioneer_lookup@instance: SURROUNDPOSITION#list xover: type: str - pioneer_command: general.settings.xover - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xover + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true loudness: type: bool - pioneer_command: general.settings.loudness - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.loudness + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.general.settings.hdmi + pioneer_read_group_trigger@instance: SC-LX87.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - SC-LX87.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - SC-LX87.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - SC-LX87.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.general - SC-LX87.general.settings - SC-LX87.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.tuner + pioneer_read_group_trigger@instance: SC-LX87.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1 + pioneer_read_group_trigger@instance: SC-LX87.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.control + pioneer_read_group_trigger@instance: SC-LX87.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.control @@ -2411,79 +2477,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.settings + pioneer_read_group_trigger@instance: SC-LX87.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -2491,21 +2557,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.settings.sound + pioneer_read_group_trigger@instance: SC-LX87.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX87.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2514,10 +2580,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2526,10 +2592,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2538,10 +2604,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2550,10 +2616,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2562,10 +2628,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2574,10 +2640,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2586,10 +2652,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2598,10 +2664,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2610,10 +2676,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2625,14 +2691,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX87.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2643,10 +2709,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2655,22 +2721,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2679,29 +2745,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone1.settings.sound.general + pioneer_read_group_trigger@instance: SC-LX87.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2710,14 +2776,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2726,14 +2792,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2742,22 +2808,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2766,14 +2832,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2782,14 +2848,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone1 - SC-LX87.zone1.settings @@ -2812,21 +2878,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2 + pioneer_read_group_trigger@instance: SC-LX87.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2.control + pioneer_read_group_trigger@instance: SC-LX87.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.control @@ -2840,89 +2906,89 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2.settings + pioneer_read_group_trigger@instance: SC-LX87.zone2.settings sound: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2.settings.sound + pioneer_read_group_trigger@instance: SC-LX87.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX87.zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.settings @@ -2931,10 +2997,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.settings @@ -2946,14 +3012,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX87.zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.settings @@ -2964,10 +3030,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.settings @@ -2976,10 +3042,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone2 - SC-LX87.zone2.settings @@ -2991,21 +3057,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone3 + pioneer_read_group_trigger@instance: SC-LX87.zone3 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone3.control + pioneer_read_group_trigger@instance: SC-LX87.zone3.control power: type: bool - pioneer_command: zone3.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.control @@ -3019,79 +3085,79 @@ item_structs: mute: type: bool - pioneer_command: zone3.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.control volume: type: num - pioneer_command: zone3.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.control volumeup: type: bool - pioneer_command: zone3.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone3.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone3.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.control lookup: type: list - pioneer_lookup: INPUT3#list + pioneer_lookup@instance: INPUT3#list inputup: type: bool - pioneer_command: zone3.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone3.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone3.settings + pioneer_read_group_trigger@instance: SC-LX87.zone3.settings standby: type: num - pioneer_command: zone3.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -3099,21 +3165,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone3.settings.sound + pioneer_read_group_trigger@instance: SC-LX87.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.zone3.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX87.zone3.settings.sound.channel_level front_left: type: num - pioneer_command: zone3.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.settings @@ -3122,10 +3188,10 @@ item_structs: front_right: type: num - pioneer_command: zone3.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.zone3 - SC-LX87.zone3.settings @@ -3137,56 +3203,56 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.hdzone + pioneer_read_group_trigger@instance: SC-LX87.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.hdzone.control + pioneer_read_group_trigger@instance: SC-LX87.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.hdzone - SC-LX87.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.hdzone - SC-LX87.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX87.hdzone.settings + pioneer_read_group_trigger@instance: SC-LX87.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX87 - SC-LX87.hdzone - SC-LX87.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours SC-LX77: @@ -3194,293 +3260,293 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77 + pioneer_read_group_trigger@instance: SC-LX77 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.general + pioneer_read_group_trigger@instance: SC-LX77.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes amp: type: str - pioneer_command: general.amp - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.amp + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general remark: 0 = AMP, 1 = THR lookup: type: list - pioneer_lookup: AMP#list + pioneer_lookup@instance: AMP#list multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.general.settings + pioneer_read_group_trigger@instance: SC-LX77.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list surroundposition: type: str - pioneer_command: general.settings.surroundposition - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.surroundposition + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SURROUNDPOSITION#list + pioneer_lookup@instance: SURROUNDPOSITION#list xover: type: str - pioneer_command: general.settings.xover - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xover + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true loudness: type: bool - pioneer_command: general.settings.loudness - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.loudness + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.general.settings.hdmi + pioneer_read_group_trigger@instance: SC-LX77.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - SC-LX77.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - SC-LX77.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - SC-LX77.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.general - SC-LX77.general.settings - SC-LX77.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.tuner + pioneer_read_group_trigger@instance: SC-LX77.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1 + pioneer_read_group_trigger@instance: SC-LX77.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.control + pioneer_read_group_trigger@instance: SC-LX77.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.control @@ -3494,79 +3560,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.settings + pioneer_read_group_trigger@instance: SC-LX77.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -3574,21 +3640,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.settings.sound + pioneer_read_group_trigger@instance: SC-LX77.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX77.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3597,10 +3663,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3609,10 +3675,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3621,10 +3687,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3633,10 +3699,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3645,10 +3711,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3657,10 +3723,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3669,10 +3735,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3681,10 +3747,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3693,10 +3759,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3708,14 +3774,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX77.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3726,10 +3792,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3738,22 +3804,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3762,29 +3828,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone1.settings.sound.general + pioneer_read_group_trigger@instance: SC-LX77.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3793,14 +3859,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3809,14 +3875,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3825,22 +3891,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3849,14 +3915,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3865,14 +3931,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone1 - SC-LX77.zone1.settings @@ -3895,21 +3961,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2 + pioneer_read_group_trigger@instance: SC-LX77.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2.control + pioneer_read_group_trigger@instance: SC-LX77.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.control @@ -3923,89 +3989,89 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2.settings + pioneer_read_group_trigger@instance: SC-LX77.zone2.settings sound: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2.settings.sound + pioneer_read_group_trigger@instance: SC-LX77.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX77.zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.settings @@ -4014,10 +4080,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.settings @@ -4029,14 +4095,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX77.zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.settings @@ -4047,10 +4113,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.settings @@ -4059,10 +4125,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone2 - SC-LX77.zone2.settings @@ -4074,21 +4140,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone3 + pioneer_read_group_trigger@instance: SC-LX77.zone3 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone3.control + pioneer_read_group_trigger@instance: SC-LX77.zone3.control power: type: bool - pioneer_command: zone3.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.control @@ -4102,79 +4168,79 @@ item_structs: mute: type: bool - pioneer_command: zone3.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.control volume: type: num - pioneer_command: zone3.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.control volumeup: type: bool - pioneer_command: zone3.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone3.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone3.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.control lookup: type: list - pioneer_lookup: INPUT3#list + pioneer_lookup@instance: INPUT3#list inputup: type: bool - pioneer_command: zone3.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone3.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone3.settings + pioneer_read_group_trigger@instance: SC-LX77.zone3.settings standby: type: num - pioneer_command: zone3.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -4182,21 +4248,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone3.settings.sound + pioneer_read_group_trigger@instance: SC-LX77.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.zone3.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX77.zone3.settings.sound.channel_level front_left: type: num - pioneer_command: zone3.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.settings @@ -4205,10 +4271,10 @@ item_structs: front_right: type: num - pioneer_command: zone3.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.zone3 - SC-LX77.zone3.settings @@ -4220,56 +4286,56 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.hdzone + pioneer_read_group_trigger@instance: SC-LX77.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.hdzone.control + pioneer_read_group_trigger@instance: SC-LX77.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.hdzone - SC-LX77.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.hdzone - SC-LX77.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX77.hdzone.settings + pioneer_read_group_trigger@instance: SC-LX77.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX77 - SC-LX77.hdzone - SC-LX77.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours SC-LX57: @@ -4277,293 +4343,293 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57 + pioneer_read_group_trigger@instance: SC-LX57 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.general + pioneer_read_group_trigger@instance: SC-LX57.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes amp: type: str - pioneer_command: general.amp - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.amp + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general remark: 0 = AMP, 1 = THR lookup: type: list - pioneer_lookup: AMP#list + pioneer_lookup@instance: AMP#list multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.general.settings + pioneer_read_group_trigger@instance: SC-LX57.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list surroundposition: type: str - pioneer_command: general.settings.surroundposition - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.surroundposition + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SURROUNDPOSITION#list + pioneer_lookup@instance: SURROUNDPOSITION#list xover: type: str - pioneer_command: general.settings.xover - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xover + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true loudness: type: bool - pioneer_command: general.settings.loudness - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.loudness + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.general.settings.hdmi + pioneer_read_group_trigger@instance: SC-LX57.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - SC-LX57.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - SC-LX57.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - SC-LX57.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.general - SC-LX57.general.settings - SC-LX57.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.tuner + pioneer_read_group_trigger@instance: SC-LX57.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1 + pioneer_read_group_trigger@instance: SC-LX57.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.control + pioneer_read_group_trigger@instance: SC-LX57.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.control @@ -4577,79 +4643,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.settings + pioneer_read_group_trigger@instance: SC-LX57.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -4657,21 +4723,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.settings.sound + pioneer_read_group_trigger@instance: SC-LX57.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX57.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4680,10 +4746,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4692,10 +4758,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4704,10 +4770,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4716,10 +4782,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4728,10 +4794,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4740,10 +4806,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4752,10 +4818,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4764,10 +4830,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4776,10 +4842,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4791,14 +4857,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX57.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4809,10 +4875,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4821,22 +4887,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4845,29 +4911,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone1.settings.sound.general + pioneer_read_group_trigger@instance: SC-LX57.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4876,14 +4942,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4892,14 +4958,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4908,22 +4974,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4932,14 +4998,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4948,14 +5014,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone1 - SC-LX57.zone1.settings @@ -4978,21 +5044,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2 + pioneer_read_group_trigger@instance: SC-LX57.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2.control + pioneer_read_group_trigger@instance: SC-LX57.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.control @@ -5006,89 +5072,89 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2.settings + pioneer_read_group_trigger@instance: SC-LX57.zone2.settings sound: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2.settings.sound + pioneer_read_group_trigger@instance: SC-LX57.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX57.zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.settings @@ -5097,10 +5163,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.settings @@ -5112,14 +5178,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-LX57.zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.settings @@ -5130,10 +5196,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.settings @@ -5142,10 +5208,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone2 - SC-LX57.zone2.settings @@ -5157,21 +5223,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone3 + pioneer_read_group_trigger@instance: SC-LX57.zone3 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone3.control + pioneer_read_group_trigger@instance: SC-LX57.zone3.control power: type: bool - pioneer_command: zone3.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.control @@ -5185,79 +5251,79 @@ item_structs: mute: type: bool - pioneer_command: zone3.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.control volume: type: num - pioneer_command: zone3.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.control volumeup: type: bool - pioneer_command: zone3.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone3.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone3.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.control lookup: type: list - pioneer_lookup: INPUT3#list + pioneer_lookup@instance: INPUT3#list inputup: type: bool - pioneer_command: zone3.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone3.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone3.settings + pioneer_read_group_trigger@instance: SC-LX57.zone3.settings standby: type: num - pioneer_command: zone3.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -5265,21 +5331,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone3.settings.sound + pioneer_read_group_trigger@instance: SC-LX57.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.zone3.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-LX57.zone3.settings.sound.channel_level front_left: type: num - pioneer_command: zone3.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.settings @@ -5288,10 +5354,10 @@ item_structs: front_right: type: num - pioneer_command: zone3.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.zone3 - SC-LX57.zone3.settings @@ -5303,56 +5369,56 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.hdzone + pioneer_read_group_trigger@instance: SC-LX57.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.hdzone.control + pioneer_read_group_trigger@instance: SC-LX57.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.hdzone - SC-LX57.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.hdzone - SC-LX57.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-LX57.hdzone.settings + pioneer_read_group_trigger@instance: SC-LX57.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-LX57 - SC-LX57.hdzone - SC-LX57.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours SC-2023: @@ -5360,268 +5426,268 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023 + pioneer_read_group_trigger@instance: SC-2023 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.general + pioneer_read_group_trigger@instance: SC-2023.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-2023 - SC-2023.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.general.settings + pioneer_read_group_trigger@instance: SC-2023.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list surroundposition: type: str - pioneer_command: general.settings.surroundposition - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.surroundposition + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SURROUNDPOSITION#list + pioneer_lookup@instance: SURROUNDPOSITION#list xover: type: str - pioneer_command: general.settings.xover - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xover + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.general.settings.hdmi + pioneer_read_group_trigger@instance: SC-2023.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - SC-2023.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - SC-2023.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - SC-2023.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.general - SC-2023.general.settings - SC-2023.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.tuner + pioneer_read_group_trigger@instance: SC-2023.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1 + pioneer_read_group_trigger@instance: SC-2023.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.control + pioneer_read_group_trigger@instance: SC-2023.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.control @@ -5635,79 +5701,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.settings + pioneer_read_group_trigger@instance: SC-2023.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -5715,21 +5781,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.settings.sound + pioneer_read_group_trigger@instance: SC-2023.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-2023.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5738,10 +5804,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5750,10 +5816,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5762,10 +5828,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5774,10 +5840,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5786,10 +5852,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5798,10 +5864,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5810,10 +5876,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5822,10 +5888,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5834,10 +5900,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5849,14 +5915,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-2023.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5867,10 +5933,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5879,22 +5945,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5903,29 +5969,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone1.settings.sound.general + pioneer_read_group_trigger@instance: SC-2023.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5934,14 +6000,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5950,14 +6016,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5966,22 +6032,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -5990,14 +6056,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -6006,14 +6072,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone1 - SC-2023.zone1.settings @@ -6036,21 +6102,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2 + pioneer_read_group_trigger@instance: SC-2023.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2.control + pioneer_read_group_trigger@instance: SC-2023.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.control @@ -6064,89 +6130,89 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2.settings + pioneer_read_group_trigger@instance: SC-2023.zone2.settings sound: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2.settings.sound + pioneer_read_group_trigger@instance: SC-2023.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-2023.zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.settings @@ -6155,10 +6221,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.settings @@ -6170,14 +6236,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-2023.zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.settings @@ -6188,10 +6254,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.settings @@ -6200,10 +6266,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone2 - SC-2023.zone2.settings @@ -6215,21 +6281,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone3 + pioneer_read_group_trigger@instance: SC-2023.zone3 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone3.control + pioneer_read_group_trigger@instance: SC-2023.zone3.control power: type: bool - pioneer_command: zone3.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.control @@ -6243,79 +6309,79 @@ item_structs: mute: type: bool - pioneer_command: zone3.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.control volume: type: num - pioneer_command: zone3.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.control volumeup: type: bool - pioneer_command: zone3.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone3.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone3.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.control lookup: type: list - pioneer_lookup: INPUT3#list + pioneer_lookup@instance: INPUT3#list inputup: type: bool - pioneer_command: zone3.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone3.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone3.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone3.settings + pioneer_read_group_trigger@instance: SC-2023.zone3.settings standby: type: num - pioneer_command: zone3.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours sound: @@ -6323,21 +6389,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone3.settings.sound + pioneer_read_group_trigger@instance: SC-2023.zone3.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.zone3.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-2023.zone3.settings.sound.channel_level front_left: type: num - pioneer_command: zone3.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.settings @@ -6346,10 +6412,10 @@ item_structs: front_right: type: num - pioneer_command: zone3.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone3.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.zone3 - SC-2023.zone3.settings @@ -6361,56 +6427,56 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.hdzone + pioneer_read_group_trigger@instance: SC-2023.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.hdzone.control + pioneer_read_group_trigger@instance: SC-2023.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.hdzone - SC-2023.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.hdzone - SC-2023.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-2023.hdzone.settings + pioneer_read_group_trigger@instance: SC-2023.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-2023 - SC-2023.hdzone - SC-2023.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours SC-1223: @@ -6418,242 +6484,242 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223 + pioneer_read_group_trigger@instance: SC-1223 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.general + pioneer_read_group_trigger@instance: SC-1223.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-1223 - SC-1223.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.general.settings + pioneer_read_group_trigger@instance: SC-1223.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.general.settings.hdmi + pioneer_read_group_trigger@instance: SC-1223.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - SC-1223.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - SC-1223.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - SC-1223.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.general - SC-1223.general.settings - SC-1223.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.tuner + pioneer_read_group_trigger@instance: SC-1223.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1 + pioneer_read_group_trigger@instance: SC-1223.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.control + pioneer_read_group_trigger@instance: SC-1223.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.control @@ -6667,79 +6733,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.settings + pioneer_read_group_trigger@instance: SC-1223.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -6747,21 +6813,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.settings.sound + pioneer_read_group_trigger@instance: SC-1223.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-1223.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6770,10 +6836,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6782,10 +6848,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6794,10 +6860,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6806,10 +6872,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6818,10 +6884,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6830,10 +6896,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6842,10 +6908,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6854,10 +6920,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6866,10 +6932,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6881,14 +6947,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-1223.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6899,10 +6965,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6911,22 +6977,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6935,29 +7001,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone1.settings.sound.general + pioneer_read_group_trigger@instance: SC-1223.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6966,14 +7032,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6982,14 +7048,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -6998,22 +7064,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -7022,14 +7088,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -7038,14 +7104,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone1 - SC-1223.zone1.settings @@ -7068,21 +7134,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2 + pioneer_read_group_trigger@instance: SC-1223.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2.control + pioneer_read_group_trigger@instance: SC-1223.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.control @@ -7096,89 +7162,89 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2.settings + pioneer_read_group_trigger@instance: SC-1223.zone2.settings sound: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2.settings.sound + pioneer_read_group_trigger@instance: SC-1223.zone2.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2.settings.sound.channel_level + pioneer_read_group_trigger@instance: SC-1223.zone2.settings.sound.channel_level front_left: type: num - pioneer_command: zone2.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.settings @@ -7187,10 +7253,10 @@ item_structs: front_right: type: num - pioneer_command: zone2.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.settings @@ -7202,14 +7268,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.zone2.settings.sound.tone_control + pioneer_read_group_trigger@instance: SC-1223.zone2.settings.sound.tone_control tone: type: bool - pioneer_command: zone2.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.settings @@ -7220,10 +7286,10 @@ item_structs: treble: type: num - pioneer_command: zone2.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.settings @@ -7232,10 +7298,10 @@ item_structs: bass: type: num - pioneer_command: zone2.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.zone2 - SC-1223.zone2.settings @@ -7247,56 +7313,56 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.hdzone + pioneer_read_group_trigger@instance: SC-1223.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.hdzone.control + pioneer_read_group_trigger@instance: SC-1223.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.hdzone - SC-1223.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.hdzone - SC-1223.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: SC-1223.hdzone.settings + pioneer_read_group_trigger@instance: SC-1223.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - SC-1223 - SC-1223.hdzone - SC-1223.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours VSX-1123: @@ -7304,242 +7370,242 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123 + pioneer_read_group_trigger@instance: VSX-1123 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.general + pioneer_read_group_trigger@instance: VSX-1123.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.general.settings + pioneer_read_group_trigger@instance: VSX-1123.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.general.settings.hdmi + pioneer_read_group_trigger@instance: VSX-1123.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - VSX-1123.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - VSX-1123.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - VSX-1123.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.general - VSX-1123.general.settings - VSX-1123.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.tuner + pioneer_read_group_trigger@instance: VSX-1123.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1 + pioneer_read_group_trigger@instance: VSX-1123.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.control + pioneer_read_group_trigger@instance: VSX-1123.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.control @@ -7553,79 +7619,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.settings + pioneer_read_group_trigger@instance: VSX-1123.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -7633,21 +7699,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.settings.sound + pioneer_read_group_trigger@instance: VSX-1123.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: VSX-1123.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7656,10 +7722,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7668,10 +7734,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7680,10 +7746,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7692,10 +7758,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7704,10 +7770,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7716,10 +7782,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7728,10 +7794,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7740,10 +7806,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7752,10 +7818,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7767,14 +7833,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: VSX-1123.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7785,10 +7851,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7797,22 +7863,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7821,29 +7887,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone1.settings.sound.general + pioneer_read_group_trigger@instance: VSX-1123.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7852,14 +7918,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7868,14 +7934,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7884,22 +7950,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7908,14 +7974,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7924,14 +7990,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone1 - VSX-1123.zone1.settings @@ -7954,21 +8020,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone2 + pioneer_read_group_trigger@instance: VSX-1123.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.zone2.control + pioneer_read_group_trigger@instance: VSX-1123.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone2 - VSX-1123.zone2.control @@ -7982,117 +8048,117 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone2 - VSX-1123.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone2 - VSX-1123.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.zone2 - VSX-1123.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true hdzone: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.hdzone + pioneer_read_group_trigger@instance: VSX-1123.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.hdzone.control + pioneer_read_group_trigger@instance: VSX-1123.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.hdzone - VSX-1123.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.hdzone - VSX-1123.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-1123.hdzone.settings + pioneer_read_group_trigger@instance: VSX-1123.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-1123 - VSX-1123.hdzone - VSX-1123.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours VSX-923: @@ -8100,242 +8166,242 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923 + pioneer_read_group_trigger@instance: VSX-923 general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.general + pioneer_read_group_trigger@instance: VSX-923.general error: type: str - pioneer_command: general.error - pioneer_read: true - pioneer_write: false + pioneer_command@instance: general.error + pioneer_read@instance: true + pioneer_write@instance: false display: type: str - pioneer_command: general.display - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: general.display + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - VSX-923 - VSX-923.general pqls: type: bool - pioneer_command: general.pqls - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.pqls + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general dimmer: type: num - pioneer_command: general.dimmer - pioneer_read: true - pioneer_write: true + pioneer_command@instance: general.dimmer + pioneer_read@instance: true + pioneer_write@instance: true remark: 0 = very bright, 1 = bright, 2 = dark, 3 = off sleep: type: num - pioneer_command: general.sleep - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.sleep + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general remark: 0 = off, 30 = 30 minutes, 60 = 60 minutes, 90 = 90 minutes multizone: - type: str - pioneer_command: general.multizone - pioneer_read: false - pioneer_write: true + type: bool + pioneer_command@instance: general.multizone + pioneer_read@instance: true + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.general.settings + pioneer_read_group_trigger@instance: VSX-923.general.settings language: type: str - pioneer_command: general.settings.language - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.language + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true name: type: str - pioneer_command: general.settings.name - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.name + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true speakersystem: type: str - pioneer_command: general.settings.speakersystem - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.speakersystem + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: SPEAKERSYSTEM#list + pioneer_lookup@instance: SPEAKERSYSTEM#list xcurve: type: str - pioneer_command: general.settings.xcurve - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.xcurve + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true hdmi: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.general.settings.hdmi + pioneer_read_group_trigger@instance: VSX-923.general.settings.hdmi control: type: bool - pioneer_command: general.settings.hdmi.control - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.control + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - VSX-923.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true controlmode: type: bool - pioneer_command: general.settings.hdmi.controlmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.controlmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - VSX-923.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true arc: type: bool - pioneer_command: general.settings.hdmi.arc - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.arc + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - VSX-923.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true standbythrough: type: str - pioneer_command: general.settings.hdmi.standbythrough - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: general.settings.hdmi.standbythrough + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.general - VSX-923.general.settings - VSX-923.general.settings.hdmi - pioneer_read_initial: true + pioneer_read_initial@instance: true lookup: type: list - pioneer_lookup: STANDBYTHROUGH#list + pioneer_lookup@instance: STANDBYTHROUGH#list tuner: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.tuner + pioneer_read_group_trigger@instance: VSX-923.tuner tunerpreset: type: num - pioneer_command: tuner.tunerpreset - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: tuner.tunerpreset + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.tuner tunerpresetup: type: bool - pioneer_command: tuner.tunerpresetup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetup + pioneer_read@instance: false + pioneer_write@instance: true tunerpresetdown: type: bool - pioneer_command: tuner.tunerpresetdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: tuner.tunerpresetdown + pioneer_read@instance: false + pioneer_write@instance: true title: type: str - pioneer_command: tuner.title - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.title + pioneer_read@instance: true + pioneer_write@instance: false genre: type: str - pioneer_command: tuner.genre - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.genre + pioneer_read@instance: true + pioneer_write@instance: false station: type: str - pioneer_command: tuner.station - pioneer_read: true - pioneer_write: false + pioneer_command@instance: tuner.station + pioneer_read@instance: true + pioneer_write@instance: false zone1: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1 + pioneer_read_group_trigger@instance: VSX-923.zone1 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.control + pioneer_read_group_trigger@instance: VSX-923.zone1.control power: type: bool - pioneer_command: zone1.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.control @@ -8349,79 +8415,79 @@ item_structs: mute: type: bool - pioneer_command: zone1.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.control volume: type: num - pioneer_command: zone1.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.control volumeup: type: bool - pioneer_command: zone1.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone1.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone1.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.control lookup: type: list - pioneer_lookup: INPUT#list + pioneer_lookup@instance: INPUT#list inputup: type: bool - pioneer_command: zone1.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone1.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.settings + pioneer_read_group_trigger@instance: VSX-923.zone1.settings standby: type: num - pioneer_command: zone1.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 15 = 15 minutes, 30 = 30 minutes, 60 = 60 minutes sound: @@ -8429,21 +8495,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.settings.sound + pioneer_read_group_trigger@instance: VSX-923.zone1.settings.sound channel_level: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.settings.sound.channel_level + pioneer_read_group_trigger@instance: VSX-923.zone1.settings.sound.channel_level front_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8452,10 +8518,10 @@ item_structs: front_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8464,10 +8530,10 @@ item_structs: front_center: type: num - pioneer_command: zone1.settings.sound.channel_level.front_center - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_center + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8476,10 +8542,10 @@ item_structs: surround_left: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8488,10 +8554,10 @@ item_structs: surround_right: type: num - pioneer_command: zone1.settings.sound.channel_level.surround_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.surround_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8500,10 +8566,10 @@ item_structs: front_height_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8512,10 +8578,10 @@ item_structs: front_height_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_height_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_height_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8524,10 +8590,10 @@ item_structs: front_wide_left: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_left - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_left + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8536,10 +8602,10 @@ item_structs: front_wide_right: type: num - pioneer_command: zone1.settings.sound.channel_level.front_wide_right - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.front_wide_right + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8548,10 +8614,10 @@ item_structs: subwoofer: type: num - pioneer_command: zone1.settings.sound.channel_level.subwoofer - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.channel_level.subwoofer + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8563,14 +8629,14 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.settings.sound.tone_control + pioneer_read_group_trigger@instance: VSX-923.zone1.settings.sound.tone_control tone: type: bool - pioneer_command: zone1.settings.sound.tone_control.tone - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.tone + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8581,10 +8647,10 @@ item_structs: treble: type: num - pioneer_command: zone1.settings.sound.tone_control.treble - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.treble + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8593,22 +8659,22 @@ item_structs: trebleup: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebleup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebleup + pioneer_read@instance: false + pioneer_write@instance: true trebledown: type: bool - pioneer_command: zone1.settings.sound.tone_control.trebledown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.trebledown + pioneer_read@instance: false + pioneer_write@instance: true bass: type: num - pioneer_command: zone1.settings.sound.tone_control.bass - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.tone_control.bass + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8617,29 +8683,29 @@ item_structs: bassup: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassup + pioneer_read@instance: false + pioneer_write@instance: true bassdown: type: bool - pioneer_command: zone1.settings.sound.tone_control.bassdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.tone_control.bassdown + pioneer_read@instance: false + pioneer_write@instance: true general: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone1.settings.sound.general + pioneer_read_group_trigger@instance: VSX-923.zone1.settings.sound.general hdmiout: type: str - pioneer_command: zone1.settings.sound.general.hdmiout - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiout + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8648,14 +8714,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIOUT#list + pioneer_lookup@instance: HDMIOUT#list hdmiaudio: type: str - pioneer_command: zone1.settings.sound.general.hdmiaudio - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.hdmiaudio + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8664,14 +8730,14 @@ item_structs: lookup: type: list - pioneer_lookup: HDMIAUDIO#list + pioneer_lookup@instance: HDMIAUDIO#list dialog: type: num - pioneer_command: zone1.settings.sound.general.dialog - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.dialog + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8680,22 +8746,22 @@ item_structs: dialogup: type: bool - pioneer_command: zone1.settings.sound.general.dialogup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogup + pioneer_read@instance: false + pioneer_write@instance: true dialogdown: type: bool - pioneer_command: zone1.settings.sound.general.dialogdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone1.settings.sound.general.dialogdown + pioneer_read@instance: false + pioneer_write@instance: true listeningmode: type: str - pioneer_command: zone1.settings.sound.general.listeningmode - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.listeningmode + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8704,14 +8770,14 @@ item_structs: lookup: type: list - pioneer_lookup: LISTENINGMODE#list + pioneer_lookup@instance: LISTENINGMODE#list playingmode: type: str - pioneer_command: zone1.settings.sound.general.playingmode - pioneer_read: true - pioneer_write: false - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.playingmode + pioneer_read@instance: true + pioneer_write@instance: false + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8720,14 +8786,14 @@ item_structs: lookup: type: list - pioneer_lookup: PLAYINGMODE#list + pioneer_lookup@instance: PLAYINGMODE#list speakers: type: num - pioneer_command: zone1.settings.sound.general.speakers - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone1.settings.sound.general.speakers + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone1 - VSX-923.zone1.settings @@ -8750,21 +8816,21 @@ item_structs: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone2 + pioneer_read_group_trigger@instance: VSX-923.zone2 control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.zone2.control + pioneer_read_group_trigger@instance: VSX-923.zone2.control power: type: bool - pioneer_command: zone2.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone2 - VSX-923.zone2.control @@ -8778,117 +8844,117 @@ item_structs: mute: type: bool - pioneer_command: zone2.control.mute - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.mute + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone2 - VSX-923.zone2.control volume: type: num - pioneer_command: zone2.control.volume - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.volume + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone2 - VSX-923.zone2.control volumeup: type: bool - pioneer_command: zone2.control.volumeup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumeup + pioneer_read@instance: false + pioneer_write@instance: true volumedown: type: bool - pioneer_command: zone2.control.volumedown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.volumedown + pioneer_read@instance: false + pioneer_write@instance: true input: type: str - pioneer_command: zone2.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: zone2.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.zone2 - VSX-923.zone2.control lookup: type: list - pioneer_lookup: INPUT2#list + pioneer_lookup@instance: INPUT2#list inputup: type: bool - pioneer_command: zone2.control.inputup - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputup + pioneer_read@instance: false + pioneer_write@instance: true inputdown: type: bool - pioneer_command: zone2.control.inputdown - pioneer_read: false - pioneer_write: true + pioneer_command@instance: zone2.control.inputdown + pioneer_read@instance: false + pioneer_write@instance: true hdzone: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.hdzone + pioneer_read_group_trigger@instance: VSX-923.hdzone control: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.hdzone.control + pioneer_read_group_trigger@instance: VSX-923.hdzone.control power: type: bool - pioneer_command: hdzone.control.power - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.power + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.hdzone - VSX-923.hdzone.control input: type: str - pioneer_command: hdzone.control.input - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.control.input + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.hdzone - VSX-923.hdzone.control lookup: type: list - pioneer_lookup: INPUTHD#list + pioneer_lookup@instance: INPUTHD#list settings: read: type: bool enforce_updates: true - pioneer_read_group_trigger: VSX-923.hdzone.settings + pioneer_read_group_trigger@instance: VSX-923.hdzone.settings standby: type: num - pioneer_command: hdzone.settings.standby - pioneer_read: true - pioneer_write: true - pioneer_read_group: + pioneer_command@instance: hdzone.settings.standby + pioneer_read@instance: true + pioneer_write@instance: true + pioneer_read_group@instance: - VSX-923 - VSX-923.hdzone - VSX-923.hdzone.settings - pioneer_read_initial: true + pioneer_read_initial@instance: true remark: 0 = OFF, 0.5 = 30 minutes, 1 = 1 hour, 3 = 3 hours, 6 = 6 hours, 9 = 9 hours plugin_functions: NONE logic_parameters: NONE diff --git a/shelly/__init__.py b/shelly/__init__.py index 35843ccfe..066731a86 100755 --- a/shelly/__init__.py +++ b/shelly/__init__.py @@ -39,7 +39,7 @@ class Shelly(MqttPlugin): the update functions for the items """ - PLUGIN_VERSION = '1.8.2' + PLUGIN_VERSION = '1.8.3' def __init__(self, sh): diff --git a/shelly/plugin.yaml b/shelly/plugin.yaml index eccdf8aca..5844d63f7 100755 --- a/shelly/plugin.yaml +++ b/shelly/plugin.yaml @@ -12,7 +12,7 @@ plugin: # documentation: http://smarthomeng.de/user/plugins/mqtt2/user_doc.html support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1451853-support-thread-für-das-shelly-plugin - version: 1.8.2 # Plugin version + version: 1.8.3 # Plugin version sh_minversion: '1.9.5.6' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: True # plugin supports multi instance @@ -123,17 +123,20 @@ item_structs: online: type: bool + description: State of Shelly device (online/offline) shelly_id: ..:. shelly_type: ..:. shelly_attr: online power: type: num + description: Power consumption shelly_id: ..:. shelly_type: ..:. shelly_group: "switch:0" shelly_attr: power energy: type: num + description: Energy measured in Milliwatt shelly_id: ..:. shelly_type: ..:. shelly_group: "switch:0" @@ -146,11 +149,13 @@ item_structs: #shelly_attr: relay temp: type: num + description: Temperature of Shelly device measured in °C shelly_id: ..:. shelly_type: ..:. shelly_attr: temp temp_f: type: num + description: Temperature of Shelly device measured in °F shelly_id: ..:. shelly_type: ..:. shelly_attr: temp_f @@ -170,22 +175,36 @@ item_structs: #shelly_attr: relay voltage: type: num + description: Voltage of switched outlet of Shelly device measured in Volt shelly_id: ..:. shelly_type: ..:. shelly_group: "switch:0" shelly_attr: voltage current: type: num + description: Current on switched outlet of Shelly device measured in Ampere shelly_id: ..:. shelly_type: ..:. shelly_group: "switch:0" shelly_attr: current energy_by_minute: type: num + cache: True + description: Energy by minute measured in Milliwatt/minute shelly_id: ..:. shelly_type: ..:. shelly_group: "switch:0" shelly_attr: energy_by_minute + energy_wh: + type: num + description: Calculated energy measured in Wh + eval: round(sh...energy_by_minute() *60 /1000, 4) + eval_trigger: ..energy_by_minute + energy_kwh: + type: num + description: Calculated energy measured in kWh + eval: round(sh...energy_wh() /1000, 4) + eval_trigger: ..energy_wh shellyht: name: WLAN Temperatur & Feuchtesensor (Zwischenstecker) @@ -238,10 +257,12 @@ item_structs: shelly_attr: illumination temp: type: num + description: Temperature of Shelly device measured in °C shelly_id: ..:. shelly_attr: temp temp_f: type: num + description: Temperature of Shelly device measured in °F shelly_id: ..:. shelly_attr: temp_f battery: @@ -251,6 +272,7 @@ item_structs: shelly_attr: battery voltage: type: num + description: Voltage on switched outlet of Shelly device measured in Volt shelly_id: ..:. shelly_group: sensor shelly_attr: voltage @@ -282,6 +304,7 @@ item_structs: shelly_attr: online temp: type: num + description: Temperature of Shelly device measured in °C shelly_id: ..:. shelly_type: ..:. shelly_attr: temp diff --git a/smartvisu/__init__.py b/smartvisu/__init__.py index 732eca49d..bb64e465b 100755 --- a/smartvisu/__init__.py +++ b/smartvisu/__init__.py @@ -46,7 +46,7 @@ class SmartVisu(SmartPlugin): - PLUGIN_VERSION="1.8.15" + PLUGIN_VERSION="1.8.16" ALLOW_MULTIINSTANCE = True visu_definition = None diff --git a/smartvisu/plugin.yaml b/smartvisu/plugin.yaml index bd6878872..cf6280780 100755 --- a/smartvisu/plugin.yaml +++ b/smartvisu/plugin.yaml @@ -12,7 +12,7 @@ plugin: #documentation: '' support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1586800-support-thread-für-das-smartvisu-plugin - version: 1.8.15 # Plugin version + version: 1.8.16 # Plugin version sh_minversion: '1.9.3.5' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) py_minversion: '3.6' # minimum Python version to use for this plugin diff --git a/smartvisu/svgenerator.py b/smartvisu/svgenerator.py index e4852f887..b2fb6cdc9 100755 --- a/smartvisu/svgenerator.py +++ b/smartvisu/svgenerator.py @@ -25,13 +25,13 @@ from lib.item import Items + class SmartVisuGenerator: valid_sv_page_entries = ['room', 'overview', 'separator', 'seperator', - 'category', 'cat_overview', 'cat_separator','cat_seperator', + 'category', 'cat_overview', 'cat_separator', 'cat_seperator', 'room_lite', 'sv_overview'] - def __init__(self, plugin_instance, visu_definition=None): self.items = Items.get_instance() @@ -43,9 +43,9 @@ def __init__(self, plugin_instance, visu_definition=None): self.smartvisu_version = plugin_instance.smartvisu_version self.overwrite_templates = plugin_instance.overwrite_templates self.visu_style = plugin_instance.visu_style.lower() - if not self.visu_style in ['std','blk']: + if self.visu_style not in ['std', 'blk']: self.visu_style = 'std' - self.logger.warning("SmartVisuGenerator: visu_style '{}' unknown, using visu_style '{1}'".format(plugin_instance.visu_style, self.visu_style)) + self.logger.warning("SmartVisuGenerator: visu_style '{0}' unknown, using visu_style '{1}'".format(plugin_instance.visu_style, self.visu_style)) self.list_deprecated_warnings = plugin_instance.list_deprecated_warnings self.logger.info("Generating pages for smartVISU v{}".format(self.smartvisu_version)) @@ -102,7 +102,6 @@ def initialize_visu_menu(self, nav_config, menu): self.add_menuentry_to_list(menu, menu_entry) self.logger.debug("initialize_visu_menu: '{}' menu_entry={}".format(menu, menu_entry)) - def handle_heading_attributes(self, room): if 'sv_heading_right' in room.conf: heading_right = room.conf['sv_heading_right'] @@ -122,7 +121,6 @@ def handle_heading_attributes(self, room): heading = '' return heading - def get_widgetblocksize(self, item): """ Returns the blocksize for the block in which the item is to be displayed. @@ -134,13 +132,12 @@ def get_widgetblocksize(self, item): """ if 'sv_blocksize' in item.conf: blocksize = item.conf['sv_blocksize'] - if not blocksize in ['1','2','3']: + if blocksize not in ['1', '2', '3']: blocksize = '2' else: blocksize = '2' return blocksize - def get_attribute(self, attr, item): if attr in item.conf: attrvalue = item.conf[attr] @@ -148,7 +145,6 @@ def get_attribute(self, attr, item): attrvalue = '' return attrvalue - def create_page(self, room, menu_entry): """ Interpretation of the room-specific item-attributes. @@ -159,7 +155,7 @@ def create_page(self, room, menu_entry): :return: html code to be included in the visu file for the room :rtype: str """ - block_style = 'std' # 'std' or 'noh' + block_style = 'std' # 'std' or 'noh' widgetblocktemplate = 'widgetblock_' + self.visu_style + '_' + block_style + '.html' widgetblocktemplate2 = 'widgetblock2_' + self.visu_style + '_' + block_style + '.html' widgets = '' @@ -215,7 +211,6 @@ def create_page(self, room, menu_entry): menu_entry['content'] += widgets return r - def pages(self): if not self.remove_oldpages(): return @@ -284,13 +279,13 @@ def pages(self): def create_menuentry(self, menu, entry_name, item_path, separator, img_name, nav_aside, nav_aside2, from_navconfig=False): for menu_entry in self.navigation[menu]: if menu_entry['name'] == entry_name: - if menu_entry.get('img', '') == '' and menu_entry.get('img_set', False) == False: + if menu_entry.get('img', '') == '' and menu_entry.get('img_set', False) is False: menu_entry['img'] = img_name if menu_entry['item_path'] == '': menu_entry['item_path'] = item_path - if menu_entry.get('nav_aside', '') == ''and menu_entry.get('nav_aside_set', False) == False: + if menu_entry.get('nav_aside', '') == '' and menu_entry.get('nav_aside_set', False) is False: menu_entry['nav_aside'] = nav_aside - if menu_entry.get('nav_aside2', '') == '' and menu_entry.get('nav_aside2_set', False) == False: + if menu_entry.get('nav_aside2', '') == '' and menu_entry.get('nav_aside2_set', False) is False: menu_entry['nav_aside2'] = nav_aside2 return menu_entry @@ -299,7 +294,7 @@ def create_menuentry(self, menu, entry_name, item_path, separator, img_name, nav menu_entry['item_path'] = item_path menu_entry['separator'] = separator menu_entry['page'] = menu + '.' + entry_name - for ch in [' ',':','/','\\']: + for ch in [' ', ':', '/', '\\']: if ch in menu_entry['page']: menu_entry['page'] = menu_entry['page'].replace(ch, '_') menu_entry['heading'] = '' @@ -329,7 +324,6 @@ def create_menuentry(self, menu, entry_name, item_path, separator, img_name, nav return menu_entry - def add_menuentry_to_list(self, menu, menu_entry): for entry in self.navigation[menu]: if entry['name'] == menu_entry['name']: @@ -358,7 +352,7 @@ def write_navigation_and_pages(self, menu, navigation_file): ('{{ visu_aside2 }}', menu_entry['nav_aside2']), ('item.name', menu_entry['name']), ("'item", "'" + menu_entry['item_path']) ] - if menu_entry['separator'] == True: + if menu_entry['separator'] is True: nav_list += self.parse_tpl('navi_sep.html', [('{{ name }}', menu_entry['name'])]) else: #menu_entry['html'] = self.parse_tpl('navi.html', parse_list) @@ -371,7 +365,7 @@ def write_navigation_and_pages(self, menu, navigation_file): nav_list += self.parse_tpl('navi.html', parse_list) # build page code - if menu_entry['separator'] == False: + if menu_entry['separator'] is False: # build and write file for a single room r = self.parse_tpl(menu+'_page.html', [('{{ visu_name }}', menu_entry['name']), ('{{ visu_widgets }}', menu_entry['content']), ('{{ visu_img }}', menu_entry['img']), ('{{ visu_heading }}', menu_entry['heading'])]) @@ -420,7 +414,6 @@ def parse_tpl(self, template, replace): tpl = tpl.replace(s, rs) return tpl - def write_parseresult(self, htmlfile, parseresult): try: with open(self.pages_dir + '/' + htmlfile, 'w') as f: @@ -428,7 +421,6 @@ def write_parseresult(self, htmlfile, parseresult): except Exception as e: self.logger.warning("Could not write to {0}/{1}: {2}".format(self.pages_dir, htmlfile, e)) - def copy_tpl(self, tplname, destname=''): if destname == '': destname = tplname @@ -487,7 +479,7 @@ def copy_templates(self): if self.smartvisu_version >= '2.9': for fn in os.listdir(self.shng_tpldir): - if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.gen_tpldir, fn)) ): + if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.gen_tpldir, fn))): self.logger.debug("copy_templates: Copying template '{}' from plugin to smartVISU v{} ({})".format(fn, self.smartvisu_version, self.gen_tpldir)) shutil.copy2(os.path.join(self.shng_tpldir, fn), self.gen_tpldir) shutil.copy2(os.path.join(self.sv_tpldir, 'index.html'), self.gen_tpldir) @@ -503,10 +495,10 @@ def copy_templates(self): pass # Open file for twig import statements (for root.html) for fn in os.listdir(self.shng_tpldir): - if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.gen_tpldir, fn)) ): + if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.gen_tpldir, fn))): self.logger.debug("copy_templates: Copying template '{}' from plugin to smartVISU v{}".format(fn, self.smartvisu_version)) try: - shutil.copy2( os.path.join(self.shng_tpldir, fn), self.gen_tpldir ) + shutil.copy2(os.path.join(self.shng_tpldir, fn), self.gen_tpldir) except Exception as e: self.logger.error("Could not copy {0} from {1} to {2}".format(fn, self.shng_tpldir, self.gen_tpldir)) return diff --git a/smartvisu/svinstallwidgets.py b/smartvisu/svinstallwidgets.py index b9b16361d..9378ab1c5 100755 --- a/smartvisu/svinstallwidgets.py +++ b/smartvisu/svinstallwidgets.py @@ -46,6 +46,7 @@ def __init__(self, plugin_instance): self.pgbdir = os.path.join(self.smartvisu_dir, 'dropins') self.icndir_ws = os.path.join(self.smartvisu_dir, 'dropins/icons/ws') self.icndir_sw = os.path.join(self.smartvisu_dir, 'dropins/icons/sw') + #if self.smartvisu_version >= '3.0' and self.smartvisu_version <= '3.4': if self.smartvisu_version >= '3.0': # v3.x self.outdir = os.path.join(self.smartvisu_dir, 'dropins/shwidgets') diff --git a/stateengine/StateEngineAction.py b/stateengine/StateEngineAction.py index e8f2392da..d6653982a 100755 --- a/stateengine/StateEngineAction.py +++ b/stateengine/StateEngineAction.py @@ -22,6 +22,7 @@ from . import StateEngineEval from . import StateEngineValue from . import StateEngineDefaults +from . import StateEngineCurrent import datetime from lib.shtime import Shtime import re @@ -41,14 +42,16 @@ def function(self): def action_status(self): return self._action_status - # Cast function for delay + # Cast function for delay and other time based attributes # value: value to cast @staticmethod - def __cast_delay(value): + def __cast_seconds(value): if isinstance(value, str): delay = value.strip() if delay.endswith('m'): return int(delay.strip('m')) * 60 + elif delay.endswith('h'): + return int(delay.strip('h')) * 3600 else: return int(delay) elif isinstance(value, int): @@ -56,7 +59,7 @@ def __cast_delay(value): elif isinstance(value, float): return int(value) else: - raise ValueError("Can not cast delay value {0} to int!".format(value)) + raise ValueError("Can not cast value {0} to int!".format(value)) # Initialize the action # abitem: parent SeItem instance @@ -71,11 +74,14 @@ def __init__(self, abitem, name: str): self.__delay = StateEngineValue.SeValue(self._abitem, "delay") self.__repeat = None self.__instanteval = None - self.conditionset = StateEngineValue.SeValue(self._abitem, "conditionset", True, "str") - self.previousconditionset = StateEngineValue.SeValue(self._abitem, "previousconditionset", True, "str") - self.previousstate_conditionset = StateEngineValue.SeValue(self._abitem, "previousstate_conditionset", True, "str") + self.nextconditionset = StateEngineValue.SeValue(self._abitem, "nextconditionset", True, "regex") + self.conditionset = StateEngineValue.SeValue(self._abitem, "conditionset", True, "regex") + self.previousconditionset = StateEngineValue.SeValue(self._abitem, "previousconditionset", True, "regex") + self.previousstate_conditionset = StateEngineValue.SeValue(self._abitem, "previousstate_conditionset", True, "regex") self.__mode = StateEngineValue.SeValue(self._abitem, "mode", True, "str") self.__order = StateEngineValue.SeValue(self._abitem, "order", False, "num") + self._minagedelta = StateEngineValue.SeValue(self._abitem, "minagedelta") + self._agedelta = 0 self._scheduler_name = None self._function = None self.__template = None @@ -83,67 +89,84 @@ def __init__(self, abitem, name: str): self._retrigger_issue = None self._suspend_issue = None self.__queue = abitem.queue - - def update_delay(self, value): + self._action_type = None + self._state = None + self._info_dict = {} + + def update_action_details(self, state, action_type): + if self._action_type is None: + self._action_type = action_type + if self._state is None: + self._state = state + self._log_develop("Updating state for action {} to {}, action type {}", self._name, state.id, action_type) + + def _update_value(self, value_type, value, attribute, cast=None): _issue_list = [] - _, _, _issue, _ = self.__delay.set(value) - if _issue: - _issue = {self._name: {'issue': _issue, 'attribute': 'delay', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - _issue_list.append(_issue) - _issue = self.__delay.set_cast(SeActionBase.__cast_delay) + if value_type is None: + return _issue_list + _, _, _issue, _ = value_type.set(value) if _issue: - _issue = {self._name: {'issue': _issue, 'attribute': 'delay', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = {self._name: {'issue': _issue, 'attribute': [attribute], + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} _issue_list.append(_issue) + if cast == 'seconds': + _issue = value_type.set_cast(SeActionBase.__cast_seconds) + if _issue: + _issue = {self._name: {'issue': _issue, 'attribute': [attribute], + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + _issue_list.append(_issue) _issue_list = StateEngineTools.flatten_list(_issue_list) return _issue_list + def update_delay(self, value): + _issue = self._update_value(self.__delay, value, 'delay', 'seconds') + return _issue + def update_instanteval(self, value): - if self.__instanteval is None: - self.__instanteval = StateEngineValue.SeValue(self._abitem, "instanteval", False, "bool") - _, _, _issue, _ = self.__instanteval.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'instanteval', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.__instanteval, value, 'instanteval') + return _issue + + def update_mindelta(self, value): + self._log_warning("Mindelta is only relevant for set (force) actions - ignoring {}", value) + _issue = {self._name: {'issue': 'Mindelta not relevant for this action type', 'attribute': ['mindelta'], + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + return _issue + + def update_minagedelta(self, value): + if self._minagedelta is None: + self._minagedelta = StateEngineValue.SeValue(self._abitem, "minagedelta", False, "num") + _issue = self._update_value(self._minagedelta, value, 'minagedelta', 'seconds') return _issue def update_repeat(self, value): if self.__repeat is None: self.__repeat = StateEngineValue.SeValue(self._abitem, "repeat", False, "bool") - _, _, _issue, _ = self.__repeat.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'repeat', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.__repeat, value, 'repeat') return _issue def update_order(self, value): - _, _, _issue, _ = self.__order.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'order', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.__order, value, 'order') + return _issue + + def update_nextconditionset(self, value): + _issue = self._update_value(self.nextconditionset, value, 'nextconditionset') return _issue def update_conditionset(self, value): - _, _, _issue, _ = self.conditionset.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'conditionset', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.conditionset, value, 'conditionset') return _issue def update_previousconditionset(self, value): - _, _, _issue, _ = self.previousconditionset.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'previousconditionset', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.previousconditionset, value, 'previousconditionset') return _issue def update_previousstate_conditionset(self, value): - _, _, _issue, _ = self.previousstate_conditionset.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'previousstate_conditionset', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = self._update_value(self.previousstate_conditionset, value, 'previousstate_conditionset') return _issue def update_mode(self, value): - _value, _, _issue, _ = self.__mode.set(value) - _issue = {self._name: {'issue': _issue, 'attribute': 'mode', - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - return _value[0], _issue + _issue = self._update_value(self.__mode, value, 'mode') + return _issue def get_order(self): order = self.__order.get(1) @@ -152,64 +175,66 @@ def get_order(self): order = 1 return order - def update_webif_actionstatus(self, state, name, success, issue=None): - try: - if self._abitem.webif_infos[state.id].get('actions_stay'): - _key = ['{}'.format(state.id), 'actions_stay', '{}'.format(name), 'actionstatus', 'success'] - self._abitem.update_webif(_key, success) - _key = ['{}'.format(state.id), 'actions_stay', '{}'.format(name), 'actionstatus', 'issue'] - self._abitem.update_webif(_key, issue) - except Exception: - pass - try: - if self._abitem.webif_infos[state.id].get('actions_enter'): - _key = ['{}'.format(state.id), 'actions_enter', '{}'.format(name), 'actionstatus', 'success'] - self._abitem.update_webif(_key, success) - _key = ['{}'.format(state.id), 'actions_enter', '{}'.format(name), 'actionstatus', 'issue'] - self._abitem.update_webif(_key, issue) - except Exception: - pass - try: - if self._abitem.webif_infos[state.id].get('actions_enter_or_stay'): - _key = ['{}'.format(state.id), 'actions_enter_or_stay', '{}'.format(name), 'actionstatus', 'success'] - self._abitem.update_webif(_key, success) - _key = ['{}'.format(state.id), 'actions_enter_or_stay', '{}'.format(name), 'actionstatus', 'issue'] - self._abitem.update_webif(_key, issue) - except Exception: - pass + def update_webif_actionstatus(self, state, name, success, issue=None, reason=None): try: - state.update_name(state.state_item) - _key_name = ['{}'.format(state.id), 'name'] - self._abitem.update_webif(_key_name, state.name) - if self._abitem.webif_infos[state.id].get('actions_leave'): - _key = ['{}'.format(state.id), 'actions_leave', '{}'.format(name), 'actionstatus', 'success'] + if self._action_type == "actions_leave": + state.update_name(state.state_item) + _key_name = ['{}'.format(state.id), 'name'] + self._abitem.update_webif(_key_name, state.name, True) + if self._abitem.webif_infos[state.id].get(self._action_type): + _key = ['{}'.format(state.id), self._action_type, '{}'.format(name), 'actionstatus', 'success'] self._abitem.update_webif(_key, success) - _key = ['{}'.format(state.id), 'actions_leave', '{}'.format(name), 'actionstatus', 'issue'] - self._abitem.update_webif(_key, issue) - except Exception: - pass + if issue is not None: + _key = ['{}'.format(state.id), self._action_type, '{}'.format(name), 'actionstatus', 'issue'] + self._abitem.update_webif(_key, issue) + if reason is not None: + _key = ['{}'.format(state.id), self._action_type, '{}'.format(name), 'actionstatus', 'reason'] + self._abitem.update_webif(_key, reason) + except Exception as ex: + self._log_warning("Error setting action status {}: {}", name, ex) # Write action to logger def write_to_logger(self): self._log_info("function: {}", self._function) - self.__delay.write_to_logger() + delay = self.__delay.write_to_logger() or 0 if self.__repeat is not None: - self.__repeat.write_to_logger() + repeat = self.__repeat.write_to_logger() + else: + repeat = False if self.__instanteval is not None: - self.__instanteval.write_to_logger() + instanteval = self.__instanteval.write_to_logger() + else: + instanteval = False + if self.nextconditionset is not None: + nextconditionset = self.nextconditionset.write_to_logger() + else: + nextconditionset = None if self.conditionset is not None: - self.conditionset.write_to_logger() + conditionset = self.conditionset.write_to_logger() + else: + conditionset = None if self.previousconditionset is not None: - self.previousconditionset.write_to_logger() + previousconditionset = self.previousconditionset.write_to_logger() + else: + previousconditionset = None if self.previousstate_conditionset is not None: - self.previousstate_conditionset.write_to_logger() + previousstate_conditionset = self.previousstate_conditionset.write_to_logger() + else: + previousstate_conditionset = None if self.__mode is not None: - self.__mode.write_to_logger() - self.__order.write_to_logger() - - def set_source(self, current_condition, previous_condition, previousstate_condition): + mode = self.__mode.write_to_logger() + else: + mode = None + order = self.__order.write_to_logger() or 0 + self._info_dict.update({'function': str(self._function), 'nextconditionset': nextconditionset, + 'conditionset': conditionset, 'repeat': str(repeat), 'delay': str(delay), 'mode': mode, + 'order': str(order), 'previousconditionset': previousconditionset, + 'instanteval': str(instanteval), 'previousstate_conditionset': previousstate_conditionset, + 'actionstatus': {}}) + + def set_source(self, current_condition, previous_condition, previousstate_condition, next_condition): source = [] - if current_condition in [[], None] and previous_condition in [[], None] and previousstate_condition in [[], None]: + if current_condition in [[], None] and previous_condition in [[], None] and previousstate_condition in [[], None] and next_condition in [[], None]: source = self._parent else: if current_condition != []: @@ -218,6 +243,8 @@ def set_source(self, current_condition, previous_condition, previousstate_condit source.append("previouscondition={}".format(previous_condition)) if previousstate_condition != []: source.append("previousstate_condition={}".format(previousstate_condition)) + if next_condition != []: + source.append("nextcondition={}".format(next_condition)) source = ", ".join(source) return source @@ -230,7 +257,7 @@ def set_source(self, current_condition, previous_condition, previousstate_condit # newly evaluated mindelta # Any issue that might have occured as a dict def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=None): - _issue = {self._name: {'issue': None, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = {self._name: {'issue': None, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} if isinstance(check_item, str): item = None #self._log_develop("Get item from eval on {} {}", self._function, check_item) @@ -245,25 +272,22 @@ def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=No "plain eval expression without a preceeding eval. "\ "Please update your config of {}" _issue = { - self._name: {'issue': _text.format(check_item), 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + self._name: {'issue': _text.format(check_item), 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} self._log_warning(_text, check_item) _, _, item = item.partition(":") elif re.match(r'^.*:', item): _text = "se_eval/item attributes have to be plain eval expression. Please update your config of {}" _issue = { self._name: {'issue': _text.format(check_item), - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} self._log_warning(_text, check_item) _, _, item = item.partition(":") item = eval(item) if item is not None: check_item, _issue = self._abitem.return_item(item) _issue = { - self._name: {'issue': _issue, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - if check_value: - check_value.set_cast(check_item.cast) - if check_mindelta: - check_mindelta.set_cast(check_item.cast) + self._name: {'issue': _issue, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + check_item, check_mindelta = self._cast_stuff(check_item, check_mindelta, check_value) self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) if self._abitem.id == check_item.property.path: self._caller += '_self' @@ -271,25 +295,60 @@ def check_getitem_fromeval(self, check_item, check_value=None, check_mindelta=No else: self._log_develop("Got no item from eval on {} with initial item {}", self._function, item) except Exception as ex: - _issue = {self._name: {'issue': ex, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = {self._name: {'issue': ex, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} # raise Exception("Problem evaluating item '{}' from eval: {}".format(check_item, ex)) self._log_error("Problem evaluating item '{}' from eval: {}", check_item, ex) check_item = None if item is None: _issue = {self._name: {'issue': ['Item {} from eval not existing'.format(check_item)], - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} # raise Exception("Problem evaluating item '{}' from eval. It does not exist.".format(check_item)) self._log_error("Problem evaluating item '{}' from eval. It does not exist", check_item) check_item = None elif check_item is None: _issue = {self._name: {'issue': ['Item is None'], - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return check_item, check_value, check_mindelta, _issue - def check_complete(self, state, check_item, check_status, check_mindelta, check_value, action_type, evals_items=None, use=None): + def eval_minagedelta(self, actioninfo, state): + lastrun = self._abitem.last_run.get(self._name) + if not lastrun: + return False + if not self._minagedelta.is_empty(): + minagedelta = self._minagedelta.get() + try: + minagedelta = float(minagedelta) + except Exception: + self._log_warning("{0}: minagedelta {1} seems to be no number.", self._name, minagedelta) + minagedelta = 0.0 + self._agedelta = float((datetime.datetime.now() - lastrun).total_seconds()) + self._info_dict.update({'agedelta': self._agedelta, 'minagedelta': str(minagedelta)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) + if self._agedelta < minagedelta: + text = "{0}: {1} because age delta '{2:.2f}' is lower than minagedelta '{3:.2f}'." + self._log_debug(text, self.name, actioninfo, self._agedelta, minagedelta) + self.update_webif_actionstatus(state, self._name, 'False', None, + f"(age delta '{self._agedelta:.2f}' < '{minagedelta:.2f})") + return True + else: + text = "{0}: Proceeding as age delta '{1:.2f}' is higher than minagedelta '{2:.2f}'." + self.update_webif_actionstatus(state, self._name, 'True', None, + f"(age delta '{self._agedelta:.2f}' > '{minagedelta:.2f})") + self._log_debug(text, self.name, self._agedelta, minagedelta) + return False + else: + return False + + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + + def check_complete(self, state, check_item, check_status, check_mindelta, check_minagedelta, check_value, action_type, evals_items=None, use=None): _issue = {self._name: {'issue': None, 'issueorigin': [{'state': state.id, 'action': self._function}]}} - self._log_develop("Check item {} status {} value {} use {} evals_items {}", check_item, check_status, check_value, use, evals_items) try: _name = evals_items.get(self.name) if _name is not None: @@ -324,41 +383,13 @@ def check_complete(self, state, check_item, check_status, check_mindelta, check_ if check_item is None and _issue[self._name].get('issue') is None: _issue = {self._name: {'issue': ['Item not defined in rules section'], 'issueorigin': [{'state': state.id, 'action': self._function}]}} - # missing status in action: Try to find it. - if check_status is None: - status = StateEngineTools.find_attribute(self._sh, state, "se_status_" + self._name, 0, use) - if status is not None: - check_status, _issue = self._abitem.return_item(status) - _issue = {self._name: {'issue': _issue, - 'issueorigin': [{'state': state.id, 'action': self._function}]}} - if check_mindelta.is_empty(): - mindelta = StateEngineTools.find_attribute(self._sh, state, "se_mindelta_" + self._name, 0, use) - if mindelta is not None: - check_mindelta.set(mindelta) - - if check_status is not None: - check_value.set_cast(check_status.cast) - check_mindelta.set_cast(check_status.cast) - self._scheduler_name = "{}-SeItemDelayTimer".format(check_status.property.path) - if self._abitem.id == check_status.property.path: - self._caller += '_self' - elif check_status is None: - if isinstance(check_item, str): - pass - elif check_item is not None: - check_value.set_cast(check_item.cast) - check_mindelta.set_cast(check_item.cast) - self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) - if self._abitem.id == check_item.property.path: - self._caller += '_self' - if _issue[self._name].get('issue') not in [[], [None], None]: - self._log_develop("Issue with {} action {}", action_type, _issue) - else: - _issue = {self._name: {'issue': None, - 'issueorigin': [{'state': state.id, 'action': self._function}]}} - - return check_item, check_status, check_mindelta, check_value, _issue + if check_minagedelta.is_empty(): + minagedelta = StateEngineTools.find_attribute(self._sh, state, "se_minagedelta_" + self._name, 0, use) + if minagedelta is not None: + check_minagedelta.set(minagedelta) + check_item, check_status, check_mindelta, check_value, _issue = self._get_status(check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue) + return check_item, check_status, check_mindelta, check_minagedelta, check_value, _issue # Execute action (considering delay, etc) # is_repeat: Indicate if this is a repeated action without changing the state @@ -366,93 +397,117 @@ def check_complete(self, state, check_item, check_status, check_mindelta, check_ # state: state item that triggered the action def execute(self, is_repeat: bool, allow_item_repeat: bool, state): # check if any conditiontype is met or not - # condition: type of condition 'conditionset'/'previousconditionset'/'previousstate_conditionset' + # condition: type of condition 'conditionset'/'previousconditionset'/'previousstate_conditionset'/'nextconditionset' def _check_condition(condition: str): + self._log_debug("Checking {}", condition) + self._log_increase_indent() _conditions_met_count = 0 _conditions_necessary_count = 0 _condition_to_meet = None - _updated__current_condition = None + _updated_current_condition = None if condition == 'conditionset': _condition_to_meet = None if self.conditionset.is_empty() else self.conditionset.get() _current_condition = self._abitem.get_lastconditionset_id() - _updated__current_condition = self._abitem.get_variable("current.state_id") if _current_condition == '' else _current_condition + _updated_current_condition = self._abitem.get_variable("current.state_id") if _current_condition == '' else _current_condition elif condition == 'previousconditionset': _condition_to_meet = None if self.previousconditionset.is_empty() else self.previousconditionset.get() _current_condition = self._abitem.get_previousconditionset_id() - _updated__current_condition = self._abitem.get_previousstate_id() if _current_condition == '' else _current_condition + _updated_current_condition = self._abitem.get_previousstate_id() if _current_condition == '' else _current_condition elif condition == 'previousstate_conditionset': _condition_to_meet = None if self.previousstate_conditionset.is_empty() else self.previousstate_conditionset.get() _current_condition = self._abitem.get_previousstate_conditionset_id() - _updated__current_condition = self._abitem.get_previousstate_id() if _current_condition == '' else _current_condition + _updated_current_condition = self._abitem.get_previousstate_id() if _current_condition == '' else _current_condition + elif condition == 'nextconditionset': + _condition_to_meet = None if self.nextconditionset.is_empty() else self.nextconditionset.get() + _current_condition = self._abitem.get_nextconditionset_id() + _updated_current_condition = self._abitem.get_variable("next.conditionset_id") if _current_condition == '' else _current_condition _condition_to_meet = _condition_to_meet if isinstance(_condition_to_meet, list) else [_condition_to_meet] _condition_met = [] + self._log_decrease_indent() for cond in _condition_to_meet: - if cond is not None: + if cond is not None and condition not in _conditions_met_type: _conditions_necessary_count += 1 _orig_cond = cond try: - cond = re.compile(cond) - _matching = cond.fullmatch(_updated__current_condition) + _matching = cond.fullmatch(_updated_current_condition) if _matching: - self._log_debug("Given {} {} matches current one: {}", condition, _orig_cond, _updated__current_condition) - _condition_met.append(_updated__current_condition) + self._log_debug("Given {} '{}' matches current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) + _condition_met.append(_updated_current_condition) _conditions_met_count += 1 + _conditions_met_type.append(condition) else: - self._log_debug("Given {} {} not matching current one: {}", condition, _orig_cond, _updated__current_condition) + self._log_debug("Given {} '{}' not matching current one: '{}'", condition, _orig_cond.pattern, _updated_current_condition) + self.update_webif_actionstatus(state, self._name, 'False', None, f"({condition} {_orig_cond.pattern} not met)") except Exception as ex: if cond is not None: - self._log_warning("Given {} {} is not a valid regex: {}", condition, _orig_cond, ex) + self._log_warning("Given {} '{}' is not a valid regex: {}", condition, _orig_cond.pattern, ex) return _condition_met, _conditions_met_count, _conditions_necessary_count # update web interface with delay info # action_type: 'actions_enter', etc. # delay_info: delay information - def _update_delay_webif(action_type: str, delay_info: str): + def _update_delay_webif(delay_info: str): + if self._action_type == "actions_leave": + try: + state.update_name(state.state_item) + _key_name = ['{}'.format(state.id), 'name'] + self._abitem.update_webif(_key_name, state.name, True) + except Exception: + pass try: - _key = ['{}'.format(state.id), '{}'.format(action_type), '{}'.format(self._name), 'delay'] - self._abitem.update_webif(_key, delay_info) + _key = ['{}'.format(state.id), self._action_type, '{}'.format(self._name), 'delay'] + self._abitem.update_webif(_key, delay_info, True) except Exception: pass # update web interface with repeat info # value: bool type True or False for repeat value def _update_repeat_webif(value: bool): - _key1 = ['{}'.format(state.id), 'actions_stay', '{}'.format(self._name), 'repeat'] - _key2 = ['{}'.format(state.id), 'actions_enter_or_stay', '{}'.format(self._name), 'repeat'] - result = self._abitem.update_webif(_key1, value) - if result is False: - self._abitem.update_webif(_key2, value) + _key1 = [state.id, self._action_type, self._name, 'repeat'] + self._abitem.update_webif(_key1, value, True) self._log_decrease_indent(50) self._log_increase_indent() - self._log_info("Action '{0}': Preparing", self._name) + self._log_info("Action '{0}' defined in '{1}': Preparing", self._name, self._action_type) self._log_increase_indent() try: self._getitem_fromeval() - self._log_decrease_indent() _validitem = True except Exception: _validitem = False - self._log_decrease_indent() if not self._can_execute(state): self._log_decrease_indent() return conditions_met = 0 condition_necessary = 0 - current_condition_met, cur_conditions_met, cur_condition_necessary = _check_condition('conditionset') - conditions_met += cur_conditions_met - condition_necessary += min(1, cur_condition_necessary) - previous_condition_met, prev_conditions_met, prev_condition_necessary = _check_condition('previousconditionset') - conditions_met += prev_conditions_met - condition_necessary += min(1, prev_condition_necessary) - previousstate_condition_met, prevst_conditions_met, prevst_condition_necessary = _check_condition('previousstate_conditionset') - conditions_met += prevst_conditions_met - condition_necessary += min(1, prevst_condition_necessary) + current_condition_met = None + previous_condition_met = None + previousstate_condition_met = None + next_condition_met = None + _conditions_met_type = [] + if not self.conditionset.is_empty(): + current_condition_met, cur_conditions_met, cur_condition_necessary = _check_condition('conditionset') + conditions_met += cur_conditions_met + condition_necessary += min(1, cur_condition_necessary) + if not self.previousconditionset.is_empty(): + previous_condition_met, prev_conditions_met, prev_condition_necessary = _check_condition('previousconditionset') + conditions_met += prev_conditions_met + condition_necessary += min(1, prev_condition_necessary) + if not self.previousstate_conditionset.is_empty(): + previousstate_condition_met, prevst_conditions_met, prevst_condition_necessary = _check_condition('previousstate_conditionset') + conditions_met += prevst_conditions_met + condition_necessary += min(1, prevst_condition_necessary) + if not self.nextconditionset.is_empty(): + next_condition_met, next_conditions_met, next_conditionset_necessary = _check_condition('nextconditionset') + conditions_met += next_conditions_met + condition_necessary += min(1, next_conditionset_necessary) + self._log_decrease_indent() self._log_develop("Action '{0}': conditions met: {1}, necessary {2}.", self._name, conditions_met, condition_necessary) if conditions_met < condition_necessary: self._log_info("Action '{0}': Skipping because not all conditions are met.", self._name) return - + elif condition_necessary > 0 and conditions_met == condition_necessary: + self.update_webif_actionstatus(state, self._name, 'True', None, "(all conditions met)") if is_repeat: if self.__repeat is None: if allow_item_repeat: @@ -460,21 +515,32 @@ def _update_repeat_webif(value: bool): _update_repeat_webif(True) else: self._log_info("Action '{0}': Repeat denied by item configuration.", self._name) + self.update_webif_actionstatus(state, self._name, 'False', None, "(no repeat by item)") _update_repeat_webif(False) return elif self.__repeat.get(): repeat_text = " Repeat allowed by action configuration." + self.update_webif_actionstatus(state, self._name, 'True') _update_repeat_webif(True) else: self._log_info("Action '{0}': Repeat denied by action configuration.", self._name) + self.update_webif_actionstatus(state, self._name, 'False', None, "(no repeat by action)") _update_repeat_webif(False) return else: - repeat_text = "" + if self.__repeat is None: + repeat_text = "" + elif self.__repeat.get(): + repeat_text = " Repeat allowed by action configuration but not applicable." + self.update_webif_actionstatus(state, self._name, 'True') + else: + repeat_text = "" self._log_increase_indent() if _validitem: delay = 0 if self.__delay.is_empty() else self.__delay.get() - plan_next = self._se_plugin.scheduler_return_next(self._scheduler_name) + plan_next = None + if self._scheduler_name: + plan_next = self._se_plugin.scheduler_return_next(self._scheduler_name) if plan_next is not None and plan_next > self.shtime.now() or delay == -1: self._log_info("Action '{0}: Removing previous delay timer '{1}'.", self._name, self._scheduler_name) self._se_plugin.scheduler_remove(self._scheduler_name) @@ -483,7 +549,7 @@ def _update_repeat_webif(value: bool): except Exception: pass - actionname = "Action '{0}'".format(self._name) if delay == 0 else "Delayed Action ({0} seconds) '{1}'".format( + actionname = "Action '{0}'".format(self._name) if delay == 0 else "Delayed Action ({0} seconds) '{1}'.".format( delay, self._scheduler_name) _delay_info = 0 if delay is None: @@ -502,18 +568,10 @@ def _update_repeat_webif(value: bool): self._log_decrease_indent() _delay_info = -1 else: - self._waitforexecute(state, actionname, self._name, repeat_text, delay, current_condition_met, previous_condition_met, previousstate_condition_met) + _delay_info = delay + self._waitforexecute(state, actionname, self._name, repeat_text, delay, current_condition_met, previous_condition_met, previousstate_condition_met, next_condition_met) - _update_delay_webif('actions_stay', str(_delay_info)) - _update_delay_webif('actions_enter', str(_delay_info)) - _update_delay_webif('actions_enter_or_stay', str(_delay_info)) - try: - state.update_name(state.state_item) - _key_name = ['{}'.format(state.id), 'name'] - self._abitem.update_webif(_key_name, state.name) - _update_delay_webif('actions_leave', str(_delay_info)) - except Exception: - pass + _update_delay_webif(str(_delay_info)) # set the action based on a set_(action_name) attribute # value: Value of the set_(action_name) attribute @@ -522,29 +580,28 @@ def update(self, value): # Complete action # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): raise NotImplementedError("Class {} doesn't implement complete()".format(self.__class__.__name__)) # Check if execution is possible def _can_execute(self, state): return True - def get(self): - return True - - def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", delay: int = 0, current_condition: list[str] = None, previous_condition: list[str] = None, previousstate_condition: list[str] = None): + def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", delay: int = 0, current_condition: list[str] = None, previous_condition: list[str] = None, previousstate_condition: list[str] = None, next_condition: list[str] = None): if current_condition is None: current_condition = [] if previous_condition is None: previous_condition = [] if previousstate_condition is None: previousstate_condition = [] + if next_condition is None: + next_condition = [] self._log_decrease_indent(50) self._log_increase_indent() if delay == 0: self._log_info("Action '{}': Running.", namevar) - self.real_execute(state, actionname, namevar, repeat_text, None, False, current_condition, previous_condition, previousstate_condition) + self.real_execute(state, actionname, namevar, repeat_text, None, False, current_condition, previous_condition, previousstate_condition, next_condition) else: instanteval = None if self.__instanteval is None else self.__instanteval.get() self._log_info("Action '{0}': Add {1} second timer '{2}' " @@ -554,7 +611,7 @@ def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text if instanteval is True: self._log_increase_indent() self._log_debug("Evaluating value for delayed action '{}'.", namevar) - value = self.real_execute(state, actionname, namevar, repeat_text, None, True, current_condition, previous_condition, previousstate_condition) + value = self.real_execute(state, actionname, namevar, repeat_text, None, True, current_condition, previous_condition, previousstate_condition, next_condition) self._log_debug("Value for delayed action is going to be '{}'.", value) self._log_decrease_indent() else: @@ -567,113 +624,168 @@ def _waitforexecute(self, state, actionname: str, namevar: str = "", repeat_text 'current_condition': current_condition, 'previous_condition': previous_condition, 'previousstate_condition': previousstate_condition, - 'state': state}, next=next_run) + 'next_condition': next_condition, 'state': state}, next=next_run) - def _delayed_execute(self, actionname: str, namevar: str = "", repeat_text: str = "", value=None, current_condition=None, previous_condition=None, previousstate_condition=None, state=None, caller=None): + def _delayed_execute(self, actionname: str, namevar: str = "", repeat_text: str = "", value=None, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None, state=None, caller=None): if state: self._log_debug("Putting delayed action '{}' from state '{}' into queue. Caller: {}", namevar, state, caller) - self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition, state]) + self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition, next_condition, state]) else: self._log_debug("Putting delayed action '{}' into queue. Caller: {}", namevar, caller) - self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition]) + self.__queue.put(["delayedaction", self, actionname, namevar, repeat_text, value, current_condition, previous_condition, previousstate_condition, next_condition]) if not self._abitem.update_lock.locked(): self._log_debug("Running queue") self._abitem.run_queue() # Really execute the action (needs to be implemented in derived classes) - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): raise NotImplementedError("Class {} doesn't implement real_execute()".format(self.__class__.__name__)) def _getitem_fromeval(self): return -# Class representing a single "se_set" action -class SeActionSetItem(SeActionBase): - # Initialize the action - # abitem: parent SeItem instance - # name: Name of action +# Class with methods that are almost the same for set and force +class SeActionMixSetForce: def __init__(self, abitem, name: str): super().__init__(abitem, name) - self.__item = None - self.__eval_item = None - self.__status = None - self.__delta = 0 - self.__value = StateEngineValue.SeValue(self._abitem, "value") - self.__mindelta = StateEngineValue.SeValue(self._abitem, "mindelta") - self._function = "set" + self._item = None + self._eval_item = None + self._status = None + self._delta = 0 + self._value = StateEngineValue.SeValue(self._abitem, "value") + self._mindelta = StateEngineValue.SeValue(self._abitem, "mindelta", False, "num") + + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + # missing status in action: Try to find it. + if check_status is None: + status = StateEngineTools.find_attribute(self._sh, state, "se_status_" + self._name, 0, use) + if status is not None: + check_status, _issue = self._abitem.return_item(status) + _issue = {self._name: {'issue': _issue, + 'issueorigin': [{'state': state.id, 'action': self._function}]}} - def __repr__(self): - return "SeAction Set {}".format(self._name) + if check_mindelta.is_empty(): + mindelta = StateEngineTools.find_attribute(self._sh, state, "se_mindelta_" + self._name, 0, use) + if mindelta is not None: + check_mindelta.set(mindelta) + + if check_status is not None: + self._log_develop("Casting value {} to status {}", check_value, check_status) + check_value.set_cast(check_status.cast) + check_mindelta.set_cast(check_status.cast) + self._scheduler_name = "{}-SeItemDelayTimer".format(check_status.property.path) + if self._abitem.id == check_status.property.path: + self._caller += '_self' + elif check_status is None: + if isinstance(check_item, str): + pass + elif check_item is not None: + self._log_develop("Casting value {} to item {}", check_value, check_item) + check_value.set_cast(check_item.cast) + check_mindelta.set_cast(check_item.cast) + self._scheduler_name = "{}-SeItemDelayTimer".format(check_item.property.path) + if self._abitem.id == check_item.property.path: + self._caller += '_self' + if _issue[self._name].get('issue') not in [[], [None], None]: + self._log_develop("Issue with {} action {}", action_type, _issue) + else: + _issue = {self._name: {'issue': None, + 'issueorigin': [{'state': state.id, 'action': self._function}]}} + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + if check_value: + check_value.set_cast(check_item.cast) + if check_mindelta: + check_mindelta.set_cast(check_item.cast) + return check_item, check_mindelta def _getitem_fromeval(self): - if self.__item is None: + if self._item is None: return - self.__eval_item = self.__item - self.__item, self.__value, self.__mindelta, _issue = self.check_getitem_fromeval(self.__item, self.__value, - self.__mindelta) - if self.__item is None: + self._eval_item = self._item + self._item, self._value, self._mindelta, _issue = self.check_getitem_fromeval(self._item, self._value, + self._mindelta) + if self._item is None: self._action_status = _issue - raise Exception("Problem evaluating item '{}' from eval.".format(self.__item)) + raise Exception("Problem evaluating item '{}' from eval.".format(self._item)) # set the action based on a set_(action_name) attribute # value: Value of the set_(action_name) attribute def update(self, value): - _, _, _issue, _ = self.__value.set(value) - _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - return _issue - - # Complete action - # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): - self.__item, self.__status, self.__mindelta, self.__value, _issue = self.check_complete( - state, self.__item, self.__status, self.__mindelta, self.__value, "set", evals_items, use) - self._action_status = _issue + _, _, _issue, _ = self._value.set(value) + _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue - # Write action to logger def write_to_logger(self): SeActionBase.write_to_logger(self) - if isinstance(self.__item, str): + if isinstance(self._item, str): try: - self._log_debug("item from eval: {0}", self.__item) + self._log_debug("item from eval: {0}", self._item) self._log_increase_indent() - current, _, _, _ = self.check_getitem_fromeval(self.__item) + current, _, _, _ = self.check_getitem_fromeval(self._item) self._log_debug("Currently eval results in {}", current) self._log_decrease_indent() except Exception as ex: + current = None self._log_warning("Issue while getting item from eval {}", ex) - elif self.__item is not None: - self._log_debug("item: {0}", self.__item.property.path) + item = current + elif self._item is not None: + self._log_debug("item: {0}", self._item.property.path) + item = self._item.property.path else: self._log_debug("item is not defined! Check log file.") - self.__mindelta.write_to_logger() - self.__value.write_to_logger() + item = None + mindelta = self._mindelta.write_to_logger() or 0 + minagedelta = self._minagedelta.write_to_logger() or 0 + value = self._value.write_to_logger() + self._info_dict.update({'item': item, 'mindelta': str(mindelta), 'minagedelta': str(minagedelta), 'agedelta': str(self._agedelta), 'delta': str(self._delta), 'value': str(value)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) + return value + + # Complete action + # state: state (item) to read from + def complete(self, evals_items=None, use=None): + self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state) + self._abitem.set_variable('current.action_name', self._name) + self._abitem.set_variable('current.state_name', self._state.name) + self._item, self._status, self._mindelta, self._minagedelta, self._value, _issue = self.check_complete( + self._state, self._item, self._status, self._mindelta, self._minagedelta, self._value, "set/force", evals_items, use) + self._action_status = _issue + + self._abitem.set_variable('current.action_name', '') + self._abitem.set_variable('current.state_name', '') + return _issue # Check if execution is possible def _can_execute(self, state): - if self.__item is None: + if self._item is None: self._log_increase_indent() self._log_warning("Action '{0}': No item defined. Ignoring.", self._name) self._log_decrease_indent() - self.update_webif_actionstatus(state, self._name, 'False', 'No item defined') + self.update_webif_actionstatus(state, self._name, 'False', 'Action {}: No item defined'.format(self._name)) return False - if self.__value.is_empty(): + if self._value.is_empty(): self._log_increase_indent() - self._log_warning("Action '{0}': No value for item {1} defined. Ignoring.", self._name, self.__item) + self._log_warning("Action '{0}': No value for item {1} defined. Ignoring.", self._name, self._item) self._log_decrease_indent() - self.update_webif_actionstatus(state, self._name, 'False', 'No value for item {}'.format(self.__item)) + self.update_webif_actionstatus(state, self._name, 'False', 'Action {}: No value for item {}'.format(self._name, self._item)) return False self.update_webif_actionstatus(state, self._name, 'True') return True # Really execute the action (needs to be implemented in derived classes) - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): self._abitem.set_variable('current.action_name', namevar) self._log_increase_indent() if value is None: - value = self.__value.get() + value = self._value.get() + self._info_dict.update({'value': str(value)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) if value is None: self._log_debug("{0}: Value is None", actionname) @@ -684,72 +796,118 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: self._log_decrease_indent() return value - - if not self.__mindelta.is_empty(): - mindelta = self.__mindelta.get() - if self.__status is not None: - # noinspection PyCallingNonCallable - delta = float(abs(self.__status() - value)) - additionaltext = "of statusitem " - else: - delta = float(abs(self.__item() - value)) + minagedelta = self.eval_minagedelta(f"Not setting {self._item.property.path} to {value}", state) + if minagedelta: + return + if not self._mindelta.is_empty(): + mindelta = float(self._mindelta.get()) + try: + if self._status is not None: + # noinspection PyCallingNonCallable + delta = float(abs(self._status() - value)) + additionaltext = "of statusitem " + else: + delta = float(abs(self._item() - value)) + additionaltext = "" + except Exception: + delta = None additionaltext = "" + self._log_warning("{0}: Can not evaluate delta as value '{1}' is no number.", self._name, value) + if delta is not None: + self._delta = delta + self._info_dict.update({'delta': str(delta), 'mindelta': str(mindelta)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) + if delta < mindelta: + text = "{0}: Not setting '{1}' to '{2}' because delta {3}'{4:.2f}' is lower than mindelta '{5:.2f}'." + self._log_debug(text, actionname, self._item.property.path, value, additionaltext, delta, mindelta) + self.update_webif_actionstatus(state, self._name, 'False', None, f"(delta '{delta:.2f}' < '{mindelta:.2f})") + return + else: + text = "{0}: Proceeding because delta {1}'{2:.2f}' is lower than mindelta '{3:.2f}'." + self.update_webif_actionstatus(state, self._name, 'True', None, + f"(delta '{delta:.2f}' > '{mindelta:.2f})") + self._log_debug(text, actionname, additionaltext, delta, mindelta) + source = self.set_source(current_condition, previous_condition, previousstate_condition, next_condition) + self._force_set(actionname, self._item, value, source) + self._execute_set_add_remove(state, actionname, namevar, repeat_text, self._item, value, source, current_condition, previous_condition, previousstate_condition, next_condition) + + def _force_set(self, actionname, item, value, source): + pass + + def update_mindelta(self, value): + if self._mindelta is None: + self._mindelta = StateEngineValue.SeValue(self._abitem, "mindelta", False, "num") + _issue = self._update_value(self._mindelta, value, 'mindelta') + return _issue - self.__delta = delta - if delta < mindelta: - text = "{0}: Not setting '{1}' to '{2}' because delta {3}'{4:.2}' is lower than mindelta '{5}'" - self._log_debug(text, actionname, self.__item.property.path, value, additionaltext, delta, mindelta) - self.update_webif_actionstatus(state, self._name, 'False') - return - - self._execute_set_add_remove(state, actionname, namevar, repeat_text, self.__item, value, current_condition, previous_condition, previousstate_condition) - - def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, current_condition, previous_condition, previousstate_condition): + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition, previous_condition, previousstate_condition, next_condition): self._log_decrease_indent() - self._log_debug("{0}: Set '{1}' to '{2}'{3}", actionname, item.property.path, value, repeat_text) - source = self.set_source(current_condition, previous_condition, previousstate_condition) + self._log_debug("{0}: Set '{1}' to '{2}'.{3}", actionname, item.property.path, value, repeat_text) pat = r"(?:[^,(]*)\'(.*?)\'" self.update_webif_actionstatus(state, re.findall(pat, actionname)[0], 'True') # noinspection PyCallingNonCallable item(value, caller=self._caller, source=source) - self.__item = self.__eval_item + self._abitem.last_run = {self._name: datetime.datetime.now()} + self._item = self._eval_item - def get(self): - orig_item = self.__item - try: - self._getitem_fromeval() - except Exception as ex: - self._log_warning("Issue while getting item from eval {}", ex) - item_from_eval = orig_item if orig_item != self.__item else False - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) + +# Class representing a single "se_set" action +class SeActionSetItem(SeActionMixSetForce, SeActionBase): + # Initialize the action + # abitem: parent SeItem instance + # name: Name of action + def __init__(self, abitem, name: str): + super().__init__(abitem, name) + self._function = "set" + + def __repr__(self): + return "SeAction Set {}".format(self._name) + + # Write action to logger + def write_to_logger(self): + super().write_to_logger() + self._log_debug("force update: no") + + +# Class representing a single "se_force" action +class SeActionForceItem(SeActionMixSetForce, SeActionBase): + # Initialize the action + # abitem: parent SeItem instance + # name: Name of action + def __init__(self, abitem, name: str): + super().__init__(abitem, name) + self._function = "force set" + + def __repr__(self): + return "SeAction Force {}".format(self._name) + + # Write action to logger + def write_to_logger(self): + super().write_to_logger() + self._log_debug("force update: yes") + + def _force_set(self, actionname, item, value, source): + # Set to different value first ("force") + current_value = item() + if current_value == value: + if self._item._type == 'bool': + self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, not value) + item(not value, caller=self._caller, source=source) + elif self._item._type == 'str': + if value != '': + self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, '') + item('', caller=self._caller, source=source) + else: + self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, '-') + item('-', caller=self._caller, source=source) + elif self._item._type == 'num': + self._log_debug("{0}: Set '{1}' to '{2}' (Force).", actionname, item.property.path, current_value+0.1) + item(current_value+0.1, caller=self._caller, source=source) else: - value = None - except Exception: - value = None - self.__item = orig_item - mindelta = self.__mindelta.get() - if mindelta is None: - result = {'function': str(self._function), 'item': item, 'item_from_eval': item_from_eval, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} + self._log_warning("{0}: Force not implemented for item type '{1}'.", actionname, item._type) else: - result = {'function': str(self._function), 'item': item, 'item_from_eval': item_from_eval, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}, - 'delta': str(self.__delta), 'mindelta': str(mindelta)} - return result + self._log_debug("{0}: New value differs from old value, no force required.", actionname) # Class representing a single "se_setbyattr" action @@ -770,15 +928,20 @@ def __repr__(self): def update(self, value): self.__byattr = value _issue = {self._name: {'issue': None, 'attribute': self.__byattr, - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue # Complete action # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): + self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state) + self._abitem.set_variable('current.action_name', self._name) + self._abitem.set_variable('current.state_name', self._state.name) self._scheduler_name = "{}-SeByAttrDelayTimer".format(self.__byattr) _issue = {self._name: {'issue': None, 'attribute': self.__byattr, - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + self._abitem.set_variable('current.action_name', '') + self._abitem.set_variable('current.state_name', '') return _issue # Write action to logger @@ -786,24 +949,25 @@ def write_to_logger(self): SeActionBase.write_to_logger(self) if self.__byattr is not None: self._log_debug("set by attribute: {0}", self.__byattr) + self._info_dict.update({'byattr': self.__byattr}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) # Really execute the action - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): self._abitem.set_variable('current.action_name', namevar) if returnvalue: return value + minagedelta = self.eval_minagedelta(f"Not setting values by attribute {self.__byattr}", state) + if minagedelta: + return self._log_info("{0}: Setting values by attribute '{1}'.{2}", actionname, self.__byattr, repeat_text) self.update_webif_actionstatus(state, self._name, 'True') - source = self.set_source(current_condition, previous_condition, previousstate_condition) + source = self.set_source(current_condition, previous_condition, previousstate_condition, next_condition) for item in self._sh.find_items(self.__byattr): self._log_info("\t{0} = {1}", item.property.path, item.conf[self.__byattr]) item(item.conf[self.__byattr], caller=self._caller, source=source) - - def get(self): - result = {'function': str(self._function), 'byattr': str(self.__byattr), - 'conditionset': self.conditionset.get(), 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result + self._abitem.last_run = {self._name: datetime.datetime.now()} # Class representing a single "se_trigger" action @@ -828,15 +992,20 @@ def update(self, value): value = None if value == "" else value _, _, _issue, _ = self.__value.set(value) _issue = {self._name: {'issue': _issue, 'logic': self.__logic, - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue # Complete action # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): + self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state) + self._abitem.set_variable('current.action_name', self._name) + self._abitem.set_variable('current.state_name', self._state.name) self._scheduler_name = "{}-SeLogicDelayTimer".format(self.__logic) _issue = {self._name: {'issue': None, 'logic': self.__logic, - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + self._abitem.set_variable('current.action_name', '') + self._abitem.set_variable('current.state_name', '') return _issue # Write action to logger @@ -845,10 +1014,16 @@ def write_to_logger(self): if self.__logic is not None: self._log_debug("trigger logic: {0}", self.__logic) if self.__value is not None: - self._log_debug("value: {0}", self.__value) + value = self.__value.write_to_logger() + else: + value = None + self._info_dict.update( + {'logic': self.__logic, 'value': str(value)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) # Really execute the action - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): self._abitem.set_variable('current.action_name', namevar) if value is None: try: @@ -858,25 +1033,17 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: return value + minagedelta = self.eval_minagedelta(f"Not triggering logic {self.__logic}", state) + if minagedelta: + return + self._info_dict.update({'value': str(value)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) self.update_webif_actionstatus(state, self._name, 'True') self._log_info("{0}: Triggering logic '{1}' using value '{2}'.{3}", actionname, self.__logic, value, repeat_text) add_logics = 'logics.{}'.format(self.__logic) if not self.__logic.startswith('logics.') else self.__logic self._sh.trigger(add_logics, by=self._caller, source=self._name, value=value) - - def get(self): - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - result = {'function': str(self._function), 'logic': str(self.__logic), - 'value': value, - 'conditionset': self.conditionset.get(), 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result + self._abitem.last_run = {self._name: datetime.datetime.now()} # Class representing a single "se_run" action @@ -903,15 +1070,20 @@ def update(self, value): if func == "eval": self.__eval = value _issue = {self._name: {'issue': None, 'eval': StateEngineTools.get_eval_name(self.__eval), - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue # Complete action # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): + self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state) + self._abitem.set_variable('current.action_name', self._name) + self._abitem.set_variable('current.state_name', self._state.name) self._scheduler_name = "{}-SeRunDelayTimer".format(StateEngineTools.get_eval_name(self.__eval)) _issue = {self._name: {'issue': None, 'eval': StateEngineTools.get_eval_name(self.__eval), - 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + self._abitem.set_variable('current.action_name', '') + self._abitem.set_variable('current.state_name', '') return _issue # Write action to logger @@ -920,10 +1092,29 @@ def write_to_logger(self): if self.__eval is not None: self._log_debug("eval: {0}", StateEngineTools.get_eval_name(self.__eval)) + self._info_dict.update({'eval': str(self.__eval)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) + # Really execute the action - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): + def log_conditions(): + if current_condition: + self._log_debug("Running eval {0} based on conditionset {1}.", self.__eval, current_condition) + if previous_condition: + self._log_debug("Running eval {0} based on previous conditionset {1}.", self.__eval, previous_condition) + if previousstate_condition: + self._log_debug("Running eval {0} based on previous state's conditionset {1}.", self.__eval, + previousstate_condition) + if next_condition: + self._log_debug("Running eval {0} based on next conditionset {1}.", self.__eval, next_condition) + + minagedelta = self.eval_minagedelta(f"Not running eval {self.__eval}", state) + if minagedelta: + return self._abitem.set_variable('current.action_name', namevar) self._log_increase_indent() + eval_result = '' if isinstance(self.__eval, str): # noinspection PyUnusedLocal sh = self._sh @@ -935,13 +1126,8 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: self._log_decrease_indent() return eval(self.__eval) - if current_condition: - self._log_debug("Running eval {0} based on conditionset {1}", self.__eval, current_condition) - if previous_condition: - self._log_debug("Running eval {0} based on previous conditionset {1}", self.__eval, previous_condition) - if previousstate_condition: - self._log_debug("Running eval {0} based on previous state's conditionset {1}", self.__eval, previousstate_condition) - eval(self.__eval) + log_conditions() + eval_result = eval(self.__eval) self.update_webif_actionstatus(state, self._name, 'True') self._log_decrease_indent() except Exception as ex: @@ -954,13 +1140,8 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s if returnvalue: self._log_decrease_indent() return self.__eval() - if current_condition: - self._log_debug("Running eval {0} based on conditionset {1}", self.__eval, current_condition) - if previous_condition: - self._log_debug("Running eval {0} based on previous conditionset {1}", self.__eval, previous_condition) - if previousstate_condition: - self._log_debug("Running eval {0} based on previous state's conditionset {1}", self.__eval, previousstate_condition) - self.__eval() + log_conditions() + eval_result = self.__eval() self.update_webif_actionstatus(state, self._name, 'True') self._log_decrease_indent() except Exception as ex: @@ -968,195 +1149,10 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s self.update_webif_actionstatus(state, self._name, 'False', 'Problem calling: {}'.format(ex)) text = "{0}: Problem calling '{0}': {1}." self._log_error(text, actionname, StateEngineTools.get_eval_name(self.__eval), ex) - - def get(self): - result = {'function': str(self._function), 'eval': str(self.__eval), - 'conditionset': self.conditionset.get(), 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result - - -# Class representing a single "se_force" action -class SeActionForceItem(SeActionBase): - # Initialize the action - # abitem: parent SeItem instance - # name: Name of action - def __init__(self, abitem, name: str): - super().__init__(abitem, name) - self.__item = None - self.__eval_item = None - self.__status = None - self.__value = StateEngineValue.SeValue(self._abitem, "value") - self.__delta = 0 - self.__mindelta = StateEngineValue.SeValue(self._abitem, "mindelta") - self._function = "force set" - - def __repr__(self): - return "SeAction Force {}".format(self._name) - - # set the action based on a set_(action_name) attribute - # value: Value of the set_(action_name) attribute - def update(self, value): - _, _, _issue, _ = self.__value.set(value) - _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} - return _issue - - # Complete action - # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): - self.__item, self.__status, self.__mindelta, self.__value, _issue = self.check_complete( - state, self.__item, self.__status, self.__mindelta, self.__value, "force", evals_items, use) - self._action_status = _issue - return _issue - - # Write action to logger - def write_to_logger(self): - SeActionBase.write_to_logger(self) - if isinstance(self.__item, str): - try: - self._log_debug("item from eval: {0}", self.__item) - self._log_increase_indent() - current, _, _, _ = self.check_getitem_fromeval(self.__item) - self._log_debug("Currently eval results in {}", current) - self._log_decrease_indent() - except Exception as ex: - self._log_warning("Issue while getting item from eval {}", ex) - elif self.__item is not None: - self._log_debug("item: {0}", self.__item.property.path) - else: - self._log_debug("item is not defined! Check log file.") - if self.__status is not None: - self._log_debug("status: {0}", self.__status.property.path) - self.__mindelta.write_to_logger() - self.__value.write_to_logger() - self._log_debug("force update: yes") - - # Check if execution is possible - def _can_execute(self, state): - if self.__item is None: - self._log_increase_indent() - self._log_warning("Action '{0}': No item defined. Ignoring.", self._name) - self._log_decrease_indent() - self.update_webif_actionstatus(state, self._name, 'False', 'No item defined') - return False - - if self.__value.is_empty(): - self._log_increase_indent() - self._log_warning("Action '{0}': No value defined for item {1}. Ignoring.", self._name, self.__item) - self._log_decrease_indent() - self.update_webif_actionstatus(state, self._name, 'False', 'No value defined for item {}'.format(self.__item)) - return False - self.update_webif_actionstatus(state, self._name, 'True') - return True - - def _getitem_fromeval(self): - if self.__item is None: - return - self.__eval_item = self.__item - self.__item, self.__value, self.__mindelta, _issue = self.check_getitem_fromeval(self.__item, self.__value, - self.__mindelta) - if self.__item is None: - self._action_status = _issue - raise Exception("Problem evaluating item '{}' from eval.".format(self.__item)) - - # Really execute the action (needs to be implemented in derived classes) - # noinspection PyProtectedMember - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): - self._abitem.set_variable('current.action_name', namevar) - self._log_increase_indent() - if value is None: - value = self.__value.get() - - if value is None: - self._log_debug("{0}: Value is None", actionname) - pat = r"(?:[^,(]*)\'(.*?)\'" - self.update_webif_actionstatus(state, re.findall(pat, actionname)[0], 'False', 'Value is None') - return - - if returnvalue: - self._log_decrease_indent() - return value - - if not self.__mindelta.is_empty(): - mindelta = self.__mindelta.get() - if self.__status is not None: - # noinspection PyCallingNonCallable - delta = float(abs(self.__status() - value)) - additionaltext = "of statusitem " - else: - delta = float(abs(self.__item() - value)) - additionaltext = "" - - self.__delta = delta - if delta < mindelta: - text = "{0}: Not setting '{1}' to '{2}' because delta {3}'{4:.2}' is lower than mindelta '{5}'" - self._log_debug(text, actionname, self.__item.property.path, value, additionaltext, delta, mindelta) - self.update_webif_actionstatus(state, self._name, 'False') - return - source = self.set_source(current_condition, previous_condition, previousstate_condition) - # Set to different value first ("force") - current_value = self.__item() - if current_value == value: - if self.__item._type == 'bool': - self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, self.__item.property.path, not value) - self.__item(not value, caller=self._caller, source=source) - elif self.__item._type == 'str': - if value != '': - self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, self.__item.property.path, '') - self.__item('', caller=self._caller, source=source) - else: - self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, self.__item.property.path, '-') - self.__item('-', caller=self._caller, source=source) - elif self.__item._type == 'num': - self._log_debug("{0}: Set '{1}' to '{2}' (Force)", actionname, self.__item.property.path, current_value+0.1) - self.__item(current_value+0.1, caller=self._caller, source=source) - else: - self._log_warning("{0}: Force not implemented for item type '{1}'", actionname, self.__item._type) - else: - self._log_debug("{0}: New value differs from old value, no force required.", actionname) - self._log_decrease_indent() - self._log_debug("{0}: Set '{1}' to '{2}'.{3}", actionname, self.__item.property.path, value, repeat_text) - self.update_webif_actionstatus(state, self._name, 'True') - # noinspection PyCallingNonCallable - self.__item(value, caller=self._caller, source=source) - self.__item = self.__eval_item - - def get(self): - orig_item = self.__item - try: - self._getitem_fromeval() - except Exception as ex: - self._log_warning("Issue while getting item from eval {}", ex) - item_from_eval = orig_item if orig_item != self.__item else False - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - self.__item = orig_item - mindelta = self.__mindelta.get() - if mindelta is None: - result = {'function': str(self._function), 'item': item, 'item_from_eval': item_from_eval, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - else: - result = {'function': str(self._function), 'item': item, 'item_from_eval': item_from_eval, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}, - 'delta': str(self.__delta), 'mindelta': str(mindelta)} - return result + self._abitem.last_run = {self._name: datetime.datetime.now()} + self._info_dict.update({'value': str(eval_result)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) # Class representing a single "se_special" action @@ -1184,18 +1180,23 @@ def update(self, value): else: raise ValueError("Action {0}: Unknown special value '{1}'!".format(self._name, special)) self.__special = special - _issue = {self._name: {'issue': None, 'special': self.__value, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = {self._name: {'issue': None, 'special': self.__value, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} return _issue # Complete action # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): + self._log_develop('Completing action {}, action type {}, state {}', self._name, self._action_type, self._state) + self._abitem.set_variable('current.action_name', self._name) + self._abitem.set_variable('current.state_name', self._state.name) if isinstance(self.__value, list): item = self.__value[0].property.path else: item = self.__value.property.path self._scheduler_name = "{}_{}-SeSpecialDelayTimer".format(self.__special, item) - _issue = {self._name: {'issue': None, 'special': item, 'issueorigin': [{'state': 'unknown', 'action': self._function}]}} + _issue = {self._name: {'issue': None, 'special': item, 'issueorigin': [{'state': self._state.id, 'action': self._function}]}} + self._abitem.set_variable('current.action_name', '') + self._abitem.set_variable('current.state_name', '') return _issue # Write action to logger @@ -1206,21 +1207,28 @@ def write_to_logger(self): self._log_debug("value: {0}", self.__value) else: self._log_debug("Retrigger item: {0}", self.__value.property.path) + self._info_dict.update({'value': str(self.__value)}) + self._info_dict.update({'special': str(self.__special)}) + _key = [self._state.id, self._action_type, self._name] + self._abitem.update_webif(_key, self._info_dict, True) # Really execute the action - def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None): + def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: str = "", value=None, returnvalue=False, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): self._abitem.set_variable('current.action_name', namevar) if returnvalue: return None + minagedelta = self.eval_minagedelta(f"Not executing special action {self.__special}", state) + if minagedelta: + return try: _log_value = self.__value.property.path except Exception: _log_value = self.__value - self._log_info("{0}: Executing special action '{1}' using item '{2}' based on '{3}/{4}/{5}'.{6}", - actionname, self.__special, _log_value, current_condition, previous_condition, previousstate_condition, repeat_text) + self._log_info("{0}: Executing special action '{1}' using item '{2}' based on current condition {3} / previous condition {4} / previousstate condition {5} / next_condition {6}.{7}", + actionname, self.__special, _log_value, current_condition, previous_condition, previousstate_condition, next_condition, repeat_text) self._log_increase_indent() if self.__special == "suspend": - self.suspend_execute(state, current_condition, previous_condition, previousstate_condition) + self.suspend_execute(state, current_condition, previous_condition, previousstate_condition, next_condition) if self._suspend_issue in ["", [], None, [None]]: self.update_webif_actionstatus(state, self._name, 'True') else: @@ -1240,6 +1248,7 @@ def real_execute(self, state, actionname: str, namevar: str = "", repeat_text: s self.update_webif_actionstatus(state, self._name, 'False', 'Unknown special value {}'.format(self.__special)) raise ValueError("{0}: Unknown special value '{1}'!".format(actionname, self.__special)) self._log_debug("Special action {0}: done", self.__special) + self._abitem.last_run = {self._name: datetime.datetime.now()} def suspend_get_value(self, value): _issue = {self._name: {'issue': None, 'issueorigin': [{'state': 'suspend', 'action': 'suspend'}]}} @@ -1301,10 +1310,10 @@ def retrigger_get_value(self, value): raise ValueError("Action {0}: {1}".format(self._name, text)) return se_item - def suspend_execute(self, state=None, current_condition=None, previous_condition=None, previousstate_condition=None): + def suspend_execute(self, state=None, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): suspend_item, _issue = self._abitem.return_item(self.__value[0]) _issue = {self._name: {'issue': _issue, 'issueorigin': [{'state': state.id, 'action': 'suspend'}]}} - source = "SuspendAction, {}".format(self.set_source(current_condition, previous_condition, previousstate_condition)) + source = "SuspendAction, {}".format(self.set_source(current_condition, previous_condition, previousstate_condition, next_condition)) if self._abitem.get_update_trigger_source() == self.__value[1]: # triggered by manual-item: Update suspend item if suspend_item.property.value: @@ -1323,23 +1332,6 @@ def suspend_execute(self, state=None, current_condition=None, previous_condition self._log_debug("Updated variable 'item.suspend_remaining' to {0}", suspend_remaining) self._action_status = _issue - def get(self): - try: - value_result = self.__value.property.path - except Exception: - value_result = self.__value - if isinstance(value_result, list): - for i, val in enumerate(value_result): - try: - value_result[i] = val.property.path - except Exception: - pass - result = {'function': str(self._function), 'special': str(self.__special), - 'value': str(value_result), 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result - # Class representing a single "se_add" action class SeActionAddItem(SeActionSetItem): @@ -1355,39 +1347,21 @@ def __repr__(self): def write_to_logger(self): SeActionSetItem.write_to_logger(self) - SeActionBase.write_to_logger(self) - def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, current_condition=None, previous_condition=None, previousstate_condition=None): - value = value if isinstance(value, list) else [value] + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): self._log_debug("{0}: Add '{1}' to '{2}'.{3}", actionname, value, item.property.path, repeat_text) + value = value if isinstance(value, list) else [value] value = item.property.value + value - source = self.set_source(current_condition, previous_condition, previousstate_condition) self.update_webif_actionstatus(state, self._name, 'True') # noinspection PyCallingNonCallable item(value, caller=self._caller, source=source) - def get(self): - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - result = {'function': str(self._function), 'item': item, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result - # Class representing a single "se_remove" action class SeActionRemoveFirstItem(SeActionSetItem): @@ -1403,9 +1377,14 @@ def __repr__(self): def write_to_logger(self): SeActionSetItem.write_to_logger(self) - SeActionBase.write_to_logger(self) - def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, current_condition=None, previous_condition=None, previousstate_condition=None): + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] for v in value: @@ -1416,32 +1395,9 @@ def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, except Exception as ex: self._log_warning("{0}: Remove first entry '{1}' from '{2}' failed: {3}", actionname, value, item.property.path, ex) - source = self.set_source(current_condition, previous_condition, previousstate_condition) self.update_webif_actionstatus(state, self._name, 'True') item(currentvalue, caller=self._caller, source=source) - def get(self): - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - result = {'function': str(self._function), 'item': item, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result - # Class representing a single "se_remove" action class SeActionRemoveLastItem(SeActionSetItem): @@ -1457,9 +1413,14 @@ def __repr__(self): def write_to_logger(self): SeActionSetItem.write_to_logger(self) - SeActionBase.write_to_logger(self) - def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, current_condition=None, previous_condition=None, previousstate_condition=None): + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] for v in value: @@ -1472,32 +1433,9 @@ def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, except Exception as ex: self._log_warning("{0}: Remove last entry '{1}' from '{2}' failed: {3}", actionname, value, item.property.path, ex) - source = self.set_source(current_condition, previous_condition, previousstate_condition) self.update_webif_actionstatus(state, self._name, 'True') item(currentvalue, caller=self._caller, source=source) - def get(self): - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - result = {'function': str(self._function), 'item': item, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result - # Class representing a single "se_removeall" action class SeActionRemoveAllItem(SeActionSetItem): @@ -1513,41 +1451,23 @@ def __repr__(self): def write_to_logger(self): SeActionSetItem.write_to_logger(self) - SeActionBase.write_to_logger(self) - def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, current_condition=None, previous_condition=None, previousstate_condition=None): + def _get_status(self, check_item, check_status, check_mindelta, check_value, state, use, action_type, _issue): + return check_item, check_status, check_mindelta, check_value, _issue + + def _cast_stuff(self, check_item, check_mindelta, check_value): + return check_item, check_mindelta + + def _execute_set_add_remove(self, state, actionname, namevar, repeat_text, item, value, source, current_condition=None, previous_condition=None, previousstate_condition=None, next_condition=None): currentvalue = item.property.value value = value if isinstance(value, list) else [value] for v in value: try: currentvalue = [i for i in currentvalue if i != v] - self._log_debug("{0}: Remove all '{1}' from '{2}'.{3}", - actionname, v, item.property.path, repeat_text) + self._log_debug("{0}: Remove all '{1}' from '{2}', value is now {3}.{4}", + actionname, v, item.property.path, currentvalue, repeat_text) except Exception as ex: self._log_warning("{0}: Remove all '{1}' from '{2}' failed: {3}", actionname, value, item.property.path, ex) - source = self.set_source(current_condition, previous_condition, previousstate_condition) self.update_webif_actionstatus(state, self._name, 'True') item(currentvalue, caller=self._caller, source=source) - - def get(self): - try: - if self.__item is not None: - item = str(self.__item.property.path) - else: - item = None - except Exception: - item = None - try: - val = self.__value.get() - if val is not None: - value = str(val) - else: - value = None - except Exception: - value = None - result = {'function': str(self._function), 'item': item, - 'value': value, 'conditionset': self.conditionset.get(), - 'previousconditionset': self.previousconditionset.get(), - 'previousstate_conditionset': self.previousstate_conditionset.get(), 'actionstatus': {}} - return result diff --git a/stateengine/StateEngineActions.py b/stateengine/StateEngineActions.py index 89bef33bd..75a64de47 100755 --- a/stateengine/StateEngineActions.py +++ b/stateengine/StateEngineActions.py @@ -33,10 +33,15 @@ class SeActions(StateEngineTools.SeItemChild): def __init__(self, abitem): super().__init__(abitem) self.__actions = {} + self.__action_type = None + self.__state = None + self.__unassigned_mindeltas = {} + self.__unassigned_minagedeltas = {} self.__unassigned_delays = {} self.__unassigned_repeats = {} self.__unassigned_instantevals = {} self.__unassigned_orders = {} + self.__unassigned_nextconditionsets = {} self.__unassigned_conditionsets = {} self.__unassigned_previousconditionsets = {} self.__unassigned_previousstate_conditionsets = {} @@ -48,18 +53,6 @@ def __init__(self, abitem): def __repr__(self): return "SeActions, count {}".format(self.count()) - def dict_actions(self, action_type, state): - result = {} - for name in self.__actions: - self._abitem.initactionname = name - result.update({name: self.__actions[name].get()}) - try: - result[name].update({'actionstatus': self._abitem.webif_infos[state][action_type][name].get('actionstatus')}) - except Exception: - pass - self._abitem.initactionname = None - return result - def reset(self): self.__actions = {} @@ -67,6 +60,13 @@ def reset(self): def count(self): return len(self.__actions) + def update_action_details(self, state, action_type): + if self.__action_type is None: + self.__action_type = action_type + if self.__state is None: + self._log_develop("Updating state for actions: {}, action type: {}", state.id, action_type) + self.__state = state + # update action # attribute: name of attribute that defines action # value: value of the attribute @@ -82,8 +82,9 @@ def update(self, attribute, value): return _count, _issue elif isinstance(value, str): value = ":".join(map(str.strip, value.split(":"))) - if value[:1] == '[' and value[-1:] == ']': - value = StateEngineTools.convert_str_to_list(value, False) + value = StateEngineTools.convert_str_to_list(value, False) + if name in self.__actions: + self.__actions[name].update_action_details(self.__state, self.__action_type) if func == "se_delay": # set delay if name not in self.__actions: @@ -92,6 +93,22 @@ def update(self, attribute, value): else: _issue = self.__actions[name].update_delay(value) return _count, _issue + elif func == "se_mindelta": + # set mindelta + if name not in self.__actions: + # If we do not have the action yet (delay-attribute before action-attribute), ... + self.__unassigned_mindeltas[name] = value + else: + _issue = self.__actions[name].update_mindelta(value) + return _count, _issue + elif func == "se_minagedelta": + # set minagedelta + if name not in self.__actions: + # If we do not have the action yet (delay-attribute before action-attribute), ... + self.__unassigned_minagedeltas[name] = value + else: + _issue = self.__actions[name].update_minagedelta(value) + return _count, _issue elif func == "se_instanteval": # set instant calculation if name not in self.__actions: @@ -108,6 +125,14 @@ def update(self, attribute, value): else: _issue = self.__actions[name].update_repeat(value) return _count, _issue + elif func == "se_nextconditionset": + # set nextconditionset + if name not in self.__actions: + # If we do not have the action yet (conditionset-attribute before action-attribute), ... + self.__unassigned_nextconditionsets[name] = value + else: + _issue = self.__actions[name].update_nextconditionset(value) + return _count, _issue elif func == "se_conditionset": # set conditionset if name not in self.__actions: @@ -236,6 +261,7 @@ def __ensure_action_exists(self, func, name): # Check if action exists _issue = None if name in self.__actions: + self.__actions[name].update_action_details(self.__state, self.__action_type) return True, _issue # Create action depending on function @@ -262,6 +288,7 @@ def __ensure_action_exists(self, func, name): else: return False, _issue _issue_list = [] + action.update_action_details(self.__state, self.__action_type) if name in self.__unassigned_delays: _issue = action.update_delay(self.__unassigned_delays[name]) if _issue: @@ -274,6 +301,18 @@ def __ensure_action_exists(self, func, name): _issue_list.append(_issue) del self.__unassigned_instantevals[name] + if name in self.__unassigned_mindeltas: + _issue = action.update_mindelta(self.__unassigned_mindeltas[name]) + if _issue: + _issue_list.append(_issue) + del self.__unassigned_mindeltas[name] + + if name in self.__unassigned_minagedeltas: + _issue = action.update_minagedelta(self.__unassigned_minagedeltas[name]) + if _issue: + _issue_list.append(_issue) + del self.__unassigned_minagedeltas[name] + if name in self.__unassigned_repeats: _issue = action.update_repeat(self.__unassigned_repeats[name]) if _issue: @@ -295,6 +334,12 @@ def __ensure_action_exists(self, func, name): _issue_list.append(_issue) del self.__unassigned_orders[name] + if name in self.__unassigned_nextconditionsets: + _issue = action.update_nextconditionset(self.__unassigned_nextconditionsets[name]) + if _issue: + _issue_list.append(_issue) + del self.__unassigned_nextconditionsets[name] + if name in self.__unassigned_conditionsets: _issue = action.update_conditionset(self.__unassigned_conditionsets[name]) if _issue: @@ -308,7 +353,7 @@ def __ensure_action_exists(self, func, name): del self.__unassigned_previousconditionsets[name] if name in self.__unassigned_previousstate_conditionsets: - _issue = action.update_previousconditionset(self.__unassigned_previousstate_conditionsets[name]) + _issue = action.update_previousstate_conditionset(self.__unassigned_previousstate_conditionsets[name]) if _issue: _issue_list.append(_issue) del self.__unassigned_previousstate_conditionsets[name] @@ -317,15 +362,15 @@ def __ensure_action_exists(self, func, name): return True, _issue_list def __handle_combined_action_attribute(self, name, value_list): - def remove_action(ex): + def remove_action(e): if name in self.__actions: del self.__actions[name] - _issue = {name: {'issue': [ex], 'issueorigin': [{'state': 'unknown', 'action': parameter['function']}], 'ignore': True}} - _issue_list.append(_issue) - self._log_warning("Ignoring action {0} because: {1}", name, ex) + i = {name: {'issue': [e], 'issueorigin': [{'state': 'unknown', 'action': parameter['function']}], 'ignore': True}} + _issue_list.append(i) + self._log_warning("Ignoring action {0} because: {1}", name, e) - parameter = {'function': None, 'force': None, 'repeat': None, 'delay': 0, 'order': None, 'conditionset': None, - 'previousconditionset': None, 'previousstate_conditionset': None, 'mode': None, 'instanteval': None} + parameter = {'function': None, 'force': None, 'repeat': None, 'delay': 0, 'order': None, 'nextconditionset': None, 'conditionset': None, + 'previousconditionset': None, 'previousstate_conditionset': None, 'mode': None, 'instanteval': None, 'mindelta': None, 'minagedelta': None} _issue = None _issue_list = [] # value_list needs to be string or list @@ -342,8 +387,7 @@ def remove_action(ex): entry = list("{!s}:{!s}".format(k, v) for (k, v) in entry.items())[0] key, val = StateEngineTools.partition_strip(entry, ":") val = ":".join(map(str.strip, val.split(":"))) - if val[:1] == '[' and val[-1:] == ']': - val = StateEngineTools.convert_str_to_list(val, False) + val = StateEngineTools.convert_str_to_list(val, False) if key == "function": parameter[key] = StateEngineTools.cast_str(val) elif key == "force": @@ -381,6 +425,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'to') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['to']) elif parameter['function'] == "force": _action_exists, _issue = self.__ensure_action_exists("se_force", name) @@ -388,6 +433,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'to') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['to']) elif parameter['function'] == "run": _action_exists, _issue = self.__ensure_action_exists("se_run", name) @@ -395,6 +441,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'eval') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['eval']) elif parameter['function'] == "byattr": _action_exists, _issue = self.__ensure_action_exists("se_byattr", name) @@ -402,6 +449,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'attribute') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['attribute']) elif parameter['function'] == "trigger": _action_exists, _issue = self.__ensure_action_exists("se_trigger", name) @@ -409,6 +457,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'logic') + self.__actions[name].update_action_details(self.__state, self.__action_type) if 'value' in parameter and parameter['value'] is not None: self.__actions[name].update(parameter['logic'] + ':' + parameter['value']) else: @@ -419,6 +468,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) elif parameter['function'] == "add": _action_exists, _issue = self.__ensure_action_exists("se_add", name) @@ -426,6 +476,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) elif parameter['function'] == "remove": _action_exists, _issue = self.__ensure_action_exists("se_remove", name) @@ -433,6 +484,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) elif parameter['function'] == "removeall": _action_exists, _issue = self.__ensure_action_exists("se_removeall", name) @@ -440,6 +492,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) elif parameter['function'] == "removefirst": _action_exists, _issue = self.__ensure_action_exists("se_removefirst", name) @@ -447,6 +500,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) elif parameter['function'] == "removelast": _action_exists, _issue = self.__ensure_action_exists("se_removelast", name) @@ -454,6 +508,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action_exists: self.__raise_missing_parameter_error(parameter, 'value') + self.__actions[name].update_action_details(self.__state, self.__action_type) self.__actions[name].update(parameter['value']) except ValueError as ex: @@ -469,6 +524,14 @@ def remove_action(ex): _issue = self.__actions[name].update_repeat(parameter['repeat']) if _issue: _issue_list.append(_issue) + if parameter['mindelta'] is not None: + _issue = self.__actions[name].update_mindelta(parameter['mindelta']) + if _issue: + _issue_list.append(_issue) + if parameter['minagedelta'] is not None: + _issue = self.__actions[name].update_minagedelta(parameter['minagedelta']) + if _issue: + _issue_list.append(_issue) if parameter['delay'] != 0: _issue = self.__actions[name].update_delay(parameter['delay']) if _issue: @@ -477,6 +540,10 @@ def remove_action(ex): _issue = self.__actions[name].update_order(parameter['order']) if _issue: _issue_list.append(_issue) + if parameter['nextconditionset'] is not None: + _issue = self.__actions[name].update_nextconditionset(parameter['nextconditionset']) + if _issue: + _issue_list.append(_issue) if parameter['conditionset'] is not None: _issue = self.__actions[name].update_conditionset(parameter['conditionset']) if _issue: @@ -498,6 +565,7 @@ def remove_action(ex): _issue_list.append(_issue) if _action: self.__actions[name] = _action + # self._log_develop("Handle combined issuelist {}", _issue_list) return _issue_list # noinspection PyMethodMayBeStatic @@ -508,16 +576,16 @@ def __raise_missing_parameter_error(self, parameter, param_name): # Check the actions optimize and complete them # state: state (item) to read from - def complete(self, state, evals_items=None, use=None): + def complete(self, evals_items=None, use=None): _status = {} - if use is None: - use = state.use.get() + if not self.__actions: + return _status for name in self.__actions: try: - _status.update(self.__actions[name].complete(state, evals_items, use)) + _status.update(self.__actions[name].complete(evals_items, use)) except ValueError as ex: - _status.update({name: {'issue': ex, 'issueorigin': {'state': state.id, 'action': 'unknown'}}}) - raise ValueError("State '{0}', Action '{1}': {2}".format(state.id, name, ex)) + _status.update({name: {'issue': ex, 'issueorigin': {'state': self.__state.id, 'action': 'unknown'}}}) + raise ValueError("Completing State '{0}', Action '{1}': {2}".format(self.__state.id, name, ex)) return _status def set(self, value): @@ -525,7 +593,7 @@ def set(self, value): try: self.__actions[name].update(value) except ValueError as ex: - raise ValueError("State '{0}', Action '{1}': {2}".format(value.property.path, name, ex)) + raise ValueError("Setting State '{0}', Action '{1}': {2}".format(value.property.path, name, ex)) # Execute all actions # is_repeat: Indicate if this is a repeated action without changing the state diff --git a/stateengine/StateEngineCondition.py b/stateengine/StateEngineCondition.py index abfe71745..19e872d6f 100755 --- a/stateengine/StateEngineCondition.py +++ b/stateengine/StateEngineCondition.py @@ -59,6 +59,7 @@ def __init__(self, abitem, name: str): self.__triggeredbynegate = None self.__agenegate = None self.__error = None + self.__state = None self.__itemClass = Item def __repr__(self): @@ -228,6 +229,7 @@ def get(self): # state: state (item) to read from # abitem_object: Related SeItem instance for later determination of current age and current delay def complete(self, state, use): + self.__state = state # check if it is possible to complete this condition if self.__min.is_empty() and self.__max.is_empty() and self.__value.is_empty() \ and self.__agemin.is_empty() and self.__agemax.is_empty() \ @@ -292,14 +294,14 @@ def complete(self, state, use): if all(item is None for item in [self.__item, self.__status, self.__eval, self.__status_eval]): raise ValueError("Neither 'item' nor 'status' nor '(status)eval' given!") - if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval])\ - and not self.__changedby.is_empty() and self.__changedbynegate is None: + if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval]) \ + and not self.__changedby.is_empty() and self.__changedbynegate is None: self.__changedbynegate = False - if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval])\ - and not self.__updatedby.is_empty() and self.__updatedbynegate is None: + if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval]) \ + and not self.__updatedby.is_empty() and self.__updatedbynegate is None: self.__updatedbynegate = False - if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval])\ - and not self.__triggeredby.is_empty() and self.__triggeredbynegate is None: + if any(item is not None for item in [self.__item, self.__status, self.__eval, self.__status_eval]) \ + and not self.__triggeredby.is_empty() and self.__triggeredbynegate is None: self.__triggeredbynegate = False # cast stuff @@ -368,50 +370,80 @@ def check(self, state): # Write condition to logger def write_to_logger(self): + def write_item(item_type, value): + item_list = [] + if value is not None: + if isinstance(value, list): + for i in value: + try: + itm = i.property.path + except Exception: + itm = i + item_list.append(itm) + self._log_info("{0}: {1} ({2})", item_type, self.__name, itm) + else: + try: + itm = value.property.path + except Exception: + itm = value + item_list.append(itm) + self._log_info("{0}: {1} ({2})", item_type, self.__name, itm) + item_list = StateEngineTools.flatten_list(item_list) + item_list = None if len(item_list) == 0 else str(item_list[0]) if len(item_list) == 1 else str(item_list) + return item_list + + def write_eval(eval_type, value): + eval_list = [] + if value is not None: + if isinstance(value, list): + for e in value: + name = StateEngineTools.get_eval_name(e) + eval_list.append(name) + self._log_info("{0}: {1}", eval_type, name) + else: + name = StateEngineTools.get_eval_name(value) + eval_list.append(name) + self._log_info("{0}: {1}", eval_type, name) + eval_list = StateEngineTools.flatten_list(eval_list) + eval_list = None if len(eval_list) == 0 else str(eval_list[0]) if len(eval_list) == 1 else str(eval_list) + return str(eval_list) + if self.__error is not None: self._log_warning("error: {0}", self.__error) - if self.__item is not None: - if isinstance(self.__item, list): - for i in self.__item: - self._log_info("item: {0} ({1})", self.__name, i.property.path) - else: - self._log_info("item: {0} ({1})", self.__name, self.__item.property.path) - if self.__status is not None: - if isinstance(self.__status, list): - for i in self.__status: - self._log_info("status item: {0} ({1})", self.__name, i.property.path) - else: - self._log_info("status item: {0} ({1})", self.__name, self.__status.property.path) - if self.__eval is not None: - if isinstance(self.__eval, list): - for e in self.__eval: - self._log_info("eval: {0}", StateEngineTools.get_eval_name(e)) - else: - self._log_info("eval: {0}", StateEngineTools.get_eval_name(self.__eval)) - if self.__status_eval is not None: - if isinstance(self.__status_eval, list): - for e in self.__status_eval: - self._log_info("status eval: {0}", StateEngineTools.get_eval_name(e)) - else: - self._log_info("status eval: {0}", StateEngineTools.get_eval_name(self.__status_eval)) - self.__value.write_to_logger() - self.__min.write_to_logger() - self.__max.write_to_logger() + item_result = self.__item + status_result = self.__status + item = write_item("item", item_result) + status = write_item("status item", status_result) + eval_result = self.__eval + status_eval_result = self.__status_eval + eval_result = write_eval("eval", eval_result) + status_eval_result = write_eval("status", status_eval_result) + val = self.__value.write_to_logger() + value_result = self.__value.get_for_webif(val) + min_result = self.__min.write_to_logger() + max_result = self.__max.write_to_logger() if self.__negate is not None: self._log_info("negate: {0}", self.__negate) - self.__agemin.write_to_logger() - self.__agemax.write_to_logger() + agemin = self.__agemin.write_to_logger() + agemax = self.__agemax.write_to_logger() if self.__agenegate is not None: self._log_info("age negate: {0}", self.__agenegate) - self.__changedby.write_to_logger() + changedby = self.__changedby.write_to_logger() if self.__changedbynegate is not None and not self.__changedby.is_empty(): self._log_info("changedby negate: {0}", self.__changedbynegate) - self.__updatedby.write_to_logger() + updatedby = self.__updatedby.write_to_logger() if self.__updatedbynegate is not None and not self.__updatedby.is_empty(): self._log_info("updatedby negate: {0}", self.__updatedbynegate) - self.__triggeredby.write_to_logger() + triggeredby = self.__triggeredby.write_to_logger() if self.__updatedbynegate is not None and not self.__triggeredby.is_empty(): self._log_info("triggeredby negate: {0}", self.__triggeredbynegate) + return {self.name: {'item': item, 'status': status, 'eval': eval_result, 'status_eval': status_eval_result, + 'value': value_result, 'min': str(min_result), 'max': str(max_result), 'agemin': str(agemin), + 'agemax': str(agemax), 'negate': str(self.__negate), 'agenegate': str(self.__agenegate), + 'changedby': str(changedby), 'updatedby': str(updatedby), + 'triggeredby': str(triggeredby), 'triggeredbynegate': str(self.__triggeredbynegate), + 'changedbynegate': str(self.__changedbynegate), + 'updatedbynegate': str(self.__updatedbynegate), 'current': {}, 'match': {}}} # Cast 'value', 'min' and 'max' using given cast function # cast_func: cast function to use @@ -462,19 +494,19 @@ def __convert(convert_value, convert_current): self.__value.set_cast(StateEngineTools.cast_str) convert_value = StateEngineTools.cast_str(convert_value) convert_current = StateEngineTools.cast_str(convert_current) - if not type(_oldvalue) == type(convert_value): + if not type(_oldvalue) is type(convert_value): self._log_debug("Value {} was type {} and therefore not the same" " type as item value {}. It got converted to {}.", _oldvalue, type(_oldvalue), convert_current, type(convert_value)) return convert_value, convert_current - current = self.__get_current(eval_type='changedby') if valuetype == "changedby" else\ - self.__get_current(eval_type='updatedby') if valuetype == "updatedby" else\ - self.__get_current(eval_type='triggeredby') if valuetype == "triggeredby" else\ + current = self.__get_current(eval_type='changedby') if valuetype == "changedby" else \ + self.__get_current(eval_type='updatedby') if valuetype == "updatedby" else \ + self.__get_current(eval_type='triggeredby') if valuetype == "triggeredby" else \ self.__get_current(eval_type='value') - negate = self.__changedbynegate if valuetype == "changedby" else\ - self.__updatedbynegate if valuetype == "updatedby" else\ - self.__triggeredbynegate if valuetype == "triggeredby" else\ + negate = self.__changedbynegate if valuetype == "changedby" else \ + self.__updatedbynegate if valuetype == "updatedby" else \ + self.__triggeredbynegate if valuetype == "triggeredby" else \ self.__negate _key_current = ['{}'.format(state.id), 'conditionsets', '{}'.format( self._abitem.get_variable('current.conditionset_name')), '{}'.format(self.__name), @@ -490,25 +522,25 @@ def __convert(convert_value, convert_current): for i, element in enumerate(value): regex_result = None regex_check = False - if valuetype == "value" and type(element) != type(current) and current is not None: + if valuetype == "value" and type(element) is not type(current) and current is not None: element, current = __convert(element, current) if isinstance(element, re.Pattern): regex_result = element.fullmatch(str(current)) regex_check = True if negate: - if (regex_result is not None and regex_check is True)\ - or (current == element and regex_check is False): + if (regex_result is not None and regex_check is True) \ + or (current == element and regex_check is False): self._log_debug("{0} found but negated -> not matching", element) self._abitem.update_webif(_key_match, 'no') return False else: - if (regex_result is not None and regex_check is True)\ - or (current == element and regex_check is False): + if (regex_result is not None and regex_check is True) \ + or (current == element and regex_check is False): self._log_debug("{0} found -> matching", element) self._abitem.update_webif(_key_match, 'yes') return True if regex_check is True: - self._log_debug("Regex '{}' result: {}, element {}", element, regex_result) + self._log_debug("Regex '{0}' result: {1}.", element, regex_result) if negate: self._log_debug("{0} not in list -> matching", current) @@ -522,7 +554,7 @@ def __convert(convert_value, convert_current): regex_result = None regex_check = False # If current and value have different types, convert both to string - if valuetype == "value" and type(value) != type(current) and current is not None: + if valuetype == "value" and type(value) is not type(current) and current is not None: value, current = __convert(value, current) text = "Condition '{0}': {1}={2} negate={3} current={4}" self._abitem.update_webif(_key_current, str(current)) @@ -532,14 +564,14 @@ def __convert(convert_value, convert_current): regex_result = value.fullmatch(str(current)) regex_check = True if negate: - if (regex_result is None and regex_check is True)\ - or (current != value and regex_check is False): + if (regex_result is None and regex_check is True) \ + or (current != value and regex_check is False): self._log_debug("not OK but negated -> matching") self._abitem.update_webif(_key_match, 'yes') return True else: - if (regex_result is not None and regex_check is True)\ - or (current == value and regex_check is False): + if (regex_result is not None and regex_check is True) \ + or (current == value and regex_check is False): self._log_debug("OK -> matching") self._abitem.update_webif(_key_match, 'yes') return True @@ -823,10 +855,10 @@ def check_eval(eval_or_status_eval): eval_result = eval(eval_or_status_eval) if isinstance(eval_result, self.__itemClass): value = eval_result.property.last_change_age if eval_type == 'age' else \ - eval_result.property.last_change_by if eval_type == 'changedby' else \ - eval_result.property.last_update_by if eval_type == 'updatedby' else \ - eval_result.property.last_trigger_by if eval_type == 'triggeredby' else \ - eval_result.property.value + eval_result.property.last_change_by if eval_type == 'changedby' else \ + eval_result.property.last_update_by if eval_type == 'updatedby' else \ + eval_result.property.last_trigger_by if eval_type == 'triggeredby' else \ + eval_result.property.value else: value = eval_result except Exception as ex: @@ -840,10 +872,10 @@ def check_eval(eval_or_status_eval): if self.__status is not None: # noinspection PyUnusedLocal self._log_debug("Trying to get {} of status item {}", eval_type, self.__status.property.path) - return self.__status.property.last_change_age if eval_type == 'age' else\ - self.__status.property.last_change_by if eval_type == 'changedby' else\ - self.__status.property.last_update_by if eval_type == 'updatedby' else\ - self.__status.property.last_trigger_by if eval_type == 'triggeredby' else\ + return self.__status.property.last_change_age if eval_type == 'age' else \ + self.__status.property.last_change_by if eval_type == 'changedby' else \ + self.__status.property.last_update_by if eval_type == 'updatedby' else \ + self.__status.property.last_trigger_by if eval_type == 'triggeredby' else \ self.__status.property.value elif self.__status_eval is not None: self._log_debug("Trying to get {} of statuseval {}", eval_type, self.__status_eval) @@ -852,10 +884,10 @@ def check_eval(eval_or_status_eval): elif self.__item is not None: # noinspection PyUnusedLocal self._log_debug("Trying to get {} of item {}", eval_type, self.__item.property.path) - return self.__item.property.last_change_age if eval_type == 'age' else\ - self.__item.property.last_change_by if eval_type == 'changedby' else\ - self.__item.property.last_update_by if eval_type == 'updatedby' else\ - self.__item.property.last_trigger_by if eval_type == 'triggeredby' else\ + return self.__item.property.last_change_age if eval_type == 'age' else \ + self.__item.property.last_change_by if eval_type == 'changedby' else \ + self.__item.property.last_update_by if eval_type == 'updatedby' else \ + self.__item.property.last_trigger_by if eval_type == 'triggeredby' else \ self.__item.property.value elif self.__eval is not None: self._log_debug("Trying to get {} of eval {}", eval_type, self.__eval) diff --git a/stateengine/StateEngineConditionSet.py b/stateengine/StateEngineConditionSet.py index ed4e4b6d0..176b6a7c9 100755 --- a/stateengine/StateEngineConditionSet.py +++ b/stateengine/StateEngineConditionSet.py @@ -78,6 +78,7 @@ def __init__(self, abitem, name, conditionid): self.__evals_items = {} self.__unused_attributes = {} self.__used_attributes = {} + self.__state = None def __repr__(self): return "SeConditionSet {}".format(self.__conditions) @@ -150,6 +151,7 @@ def update(self, item, grandparent_item): # Check the condition set, optimize and complete it # state: state (item) to read from def complete(self, state, use): + self.__state = state conditions_to_remove = [] # try to complete conditions @@ -174,25 +176,21 @@ def write_to_logger(self): for name in self.__conditions: self._log_info("Condition '{0}':", name) self._log_increase_indent() - self.__conditions[name].write_to_logger() + _webif = self.__conditions[name].write_to_logger() + _key = [self.__state.id, 'conditionsets', self.name] + self._abitem.update_webif(_key, _webif, True) self._log_decrease_indent() def __currentconditionset_set(self, conditionsetid, name): self._abitem.set_variable('current.conditionset_id', conditionsetid) self._abitem.set_variable('current.conditionset_name', name) - def __previousconditionset_set(self, conditionsetid, name): - self._abitem.set_variable('previous.conditionset_id', conditionsetid) - self._abitem.set_variable('previous.conditionset_name', name) - # Check all conditions in the condition set. Return # returns: True = all conditions in set are matching, False = at least one condition is not matching def all_conditions_matching(self, state): try: self._log_info("Check condition set '{0}'", self.__name) self._log_increase_indent() - self.__previousconditionset_set(self._abitem.get_variable('current.conditionset_id'), - self._abitem.get_variable('current.conditionset_name')) self.__currentconditionset_set(self.__id.property.path, self.__name) for name in self.__conditions: diff --git a/stateengine/StateEngineConditionSets.py b/stateengine/StateEngineConditionSets.py index c8dbf3612..0cf4212ff 100755 --- a/stateengine/StateEngineConditionSets.py +++ b/stateengine/StateEngineConditionSets.py @@ -91,9 +91,9 @@ def write_to_logger(self): def one_conditionset_matching(self, state): if self.count() == 0: self._log_debug("No condition sets defined -> matching") - return True + return True, '' for name in self.__condition_sets: if self.__condition_sets[name].all_conditions_matching(state): - return True + return True, name - return False + return False, '' diff --git a/stateengine/StateEngineFunctions.py b/stateengine/StateEngineFunctions.py index ae5c2d7b1..1e869d033 100755 --- a/stateengine/StateEngineFunctions.py +++ b/stateengine/StateEngineFunctions.py @@ -85,14 +85,14 @@ def check_include_exclude(entry_type): # If current value is in list -> Return "Trigger" for e in conf_entry: e = re.compile(e, re.IGNORECASE) - result = e.match(original) - elog.info("Checking regex result {}", result) - if result is not None: + r = e.match(original) + elog.info("Checking regex result {}", r) + if r is not None: elog.info("{0}: matching.", e) elog.decrease_indent() - returnvalue = retval_trigger if entry_type == "include" else retval_no_trigger - elog.info("Writing value {0}", returnvalue) - return returnvalue + retval = retval_trigger if entry_type == "include" else retval_no_trigger + elog.info("Writing value {0}", retval) + return retval elog.info("{0}: not matching", e) elog.decrease_indent() return None diff --git a/stateengine/StateEngineItem.py b/stateengine/StateEngineItem.py index 42202d905..b171a8823 100755 --- a/stateengine/StateEngineItem.py +++ b/stateengine/StateEngineItem.py @@ -103,6 +103,14 @@ def logger(self): def instant_leaveaction(self): return self.__instant_leaveaction.get() + @property + def last_run(self): + return self.__last_run + + @last_run.setter + def last_run(self, value_dict): + self.__last_run.update(value_dict) + @property def default_instant_leaveaction(self): return self.__default_instant_leaveaction.get() @@ -111,6 +119,10 @@ def default_instant_leaveaction(self): def default_instant_leaveaction(self, value): self.__default_instant_leaveaction.set(value) + @property + def nextconditionset(self): + return self.__nextconditionset_id + @property def laststate(self): _returnvalue = None if self.__laststate_item_id is None else self.__laststate_item_id.property.value @@ -167,6 +179,14 @@ def previousstate_conditionset_name(self): _returnvalue = None if self.__previousstate_conditionset_item_name is None else self.__previousstate_conditionset_item_name.property.value return _returnvalue + @property + def cache(self): + return self.__cache + + @cache.setter + def cache(self, value): + self.__cache.update(value) + @property def ab_alive(self): return self.__ab_alive @@ -191,6 +211,9 @@ def __init__(self, smarthome, item, se_plugin): self.__se_plugin = se_plugin self.__active_schedulers = [] self.__release_info = {} + self.__cache = {} + self.__last_run = {} + self.__pass_repeat = {} self.__default_instant_leaveaction = StateEngineValue.SeValue(self, "Default Instant Leave Action", False, "bool") self.__instant_leaveaction = StateEngineValue.SeValue(self, "Instant Leave Action", False, "num") try: @@ -200,17 +223,17 @@ def __init__(self, smarthome, item, se_plugin): self.__name = str(self.__item) self.__itemClass = Item # initialize logging - + self.__logger.log_level_as_num = 2 self.__log_level = StateEngineValue.SeValue(self, "Log Level", False, "num") _default_log_level = self.__logger.default_log_level.get() - _returnvalue, _returntype, _using_default, _issue, _ = self.__log_level.set_from_attr(self.__item, "se_log_level", - _default_log_level) + _returnvalue, _returntype, _using_default, _issue, _ = self.__log_level.set_from_attr(self.__item, + "se_log_level", + default_value=_default_log_level) self.__using_default_log_level = _using_default _returnvalue = self.__log_level.get() if isinstance(_returnvalue, list) and len(_returnvalue) == 1: _returnvalue = _returnvalue[0] - self.__logger.log_level_as_num = 2 _startup_log_level = self.__logger.startup_log_level.get() @@ -242,13 +265,13 @@ def __init__(self, smarthome, item, se_plugin): # get startup delay self.__startup_delay = StateEngineValue.SeValue(self, "Startup Delay", False, "num") - self.__startup_delay.set_from_attr(self.__item, "se_startup_delay", StateEngineDefaults.startup_delay) + self.__startup_delay.set_from_attr(self.__item, "se_startup_delay", default_value=StateEngineDefaults.startup_delay) self.__startup_delay_over = False # Init suspend settings self.__default_suspend_time = StateEngineDefaults.suspend_time.get() self.__suspend_time = StateEngineValue.SeValue(self, "Suspension time on manual changes", False, "num") - self.__suspend_time.set_from_attr(self.__item, "se_suspend_time", self.__default_suspend_time) + self.__suspend_time.set_from_attr(self.__item, "se_suspend_time", default_value=self.__default_suspend_time) # Init laststate and previousstate items/values self.__config_issues = {} @@ -266,6 +289,8 @@ def __init__(self, smarthome, item, se_plugin): self.__config_issues.update(_issue) # Init lastconditionset items/values + self.__nextconditionset_id = "" + self.__nextconditionset_name = "" self.__lastconditionset_item_id, _issue = self.return_item_by_attribute("se_lastconditionset_item_id") self.__lastconditionset_internal_id = "" if self.__lastconditionset_item_id is None else \ self.__lastconditionset_item_id.property.value @@ -305,7 +330,7 @@ def __init__(self, smarthome, item, se_plugin): self.__templates = {} self.__unused_attributes = {} self.__used_attributes = {} - self.__action_status = {} + self.__action_status = {"enter": {}, "enter_or_stay": {}, "stay": {}, "pass": {}, "leave": {}} self.__state_issues = {} self.__struct_issues = {} self.__webif_infos = OrderedDict() @@ -338,6 +363,8 @@ def __init__(self, smarthome, item, se_plugin): "release.has_released": "", "release.was_released_by": "", "release.will_release": "", + "next.conditionset_id": "", + "next.conditionset_name": "", "current.state_id": "", "current.state_name": "", "current.conditionset_id": "", @@ -402,9 +429,21 @@ def show_issues_summary(self): if self.__unused_attributes: issues += 1 self.__log_issues('attributes') - if self.__action_status: + if self.__action_status['enter']: + issues += 1 + self.__log_issues('actions_enter') + if self.__action_status['enter_or_stay']: + issues += 1 + self.__log_issues('actions_enter_or_stay') + if self.__action_status['stay']: + issues += 1 + self.__log_issues('actions_stay') + if self.__action_status['leave']: + issues += 1 + self.__log_issues('actions_leave') + if self.__action_status['pass']: issues += 1 - self.__log_issues('actions') + self.__log_issues('actions_pass') if self.__state_issues: issues += 1 self.__log_issues('states') @@ -419,7 +458,7 @@ def update_leave_action(self, default_instant_leaveaction): self.__default_instant_leaveaction = default_instant_leaveaction _returnvalue_leave, _returntype_leave, _using_default_leave, _issue, _ = self.__instant_leaveaction.set_from_attr( - self.__item, "se_instant_leaveaction", default_instant_leaveaction) + self.__item, "se_instant_leaveaction", default_value=default_instant_leaveaction) if len(_returnvalue_leave) > 1: self.__logger.warning("se_instant_leaveaction for item {} can not be defined as a list" @@ -462,6 +501,15 @@ def remove_all_schedulers(self): # region Updatestate *********************************************************************************************** # run queue def run_queue(self): + def update_current_to_empty(d): + if isinstance(d, dict): # Check if the current level is a dictionary + for key, val in d.items(): + if key in ['current', 'match', 'actionstatus'] and isinstance(val, dict): # If the key is 'current', and value is a dict + d[key] = {} # Set it to an empty dict + else: + # Recur for nested dictionaries + update_current_to_empty(val) + if not self.__ab_alive: self.__logger.debug("{} not running (anymore). Queue not activated.", StateEngineDefaults.plugin_identification) @@ -532,15 +580,16 @@ def run_queue(self): elif job[0] == "delayedaction": self.__logger.debug("Job {}", job) (_, action, actionname, namevar, repeat_text, value, current_condition, previous_condition, - previousstate_condition, state) = job + previousstate_condition, next_condition, state) = job self.__logger.info( - "Running delayed action: {0} based on current condition {1} or previous condition {2}", - actionname, current_condition, previous_condition) - action.real_execute(state, actionname, namevar, repeat_text, value, False, current_condition) + "Running delayed action: {0} based on current_condition {1} / previous_condition {2} / previousstate_condition {3} or next condition {4}", + actionname, current_condition, previous_condition, previousstate_condition, next_condition) + action.real_execute(state, actionname, namevar, repeat_text, value, False, current_condition, previous_condition, previousstate_condition, next_condition) else: (_, item, caller, source, dest) = job item_id = item.property.path if item is not None else "(no item)" self.__logger.update_logfile() + self.__cache = {} self.__logger.header("Update state of item {0}".format(self.__name)) if caller: self.__logger.debug("Update triggered by {0} (item={1} source={2} dest={3})", caller, item_id, @@ -612,6 +661,7 @@ def run_queue(self): # find new state _leaveactions_run = False + _pass_state = None if _instant_leaveaction >= 1 and caller != "Released_by Retrigger": evaluated_instant_leaveaction = True @@ -619,6 +669,9 @@ def run_queue(self): evaluated_instant_leaveaction = False _previousstate_conditionset_id = '' _previousstate_conditionset_name = '' + + update_current_to_empty(self.__webif_infos) + self.__logger.develop("Reset current info for webif info. It is now: {}", self.__webif_infos) for state in self.__states: if not self.__ab_alive: self.__logger.debug("StateEngine Plugin not running (anymore). Stop state evaluation.") @@ -627,7 +680,13 @@ def run_queue(self): _key_name = ['{}'.format(state.id), 'name'] self.update_webif(_key_name, state.name) - result = self.__update_check_can_enter(state, _instant_leaveaction) + result, self.__nextconditionset_name = self.__update_check_can_enter(state, _instant_leaveaction) + if self.__nextconditionset_name: + self.__nextconditionset_id = f"{state.state_item.property.path}.{self.__nextconditionset_name}" + else: + self.__nextconditionset_id = "" + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) _previousstate_conditionset_id = _last_conditionset_id _previousstate_conditionset_name = _last_conditionset_name _last_conditionset_id = self.__lastconditionset_internal_id @@ -636,27 +695,47 @@ def run_queue(self): self.__conditionsets.update( {state.state_item.property.path: [_last_conditionset_id, _last_conditionset_name]}) # New state is different from last state - - if result is False and last_state == state and evaluated_instant_leaveaction is True: - self.__logger.info("Leaving {0} ('{1}'). Running actions immediately.", last_state.id, - last_state.name) - last_state.run_leave(self.__repeat_actions.get()) - _leaveactions_run = True + if result is False and last_state == state: + if evaluated_instant_leaveaction is True: + self.__logger.info("Leaving {0} ('{1}'). Running actions immediately.", last_state.id, + last_state.name) + last_state.run_leave(self.__repeat_actions.get()) + _leaveactions_run = True + elif result is False and last_state != state and state.actions_pass.count() > 0: + _pass_state = state + _pass_state.run_pass(self.__pass_repeat.get(state, False), self.__repeat_actions.get()) + _key_pass = ['{}'.format(_pass_state.id), 'pass'] + self.update_webif(_key_pass, True) + self.__pass_repeat.update({state: True}) if result is True: new_state = state + for repeat_state in self.__pass_repeat: + if new_state.order < repeat_state.order: + self.__pass_repeat.update({repeat_state: False}) + _key_pass = ['{}'.format(repeat_state.id), 'pass'] + self.update_webif(_key_pass, False) break # no new state -> stay if new_state is None: if last_state is None: + self.__nextconditionset_id = '' + self.__nextconditionset_name = '' + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) self.__logger.info("No matching state found, no previous state available. Doing nothing.") else: - if last_state.conditions.count() == 0: + if last_state.conditionsets.count() == 0: self.lastconditionset_set('', '') _last_conditionset_id = '' _last_conditionset_name = '' else: self.lastconditionset_set(_last_conditionset_id, _last_conditionset_name) + self.__nextconditionset_id = _last_conditionset_id + self.__nextconditionset_name = _last_conditionset_name + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) + self.__logger.develop("Current variables: {}", self.__variables) if _last_conditionset_id in ['', None]: text = "No matching state found, staying at {0} ('{1}')" self.__logger.info(text, last_state.id, last_state.name) @@ -681,17 +760,23 @@ def run_queue(self): "State is a copy and therefore just releasing {}. Skipping state actions, running leave actions " "of last state, then retriggering.", new_state.is_copy_for.id) if last_state is not None and self.__ab_alive: - self.__logger.info("Leaving {0} ('{1}'). Condition set was: {2}.", - last_state.id, last_state.name, _original_conditionset_id) - self.__update_check_can_enter(last_state, _instant_leaveaction, False) + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) + self.__logger.develop("Current variables: {}", self.__variables) + self.__logger.info("Leaving {0} ('{1}'). Condition set was: {2} ({3}), will be {4} ({5}).", + last_state.id, last_state.name, _original_conditionset_id, + _original_conditionset_name, self.__nextconditionset_id, + self.__nextconditionset_name) last_state.run_leave(self.__repeat_actions.get()) _key_leave = ['{}'.format(last_state.id), 'leave'] _key_stay = ['{}'.format(last_state.id), 'stay'] _key_enter = ['{}'.format(last_state.id), 'enter'] + _key_pass = ['{}'.format(last_state.id), 'pass'] self.update_webif(_key_leave, True) self.update_webif(_key_stay, False) self.update_webif(_key_enter, False) + self.update_webif(_key_pass, False) self.__handle_releasedby(new_state, last_state, _instant_leaveaction) if self.update_lock.locked(): @@ -702,7 +787,7 @@ def run_queue(self): _last_conditionset_id = self.__lastconditionset_internal_id _last_conditionset_name = self.__lastconditionset_internal_name - if new_state.conditions.count() == 0: + if new_state.conditionsets.count() == 0: self.lastconditionset_set('', '') _last_conditionset_id = '' _last_conditionset_name = '' @@ -710,6 +795,11 @@ def run_queue(self): # endblock # get data for new state if last_state is not None and new_state.id == last_state.id: + self.__nextconditionset_id = _last_conditionset_id + self.__nextconditionset_name = _last_conditionset_name + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) + self.__logger.develop("Current variables: {}", self.__variables) if _last_conditionset_id in ['', None]: self.__logger.info("Staying at {0} ('{1}')", new_state.id, new_state.name) else: @@ -726,23 +816,30 @@ def run_queue(self): self.__logger.info("Leave actions already run during state release.") elif last_state is not None and _leaveactions_run is True: self.__logger.info("Left {0} ('{1}')", last_state.id, last_state.name) - if last_state.leaveactions.count() > 0: + if last_state.actions_leave.count() > 0: self.__logger.info( "Maybe some actions were performed directly after leave - see log above.") elif last_state is not None: self.lastconditionset_set(_original_conditionset_id, _original_conditionset_name) - self.__logger.info("Leaving {0} ('{1}'). Condition set was: {2}.", - last_state.id, last_state.name, _original_conditionset_id) + self.__logger.develop("Current variables: {}", self.__variables) + self.__logger.info("Leaving {0} ('{1}'). Condition set was: {2} ({3}), will be {4} ({5}).", + last_state.id, last_state.name, _original_conditionset_id, + _original_conditionset_name, self.__nextconditionset_id, self.__nextconditionset_name) last_state.run_leave(self.__repeat_actions.get()) _leaveactions_run = True - if new_state.conditions.count() == 0: + if new_state.conditionsets.count() == 0: self.lastconditionset_set('', '') _last_conditionset_id = '' _last_conditionset_name = '' + self.__nextconditionset_id = _last_conditionset_id + self.__nextconditionset_name = _last_conditionset_name else: self.lastconditionset_set(_last_conditionset_id, _last_conditionset_name) self.previousstate_conditionset_set(_previousstate_conditionset_id, _previousstate_conditionset_name) + self.set_variable('next.conditionset_id', self.__nextconditionset_id) + self.set_variable('next.conditionset_name', self.__nextconditionset_name) + self.__logger.develop("Current variables: {}", self.__variables) if _last_conditionset_id in ['', None]: self.__logger.info("Entering {0} ('{1}')", new_state.id, new_state.name) else: @@ -750,16 +847,19 @@ def run_queue(self): new_state.id, new_state.name, _last_conditionset_id, _last_conditionset_name) new_state.run_enter(self.__repeat_actions.get()) + self.__laststate_set(new_state) self.__previousstate_set(last_state) if _leaveactions_run is True and self.__ab_alive: _key_leave = ['{}'.format(last_state.id), 'leave'] _key_stay = ['{}'.format(last_state.id), 'stay'] _key_enter = ['{}'.format(last_state.id), 'enter'] + _key_pass = ['{}'.format(last_state.id), 'pass'] self.update_webif(_key_leave, True) self.update_webif(_key_stay, False) self.update_webif(_key_enter, False) + self.update_webif(_key_pass, False) self.__logger.debug("State evaluation finished") all_released_by = self.__handle_releasedby(new_state, last_state, _instant_leaveaction) @@ -826,15 +926,16 @@ def __update_can_release(self, can_release, new_state=None): def __handle_releasedby(self, new_state, last_state, instant_leaveaction): def update_can_release_list(): - for e in _returnvalue: - e = self.__update_release_item_value(e, new_state) - e = e if isinstance(e, list) else [e] - for entry in e: + for r in _returnvalue: + r = self.__update_release_item_value(r, new_state) + r = r if isinstance(r, list) else [r] + for entry in r: if entry and state.id not in can_release.setdefault(entry, [state.id]): can_release[entry].append(state.id) self.__logger.info("".ljust(80, "_")) self.__logger.info("Handling released_by attributes") + self.__logger.increase_indent("Handling released_by... ") can_release = {} all_released_by = {} skip_copy = True @@ -918,7 +1019,7 @@ def update_can_release_list(): current_log_level = self.__log_level.get() if current_log_level < 3: self.__logger.log_level_as_num = 0 - can_enter = self.__update_check_can_enter(relevant_state, instant_leaveaction) + can_enter, _ = self.__update_check_can_enter(relevant_state, instant_leaveaction) self.__logger.log_level_as_num = current_log_level if relevant_state == last_state: self.__logger.debug("Possible release state {} = last state {}, " @@ -936,20 +1037,28 @@ def update_can_release_list(): self.__release_info = {new_state.id: _can_release_list} _key_releasedby = ['{}'.format(new_state.id), 'releasedby'] self.update_webif(_key_releasedby, _can_release_list) - + self.__logger.increase_indent("") self.__logger.info("".ljust(80, "_")) return all_released_by - def update_webif(self, key, value): + def update_webif(self, key, value, update=False): def _nested_set(dic, keys, val): for nestedkey in keys[:-1]: dic = dic.setdefault(nestedkey, {}) - dic[keys[-1]] = val + # Check if both existing value and new value are dictionaries + if update is True and isinstance(dic.get(keys[-1]), dict) and isinstance(val, dict): + # Update the existing dictionary with the new dictionary + dic[keys[-1]].update(val) + #self.__logger.develop("Updating WEBIF with list {}, value: {}. infos is {}", key, value, self.__webif_infos) + else: + # Otherwise, set the value as is + dic[keys[-1]] = val + #self.__logger.develop("Setting WEBIF with list {}, value: {}. infos is {}", key, value, self.__webif_infos) def _nested_test(dic, keys): for nestedkey in keys[:-2]: dic = dic.setdefault(nestedkey, {}) - return dic[keys[-2]] + return dic.get(keys[-2], {}) if isinstance(key, list): try: @@ -959,24 +1068,41 @@ def _nested_test(dic, keys): except Exception: return False else: - self.__webif_infos[key] = value + if update is True: + self.__webif_infos.setdefault(key, {}).update(value) + self.__logger.develop("Updating WEBIF {}, value: {}. infos is {}", key, value, self.__webif_infos) + else: + self.__webif_infos[key] = value + self.__logger.develop("Setting WEBIF {}, value: {}. infos is {}", key, value, self.__webif_infos) return True def update_action_status(self, action_status): def combine_dicts(dict1, dict2): - combined_dict = dict1.copy() - for key, value in dict2.items(): - if key in combined_dict: - for k, v in combined_dict.items(): - v['issueorigin'].extend( - [item for item in v['issueorigin'] if item not in combined_dict[k]['issueorigin']]) - v['issue'].extend([item for item in v['issue'] if item not in combined_dict[k]['issue']]) + combined = copy.deepcopy(dict1) + for action_type, action_dict in dict2.items(): + if action_type in combined: + # Merge the inner dictionary for this action_type + for key, value in action_dict.items(): + if key in combined[action_type]: + combined[action_type][key]['issueorigin'].extend( + [item for item in value['issueorigin'] if + item not in combined[action_type][key]['issueorigin']] + ) + combined[action_type][key]['issue'].extend( + [item for item in value['issue'] if item not in combined[action_type][key]['issue']] + ) + else: + # Add new key at the inner level if it doesn't exist + combined[action_type][key] = value else: - combined_dict[key] = value - return combined_dict + # Add the entire action_type dictionary if it's not in combined + combined[action_type] = action_dict - combined_dict = combine_dicts(action_status, self.__action_status) + return combined + + combined_dict = combine_dicts(copy.deepcopy(action_status), copy.deepcopy(self.__action_status)) self.__action_status = combined_dict + del combined_dict def update_issues(self, issue_type, issues): def combine_dicts(dict1, dict2): @@ -992,19 +1118,19 @@ def update_list(existing, new_entries): existing.append(entry) return existing - combined_dict = dict1.copy() + comb_dict = dict1.copy() for key, value in dict2.items(): - if key not in combined_dict: - combined_dict[key] = value + if key not in comb_dict: + comb_dict[key] = value continue - combined_entry = combined_dict[key] + combined_entry = comb_dict[key] if 'issue' in value: combined_entry['issue'] = update_list(combined_entry.get('issue', []), value['issue']) if 'issueorigin' in value: combined_entry['issueorigin'] = update_list(combined_entry.get('issueorigin', []), value['issueorigin']) - return combined_dict + return comb_dict if issue_type == "state": combined_dict = combine_dicts(issues, self.__state_issues) @@ -1048,7 +1174,7 @@ def update_attributes(self, unused_attributes, used_attributes): self.__used_attributes = combined_dict def __log_issues(self, issue_type): - def print_readable_dict(data): + def print_readable_dict(attr, data): for key, value in data.items(): if isinstance(value, list): formatted_entries = [] @@ -1062,45 +1188,64 @@ def print_readable_dict(data): else: formatted_entries.append(item) if formatted_entries: - self.__logger.info("- {}: {}", key, ', '.join(formatted_entries)) + self.__logger.info("- {}{}: {}", attr, key, ', '.join(formatted_entries)) else: - self.__logger.info("- {}: {}", key, value) + self.__logger.info("- {}{}: {}", attr, key, value) + def list_issues(v): _issuelist = StateEngineTools.flatten_list(v.get('issue')) + _attrlist = StateEngineTools.flatten_list(v.get('attribute')) if isinstance(_issuelist, list) and len(_issuelist) > 1: self.__logger.info("has the following issues:") self.__logger.increase_indent() - for e in _issuelist: + for i, e in enumerate(_issuelist): + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[i] else "attribute {}: ".format(_attrlist[i]) if isinstance(e, dict): - print_readable_dict(e) + print_readable_dict(_attr, e) else: - self.__logger.info("- {}", e) + self.__logger.info("- {}{}", _attr, e) self.__logger.decrease_indent() elif isinstance(_issuelist, list) and len(_issuelist) == 1: if isinstance(_issuelist[0], dict): self.__logger.info("has the following issues:") self.__logger.increase_indent() - print_readable_dict(_issuelist[0]) + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[0] else "attribute {}: ".format(_attrlist[0]) + print_readable_dict(_attr, _issuelist[0]) self.__logger.decrease_indent() else: - self.__logger.info("has the following issue: {}", _issuelist[0]) + _attr = "" if _attrlist is None or not isinstance(_attrlist, list) or not _attrlist[0] else " for attribute {}".format(_attrlist[0]) + self.__logger.info("has the following issue{}: {}", _attr, _issuelist[0]) else: if isinstance(_issuelist, dict): self.__logger.info("has the following issues:") self.__logger.increase_indent() - print_readable_dict(_issuelist) + _attr = "" if not _attrlist else "attribute {}: ".format(_attrlist) + print_readable_dict(_attr, _issuelist) self.__logger.decrease_indent() else: - self.__logger.info("has the following issue: {}", _issuelist) + _attr = "" if not _attrlist else " for attribute {}".format(_attrlist) + self.__logger.info("has the following issue{}: {}", _attr, _issuelist) if "ignore" in v: self.__logger.info("It will be ignored") warn_unused = "" warn_issues = "" warn = "" - if issue_type == 'actions': - to_check = self.__action_status.items() - warn = ', '.join(key for key in self.__action_status.keys()) + if issue_type == 'actions_enter': + to_check = self.__action_status['enter'].items() + warn = ', '.join(key for key in self.__action_status['enter'].keys()) + elif issue_type == 'actions_enter_or_stay': + to_check = self.__action_status['enter_or_stay'].items() + warn = ', '.join(key for key in self.__action_status['enter_or_stay'].keys()) + elif issue_type == 'actions_stay': + to_check = self.__action_status['stay'].items() + warn = ', '.join(key for key in self.__action_status['stay'].keys()) + elif issue_type == 'actions_pass': + to_check = self.__action_status['pass'].items() + warn = ', '.join(key for key in self.__action_status['pass'].keys()) + elif issue_type == 'actions_leave': + to_check = self.__action_status['leave'].items() + warn = ', '.join(key for key in self.__action_status['leave'].keys()) elif issue_type == 'structs': to_check = self.__struct_issues.items() warn = ', '.join(key for key in self.__struct_issues.keys()) @@ -1159,9 +1304,8 @@ def list_issues(v): self.__logger.info("Definition {}{}", entry, additional) self.__logger.increase_indent() for origin in origin_list: - if issue_type == 'actions': - origin_text = 'state {}, action {}, on_{}'.format(origin.get('state'), origin.get('action'), - origin.get('type')) + if issue_type.startswith('actions_'): + origin_text = 'state {}, action {}'.format(origin.get('state'), origin.get('action')) elif issue_type == 'states': if origin.get('condition') == 'GeneralError' and len(origin_list) == 1: origin_text = 'there was a general error. The state' @@ -1181,7 +1325,7 @@ def list_issues(v): self.__logger.info("") for entry, value in to_check: if 'issue' not in value: - text = "Definition {} not used in any action or condition.".format(entry) + text = "Definition {} not used in any action or condition.".format(value.get('attribute', entry)) self.__logger.info("{}", text) self.__logger.decrease_indent() @@ -1189,6 +1333,7 @@ def __reorder_states(self, init=True): _reordered_states = [] self.__logger.info("".ljust(80, "_")) self.__logger.info("Recalculating state order. Current order: {}", self.__states) + self.__logger.increase_indent() _copied_states = {} _add_order = 0 _changed_orders = [] @@ -1254,6 +1399,7 @@ def __reorder_states(self, init=True): else: _reorder_webif[state.id] = self.__webif_infos[state.id] self.__webif_infos = _reorder_webif + self.__logger.decrease_indent() self.__logger.info("Recalculated state order. New order: {}", self.__states) self.__logger.info("".ljust(80, "_")) @@ -1263,7 +1409,7 @@ def __initialize_state(self, item_state, _statecount): _issue = _state.update_order(_statecount) if _issue: self.__config_issues.update({item_state.property.path: - {'issue': _issue, 'attribute': 'se_stateorder'}}) + {'issue': _issue, 'attribute': 'se_stateorder'}}) self.__logger.error("Issue with state {0} while setting order: {1}", item_state.property.path, _issue) self.__states.append(_state) @@ -1276,13 +1422,13 @@ def __initialize_state(self, item_state, _statecount): return _statecount + 1 except ValueError as ex: self.update_issues('state', {item_state.property.path: {'issue': ex, 'issueorigin': - [{'conditionset': 'None', 'condition': 'ValueError'}]}}) + [{'conditionset': 'None', 'condition': 'ValueError'}]}}) self.__logger.error("Ignoring state {0} because ValueError: {1}", item_state.property.path, ex) return _statecount except Exception as ex: self.update_issues('state', {item_state.property.path: {'issue': ex, 'issueorigin': - [{'conditionset': 'None', 'condition': 'GeneralError'}]}}) + [{'conditionset': 'None', 'condition': 'GeneralError'}]}}) self.__logger.error("Ignoring state {0} because: {1}", item_state.property.path, ex) return _statecount @@ -1333,6 +1479,8 @@ def __update_check_can_enter(self, state, instant_leaveaction, refill=True): self.__variables["previous.state_id"] = self.__previousstate_internal_id self.__variables["previous.state_name"] = self.__previousstate_internal_name self.__variables["item.instant_leaveaction"] = instant_leaveaction + self.__variables["next.conditionset_id"] = self.__nextconditionset_id + self.__variables["next.conditionset_name"] = self.__nextconditionset_name self.__variables["current.state_id"] = state.id self.__variables["current.state_name"] = state.name self.__variables["current.conditionset_id"] = self.__lastconditionset_internal_id @@ -1344,7 +1492,8 @@ def __update_check_can_enter(self, state, instant_leaveaction, refill=True): self.__logger.develop("Current variables: {}", self.__variables) if refill: state.refill() - return state.can_enter() + can_enter = state.can_enter() + return can_enter except Exception as ex: self.__logger.warning("Problem with currentstate {0}. Error: {1}", state.id, ex) # The variables where originally reset in a finally: statement. No idea why... ;) @@ -1354,6 +1503,8 @@ def __update_check_can_enter(self, state, instant_leaveaction, refill=True): self.__variables["release.was_released_by"] = "" self.__variables["release.will_release"] = "" self.__variables["item.instant_leaveaction"] = "" + self.__variables["next.conditionset_id"] = "" + self.__variables["next.conditionset_name"] = "" self.__variables["current.state_id"] = "" self.__variables["current.state_name"] = "" self.__variables["current.conditionset_id"] = "" @@ -1732,11 +1883,11 @@ def process_returnvalue(value): return _returnvalue_issue def update_can_release_list(): - for i, value in enumerate(_convertedlist): - if _converted_typelist[i] == 'item': - value = self.__update_release_item_value(_converted_evaluatedlist[i], state) - elif _converted_typelist[i] == 'eval': - value = _converted_evaluatedlist[i] + for z, value in enumerate(_convertedlist): + if _converted_typelist[z] == 'item': + value = self.__update_release_item_value(_converted_evaluatedlist[z], state) + elif _converted_typelist[z] == 'eval': + value = _converted_evaluatedlist[z] value = value if isinstance(value, list) else [value] for v in value: if v and can_release.get(v) and state.id not in can_release.get(v): @@ -1746,6 +1897,7 @@ def update_can_release_list(): self.__logger.info("".ljust(80, "_")) self.__logger.info("Initializing released_by attributes") + self.__logger.increase_indent() can_release = {} state_dict = {state.id: state for state in self.__states} for state in self.__states: @@ -1771,7 +1923,7 @@ def update_can_release_list(): self.__config_issues.update({state.id: {'issue': _issuelist, 'attribute': 'se_released_by'}}) state.update_releasedby_internal(_convertedlist) self.__update_can_release(can_release, state) - + self.__logger.decrease_indent() self.__logger.info("".ljust(80, "_")) # log item data @@ -1854,6 +2006,8 @@ def cli_detail(self, handler): self.get_previousstate_conditionset_name())) handler.push("\tPrevious conditionset: {0} ('{1}')\n".format(self.get_previousconditionset_id(), self.get_previousconditionset_name())) + handler.push("\tNext conditionset: {0} ('{1}')\n".format(self.get_nextconditionset_id(), + self.get_nextconditionset_name())) handler.push(self.__startup_delay.get_text("\t", "\n")) handler.push("\tCycle: {0}\n".format(cycles)) handler.push("\tCron: {0}\n".format(crons)) @@ -1878,6 +2032,14 @@ def get_condition_age(self): self.__logger.warning('No item for last condition id given. Can not determine age!') return 0 + # return id of new (upcoming) conditionset + def get_nextconditionset_id(self): + return self.__nextconditionset_id + + # return name of new (upcoming) conditionset + def get_nextconditionset_name(self): + return self.__nextconditionset_name + # return id of last state def get_laststate_id(self): return self.__laststate_internal_id @@ -1948,7 +2110,12 @@ def get_update_original_source(self): # return value of variable def get_variable(self, varname): - return self.__variables[varname] if varname in self.__variables else "(Unknown variable '{0}'!)".format(varname) + if varname not in self.__variables: + returnvalue = "(Unknown variable '{0}'!)".format(varname) + self.__logger.warning("Issue when getting variable {}".format(returnvalue)) + else: + returnvalue = self.__variables[varname] + return returnvalue # set value of variable def set_variable(self, varname, value): @@ -2031,7 +2198,7 @@ def return_item(self, item_id): _, _, item = item.partition(":") return item, None elif match: - _issue = ("Item '{0}' has to be defined as an item path " + _issue = ("Item '{}' has to be defined as an item path " "or eval expression without {}.").format(match.group(1), item_id) self.__logger.warning(_issue) return None, [_issue] diff --git a/stateengine/StateEngineLogger.py b/stateengine/StateEngineLogger.py index a398a6b15..645bf020a 100755 --- a/stateengine/StateEngineLogger.py +++ b/stateengine/StateEngineLogger.py @@ -131,6 +131,7 @@ def __init__(self, item, manual=False): self.__name = 'stateengine.{}'.format(item.property.path) self.__section = item.property.path.replace(".", "_").replace("/", "") self.__indentlevel = 0 + self.__indentprefix = "" if manual: self.__log_level_as_num = 2 else: @@ -151,7 +152,10 @@ def update_logfile(self): # Increase indentation level # by: number of levels to increase def increase_indent(self, by=1): - self.__indentlevel += by + if isinstance(by, int): + self.__indentlevel += by + else: + self.__indentprefix = by # Decrease indentation level # by: number of levels to decrease @@ -170,7 +174,7 @@ def log(self, level, text, *args): indent = "\t" * self.__indentlevel if args: text = text.format(*args) - logtext = "{0}{1} {2}\r\n".format(datetime.datetime.now(), indent, text) + logtext = "{0} {1}{2}{3}\r\n".format(datetime.datetime.now(), self.__indentprefix, indent, text) try: with open(self.__filename, mode="a", encoding="utf-8") as f: f.write(logtext) @@ -194,7 +198,7 @@ def header(self, text): def info(self, text, *args): self.log(1, text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.info(text) @@ -205,7 +209,7 @@ def info(self, text, *args): def debug(self, text, *args): self.log(2, text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.debug(text) @@ -216,7 +220,7 @@ def debug(self, text, *args): def develop(self, text, *args): self.log(3, "DEV: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.log(StateEngineDefaults.VERBOSE, text) @@ -228,7 +232,7 @@ def develop(self, text, *args): def warning(self, text, *args): self.log(1, "WARNING: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.warning(text) @@ -240,7 +244,7 @@ def warning(self, text, *args): def error(self, text, *args): self.log(1, "ERROR: " + text, *args) indent = "\t" * self.__indentlevel - text = '{}{}'.format(indent, text) + text = '{}{}{}'.format(self.__indentprefix, indent, text) if args: text = text.format(*args) self.logger.error(text) diff --git a/stateengine/StateEngineState.py b/stateengine/StateEngineState.py index c8986e21b..b2ab0221a 100755 --- a/stateengine/StateEngineState.py +++ b/stateengine/StateEngineState.py @@ -28,7 +28,7 @@ from lib.item import Items from lib.item.item import Item -from copy import copy +from copy import copy, deepcopy # Class representing an object state, consisting of name, conditions to be met and configured actions for state @@ -57,9 +57,25 @@ def name(self): # Return leave actions @property - def leaveactions(self): + def actions_leave(self): return self.__actions_leave + @property + def actions_enter(self): + return self.__actions_enter + + @property + def actions_enter_or_stay(self): + return self.__actions_enter_or_stay + + @property + def actions_stay(self): + return self.__actions_stay + + @property + def actions_pass(self): + return self.__actions_pass + # Return text of state @property def text(self): @@ -67,8 +83,8 @@ def text(self): # Return conditions @property - def conditions(self): - return self.__conditions + def conditionsets(self): + return self.__conditionsets # Return orphaned definitions @property @@ -164,15 +180,16 @@ def __init__(self, abitem, item_state): self.__name = '' self.__unused_attributes = {} self.__used_attributes = {} - self.__action_status = {} + self.__action_status = {"enter": {}, "enter_or_stay": {}, "stay": {}, "pass": {}, "leave": {}} self.__use_done = [] self.__use_list = [] self.__use_ignore_list = [] - self.__conditions = StateEngineConditionSets.SeConditionSets(self._abitem) + self.__conditionsets = StateEngineConditionSets.SeConditionSets(self._abitem) self.__actions_enter_or_stay = StateEngineActions.SeActions(self._abitem) self.__actions_enter = StateEngineActions.SeActions(self._abitem) self.__actions_stay = StateEngineActions.SeActions(self._abitem) self.__actions_leave = StateEngineActions.SeActions(self._abitem) + self.__actions_pass = StateEngineActions.SeActions(self._abitem) self.__order = StateEngineValue.SeValue(self._abitem, "State Order", False, "num") self._log_increase_indent() try: @@ -193,13 +210,13 @@ def can_enter(self): self.__is_copy_for.write_to_logger() self.__releasedby.write_to_logger() self.__can_release.write_to_logger() - result = self.__conditions.one_conditionset_matching(self) + result, conditionset = self.__conditionsets.one_conditionset_matching(self) self._log_decrease_indent() if result: - self._log_info("State {} can be entered", self.id) + self._log_info("State {} can be entered based on conditionset {}", self.id, conditionset) else: self._log_info("State {} can not be entered", self.id) - return result + return result, conditionset # log state data def write_to_log(self): @@ -219,20 +236,21 @@ def write_to_log(self): self._log_info("Updating Web Interface...") self._log_increase_indent() self._abitem.update_webif(self.id, {'name': self.name, - 'conditionsets': self.__conditions.get(), + 'conditionsets': {}, 'actions_enter': {}, 'actions_enter_or_stay': {}, 'actions_stay': {}, 'actions_leave': {}, - 'leave': False, 'enter': False, 'stay': False, + 'actions_pass': {}, + 'leave': False, 'enter': False, 'stay': False, 'pass': False, 'is_copy_for': None, 'releasedby': None}) self._log_decrease_indent() self._log_info("Finished Web Interface Update") - if self.__conditions.count() > 0: + if self.__conditionsets.count() > 0: self._log_info("Condition sets to enter state:") self._log_increase_indent() - self.__conditions.write_to_logger() + self.__conditionsets.write_to_logger() self._log_decrease_indent() if self.__actions_enter.count() > 0: @@ -240,28 +258,31 @@ def write_to_log(self): self._log_increase_indent() self.__actions_enter.write_to_logger() self._log_decrease_indent() - self._abitem.update_webif([self.id, 'actions_enter'], self.__actions_enter.dict_actions('actions_enter', self.id)) if self.__actions_stay.count() > 0: self._log_info("Actions to perform on stay:") self._log_increase_indent() self.__actions_stay.write_to_logger() self._log_decrease_indent() - self._abitem.update_webif([self.id, 'actions_stay'], self.__actions_stay.dict_actions('actions_stay', self.id)) if self.__actions_enter_or_stay.count() > 0: self._log_info("Actions to perform on enter or stay:") self._log_increase_indent() self.__actions_enter_or_stay.write_to_logger() self._log_decrease_indent() - self._abitem.update_webif([self.id, 'actions_enter_or_stay'], self.__actions_enter_or_stay.dict_actions('actions_enter_or_stay', self.id)) if self.__actions_leave.count() > 0: self._log_info("Actions to perform on leave (instant leave: {})", self._abitem.instant_leaveaction) self._log_increase_indent() self.__actions_leave.write_to_logger() self._log_decrease_indent() - self._abitem.update_webif([self.id, 'actions_leave'], self.__actions_leave.dict_actions('actions_leave', self.id)) + + if self.__actions_pass.count() > 0: + self._log_info("Actions to perform on pass / transit:") + self._log_increase_indent() + self.__actions_pass.write_to_logger() + self._log_decrease_indent() + self._abitem.set_variable("current.state_name", "") self._abitem.set_variable("current.state_id", "") self._log_decrease_indent() @@ -291,20 +312,13 @@ def run_enter(self, allow_item_repeat: bool): _key_leave = ['{}'.format(self.id), 'leave'] _key_stay = ['{}'.format(self.id), 'stay'] _key_enter = ['{}'.format(self.id), 'enter'] + _key_pass = ['{}'.format(self.id), 'pass'] self._abitem.update_webif(_key_leave, False) self._abitem.update_webif(_key_stay, False) self._abitem.update_webif(_key_enter, True) + self._abitem.update_webif(_key_pass, False) self.__actions_enter.execute(False, allow_item_repeat, self, self.__actions_enter_or_stay) self._log_decrease_indent(50) - self._log_increase_indent() - self._log_debug("Update web interface enter {}", self.id) - self._log_increase_indent() - if self.__actions_enter_or_stay.count() > 0: - self._abitem.update_webif([self.id, 'actions_enter_or_stay'], self.__actions_enter_or_stay.dict_actions('actions_enter_or_stay', self.id)) - if self.__actions_enter.count() > 0: - self._abitem.update_webif([self.id, 'actions_enter'], self.__actions_enter.dict_actions('actions_enter', self.id)) - self._log_decrease_indent() - self._log_decrease_indent() # run actions when staying at the state # item_allow_repeat: Is repeating actions generally allowed for the item? @@ -313,20 +327,29 @@ def run_stay(self, allow_item_repeat: bool): _key_leave = ['{}'.format(self.id), 'leave'] _key_stay = ['{}'.format(self.id), 'stay'] _key_enter = ['{}'.format(self.id), 'enter'] + _key_pass = ['{}'.format(self.id), 'pass'] self._abitem.update_webif(_key_leave, False) self._abitem.update_webif(_key_stay, True) self._abitem.update_webif(_key_enter, False) + self._abitem.update_webif(_key_pass, False) self.__actions_stay.execute(True, allow_item_repeat, self, self.__actions_enter_or_stay) self._log_decrease_indent(50) + + # run actions when passing the state + # item_allow_repeat: Is repeating actions generally allowed for the item? + def run_pass(self, is_repeat: bool, allow_item_repeat: bool): + self._log_info("Passing state {}, running pass actions.", self.id) self._log_increase_indent() - self._log_debug("Update web interface stay {}", self.id) - self._log_increase_indent() - if self.__actions_enter_or_stay.count() > 0: - self._abitem.update_webif([self.id, 'actions_enter_or_stay'], self.__actions_enter_or_stay.dict_actions('actions_enter_or_stay', self.id)) - if self.__actions_stay.count() > 0: - self._abitem.update_webif([self.id, 'actions_stay'], self.__actions_stay.dict_actions('actions_stay', self.id)) - self._log_decrease_indent() - self._log_decrease_indent() + _key_leave = ['{}'.format(self.id), 'leave'] + _key_stay = ['{}'.format(self.id), 'stay'] + _key_enter = ['{}'.format(self.id), 'enter'] + _key_pass = ['{}'.format(self.id), 'pass'] + self._abitem.update_webif(_key_leave, False) + self._abitem.update_webif(_key_stay, False) + self._abitem.update_webif(_key_enter, False) + self._abitem.update_webif(_key_pass, True) + self.__actions_pass.execute(is_repeat, allow_item_repeat, self) + self._log_decrease_indent(50) # run actions when leaving the state # item_allow_repeat: Is repeating actions generally allowed for the item? @@ -337,13 +360,6 @@ def run_leave(self, allow_item_repeat: bool): self._abitem.update_webif(_key_leave, False) self.__actions_leave.execute(False, allow_item_repeat, self) self._log_decrease_indent(50) - self._log_increase_indent() - self._log_debug("Update web interface leave {}", self.id) - self._log_increase_indent() - if self.__actions_leave.count() > 0: - self._abitem.update_webif([self.id, 'actions_leave'], self.__actions_leave.dict_actions('actions_leave', self.id)) - self._log_decrease_indent() - self._log_decrease_indent() def refill(self): cond_refill = not self.__use.is_empty() and ("eval" in self.__use.get_type() or "item" in self.__use.get_type()) @@ -379,13 +395,16 @@ def update_name(self, item_state, recursion_depth=0): # use item name as state name if "se_name" in item_state.conf: self.__text.set_from_attr(item_state, "se_name") + self._log_develop("Updated name of state {} to {} based on se_name.", item_state, self.__name) elif str(item_state) != item_state.property.path or (self.__name == "" and recursion_depth == 0): _name = str(item_state).split('.')[-1] self.__text.set(_name) + self._log_develop("Updated name of state {} to {} based on item name (recursion_depth = {}).", + item_state, self.__name, recursion_depth) elif self.__text.is_empty() and recursion_depth == 0: self.__text.set("value:" + self.__name) + self._log_develop("Set name of state {} to {} as it was empty.", item_state, self.__name) self.__name = self.text - self._log_develop("Updated name of state {} to {}.", item_state, self.__name) return self.__name def __fill_list(self, item_states, recursion_depth, se_use=None, use=None): @@ -402,7 +421,6 @@ def __fill_list(self, item_states, recursion_depth, se_use=None, use=None): self.__use_done.append(element) self.__fill(element, recursion_depth, se_use, use) - def __initialize_se_use(self, state, recursion_depth): # Import data from other item if attribute "use" is found if isinstance(state, SeState): @@ -486,9 +504,8 @@ def __initialize_se_use(self, state, recursion_depth): _configorigvalue[i] in item)): _issue_list = [item for key, value in _issues.items() if value for item in value] self._log_warning("se_use {} points to invalid item. Ignoring.", _configorigvalue[i]) - self._abitem.update_issues('config', {state.id: - {'issue': _issue_list, - 'attribute': 'se_use', 'origin': state_type}}) + self._abitem.update_issues('config', {state.id: {'issue': _issue_list, + 'attribute': 'se_use', 'origin': state_type}}) self.__use_ignore_list.append(_configorigvalue[i]) _path = None elif _returntype[i] in ['item', 'eval']: @@ -502,9 +519,8 @@ def __initialize_se_use(self, state, recursion_depth): _issue_list = [item for key, value in _issues.items() if value for item in value] self._log_warning("se_use {} defined by invalid item/eval. Ignoring.", _path) - self._abitem.update_issues('config', {state.id: - {'issue': _issue_list, - 'attribute': 'se_use', 'origin': state_type}}) + self._abitem.update_issues('config', {state.id: {'issue': _issue_list, + 'attribute': 'se_use', 'origin': state_type}}) self.__use_ignore_list.append(_path) _path = None if _path is None: @@ -557,51 +573,51 @@ def update_unused(used_attributes, attrib_type, attrib_name): used_attributes[nested_entry].update({attrib_type: attrib_name}) used_attributes[nested_entry].update(nested_dict) self.__used_attributes.update(used_attributes) + self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) + + def update_action_status(actn_type, action_status): + def filter_issues(input_dict): + return { + key: {sub_key: sub_value for sub_key, sub_value in value.items() if + sub_value.get('issue') not in (None, [], [None])} + for key, value in input_dict.items() + } - def update_action_status(action_status, actiontype): if action_status is None: return action_status = StateEngineTools.flatten_list(action_status) if isinstance(action_status, list): for e in action_status: - update_action_status(e, actiontype) + update_action_status(actn_type, e) return for itm, dct in action_status.items(): - if itm not in self.__action_status: - self.__action_status.update({itm: dct}) + if itm not in self.__action_status[actn_type]: + self.__action_status[actn_type].update({itm: dct}) for (itm, dct) in action_status.items(): issues = dct.get('issue') + attributes = dct.get('attribute') if issues: if isinstance(issues, list): - self.__action_status[itm]['issue'].extend( - [issue for issue in issues if issue not in self.__action_status[itm]['issue']]) - origin_list = self.__action_status[itm].get('issueorigin', []) - new_list = origin_list.copy() - for i, listitem in enumerate(origin_list): - entry_unknown = {'state': 'unknown', 'action': listitem.get('action')} - entry_unknown2 = {'state': 'unknown', 'action': 'unknown'} - entry_notype = {'state': self.id, 'action': listitem.get('action')} - entry_final = {'state': self.id, 'action': listitem.get('action'), 'type': actiontype} - - if listitem in (entry_unknown, entry_unknown2, entry_notype): - new_list[i] = entry_final - elif entry_final not in origin_list: - new_list.append(entry_final) - - self.__action_status[itm]['issueorigin'] = new_list - - filtered_dict = {} - for key, nested_dict in self.__action_status.items(): - filtered_dict.update({key: {}}) - filtered_dict[key].update({'used in': actiontype}) - filtered_dict[key].update(nested_dict) - #self._log_develop("Add {} to used {}", key, filtered_dict) - self.__used_attributes = copy(filtered_dict) - filtered_dict = {key: value for key, value in self.__action_status.items() - if value.get('issue') not in [[], [None], None]} - self.__action_status = filtered_dict - #self._log_develop("Updated action status: {}, updated used {}", self.__action_status, self.__used_attributes) + for i, issue in enumerate(issues): + if issue not in self.__action_status[actn_type][itm]['issue']: + self.__action_status[actn_type][itm]['issue'].append(issue) + self.__action_status[actn_type][itm]['attribute'].append(attributes[i]) + + flattened_dict = {} + for key, action_type_dict in self.__action_status.items(): + # Iterate through the inner dictionaries + for inner_key, nested_dict in action_type_dict.items(): + # Initialize the entry in the flattened dictionary + if inner_key not in flattened_dict: + flattened_dict[inner_key] = {} + # Add 'used in' and update with existing data + flattened_dict[inner_key]['used in'] = key + flattened_dict[inner_key].update(nested_dict) + + self.__used_attributes = deepcopy(flattened_dict) + self.__action_status = filter_issues(self.__action_status) + self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) if isinstance(state, SeState): item_state = state.state_item @@ -610,11 +626,12 @@ def update_action_status(action_status, actiontype): self._log_develop("Fill state {} type {}, called by {}, recursion {}", item_state, type(item_state), se_use, recursion_depth) if se_use == "reinit": self._log_develop("Resetting conditions and actions at re-init use is {}", use) - self.__conditions.reset() + self.__conditionsets.reset() self.__actions_enter_or_stay.reset() self.__actions_enter.reset() self.__actions_stay.reset() self.__actions_leave.reset() + self.__actions_pass.reset() self.__use_done = [] use = self.__use.get() @@ -625,13 +642,12 @@ def update_action_status(action_status, actiontype): use = StateEngineTools.flatten_list(use) self.__fill_list(use, recursion_depth, se_use, use) # Get action sets and condition sets - self._log_develop("Use is {}", use) parent_item = item_state.return_parent() if parent_item == Items.get_instance(): parent_item = None child_items = item_state.return_children() _conditioncount = 0 - _action_counts = {"enter": 0, "stay": 0, "enter_or_stay": 0, "leave": 0} + _action_counts = {"enter": 0, "stay": 0, "enter_or_stay": 0, "leave": 0, "pass": 0} _unused_attributes = {} _used_attributes = {} _action_status = {} @@ -641,7 +657,7 @@ def update_action_status(action_status, actiontype): try: if child_name == "enter" or child_name.startswith("enter_"): _conditioncount += 1 - _unused_attributes, _used_attributes = self.__conditions.update(child_name, child_item, parent_item) + _unused_attributes, _used_attributes = self.__conditionsets.update(child_name, child_item, parent_item) self.__unused_attributes = copy(_unused_attributes) self.__used_attributes = copy(_used_attributes) for item in self.__unused_attributes.keys(): @@ -670,20 +686,23 @@ def update_action_status(action_status, actiontype): child_name = StateEngineTools.get_last_part_of_item_id(child_item) try: action_mapping = { - "on_enter": ("enter", self.__actions_enter), - "on_stay": ("stay", self.__actions_stay), - "on_enter_or_stay": ("enter_or_stay", self.__actions_enter_or_stay), - "on_leave": ("leave", self.__actions_leave) + "on_enter": ("enter", "actions_enter", self.__actions_enter), + "on_stay": ("stay", "actions_stay", self.__actions_stay), + "on_enter_or_stay": ("enter_or_stay", "actions_enter_or_stay", self.__actions_enter_or_stay), + "on_leave": ("leave", "actions_leave", self.__actions_leave), + "on_pass": ("pass", "actions_pass", self.__actions_pass) } if child_name in action_mapping: - action_name, action_method = action_mapping[child_name] + action_name, action_type, action_method = action_mapping[child_name] for attribute in child_item.conf: - self._log_develop("Filling state with {} action named {}", child_name, attribute) - _action_counts[action_name] += 1 - _, _action_status = action_method.update(attribute, child_item.conf[attribute]) + self._log_develop("Filling state with {} action named {} for state {} with config {}", child_name, attribute, state.id, child_item.conf) + action_method.update_action_details(self, action_type) + _result = action_method.update(attribute, child_item.conf.get(attribute)) + _action_counts[action_name] += _result[0] if _result else 0 + _action_status = _result[1] if _action_status: - update_action_status(_action_status, action_name) + update_action_status(action_name, _action_status) self._abitem.update_action_status(self.__action_status) update_unused(_used_attributes, 'action', child_name) @@ -693,48 +712,53 @@ def update_action_status(action_status, actiontype): self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) # Actions defined directly in the item go to "enter_or_stay" for attribute in item_state.conf: - _result = self.__actions_enter_or_stay.update(attribute, item_state.conf[attribute]) + self.__actions_enter_or_stay.update_action_details(self, "actions_enter_or_stay") + _result = self.__actions_enter_or_stay.update(attribute, item_state.conf.get(attribute)) _action_counts["enter_or_stay"] += _result[0] if _result else 0 _action_status = _result[1] if _action_status: - update_action_status(_action_status, 'enter_or_stay') + update_action_status("enter_or_stay", _action_status) self._abitem.update_action_status(self.__action_status) - _total_actioncount = _action_counts["enter"] + _action_counts["stay"] + _action_counts["enter_or_stay"] + _action_counts["leave"] + _total_actioncount = _action_counts["enter"] + _action_counts["stay"] + _action_counts["enter_or_stay"] + _action_counts["leave"] + _action_counts["pass"] self.update_name(item_state, recursion_depth) # Complete condition sets and actions at the end if recursion_depth == 0: - self.__conditions.complete(self, use) - _action_status = self.__actions_enter.complete(self, self.__conditions.evals_items, use) + self.__conditionsets.complete(self, use) + _action_status = self.__actions_enter.complete(self.__conditionsets.evals_items, use) + if _action_status: + update_action_status("enter", _action_status) + self._abitem.update_action_status(self.__action_status) + _action_status = self.__actions_stay.complete(self.__conditionsets.evals_items, use) if _action_status: - update_action_status(_action_status, 'enter') + update_action_status("stay", _action_status) self._abitem.update_action_status(self.__action_status) - _action_status = self.__actions_stay.complete(self, self.__conditions.evals_items, use) + _action_status = self.__actions_enter_or_stay.complete(self.__conditionsets.evals_items, use) if _action_status: - update_action_status(_action_status, 'stay') + update_action_status("enter_or_stay", _action_status) self._abitem.update_action_status(self.__action_status) - _action_status = self.__actions_enter_or_stay.complete(self, self.__conditions.evals_items, use) + _action_status = self.__actions_pass.complete(self.__conditionsets.evals_items, use) if _action_status: - update_action_status(_action_status, 'enter_or_stay') + update_action_status("pass", _action_status) self._abitem.update_action_status(self.__action_status) - _action_status = self.__actions_leave.complete(self, self.__conditions.evals_items, use) + _action_status = self.__actions_leave.complete(self.__conditionsets.evals_items, use) if _action_status: - update_action_status(_action_status, 'leave') + update_action_status("leave", _action_status) self._abitem.update_action_status(self.__action_status) self._abitem.update_action_status(self.__action_status) self._abitem.update_attributes(self.__unused_attributes, self.__used_attributes) - _summary = "{} on_enter, {} on_stay , {} on_enter_or_stay, {} on_leave" + _summary = "{} on_enter, {} on_stay , {} on_enter_or_stay, {} on_leave, {} on_pass" if self.__action_status: _ignore_list = [entry for entry in self.__action_status if self.__action_status[entry].get('ignore') is True] if _ignore_list: self._log_info("Ignored {} action(s) due to errors: {}", len(_ignore_list), _ignore_list) if se_use is not None: self._log_debug("Added {} action(s) based on se_use {}. " + _summary, _total_actioncount, se_use, - _action_counts["enter"], _action_counts["stay"], _action_counts["enter_or_stay"], _action_counts["leave"]) + _action_counts["enter"], _action_counts["stay"], _action_counts["enter_or_stay"], _action_counts["leave"], _action_counts["pass"]) self._log_debug("Added {} condition set(s) based on se_use: {}", _conditioncount, se_use) else: self._log_debug("Added {} action(s) based on item configuration: " + _summary, _total_actioncount, - _action_counts["enter"], _action_counts["stay"], _action_counts["enter_or_stay"], _action_counts["leave"]) + _action_counts["enter"], _action_counts["stay"], _action_counts["enter_or_stay"], _action_counts["leave"], _action_counts["pass"]) self._log_debug("Added {} condition set(s) based on item configuration", _conditioncount) diff --git a/stateengine/StateEngineStructs.py b/stateengine/StateEngineStructs.py index de630d0ab..98a6afeb2 100755 --- a/stateengine/StateEngineStructs.py +++ b/stateengine/StateEngineStructs.py @@ -25,7 +25,7 @@ def create(_abitem, struct): - _find_result = next((item for item in __allstructs if item["name"] == struct), False) + _find_result = next((item for item in __allstructs if item["name"] == struct), {}) if not _find_result: created_struct = StateEngineStruct.SeStructMain(_abitem, struct, global_struct) __allstructs.append({'name': struct, 'struct': created_struct}) diff --git a/stateengine/StateEngineTools.py b/stateengine/StateEngineTools.py index a29968b0a..b423c9c6a 100755 --- a/stateengine/StateEngineTools.py +++ b/stateengine/StateEngineTools.py @@ -317,14 +317,30 @@ def partition_strip(value, splitchar): # value: list as string # returns: list or original value def convert_str_to_list(value, force=True): - if isinstance(value, str) and (value[:1] == '[' and value[-1:] == ']'): - value = value.strip("[]") + if isinstance(value, str): + orig_value = value + value = value.strip() + if value.startswith('[') and value.endswith(']'): + value = value[1:-1].strip() + else: + return orig_value + if isinstance(value, str) and "," in value: try: - elements = re.findall(r"'([^']+)'|([^,]+)", value) - flattened_elements = [element[0] if element[0] else element[1] for element in elements] - formatted_str = "[" + ", ".join( - ["'" + element.strip(" '\"") + "'" for element in flattened_elements]) + "]" + elements = re.findall(r"'([^']*)'|\"([^\"]*)\"|([^,]+)", value) + flattened_elements = [element[0] if element[0] else (element[1] if element[1] else element[2].strip()) for + element in elements] + flattened_elements = [element.strip() for element in flattened_elements] + formatted_elements = [] + for element in flattened_elements: + element = element.strip(" '\"") + if "'" in element: + formatted_elements.append(f'"{element}"') # If element contains single quote, wrap in double quotes + elif '"' in element: + formatted_elements.append(f"'{element}'") # If element contains double quote, wrap in single quotes + else: + formatted_elements.append(f"'{element}'") # Default case, wrap in single quotes + formatted_str = "[" + ", ".join(formatted_elements) + "]" return literal_eval(formatted_str) except Exception as ex: raise ValueError("Problem converting string to list: {}".format(ex)) diff --git a/stateengine/StateEngineValue.py b/stateengine/StateEngineValue.py index bfc2c87b8..3c0678e6a 100755 --- a/stateengine/StateEngineValue.py +++ b/stateengine/StateEngineValue.py @@ -54,7 +54,7 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): self.__varname = None self.__template = None self.__issues = [] - self.__get_issues = {'cast_item': [], 'eval': [], 'regex': [], 'struct': [], 'var': [], 'item': []} + self.__get_issues = {'cast_item': [], 'cast_regex': [], 'eval': [], 'regex': [], 'struct': [], 'var': [], 'item': []} self._additional_sources = [] self.itemsApi = Items.get_instance() self.__itemClass = Item @@ -64,18 +64,28 @@ def __init__(self, abitem, name, allow_value_list=False, value_type=None): self.__valid_valuetypes = ["value", "regex", "eval", "var", "item", "template", "struct"] if value_type == "str": self.__cast_func = StateEngineTools.cast_str + self.__cast = "str" + elif value_type == "regex": + self.__cast_func = self.cast_regex + self.__cast = "regex" elif value_type == "num": self.__cast_func = StateEngineTools.cast_num + self.__cast = "num" elif value_type == "item": self.__cast_func = self.cast_item + self.__cast = "item" elif value_type == "bool": self.__cast_func = StateEngineTools.cast_bool + self.__cast = "bool" elif value_type == "time": self.__cast_func = StateEngineTools.cast_time + self.__cast = "time" elif value_type == "list": self.__cast_func = StateEngineTools.cast_list + self.__cast = "list" else: self.__cast_func = None + self.__cast = None def __repr__(self): return "{}".format(self.get()) @@ -115,7 +125,7 @@ def set_from_attr(self, item, attribute_name, default_value=None, reset=True, at value = default_value _using_default = True self._log_develop("Processing value from attribute name {0}, reset {1}, type {2}: using default value {3}", - attribute_name, reset, value, attr_type) + attribute_name, reset, attr_type, value) value_list = [] if value is not None and isinstance(value, list) and attr_type is not None: for i, entry in enumerate(value): @@ -136,7 +146,8 @@ def set_from_attr(self, item, attribute_name, default_value=None, reset=True, at self._log_develop("Setting value {0}, attribute name {1}, reset {2}, type {3}", value, attribute_name, reset, attr_type) _returnvalue, _returntype, _issue, _origvalue = self.set(value, attribute_name, reset) - self._log_develop("Set from attribute returnvalue {}, returntype {}, issue {}, original {}", _returnvalue, _returntype, _issue, _origvalue) + self._log_develop("Set from attribute returnvalue {}, returntype {}, issue {}, original {}", + _returnvalue, _returntype, _issue, _origvalue) return _returnvalue, _returntype, _using_default, _issue, _origvalue def _set_additional(self, _additional_sources): @@ -153,6 +164,7 @@ def __resetvalue(self): self.__struct = None self.__varname = None self.__template = None + self.__cast = None self._additional_sources = [] self.__listorder = [] self.__type_listorder = [] @@ -161,11 +173,15 @@ def __resetvalue(self): # Set value # value: string indicating value or source of value # name: name of object ("time" is being handled differently) - def set(self, value, name="", reset=True, copyvalue=True): + def set(self, value, name="", reset=True, copyvalue=True, returnvalue=False): + self.__issues = [] if copyvalue is True: value = copy.copy(value) if reset: self.__resetvalue() + if name: + self.__cast = name + returnvalues = [] if isinstance(value, list): source = [] field_value = [] @@ -181,7 +197,8 @@ def set(self, value, name="", reset=True, copyvalue=True): f = val source.append(s) field_value.append(f) - self.__listorder.append("{}:{}".format(s, f)) + if not returnvalue: + self.__listorder.append("{}:{}".format(s, f)) if field_value[i] == "": source[i] = "value" field_value[i] = value[i] @@ -196,10 +213,11 @@ def set(self, value, name="", reset=True, copyvalue=True): "will be handled the same as the item type, e.g. string, bool, etc.", _issue, self.__valid_valuetypes, field_value[i]) source[i] = "value" - self.__type_listorder.append(source[i]) - self.__orig_listorder.append(val) - if source[i] == "value": - self.__listorder[i] = value[i] + if not returnvalue: + self.__type_listorder.append(source[i]) + self.__orig_listorder.append(val) + if source[i] == "value": + self.__listorder[i] = value[i] if source[i] == "template": if self.__template is None: self.__template = [] @@ -208,11 +226,14 @@ def set(self, value, name="", reset=True, copyvalue=True): if _template is not None: try: source[i], field_value[i] = StateEngineTools.partition_strip(_template, ":") - if val in self.__listorder and field_value[i] in self._abitem.templates: + if not returnvalue and val in self.__listorder and field_value[i] in self._abitem.templates: self.__listorder[self.__listorder.index(val)] = self._abitem.templates.get(field_value[i]) + elif returnvalue and val in returnvalues and field_value[i] in self._abitem.templates: + returnvalues[returnvalues.index(val)] = self._abitem.templates.get(field_value[i]) except Exception as ex: self._abitem.updatetemplates(field_value[i], None) - self.__listorder = [i for i in self.__listorder if i != val] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != val] self._log_warning("Removing template {}: {}", field_value[i], ex) val, field_value[i], source[i] = None, None, None else: @@ -220,7 +241,8 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue not in self.__issues: self.__issues.append(_issue) self._log_warning(_issue) - self.__listorder = [i for i in self.__listorder if i != val] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != val] source[i], field_value[i], val = None, None, None try: if isinstance(self.__template, list) and len(self.__template) == 1: @@ -230,17 +252,21 @@ def set(self, value, name="", reset=True, copyvalue=True): elif isinstance(value, str): source, field_value = StateEngineTools.partition_strip(value, ":") - self.__listorder.append("{}{}{}".format(source, ":" if field_value else "", field_value)) + if not returnvalue: + self.__listorder.append("{}{}{}".format(source, ":" if field_value else "", field_value)) if source == "template": self.__template = field_value _template = self._abitem.templates.get(self.__template) if _template is not None: try: source, field_value = StateEngineTools.partition_strip(_template, ":") - if value in self.__listorder and field_value in self._abitem.templates: + if not returnvalue and value in self.__listorder and field_value in self._abitem.templates: self.__listorder[self.__listorder.index(value)] = self._abitem.templates[self.__template] + elif returnvalue and value in returnvalues and field_value in self._abitem.templates: + returnvalues[returnvalues.index(value)] = self._abitem.templates[self.__template] except Exception as ex: - self.__listorder = [i for i in self.__listorder if i != value] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != value] source, field_value, value = None, None, None self._abitem.updatetemplates(self.__template, None) self._log_warning("Removing template {}: {}", self.__template, ex) @@ -249,7 +275,8 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue not in self.__issues: self.__issues.append(_issue) self._log_warning(_issue) - self.__listorder = [i for i in self.__listorder if i != value] + if not returnvalue: + self.__listorder = [i for i in self.__listorder if i != value] source, field_value, value = None, None, None try: cond1 = source.lstrip('-').replace('.', '', 1).isdigit() @@ -257,7 +284,7 @@ def set(self, value, name="", reset=True, copyvalue=True): except Exception: cond1 = False cond2 = False - if name == "time" and cond1 and cond2: + if (name == "time" or self.__cast == "time") and cond1 and cond2: field_value = value source = "value" elif field_value == "": @@ -271,10 +298,11 @@ def set(self, value, name="", reset=True, copyvalue=True): "will be handled the same as the item type, e.g. string, bool, etc.", _issue, self.__valid_valuetypes, field_value) source = "value" - if source == "value": - self.__listorder = [field_value] - self.__type_listorder.append(source) - self.__orig_listorder.append(value) + if not returnvalue: + if source == "value": + self.__listorder = [field_value] + self.__type_listorder.append(source) + self.__orig_listorder.append(value) else: source = "value" field_value = value @@ -307,14 +335,15 @@ def set(self, value, name="", reset=True, copyvalue=True): except Exception: cond1 = False cond2 = False - if name == "time" and cond1 and cond2: + if (name == "time" or self.__cast == "time") and cond1 and cond2: field_value[i] = '{}:{}'.format(source[i], field_value[i]) s = "value" elif field_value[i] == "": field_value[i] = s s = "value" - self.__value = [] if self.__value is None else [self.__value] if not isinstance(self.__value, - list) else self.__value + if not returnvalue: + self.__value = [] if self.__value is None else [self.__value] if not isinstance(self.__value, + list) else self.__value if s == "value": cond3 = isinstance(field_value[i], str) and field_value[i].lstrip('-').replace('.', '', 1).isdigit() if cond3: @@ -327,10 +356,14 @@ def set(self, value, name="", reset=True, copyvalue=True): _value, _issue = self.__do_cast(field_value[i]) if _issue not in [[], None, [None], self.__issues]: self.__issues.append(_issue) - self.__value.append(_value) + if not returnvalue: + self.__value.append(_value) + else: + returnvalues.append(_value) else: self.__value.append(None) - self.__item = [] if self.__item is None else [self.__item] if not isinstance(self.__item, list) else self.__item + if not returnvalue: + self.__item = [] if self.__item is None else [self.__item] if not isinstance(self.__item, list) else self.__item if s == "item": _item, _issue = self._abitem.return_item(field_value[i]) if _issue not in [[], None, [None], self.__issues]: @@ -338,35 +371,47 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) self.__issues.append(_issue) - self.__item.append(None if s != "item" else self.__absolute_item(_item, field_value[i])) - self.__eval = [] if self.__eval is None else [self.__eval] if not isinstance(self.__eval, list) else self.__eval - self.__eval.append(None if s != "eval" else field_value[i]) - self.__regex = [] if self.__regex is None else [self.__regex] if not isinstance(self.__regex, list) else self.__regex - self.__regex.append(None if s != "regex" else field_value[i]) - self.__struct = [] if self.__struct is None else [self.__struct] if not isinstance(self.__struct, list) else self.__struct - self.__struct.append(None if s != "struct" else StateEngineStructs.create(self._abitem, field_value[i])) - self.__varname = [] if self.__varname is None else [self.__varname] if not isinstance(self.__varname, list) else self.__varname - self.__varname.append(None if s != "var" else field_value[i]) - - if self.__item: - self.__item = [i for i in self.__item if i is not None] - self.__item = self.__item[0] if len(self.__item) == 1 else None if len(self.__item) == 0 else self.__item - if self.__eval: - self.__eval = [i for i in self.__eval if i is not None] - self.__eval = self.__eval[0] if len(self.__eval) == 1 else None if len(self.__eval) == 0 else self.__eval - if self.__regex: - self.__regex = [i for i in self.__regex if i is not None] - self.__regex = self.__regex[0] if len(self.__regex) == 1 else None if len(self.__regex) == 0 else self.__regex - if self.__struct: - self.__struct = [i for i in self.__struct if i is not None] - self.__struct = None if len(self.__struct) == 0 else self.__struct - if self.__varname: - self.__varname = [i for i in self.__varname if i is not None] - self.__varname = self.__varname[0] if len(self.__varname) == 1 else None if len(self.__varname) == 0 else self.__varname - if self.__value: - self.__value = [i for i in self.__value if i is not None] - self.__value = self.__value[0] if len(self.__value) == 1 else None if len(self.__value) == 0 else self.__value - + returnvalues.append(_item) + if not returnvalue: + self.__item.append(None if s != "item" else self.__absolute_item(_item, field_value[i])) + self.__eval = [] if self.__eval is None else [self.__eval] if not isinstance(self.__eval, list) else self.__eval + self.__eval.append(None if s != "eval" else field_value[i]) + self.__regex = [] if self.__regex is None else [self.__regex] if not isinstance(self.__regex, list) else self.__regex + self.__regex.append(None if s != "regex" else field_value[i]) + self.__struct = [] if self.__struct is None else [self.__struct] if not isinstance(self.__struct, list) else self.__struct + self.__struct.append(None if s != "struct" else StateEngineStructs.create(self._abitem, field_value[i])) + self.__varname = [] if self.__varname is None else [self.__varname] if not isinstance(self.__varname, list) else self.__varname + self.__varname.append(None if s != "var" else field_value[i]) + else: + if s == "item": + returnvalues.append(self.__get_from_item(field_value[i])) + elif s == "struct": + returnvalues.append(self.__get_from_struct(field_value[i])) + elif s == "eval": + returnvalues.append(self.__get_eval(field_value[i])) + elif s == "regex": + returnvalues.append(self.__get_from_regex(field_value[i])) + elif s == "var": + returnvalues.append(self.__get_from_variable(field_value[i])) + if not returnvalue: + if self.__item: + self.__item = [i for i in self.__item if i is not None] + self.__item = self.__item[0] if len(self.__item) == 1 else None if len(self.__item) == 0 else self.__item + if self.__eval: + self.__eval = [i for i in self.__eval if i is not None] + self.__eval = self.__eval[0] if len(self.__eval) == 1 else None if len(self.__eval) == 0 else self.__eval + if self.__regex: + self.__regex = [i for i in self.__regex if i is not None] + self.__regex = self.__regex[0] if len(self.__regex) == 1 else None if len(self.__regex) == 0 else self.__regex + if self.__struct: + self.__struct = [i for i in self.__struct if i is not None] + self.__struct = None if len(self.__struct) == 0 else self.__struct + if self.__varname: + self.__varname = [i for i in self.__varname if i is not None] + self.__varname = self.__varname[0] if len(self.__varname) == 1 else None if len(self.__varname) == 0 else self.__varname + if self.__value: + self.__value = [i for i in self.__value if i is not None] + self.__value = self.__value[0] if len(self.__value) == 1 else None if len(self.__value) == 0 else self.__value else: if source == "item": _item, _issue = self._abitem.return_item(field_value) @@ -375,11 +420,24 @@ def set(self, value, name="", reset=True, copyvalue=True): if _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) self.__issues.append(_issue) - self.__item = None if source != "item" else self.__absolute_item(_item, field_value) - self.__eval = None if source != "eval" else field_value - self.__regex = None if source != "regex" else field_value - self.__struct = None if source != "struct" else StateEngineStructs.create(self._abitem, field_value) - self.__varname = None if source != "var" else field_value + returnvalues.append(_item) + if not returnvalue: + self.__item = None if source != "item" else self.__absolute_item(_item, field_value) + self.__eval = None if source != "eval" else field_value + self.__regex = None if source != "regex" else field_value + self.__struct = None if source != "struct" else StateEngineStructs.create(self._abitem, field_value) + self.__varname = None if source != "var" else field_value + else: + if source == "item": + returnvalues.append(self.__get_from_item(field_value)) + elif source == "struct": + returnvalues.append(self.__get_from_struct(field_value)) + elif source == "eval": + returnvalues.append(self.__get_eval(field_value)) + elif source == "regex": + returnvalues.append(self.__get_from_regex(field_value)) + elif source == "var": + returnvalues.append(self.__get_from_variable(field_value)) if source == "value": if isinstance(field_value, list) and not self.__allow_value_list: raise ValueError("{0}: value_in is not allowed, problem with {1}. Allowed = {2}".format( @@ -391,16 +449,24 @@ def set(self, value, name="", reset=True, copyvalue=True): field_value = True elif isinstance(field_value, str) and field_value.lower() in ['false', 'no']: field_value = False - self.__value, _issue = self.__do_cast(field_value) + if not returnvalue: + self.__value, _issue = self.__do_cast(field_value) + else: + val, _issue = self.__do_cast(field_value) + returnvalues.append(val) if _issue not in [[], None, [None], self.__issues]: self.__issues.append(_issue) else: self.__value = None self.__issues = StateEngineTools.flatten_list(self.__issues) + del value + if returnvalue: + returnvalues = StateEngineTools.flatten_list(returnvalues) + returnvalues = returnvalues[0] if len(returnvalues) == 1 else None if len(returnvalues) == 0 else returnvalues + return returnvalues, self.__issues self.__listorder = StateEngineTools.flatten_list(self.__listorder) self.__type_listorder = StateEngineTools.flatten_list(self.__type_listorder) self.__orig_listorder = StateEngineTools.flatten_list(self.__orig_listorder) - del value return self.__listorder, self.__type_listorder, self.__issues, self.__orig_listorder # Set cast function @@ -446,10 +512,14 @@ def get(self, default=None, originalorder=True): else: return returnvalues - def get_for_webif(self): - returnvalues = self.get() + def get_for_webif(self, value=None): + if value is None: + returnvalues = self.get() + else: + returnvalues = value returnvalues = self.__varname if returnvalues == '' else returnvalues - return str(returnvalues) + returnvalues = str(returnvalues) + return returnvalues def get_type(self): if len(self.__listorder) <= 1: @@ -470,6 +540,7 @@ def get_type(self): # Write condition to logger def write_to_logger(self): + returnvalues = [] if self.__template is not None: self._log_info("{0}: Using template(s) {1}", self.__name, self.__template) if self.__value is not None: @@ -479,6 +550,7 @@ def write_to_logger(self): self._log_debug("{0}: {1} ({2})", self.__name, i, type(i)) else: self._log_debug("{0}: {1} ({2})", self.__name, self.__value, type(self.__value)) + returnvalues.append(self.__value) if self.__regex is not None: if isinstance(self.__regex, list): for i in self.__regex: @@ -486,6 +558,7 @@ def write_to_logger(self): self._log_debug("{0} from regex: {1}", self.__name, i) else: self._log_debug("{0} from regex: {1}", self.__name, self.__regex) + returnvalues.append(f"regex:{self.__regex}") if self.__struct is not None: if isinstance(self.__struct, list): for i in self.__struct: @@ -494,22 +567,30 @@ def write_to_logger(self): else: self._log_debug("{0} from struct: {1}", self.__name, self.__struct.property.path) + returnvalues.append(self.__struct) if self.__item is not None: _original_listorder = self.__listorder.copy() + items = [] if isinstance(self.__item, list): for i, item in enumerate(self.__item): if item is not None: self._log_debug("{0} from item: {1}", self.__name, item.property.path) - self._log_debug("Currently item results in {}", self.__get_from_item()[i]) + current = self.__get_from_item()[i] + items.append(current) + self._log_debug("Currently item results in {}", current) else: self._log_debug("{0} from item: {1}", self.__name, self.__item.property.path) - self._log_debug("Currently item results in {}", self.__get_from_item()) + items = self.__get_from_item() + self._log_debug("Currently item results in {}", items) self.__listorder = _original_listorder + returnvalues.append(items) if self.__eval is not None: self._log_debug("{0} from eval: {1}", self.__name, self.__eval) _original_listorder = self.__listorder.copy() - self._log_debug("Currently eval results in {}. ", self.__get_eval()) + eval_result = self.__get_eval() + self._log_debug("Currently eval results in {}. ", eval_result) self.__listorder = _original_listorder + returnvalues.append(eval_result) if self.__varname is not None: if isinstance(self.__varname, list): for i in self.__varname: @@ -517,6 +598,13 @@ def write_to_logger(self): self._log_debug("{0} from variable: {1}", self.__name, i) else: self._log_debug("{0} from variable: {1}", self.__name, self.__varname) + _original_listorder = self.__listorder.copy() + var_result = self.__get_from_variable() + self.__listorder = _original_listorder + returnvalues.append(var_result) + returnvalues = StateEngineTools.flatten_list(returnvalues) + returnvalues = returnvalues[0] if len(returnvalues) == 1 else None if len(returnvalues) == 0 else returnvalues + return returnvalues # Get Text (similar to logger text) # prefix: Prefix for text @@ -541,6 +629,31 @@ def get_text(self, prefix=None, suffix=None): value = value if suffix is None else value + suffix return value + # cast a value as regex. Throws ValueError if cast is not possible + # value: value to cast + # returns: value as regex + def cast_regex(self, value): + try: + _issue_dict = {} + _returnvalue = value + if isinstance(value, str): + try: + _returnvalue = re.compile(value, re.IGNORECASE) + except Exception as ex: + _issue = "Issue converting {} to regex: {}".format(value, ex) + _issue_dict = {str(value): _issue} + self._log_error(_issue) + if _issue_dict and _issue_dict not in self.__get_issues['cast_regex']: + self.__get_issues['cast_regex'].append(_issue_dict) + return _returnvalue + except Exception as ex: + _issue = "Can't cast {0} to regex! {1}".format(value, ex) + _issue_dict = {str(value): _issue} + if _issue_dict not in self.__get_issues['cast_regex']: + self.__get_issues['cast_regex'].append(_issue_dict) + self._log_error(_issue) + return value + # cast a value as item. Throws ValueError if cast is not possible # value: value to cast # returns: value as item or struct @@ -613,8 +726,8 @@ def __do_cast(self, value, item_id=None): try: _newvalue = element if element == 'novalue' else self.__cast_func(element) except Exception as ex: - _newvalue = None - _issue = "Problem casting element '{0}' to {1}: {2}.".format(element, self.__cast_func, ex) + _newvalue = element + _issue = "Problem casting element '{0}': {1}.".format(element, ex) self._log_warning(_issue) valuelist.append(_newvalue) if element in self.__listorder: @@ -644,7 +757,7 @@ def __do_cast(self, value, item_id=None): _issue = "You most likely forgot to prefix your expression with 'eval:'" raise ValueError(_issue) else: - _issue = "Not possible to cast '{}' because {}".format(value, ex) + _issue = "{}".format(ex) raise ValueError(_issue) if value in self.__listorder: self.__listorder[self.__listorder.index(value)] = _newvalue @@ -664,14 +777,16 @@ def __do_cast(self, value, item_id=None): self._log_debug("Original casting of {} to {} failed. New cast is now: {}.", value, self.__cast_func, type(value)) return value, _issue - return None, _issue + return value, _issue return value, _issue # Determine value by using a struct - def __get_from_struct(self): + def __get_from_struct(self, struct_get=None): values = [] - if isinstance(self.__struct, list): - for val in self.__struct: + if struct_get is None: + struct_get = self.__struct + if isinstance(struct_get, list): + for val in struct_get: if val is not None: _newvalue, _issue = self.__do_cast(val) _issue_dict = {val: _issue} @@ -681,28 +796,28 @@ def __get_from_struct(self): if 'struct:{}'.format(val.property.path) in self.__listorder: self.__listorder[self.__listorder.index('struct:{}'.format(val.property.path))] = _newvalue else: - if self.__struct is not None: - _newvalue, _issue = self.__do_cast(self.__struct) - _issue_dict = {self.__struct: _issue} + if struct_get is not None: + _newvalue, _issue = self.__do_cast(struct_get) + _issue_dict = {struct_get: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['struct']: self.__get_issues['struct'].append(_issue_dict) - if 'struct:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('struct:{}'.format(self.__struct))] = _newvalue + if 'struct:{}'.format(struct_get) in self.__listorder: + self.__listorder[self.__listorder.index('struct:{}'.format(struct_get))] = _newvalue values = _newvalue if values: return values try: - _newvalue, _issue = self.__do_cast(self.__struct) + _newvalue, _issue = self.__do_cast(struct_get) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['struct']: self.__get_issues['struct'].append(_issue_dict) - if 'struct:{}'.format(self.__struct) in self.__listorder: - self.__listorder[self.__listorder.index('struct:{}'.format(self.__struct))] = _newvalue + if 'struct:{}'.format(struct_get) in self.__listorder: + self.__listorder[self.__listorder.index('struct:{}'.format(struct_get))] = _newvalue values = _newvalue except Exception as ex: - values = self.__struct + values = struct_get _issue = "Problem while getting from struct '{0}': {1}.".format(values, ex) _issue_dict = {values: _issue} if _issue_dict not in self.__get_issues['struct']: @@ -711,28 +826,30 @@ def __get_from_struct(self): return values # Determine value by regular expression - def __get_from_regex(self): - if isinstance(self.__regex, list): + def __get_from_regex(self, regex_get=None): + if regex_get is None: + regex_get = self.__regex + if isinstance(regex_get, list): values = [] - for val in self.__regex: + for val in regex_get: _newvalue = re.compile(val, re.IGNORECASE) values.append(_newvalue) if 'regex:{}'.format(val) in self.__listorder: self.__listorder[self.__listorder.index('regex:{}'.format(val))] = _newvalue else: - _newvalue = re.compile(self.__regex, re.IGNORECASE) - if 'regex:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('regex:{}'.format(self.__regex))] = _newvalue + _newvalue = re.compile(regex_get, re.IGNORECASE) + if 'regex:{}'.format(regex_get) in self.__listorder: + self.__listorder[self.__listorder.index('regex:{}'.format(regex_get))] = _newvalue values = _newvalue if values is not None: return values try: - _newvalue = re.compile(self.__regex, re.IGNORECASE) - if 'regex:{}'.format(self.__regex) in self.__listorder: - self.__listorder[self.__listorder.index('regex:{}'.format(self.__regex))] = _newvalue + _newvalue = re.compile(regex_get, re.IGNORECASE) + if 'regex:{}'.format(regex_get) in self.__listorder: + self.__listorder[self.__listorder.index('regex:{}'.format(regex_get))] = _newvalue values = _newvalue except Exception as ex: - values = self.__regex + values = regex_get _issue = "Problem while creating regex '{0}': {1}.".format(values, ex) _issue_dict = {values: _issue} if _issue_dict not in self.__get_issues['regex']: @@ -741,31 +858,55 @@ def __get_from_regex(self): return values # Determine value by executing eval-function - def __get_eval(self): + def __get_eval(self, eval_get=None): # noinspection PyUnusedLocal sh = self._sh + # noinspection PyUnusedLocal shtime = self._shtime - if isinstance(self.__eval, str): - self.__eval = StateEngineTools.parse_relative(self.__eval, 'sh.', ['()', '.property.']) - if "stateengine_eval" in self.__eval or "se_eval" in self.__eval: + patterns = [ + "get_variable('current.", + 'get_variable("current.', + "get_variable('next.", + 'get_variable("next.' + ] + set_eval = False + if eval_get is None: + eval_get = self.__eval + set_eval = True + if isinstance(eval_get, str): + if set_eval: + self.__eval = StateEngineTools.parse_relative(eval_get, 'sh.', ['()', '.property.']) + else: + eval_get = StateEngineTools.parse_relative(eval_get, 'sh.', ['()', '.property.']) + if "stateengine_eval" in eval_get or "se_eval" in eval_get: # noinspection PyUnusedLocal stateengine_eval = se_eval = StateEngineEval.SeEval(self._abitem) - self._log_debug("Checking eval: {0}", self.__eval) + self._log_debug("Checking eval: {0}", eval_get) + if eval_get in self._abitem.cache: + self._log_increase_indent() + result = self._abitem.cache.get(eval_get) + self._log_debug("Loading eval from cache: {}", result) + self._log_decrease_indent() + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [result] + return result self._log_increase_indent() try: - _newvalue, _issue = self.__do_cast(eval(self.__eval)) - _issue_dict = {StateEngineTools.get_eval_name(self.__eval): _issue} + _newvalue, _issue = self.__do_cast(eval(eval_get)) + _issue_dict = {StateEngineTools.get_eval_name(eval_get): _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [_newvalue] values = _newvalue self._log_decrease_indent() self._log_debug("Eval result: {0} ({1}).", values, type(values)) + if not any(pattern in eval_get for pattern in patterns): + self._abitem.cache = {eval_get: values} self._log_increase_indent() except Exception as ex: self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) + _name = StateEngineTools.get_eval_name(eval_get) _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) _issue_dict = {_name: _issue} if _issue_dict not in self.__get_issues['eval']: @@ -776,15 +917,23 @@ def __get_eval(self): finally: self._log_decrease_indent() else: - if isinstance(self.__eval, list): + if isinstance(eval_get, list): values = [] - for val in self.__eval: + for val in eval_get: try: val = val.replace("\n", "") except Exception: pass - self._log_debug("Checking eval {0} from list {1}.", val, self.__eval) + self._log_debug("Checking eval {0} from list {1}.", val, eval_get) self._log_increase_indent() + if val in self._abitem.cache: + result = self._abitem.cache.get(val) + self._log_debug("Loading eval in list from cache: {} ({})", result, type(result)) + self._log_decrease_indent() + values.append(result) + if 'eval:{}'.format(val) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(val))] = [result] + continue if isinstance(val, str): if "stateengine_eval" in val or "se_eval" in val: # noinspection PyUnusedLocal @@ -830,24 +979,35 @@ def __get_eval(self): value = None if value is not None: values.append(value) + if not any(pattern in val for pattern in patterns): + self._abitem.cache = {val: value} self._log_decrease_indent() else: - self._log_debug("Checking eval (no str, no list): {0}.", self.__eval) + self._log_debug("Checking eval (no str, no list): {0}.", eval_get) + if eval_get in self._abitem.cache: + self._log_increase_indent() + result = self._abitem.cache.get(eval_get) + self._log_debug("Loading eval (no str, no list) from cache: {}", result) + self._log_decrease_indent() + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [result] + return result try: self._log_increase_indent() - _newvalue, _issue = self.__do_cast(self.__eval()) + _newvalue, _issue = self.__do_cast(eval_get()) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['eval']: self.__get_issues['eval'].append(_issue_dict) - if 'eval:{}'.format(self.__eval) in self.__listorder: - self.__listorder[self.__listorder.index('eval:{}'.format(self.__eval))] = [_newvalue] + if 'eval:{}'.format(eval_get) in self.__listorder: + self.__listorder[self.__listorder.index('eval:{}'.format(eval_get))] = [_newvalue] values = _newvalue self._log_decrease_indent() self._log_debug("Eval result (no str, no list): {0}.", values) + self._abitem.cache = {eval_get: values} self._log_increase_indent() except Exception as ex: self._log_decrease_indent() - _name = StateEngineTools.get_eval_name(self.__eval) + _name = StateEngineTools.get_eval_name(eval_get) _issue = "Problem evaluating '{0}': {1}.".format(_name, ex) self._log_warning(_issue) self._log_increase_indent() @@ -858,10 +1018,12 @@ def __get_eval(self): return values # Determine value from item - def __get_from_item(self): - if isinstance(self.__item, list): + def __get_from_item(self, get_item=None): + if get_item is None: + get_item = self.__item + if isinstance(get_item, list): values = [] - for val in self.__item: + for val in get_item: _new_values = [] if val is None: _newvalue = None @@ -874,7 +1036,9 @@ def __get_from_item(self): checked_entry = checked_entry if isinstance(checked_entry, list) else [checked_entry] for entry in checked_entry: - _newvalue, _issue = self.__do_cast(entry) + # _newvalue, _issue = self.__do_cast(entry) + _newvalue, _issue = self.set(entry, reset=False, returnvalue=True) + self._log_develop("Return from set from item list: {}, issue {}", _newvalue, _issue) _issue_dict = {entry: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) @@ -895,79 +1059,81 @@ def __get_from_item(self): if values is not None: return values else: - if self.__item is None: + if get_item is None: return None try: - checked_entry = StateEngineTools.convert_str_to_list(self.__item.property.value) + checked_entry = StateEngineTools.convert_str_to_list(get_item.property.value) except Exception as ex: self._log_warning("While getting from item: {}", ex) checked_entry = [] checked_entry = checked_entry if isinstance(checked_entry, list) else [checked_entry] _new_values = [] for entry in checked_entry: - _newvalue, _issue = self.__do_cast(entry) + #_newvalue, _issue = self.__do_cast(entry) + _newvalue, _issue = self.set(entry, reset=False, returnvalue=True) + self._log_develop("Return from set from item: {}, issue {}, listorder {}", _newvalue, _issue, self.__listorder) _issue_dict = {entry: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) if _newvalue is not None: _new_values.append(_newvalue) _new_values = _new_values[0] if len(_new_values) == 1 else None if len(_new_values) == 0 else [_new_values] - search_item = 'item:{}'.format(self.__item) + search_item = 'item:{}'.format(get_item) if search_item in self.__listorder: index = self.__listorder.index(search_item) self.__listorder[index] = _new_values - if self.__item in self.__listorder: - index = self.__listorder.index(self.__item) + if get_item in self.__listorder: + index = self.__listorder.index(get_item) self.__listorder[index] = _new_values values = _new_values if values is not None: return values try: - _newvalue = self.__item.property.path - search_item = 'item:{}'.format(self.__item) + _newvalue = get_item.property.path + search_item = 'item:{}'.format(get_item) if search_item in self.__listorder: index = self.__listorder.index(search_item) self.__listorder[index] = _newvalue values = _newvalue except Exception as ex: - values = self.__item + values = get_item _issue = "Problem while reading item path '{0}': {1}.".format(values, ex) self._log_info(_issue) - _newvalue, _issue = self.__do_cast(values) + #_newvalue, _issue = self.__do_cast(values) + _newvalue, _issue = self.set(values, reset=False, returnvalue=True) + self._log_develop("Return from set from item end: {}, issue {}", _newvalue, _issue) _issue_dict = {_newvalue: _issue} if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['item']: self.__get_issues['item'].append(_issue_dict) return _newvalue # Determine value from variable - def __get_from_variable(self): + def __get_from_variable(self, var_get=None): def update_value(varname): value = self._abitem.get_variable(varname) new_value, _issue = self.__do_cast(value) new_value = 'var:{}'.format(varname) if new_value == '' else new_value - if isinstance(new_value, str) and 'Unknown variable' in new_value: + if isinstance(new_value, str) and '(Unknown variable' in new_value: _issue = "There is a problem with your variable {}".format(new_value) - self._log_warning(_issue) new_value = '' _issue_dict = {varname: _issue} - if _issue not in [[], None, [None]] and _issue_dict not in self.__get_issues['var']: + if _issue_dict not in self.__get_issues['var']: self.__get_issues['var'].append(_issue_dict) - self._log_debug("Checking variable '{0}', value {1} from list {2}", - varname, new_value, self.__listorder) if 'var:{}'.format(varname) in self.__listorder: self.__listorder[self.__listorder.index('var:{}'.format(varname))] = new_value return new_value - + _issue = "" values = [] - - if isinstance(self.__varname, list): - for var in self.__varname: - values.append(update_value(var)) + if var_get is None: + var_get = self.__varname + if isinstance(var_get, list): + for var in var_get: self._log_debug("Checking variable in loop '{0}', value {1} from list {2}", var, values[-1], self.__listorder) + values.append(update_value(var)) else: - values = update_value(self.__varname) - self._log_debug("Variable result: {0}", values) + values = update_value(var_get) + self._log_debug("Variable result: '{}'.", values) return values diff --git a/stateengine/StateEngineWebif.py b/stateengine/StateEngineWebif.py index 3181788b2..040ff6209 100755 --- a/stateengine/StateEngineWebif.py +++ b/stateengine/StateEngineWebif.py @@ -59,98 +59,66 @@ def __init__(self, abitem): def __repr__(self): return "WebInterface item: {}, id {}".format(self.__states, self.__name) if REQUIRED_PACKAGE_IMPORTED else "None" - def _actionlabel(self, state, label_type, conditionset, previousconditionset, previousstate_conditionset): + def _strip_regex(self, regex_list): + pattern_strings = [] + if not isinstance(regex_list, list): + regex_list = [regex_list] + for item in regex_list: + if isinstance(item, re.Pattern): + pattern_strings.append(item.pattern) + else: + pattern_match = re.search(r"re\.compile\('([^']*)'", item) + if pattern_match: + item = f"regex:{pattern_match.group(1)}" + pattern_strings.append(str(item)) + if len(pattern_strings) <= 1: + pattern_strings = pattern_strings[0] + return str(pattern_strings) + + def _actionlabel(self, state, label_type, conditionset, active): # Check if conditions for action are met or not - # action_dict: abitem[state]['on_enter'/'on_stay'/'on_enter_or_stay'/'on_leave'].get(action) - # condition_to_meet: 'conditionset'/'previousconditionset''previousstate_conditionset' - # conditionset: name of conditionset that should get checked - def _strip_regex(regex_list): - pattern_strings = [] - for item in regex_list: - if isinstance(item, re.Pattern): - pattern_strings.append(item.pattern) - else: - pattern_strings.append(str(item)) - return str(pattern_strings) - - def _check_webif_conditions(action_dict, condition_to_meet: str, conditionset: str): - _condition_check = action_dict.get(condition_to_meet) - _condition_check = StateEngineTools.flatten_list(_condition_check) - _condition_necessary = 1 if _condition_check != 'None' else 0 - _condition_check = _condition_check if isinstance(_condition_check, list) else [_condition_check] - _condition_count = 0 - _condition = False - for cond in _condition_check: - try: - if isinstance(cond, str): - cond = re.compile(cond) - _matching = cond.fullmatch(conditionset) - except Exception: - _matching = True - _condition_count += 1 if _matching else 0 - _condition = True if _matching else False - return _condition_count, _condition, _condition_check, _condition_necessary + # state: state where action is defined in + # label_type: on_enter, on_stay, on_leave, on_pass + # active: if action is currently run actionlabel = actionstart = '<' action_tooltip = '' - originaltype = label_type - types = [label_type] if label_type == 'actions_leave' else ['actions_enter_or_stay', label_type] + types = [label_type] if label_type in ['actions_leave', 'actions_pass'] else ['actions_enter_or_stay', label_type] tooltip_count = 0 + for label_type in types: for action in self.__states[state].get(label_type): action_dict = self.__states[state][label_type].get(action) if action_dict.get('actionstatus'): _success = action_dict['actionstatus'].get('success') _issue = action_dict['actionstatus'].get('issue') + _reason = action_dict['actionstatus'].get('reason') else: _success = None _issue = None + _reason = None _repeat = action_dict.get('repeat') _delay = int(float(action_dict.get('delay') or 0)) - _delta = action_dict.get('delta') or '0' - _mindelta = action_dict.get('mindelta') or '0' - - condition_necessary = 0 - condition_met = True - condition_count = 0 - count, condition1, condition_to_meet, necessary = _check_webif_conditions(action_dict, 'conditionset', conditionset) - condition_count += count - condition_necessary += min(1, necessary) - count, condition2, previouscondition_to_meet, necessary = _check_webif_conditions(action_dict, 'previousconditionset', previousconditionset) - condition_count += count - condition_necessary += min(1, necessary) - count, condition3, previousstate_condition_to_meet, necessary = _check_webif_conditions(action_dict, 'previousstate_conditionset', previousstate_conditionset) - condition_count += count - condition_necessary += min(1, necessary) - - if condition_count < condition_necessary: - condition_met = False + cond1 = conditionset in ['', self.__active_conditionset] and state == self.__active_state cond2 = self.__states[state]['conditionsets'].get(conditionset) is not None - cond_delta = float(_delta) < float(_mindelta) - fontcolor = "white" if cond1 and cond2 and ( - cond_delta or - (not condition_met or (_repeat is False and originaltype == 'actions_stay'))) \ - else "#5c5646" if _delay > 0 else "darkred" if _delay < 0 \ - else "#303030" if not condition_met or _issue else "black" - condition_info = _strip_regex(condition_to_meet) if condition1 is False \ - else _strip_regex(previouscondition_to_meet) if condition2 is False \ - else _strip_regex(previousstate_condition_to_meet) if condition3 is False \ - else "" + + fontcolor = "white" if (_success == "False" and active) and ((cond1 and cond2 and _reason) or (_reason and label_type in ['actions_leave', 'actions_pass'])) \ + else "#f4c430" if _delay > 0 and active else "darkred" if _delay < 0 and active \ + else "#303030" if _issue else "black" + if _issue: if tooltip_count > 0: action_tooltip += ' ' tooltip_count += 1 action_tooltip += '{}'.format(_issue) if _issue is not None else '' - additionaltext = " (issue: see tooltip)" if _issue is not None\ - else " ({} not met)".format(condition_info) if not condition_met\ - else " (no repeat)" if _repeat is False and originaltype == 'actions_stay'\ - else " (delay: {})".format(_delay) if _delay > 0\ - else " (cancel delay!)" if _delay == -1 \ - else " (wrong delay!)" if _delay < -1 \ - else " (delta {} < {})".format(_delta, _mindelta) if cond_delta and cond1 and cond2\ - else "" + additionaltext = " (issue: see tooltip)" if _issue is not None \ + else _reason if _reason is not None \ + else " (delay: {})".format(_delay) if _delay > 0\ + else " (cancel delay!)" if _delay == -1 \ + else " (wrong delay!)" if _delay < -1 \ + else "" action1 = action_dict.get('function') if action1 in ['set', 'force set']: action2 = str(action_dict.get('item')) @@ -166,14 +134,10 @@ def _check_webif_conditions(action_dict, condition_to_meet: str, conditionset: s else: action2 = 'None' action3 = "" - cond1 = conditionset in ['', self.__active_conditionset] and state == self.__active_state - cond_enter = originaltype == 'actions_enter' and self.__states[state].get('enter') is True - cond_stay = originaltype == 'actions_stay' and self.__states[state].get('stay') is True - active = True if (cond_enter or cond_stay) and cond1 else False success_info = '' \ if _issue is not None and active \ else '' \ - if (_success == 'False' or not condition_met) and active \ + if _success == 'False' and active \ else '' \ if _success == 'Scheduled' and active \ else '' \ @@ -192,19 +156,19 @@ def _check_webif_conditions(action_dict, condition_to_meet: str, conditionset: s #self._log_debug('actionlabel: {}', actionlabel) return actionlabel, action_tooltip, tooltip_count - def _conditionlabel(self, state, conditionset, i): + def _conditionlabel(self, state, conditionset): condition_tooltip = '' conditions_done = [] _empty_set = self.__states[state]['conditionsets'].get(conditionset) == '' if _empty_set: return '', '', 0 conditionlist = '<
' + conditionlist += ''.format(conditionset) tooltip_count = 0 for k, condition in enumerate(self.__states[state]['conditionsets'].get(conditionset)): condition_dict = self.__states[state]['conditionsets'][conditionset].get(condition) current = condition_dict.get('current') match = condition_dict.get('match') - status_none = str(condition_dict.get('status')) == 'None' item_none = str(condition_dict.get('item')) == 'None' or not status_none status_eval_none = condition_dict.get('status_eval') == 'None' @@ -224,24 +188,21 @@ def _conditionlabel(self, state, conditionset, i): 'updatedbynegate', 'triggeredbynegate', 'status', 'current', 'match', 'status_eval'] if cond1 and compare not in excluded_values: - try: - list_index = list(self.__states.keys()).index(self.__active_state) - except Exception: - list_index = 0 if condition not in conditions_done: current_clean = ", ".join(f"{k} = {v}" for k, v in current.items()) - text = " Current {}".format(current_clean) if current and len(current) > 0 else " Not evaluated." + text = " Current {}".format(current_clean) if current is not None and len(current) > 0 else " Not evaluated." conditionlist += ('').format(condition.upper(), text) conditions_done.append(condition) conditionlist += ''.format(info) comparison = ">=" if not min_none and compare == "min"\ else "<=" if not max_none and compare == "max"\ @@ -332,10 +294,9 @@ def _conditionlabel(self, state, conditionset, i): conditionlist += ' (negate)' if condition_dict.get('negate') == 'True' and "age" \ not in compare and not compare == "value" else '' conditionlist += ' (negate)' if condition_dict.get('agenegate') == 'True' and "age" in compare else '' - active = i < list_index or (i == list_index and conditionset in ['', self.__active_conditionset]) - match_info = '' if match_info == 'yes' and active\ - else '' if match_info == 'no' and active\ - else '' if match_info and len(match_info) > 0 and active\ + match_info = '' if match_info == 'yes'\ + else '' if match_info == 'no'\ + else '' if match_info and len(match_info) > 0 \ else '' conditionlist += ''.format(match_info) conditionlist += '
{}
' - '' + '' '
{}:{}
' - info_status = str(condition_dict.get('status') or '') - info_item = str(condition_dict.get('item') or '') - info_eval = str(condition_dict.get('eval') or '') - info_status_eval = str(condition_dict.get('status_eval') or '') + info_status = str(condition_dict.get('status') or 'n/a') + info_item = str(condition_dict.get('item') or 'n/a') + info_eval = str(condition_dict.get('eval') or 'n/a') + info_status_eval = str(condition_dict.get('status_eval') or 'n/a') info_compare = str(condition_dict.get(compare) or '') + info_compare = self._strip_regex(info_compare) if not status_none: textlength = len(info_status) if textlength > self.__textlimit: @@ -294,7 +255,8 @@ def _conditionlabel(self, state, conditionset, i): elif not item_none: info = info_item else: - info = "" + info = "n/a" + conditionlist += '{}{}
>' @@ -344,16 +305,16 @@ def _conditionlabel(self, state, conditionset, i): def _add_actioncondition(self, state, conditionset, action_type, new_y, cond1, cond2): cond4 = conditionset in ['', self.__active_conditionset] and state == self.__active_state cond5 = self.__states[state]['conditionsets'].get(conditionset) is not None - cond_enter = action_type == 'actions_enter' and self.__states[state].get('enter') is False - cond_stay = action_type == 'actions_stay' and self.__states[state].get('stay') is False + cond_enter = action_type in ['actions_enter', 'actions_enter_or_stay'] and self.__states[state].get('enter') is False + cond_stay = action_type in ['actions_stay', 'actions_enter_or_stay'] and self.__states[state].get('stay') is False color_enter = "gray" if (cond1 and cond2 and cond5) or \ (cond_enter and cond4 and cond5) else "olivedrab" if cond4 else "indianred2" color_stay = "gray" if (cond1 and cond2 and cond5) or \ (cond_stay and cond4 and cond5) else "olivedrab" if cond4 else "indianred2" - label = 'first enter' if action_type == 'actions_enter' else 'staying at state' + label = 'first enter' if action_type in ['actions_enter', 'actions_enter_or_stay'] else 'staying at state' - position = '{},{}!'.format(0.63, new_y) + position = '{},{}!'.format(0.38, new_y) color = color_enter if label == 'first enter' else color_stay self.__nodes['{}_{}_state_{}'.format(state, conditionset, action_type)] = \ pydotplus.Node('{}_{}_state_{}'.format(state, conditionset, action_type), style="filled", fillcolor=color, @@ -386,8 +347,6 @@ def drawgraph(self, filename): new_y = 2 previous_state = '' previous_conditionset = '' - previousconditionset = '' - previousstate_conditionset = '' for i, state in enumerate(self.__states): #self._log_debug('Adding state for webif {}', self.__states[state]) if isinstance(self.__states[state], (OrderedDict, dict)): @@ -404,13 +363,14 @@ def drawgraph(self, filename): new_y -= 1 * self.__scalefactor position = '{},{}!'.format(0, new_y) if not i == 0: - condition_node = 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ - else list(self.__states[previous_state]['conditionsets'].keys())[-1] + condition_node = 'pass' if self.__nodes.get('{}_pass'.format(previous_state)) \ + else 'leave' if self.__nodes.get('{}_leave'.format(previous_state)) \ + else list(self.__states[previous_state]['conditionsets'].keys())[-1] lastnode = self.__nodes['{}_{}'.format(previous_state, condition_node)] self.__nodes['{}_above'.format(state)] = pydotplus.Node('{}_above'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above'.format(state)]) - position = '{},{}!'.format(0.5, new_y) + position = '{},{}!'.format(0.3, new_y) self.__nodes['{}_above_right'.format(state)] = pydotplus.Node('{}_above_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes['{}_above_right'.format(state)]) @@ -430,7 +390,7 @@ def drawgraph(self, filename): label='<
' '
{}
{}
>'.format( state, self.__states[state]['name'])) - position = '{},{}!'.format(0.5, new_y) + position = '{},{}!'.format(0.3, new_y) self.__nodes['{}_right'.format(state)] = pydotplus.Node('{}_right'.format(state), pos=position, shape="square", width="0", label="") self.__graph.add_node(self.__nodes[state]) @@ -439,66 +399,82 @@ def drawgraph(self, filename): actionlist_enter = '' actionlist_stay = '' actionlist_leave = '' + actionlist_pass = '' condition_tooltip = '' action_tooltip = '' j = 0 - new_x = 0.9 + new_x = 0.55 actions_enter = self.__states[state].get('actions_enter') or [] actions_enter_or_stay = self.__states[state].get('actions_enter_or_stay') or [] actions_stay = self.__states[state].get('actions_stay') or [] actions_leave = self.__states[state].get('actions_leave') or [] + actions_pass = self.__states[state].get('actions_pass') or [] action_tooltip_count_enter = 0 action_tooltip_count_stay = 0 action_tooltip_count_leave = 0 + action_tooltip_count_pass = 0 action_tooltip_enter = "" action_tooltip_stay = "" action_tooltip_leave = "" + action_tooltip_pass = "" for j, conditionset in enumerate(self.__states[state]['conditionsets']): - - if len(actions_enter) > 0 or len(actions_enter_or_stay) > 0: - actionlist_enter, action_tooltip_enter, action_tooltip_count_enter = \ - self._actionlabel(state, 'actions_enter', conditionset, previousconditionset, previousstate_conditionset) - - if len(actions_stay) > 0 or len(actions_enter_or_stay) > 0: - actionlist_stay, action_tooltip_stay, action_tooltip_count_stay = \ - self._actionlabel(state, 'actions_stay', conditionset, previousconditionset, previousstate_conditionset) - - if len(actions_leave) > 0: - actionlist_leave, action_tooltip_leave, action_tooltip_count_leave = \ - self._actionlabel(state, 'actions_leave', conditionset, previousconditionset, previousstate_conditionset) - - new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor - position = '{},{}!'.format(0.5, new_y) - conditionset_positions.append(new_y) - #self._log_debug('conditionset: {} {}, previous {}', conditionset, position, previous_conditionset) - - conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset, i) cond3 = conditionset == '' try: cond1 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception as ex: - #self._log_debug('Condition 1 problem {}'.format(ex)) + except Exception: cond1 = True try: cond4 = i == list(self.__states.keys()).index(self.__active_state) - except Exception as ex: - #self._log_debug('Condition 4 problem {}'.format(ex)) + except Exception: cond4 = True #self._log_debug('i {}, index of active state {}', i, list(self.__states.keys()).index(self.__active_state)) try: cond2 = (j > list(self.__states[state]['conditionsets'].keys()).index(self.__active_conditionset) or i > list(self.__states.keys()).index(self.__active_state)) - except Exception as ex: - #self._log_debug('Condition 2 problem {}'.format(ex)) + except Exception: cond2 = False if cond3 and cond4 else True color = "gray" if cond1 and cond2 else "olivedrab" \ if (conditionset == self.__active_conditionset or cond3) and state == self.__active_state else "indianred2" + try: + cond5 = i >= list(self.__states.keys()).index(self.__active_state) + except Exception: + cond5 = True + + cond6 = conditionset in ['', self.__active_conditionset] and state == self.__active_state + cond_enter = True if self.__states[state].get('enter') is True else False + cond_stay = True if self.__states[state].get('stay') is True else False + active = True if cond_enter and cond6 else False + + if len(actions_enter) > 0 or len(actions_enter_or_stay) > 0: + actionlist_enter, action_tooltip_enter, action_tooltip_count_enter = \ + self._actionlabel(state, 'actions_enter', conditionset, active) + active = True if cond_stay and cond6 else False + if len(actions_stay) > 0 or len(actions_enter_or_stay) > 0: + actionlist_stay, action_tooltip_stay, action_tooltip_count_stay = \ + self._actionlabel(state, 'actions_stay', conditionset, active) + cond_leave = True if self.__states[state].get('leave') is True else False + active = True if cond_leave else False + + if len(actions_leave) > 0: + actionlist_leave, action_tooltip_leave, action_tooltip_count_leave = \ + self._actionlabel(state, 'actions_leave', conditionset, active) + cond_pass = True if self.__states[state].get('pass') is True else False + active = False if (cond5 and not cond_pass) or cond_leave else True + if len(actions_pass) > 0: + actionlist_pass, action_tooltip_pass, action_tooltip_count_pass = \ + self._actionlabel(state, 'actions_pass', conditionset, active) + + new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor + position = '{},{}!'.format(0.3, new_y) + conditionset_positions.append(new_y) + conditionlist, condition_tooltip, condition_tooltip_count = self._conditionlabel(state, conditionset) + label = 'no condition' if conditionset == '' else conditionset self.__nodes['{}_{}'.format(state, conditionset)] = pydotplus.Node( '{}_{}'.format(state, conditionset), style="filled", fillcolor=color, shape="diamond", label=label, pos=position) - #self._log_debug('Node {} {} drawn', state, conditionset) - position = '{},{}!'.format(0.2, new_y) + #self._log_debug('Node {} {} drawn. Conditionlist {}', state, conditionset, conditionlist) + position = '{},{}!'.format(0.1, new_y) xlabel = '1 tooltip' if condition_tooltip_count == 1\ else '{} tooltips'.format(condition_tooltip_count)\ if condition_tooltip_count > 1 else '' @@ -507,9 +483,12 @@ def drawgraph(self, filename): '{}_{}_conditions'.format(state, conditionset), style="filled", fillcolor=color, shape="rect", label=conditionlist, pos=position, tooltip=condition_tooltip, xlabel=xlabel) self.__graph.add_node(self.__nodes['{}_{}_conditions'.format(state, conditionset)]) + # Create a dotted line between conditionlist and conditionset name + parenthesis_edge = pydotplus.Edge(self.__nodes['{}_{}_conditions'.format(state, conditionset)], self.__nodes['{}_{}'.format(state, conditionset)], arrowhead="none", color="black", style="dotted", constraint="false") + self.__graph.add_edge(parenthesis_edge) self.__graph.add_node(self.__nodes['{}_{}'.format(state, conditionset)]) - new_x = 0.9 + new_x = 0.55 if not actionlist_enter == '': position = '{},{}!'.format(new_x, new_y) xlabel = '1 tooltip' if action_tooltip_count_enter == 1\ @@ -537,11 +516,12 @@ def drawgraph(self, filename): self.__graph.add_node(self.__nodes['{}_{}_actions_stay'.format(state, conditionset)]) self._add_actioncondition(state, conditionset, 'actions_stay', new_y, cond1, cond2) - position = '{},{}!'.format(0.9, new_y) + position = '{},{}!'.format(0.55, new_y) cond1 = self.__nodes.get('{}_{}_actions_enter'.format(state, conditionset)) is None cond2 = self.__nodes.get('{}_{}_actions_stay'.format(state, conditionset)) is None cond3 = self.__nodes.get('{}_{}_actions_leave'.format(state, conditionset)) is None - if cond1 and cond2 and cond3: + cond4 = self.__nodes.get('{}_{}_actions_pass'.format(state, conditionset)) is None + if cond1 and cond2 and cond3 and cond4: self.__nodes['{}_{}_right'.format(state, conditionset)] = pydotplus.Node('{}_{}_right'.format( state, conditionset), shape="circle", width="0.7", pos=position, label="", fillcolor="black", style="filled", tooltip="No Action") @@ -557,7 +537,7 @@ def drawgraph(self, filename): xlabel = "" if j == 0: self.__graph.add_edge(pydotplus.Edge(self.__nodes[state], self.__nodes['{}_right'.format(state)], - style='bold', color='black', dir='none', + style='bold', color='black', dir='none', xlabel=xlabel, edgetooltip='check first conditionset')) self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_right'.format(state)], self.__nodes['{}_{}'.format(state, conditionset)], @@ -571,18 +551,13 @@ def drawgraph(self, filename): if len(actions_leave) > 0: new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor - position = '{},{}!'.format(0.5, new_y) - #self._log_debug('leaveconditions {}', position) - try: - cond1 = j > list(self.__states[state]['conditionsets'].keys()).index(self.__active_conditionset) - except Exception: - cond1 = True + position = '{},{}!'.format(0.3, new_y) try: cond2 = i >= list(self.__states.keys()).index(self.__active_state) - except Exception: + except Exception as ex: cond2 = True cond3 = True if self.__states[state].get('leave') is True else False - color = "gray" if cond1 and cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" + color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" self.__nodes['{}_leave'.format(state)] = pydotplus.Node('{}_leave'.format(state), style="filled", fillcolor=color, shape="diamond", label='leave', pos=position) @@ -605,6 +580,39 @@ def drawgraph(self, filename): self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_leave'.format(state)], self.__nodes['{}_actions_leave'.format(state)], style='bold', taillabel=" True", tooltip='run leave actions')) + previous_conditionset = self.__nodes['{}_leave'.format(state)] + + if len(actions_pass) > 0: + new_y -= 1 * self.__scalefactor if j == 0 else 2 * self.__scalefactor + position = '{},{}!'.format(0.3, new_y) + try: + cond2 = i >= list(self.__states.keys()).index(self.__active_state) + except Exception: + cond2 = True + cond3 = True if self.__states[state].get('pass') is True else False + color = "gray" if cond2 and not cond3 else "olivedrab" if cond3 else "indianred2" + self.__nodes['{}_pass'.format(state)] = pydotplus.Node('{}_pass'.format(state), + style="filled", fillcolor=color, shape="diamond", + label='pass', pos=position) + self.__graph.add_node(self.__nodes['{}_pass'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(previous_conditionset, self.__nodes['{}_pass'.format(state)], + style='bold', color='black', tooltip='check pass')) + + position = '{},{}!'.format(new_x, new_y) + xlabel = '1 tooltip' if action_tooltip_count_pass == 1\ + else '{} tooltips'.format(action_tooltip_count_pass)\ + if action_tooltip_count_pass > 1 else '' + #self._log_debug('action pass: {}', position) + self.__nodes['{}_actions_pass'.format(state)] = pydotplus.Node('{}_actions_pass'.format(state), + style="filled", fillcolor=color, + shape="rectangle", label=actionlist_pass, + pos=position, align="center", + tooltip=action_tooltip_pass, + xlabel=xlabel) + self.__graph.add_node(self.__nodes['{}_actions_pass'.format(state)]) + self.__graph.add_edge(pydotplus.Edge(self.__nodes['{}_pass'.format(state)], + self.__nodes['{}_actions_pass'.format(state)], style='bold', + taillabel=" True", tooltip='run pass actions')) previous_state = state diff --git a/stateengine/__init__.py b/stateengine/__init__.py index bfa0c5c71..5b5296938 100755 --- a/stateengine/__init__.py +++ b/stateengine/__init__.py @@ -47,7 +47,7 @@ class StateEngine(SmartPlugin): - PLUGIN_VERSION = '2.1.0' + PLUGIN_VERSION = '2.2.0' # Constructor # noinspection PyUnusedLocal,PyMissingConstructor diff --git a/stateengine/plugin.yaml b/stateengine/plugin.yaml index 5897f722a..be3495046 100755 --- a/stateengine/plugin.yaml +++ b/stateengine/plugin.yaml @@ -39,7 +39,7 @@ plugin: state: ready support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1303071-stateengine-plugin-support - version: '2.1.0' + version: '2.2.0' sh_minversion: '1.6' multi_instance: False classname: StateEngine @@ -853,6 +853,12 @@ item_structs: eval: sh..suspendduration(sh..suspendduration(), "Init", "Start") crontab: init = True + conditionset_leaveactions: + suspend: + type: str + initial_value: '.*' + cache: True + suspendduration: remark: duration of suspend mode in minutes (gets converted automatically) type: num @@ -937,29 +943,52 @@ item_structs: - 'repeat: True' - 'order: 5' + on_pass: + se_action_suspend: + - 'function: set' + - 'to: False' + - 'repeat: False' + se_action_suspend_visu: + - 'function: set' + - 'to: False' + - 'repeat: False' + se_action_suspend_end: + - 'function: set' + - 'to: ' + - 'repeat: False' + se_action_suspend_start: + - 'function: set' + - 'to: ' + - 'repeat: False' + on_leave: se_action_suspend: - 'function: set' - 'to: False' - 'order: 2' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_visu: - 'function: set' - 'to: False' - 'order: 3' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_end: - 'function: set' - 'to: ' - 'order: 4' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_start: - 'function: set' - 'to: ' - 'order: 5' - 'delay: 1' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_retrigger: - 'function: special' - 'value: retrigger:..retrigger' - 'delay: -1' - 'order: 1' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' enter_manuell: se_value_trigger_source: eval:se_eval.get_relative_itemproperty('..manuell', 'path') @@ -1049,6 +1078,12 @@ item_structs: eval: (sh..suspendduration(sh..suspendduration(), "Init", "Start"), sh..suspendvariant.suspendduration0(sh..suspendduration(), "Init", "Start"), sh..suspendvariant.suspendduration1(sh..suspendvariant.suspendduration1(), "Init", "Start"), sh..suspendvariant.suspendduration2(sh..suspendvariant.suspendduration2(), "Init", "Start")) crontab: init = True + conditionset_leaveactions: + suspend: + type: str + initial_value: '.*' + cache: True + suspendvariant: remark: number between 0 and 2 to define which suspendduration should be used type: num @@ -1241,33 +1276,57 @@ item_structs: - 'repeat: True' - 'order: 5' + on_pass: + se_action_suspend: + - 'function: set' + - 'to: False' + - 'repeat: False' + se_action_suspend_visu: + - 'function: set' + - 'to: False' + - 'repeat: False' + se_action_suspend_end: + - 'function: set' + - 'to: ' + - 'repeat: False' + se_action_suspend_start: + - 'function: set' + - 'to: ' + - 'repeat: False' + on_leave: se_action_suspendvariant: - 'function: set' - 'to: eval:se_eval.get_relative_itemproperty("..settings.suspendvariant", "last_value")' - 'order: 1' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend: - 'function: set' - 'to: False' - 'order: 2' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_visu: - 'function: set' - 'to: False' - 'order: 3' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_end: - 'function: set' - 'to: ' - 'order: 4' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_suspend_start: - 'function: set' - 'to: ' - 'order: 5' - 'delay: 1' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' se_action_retrigger: - 'function: special' - 'value: retrigger:..retrigger' - 'delay: -1' - 'order: 1' + - 'nextconditionset: item:..settings.conditionset_leaveactions.suspend' enter_manuell: se_value_trigger_source: eval:se_eval.get_relative_itemproperty('..manuell', 'path') @@ -1418,6 +1477,12 @@ item_attribute_prefixes: de: 'Definiert um welchen Wert sich das Item mindestens geändert haben muss, um neu gesetzt zu werden' en: 'Definition of a threshold the item has to surpass to be newly set' + se_minagedelta_: + type: foo + description: + de: 'Definiert eine Pause zwischen mehreren Aktionsaufrufen.' + en: 'Definition of pause interval between multiple runs of actions' + se_min_: type: foo description: @@ -1431,6 +1496,7 @@ item_attribute_prefixes: en: 'Condition: The value of the item has to be lower than defined by se_max' se_value_: + type: foo description: de: 'Bedingung: Das Item muss exakt dem durch se_value angegebenem Wert entsprechen' en: 'Condition: The item has to have the exact same value as defined by se_value' @@ -1508,11 +1574,13 @@ item_attribute_prefixes: en: 'Special action like suspend or retrigger' se_set_: + type: foo description: de: 'Setzen eines Items auf einen bestimmten Wert (veraltet - Nutze stattdessen se_action)' en: 'Setting an item to a specific value (deprecated - use se_action instead)' se_force_: + type: foo description: de: 'Setzen eines Items auf einen bestimmten Wert, egal ob sich der Wert geändert hat oder nicht (veraltet - Nutze stattdessen se_action)' en: 'Setting an item to a specific value no matter whether the value has changed or not (deprecated - use se_action instead)' @@ -1542,17 +1610,19 @@ item_attribute_prefixes: en: 'Delaying an action (deprecated - use se_action instead)' se_repeat_: - type: bool + type: foo description: de: 'Definiert, ob eine Aktion beim erneuten Eintritt in den gleichen Status wiederholt werden soll oder nicht (veraltet - Nutze stattdessen se_action)' en: 'Definition wether an action should be repeated or not when reentering the same state (deprecated - use se_action instead)' se_order_: + type: foo description: de: 'Definiert die Reihenfolge einer Aktion als Integerzahl (veraltet - Nutze stattdessen se_action)' en: 'Definition of the running order of an action as integer (deprecated - use se_action instead)' se_manual_: + type: foo description: de: 'Diverse Funktion für den Suspendmodus wie include, exclude, invert, logitem.' en: 'Some functions relevant for the suspend mode like include, exclude, invert, logitem.' diff --git a/stateengine/user_doc/06_aktionen.rst b/stateengine/user_doc/06_aktionen.rst index 339427c70..c278d3752 100755 --- a/stateengine/user_doc/06_aktionen.rst +++ b/stateengine/user_doc/06_aktionen.rst @@ -18,8 +18,9 @@ definiert und benannt wurde. Die Herangehensweise ähnelt also stark der Deklara Zusätzlich zu ``se_item_`` lässt sich über den Eintrag ``se_mindelta_`` definieren, um welchen Wert -sich ein Item mindestens geändert haben muss, um neu gesetzt zu werden. Im unten -stehenden Beispiel wird der Lamellenwert abhängig vom Sonnenstand berechnet. Ohne mindelta +sich ein Item mindestens geändert haben muss, um neu gesetzt zu werden. Diese Konfiguration +kann für einzelne Aktionen individuell über die Angabe ``mindelta`` überschrieben werden. +Im unten stehenden Beispiel wird der Lamellenwert abhängig vom Sonnenstand berechnet. Ohne mindelta würden sich die Lamellen ständig um wenige Grad(bruchteile) ändern. Wird jedoch mindelta beispielsweise auf den Wert 10 gesetzt, findet eine Änderung erst statt, wenn sich der errechnete Wert um mindestens 10 Grad vom aktuellen Lamellenwert unterscheidet. @@ -300,6 +301,7 @@ ursprünglichen Zustands (regen) gesetzt werden soll, kann der Parameter ``insta .. code-block:: yaml + 'repeat: /' --> Ergebnis eines Eval-Ausdrucks oder eines Items 'repeat: [True|False]' Über das Attribut wird unabhängig vom globalen Setting für das @@ -393,7 +395,7 @@ regnet hingegen auf den Wert, der in den Settings hinterlegt ist. .. code-block:: yaml - previousconditionset: regex:enter_(.*)_test" + "previousconditionset: regex:enter_(.*)_test" Über das Attribut wird festgelegt, dass die Aktion nur dann ausgeführt werden soll, wenn die vorherige Bedingungsgruppe des aktuellen Zustands mit dem angegebenen Ausdruck übereinstimmt. @@ -403,12 +405,65 @@ Die Abfrage erfolgt dabei nach den gleichen Regeln wie bei ``conditionset`` oben .. code-block:: yaml - previousstate_conditionset: regex:enter_(.*)_test" + "previousstate_conditionset: regex:enter_(.*)_test" Über das Attribut wird festgelegt, dass die Aktion nur dann ausgeführt werden soll, wenn die Bedingungsgruppe, mit der der vorherige Zustand eingenommen wurde, mit dem angegebenen Ausdruck übereinstimmt. Die Abfrage erfolgt dabei ebenfalls nach den gleichen Regeln wie bei ``conditionset`` oben angegeben. +**next_conditionset: ** + +.. code-block:: yaml + + "next_conditionset: regex:enter_(.*)_test" + +Über das Attribut wird festgelegt, dass die Aktion nur dann ausgeführt werden +soll, wenn die Bedingungsgruppe, mit der der zukünftige Zustand eingenommen wird, mit dem angegebenen Ausdruck übereinstimmt. +Die Abfrage erfolgt dabei ebenfalls nach den gleichen Regeln wie bei ``conditionset`` oben angegeben. +Diese Angabe ist primär bei leave_actions sinnvoll. + +**mindelta: ** + +Im folgenden Beispiel wird mindelta für eine einzelne Aktion gesetzt. Anstatt also eine minimale Änderung +für alle Aktionen mit bestimmtem Namen festzulegen, wird eine einzelne Aktion nur dann ausgeführt, +wenn sich der Wert um mindestens den angegeben Wert geändert hat. +Wird mindelta beispielsweise auf den Wert 10 gesetzt, findet eine Änderung erst statt, wenn sich der +errechnete Wert um mindestens 10 Grad vom aktuellen Lamellenwert unterscheidet. + +.. code-block:: yaml + + #items/item.yaml + raffstore1: + automatik: + struct: stateengine.general + rules: + se_item_height: raffstore1.hoehe # Definition des zu ändernden Höhe-Items + se_item_lamella: raffstore1.lamelle # Definition des zu ändernden Lamellen-Items + se_status_lamella: raffstore1.lamelle.status # Definition des Lamellen Statusitems + Daemmerung: + <...> + se_action_height: + - 'function: set' + - 'to: value:100' + se_action_lamella: + - 'function: set' + - 'to: value:25' + - 'mindelta: 10' + <...> + +**minagedelta: ** + +.. code-block:: yaml + + minagedelta: 300 + +Über das Attribut wird festgelegt, dass eine Aktion nur in einem vorgegebenen Intervall ausgeführt wird. +Im angegebenen Beispiel wird die Aktion also nur ausgeführt, wenn der letzte Ausführungszeitpunkt mindestens +5 Minuten zurück liegt. So kann verhindert werden, dass die Aktion bei jeder Neuevaluierung des Status ausgeführt wird. +Ein Neustart des Plugins setzt den Counter wieder zurück, der letzte Ausführungszeitpunkt wird also nur bei laufendem +Betrieb gespeichert und überdauert einen Neustart nicht. + + Templates für Aktionen ---------------------- diff --git a/stateengine/user_doc/10_funktionen_variablen.rst b/stateengine/user_doc/10_funktionen_variablen.rst index c1ee17a7b..e84e1f0e7 100755 --- a/stateengine/user_doc/10_funktionen_variablen.rst +++ b/stateengine/user_doc/10_funktionen_variablen.rst @@ -282,13 +282,10 @@ die Situation deutlich vereinfachen würde. **current.conditionset_name:** *Der Name der Bedingungsgruppe, die gerade geprüft wird* -Beide current.conditionset Variablen können ebenso wie die oben erwähnten current.state_id/name -nur während der Prüfung der Bedingungen genutzt werden, nicht jedoch für Aktionen. - Das Beispiel zeigt einen Windzustand. Dieser übernimmt keine Funktionen, sondern dient lediglich der Visualisierung (Sicherheitsrelevante Features sollten unbedingt z.B. über den KNX Bus erfolgen!). Außerdem wird davon -ausgegangen, dass es einen untergeordneten Zustand names x gibt. +ausgegangen, dass es einen untergeordneten Zustand namens x gibt. - enter_normal wird angenommen, sobald das Wind-Item aktiv ist, zuvor aber nicht der x-Zustand aktiv war. @@ -364,6 +361,14 @@ Zustand aktiv gewesen ist. Ansonsten gelten alle vorhin beschriebenen Regeln. **previous.state_conditionset_name:** *Der Name der Bedingungsgruppe, die beim vorigen Zustand zuletzt aktiv war* +**next.conditionset_id:** +*Die Id der Bedingungsgruppe, die für einen Zustandswechsel verantwortlich ist* + +**next.conditionset_name:** +*Der Name der Bedingungsgruppe, die für einen Zustandswechsel verantwortlich ist* + +Beide next.conditionset Variablen können sinnvoll nur für Aktionen genutzt werden. + **release.can_be_released_by:** *Die Definitionen, die den aktuellen Zustand generell auflösen könnten* diff --git a/stateengine/user_doc/12_aktioneneinzeln.rst b/stateengine/user_doc/12_aktioneneinzeln.rst index 9363459ae..cb00bf9dc 100755 --- a/stateengine/user_doc/12_aktioneneinzeln.rst +++ b/stateengine/user_doc/12_aktioneneinzeln.rst @@ -150,8 +150,50 @@ Es ist möglich, eine Minimumabweichung für aktuellen Wert des Items und dem ermittelten neuen Wert kleiner ist als die festgelegte Minimumabweichung wird keine Änderung vorgenommen. Die Minimumabweichung wird über das Attribut -``se_mindelta_`` auf der Ebene des Regelwerk-Items -festgelegt. +``se_mindelta_`` auf der Ebene des Regelwerk-Items für alle +Aktionen mit dem entsprechenden Namen festgelegt. Alternativ kann dieses +Attribut aber auch für einzelne Aktionen festgelegt und/oder überschrieben werden. + +Im angeführten Beispiel werden die Lamellen beim Eintritt in einen Zustand und +beim Verlassen des Zustands nur dann gesetzt, wenn sich der Wert der Lamellen inzwischen +um mindestens 10 verändert hat. Wird der Zustand erneut eingenommen (stay), wird der +Wert hingegen mit 5 überschrieben. + +.. code-block:: yaml + + #items/item.yaml + raffstore1: + automatik: + struct: stateengine.general + rules: + se_item_height: raffstore1.hoehe # Definition des zu ändernden Höhe-Items + se_item_lamella: raffstore1.lamelle # Definition des zu ändernden Lamellen-Items + se_status_lamella: raffstore1.lamelle.status # Definition des Lamellen Statusitems + se_mindelta_lamella: 10 # Alle Aktionen mit Namen lamella sind betroffen + Daemmerung: + on_enter: + se_set_height: 100 + se_set_lamella: 20 + on_leave: + se_set_height: 100 + se_set_lamella: 15 + on_stay: + se_set_height: 100 + se_set_lamella: 20 + se_set_lamella: 20 + se_mindelta_lamella: 5 + +**minagedelta: ** + +.. code-block:: yaml + + se_minagedelta_: 300 (Sekunden)|30m (Minuten) + +Über das Attribut wird festgelegt, dass eine Aktion nur in einem vorgegebenen Intervall ausgeführt wird. +Im angegebenen Beispiel wird die Aktion also nur ausgeführt, wenn der letzte Ausführungszeitpunkt mindestens +300 Sekunden zurück liegt. So kann verhindert werden, dass die Aktion bei jeder Neuevaluierung des Status ausgeführt wird. +Ein Neustart des Plugins setzt den Counter wieder zurück, der letzte Ausführungszeitpunkt wird also nur bei laufendem +Betrieb gespeichert und überdauert einen Neustart nicht. **delay: ** @@ -167,8 +209,10 @@ Der Timer zur Ausführung der Aktion nach der angegebenen Verzögerung wird entfernt, wenn eine gleichartige Aktion ausgeführt werden soll (egal ob verzögert oder nicht). Wenn also die Verzögerung größer als der ``cycle`` ist, wird die Aktion -nie durchgeführt werden, es sei denn die Aktion soll nur -einmalig ausgeführt werden. +nicht mehr durchgeführt. +Außerdem ist es möglich, den Timer bewusst abzubrechen, ohne eine Aktion auszuführen, +indem der Delay auf -1 gesetzt wird. Dies macht insbesondere beim Verlassen von +Zuständen Sinn, um ungewünschte verzögerte Aktionen vom "alten" Zustand zu verhindern. **repeat: ** diff --git a/telegram/__init__.py b/telegram/__init__.py index c3ec5507e..6c0dded7b 100755 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -2,7 +2,7 @@ # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab ######################################################################### # Copyright 2017 Markus Garscha http://knx-user-forum.de/ -# 2018-2023 Ivan De Filippis +# 2018-2024 Ivan De Filippis # 2018-2021 Bernd Meiners Bernd.Meiners@mail.de ######################################################################### # @@ -68,7 +68,7 @@ class Telegram(SmartPlugin): - PLUGIN_VERSION = "2.0.1" + PLUGIN_VERSION = '2.0.3' _items = [] # all items using attribute ``telegram_message`` _items_info = {} # dict used whith the info-command: key = attribute_value, val= item_list telegram_info @@ -77,6 +77,7 @@ class Telegram(SmartPlugin): _chat_ids_item = {} # an item with a dict of chat_id and write access _waitAnswer = None # wait a specific answer Yes/No - or num (change_item) _queue = None # queue for the messages to be sent + _cHandlers = [] # CommandHandler from parse_item def __init__(self, sh): """ @@ -100,8 +101,9 @@ def __init__(self, sh): self.logger.error(f"{self.get_fullname()}: Unable to import Python package 'python-telegram-bot' [{REQUIRED_PACKAGE_IMPORTED}]") return - self._loop = asyncio.new_event_loop() # new_event is required for multi-instance - asyncio.set_event_loop(self._loop) + #self._loop = asyncio.new_event_loop() # new_event is required for multi-instance + #self.log_event_loop_details() + #asyncio.set_event_loop(self._loop) self.alive = False self._name = self.get_parameter_value('name') @@ -116,27 +118,12 @@ def __init__(self, sh): self._resend_delay = self.get_parameter_value('resend_delay') self._resend_attemps = self.get_parameter_value('resend_attemps') + self._pause_item = None + self._pause_item_path = self.get_parameter_value('pause_item') + self._bot = None self._queue = Queue() - self._application = Application.builder().token(self._token).build() - - if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug("adding command handlers to application") - - self._application.add_error_handler(self.eHandler) - self._application.add_handler(CommandHandler('time', self.cHandler_time)) - self._application.add_handler(CommandHandler('help', self.cHandler_help)) - self._application.add_handler(CommandHandler('hide', self.cHandler_hide)) - self._application.add_handler(CommandHandler('list', self.cHandler_list)) - self._application.add_handler(CommandHandler('info', self.cHandler_info)) - self._application.add_handler(CommandHandler('start', self.cHandler_start)) - self._application.add_handler(CommandHandler('lo', self.cHandler_lo)) - self._application.add_handler(CommandHandler('tr', self.cHandler_tr)) - self._application.add_handler(CommandHandler('control', self.cHandler_control)) - # Filters.text includes also commands, starting with ``/`` so it is needed to exclude them. - self._application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), self.mHandler)) - self.init_webinterface() if not self.init_webinterface(WebInterface): self.logger.error("Unable to start Webinterface") @@ -162,22 +149,56 @@ def run(self): This is called when the plugins thread is about to run """ if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug("Run method called") + self.logger.debug(f"Plugin '{self.get_fullname()}': run method called") - self.logics = Logics.get_instance() # Returns the instance of the Logics class, to be used to access the logics-api + if self.alive: + return + if self._pause_item is not None and bool(self._pause_item()): + self.logger.info(f'plugin not startet - pause_item is True') + return + + self._loop = asyncio.new_event_loop() # new_event is required for multi-instance + asyncio.set_event_loop(self._loop) + + self._application = Application.builder().token(self._token).build() + + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug("adding command handlers to application") + + self._application.add_error_handler(self.eHandler) + self._application.add_handler(CommandHandler('time', self.cHandler_time)) + self._application.add_handler(CommandHandler('help', self.cHandler_help)) + self._application.add_handler(CommandHandler('hide', self.cHandler_hide)) + self._application.add_handler(CommandHandler('list', self.cHandler_list)) + self._application.add_handler(CommandHandler('info', self.cHandler_info)) + self._application.add_handler(CommandHandler('start', self.cHandler_start)) + self._application.add_handler(CommandHandler('lo', self.cHandler_lo)) + self._application.add_handler(CommandHandler('tr', self.cHandler_tr)) + self._application.add_handler(CommandHandler('control', self.cHandler_control)) + # Filters.text includes also commands, starting with ``/`` so it is needed to exclude them. + self._application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), self.mHandler)) + # add CommandHandlers from parse_item + for cHandler in self._cHandlers: + self._application.add_handler(cHandler) + self.alive = True + self.logics = Logics.get_instance() # Returns the instance of the Logics class, to be used to access the logics-api + self._loop.run_until_complete(self.run_coros()) if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug(f"Run method ended") + self.logger.debug(f"Plugin '{self.get_fullname()}': run method finished ") def stop(self): """ This is called when the plugins thread is about to stop """ if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug("stop telegram plugin") + self.logger.debug(f"Plugin '{self.get_fullname()}': stop method called") + + if self.scheduler_get('telegram_change_item_timeout'): + self.scheduler_remove('telegram_change_item_timeout') try: if self._bye_msg: @@ -188,8 +209,18 @@ def stop(self): except Exception as e: self.logger.error(f"could not send bye message [{e}]") - time.sleep(1) - self.alive = False # Clears the infiniti loop in sendQueue + if not self._queue.empty(): + time.sleep(5) + if not self._queue.empty(): + try: + self.alive = False # Clears the infiniti loop in sendQueue + self.remove_all_events() + self.logger.debug(f"Events removed.") + except Exception as e: + self.logger.error(f"An error occurred while removing the events: [{e}]") + + self.alive = False + try: asyncio.gather(self._taskConn, self._taskQueue) self.disconnect() @@ -204,8 +235,20 @@ def stop(self): self.logger.error(f"An error occurred while stopping the plugin [{e}]") if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug("stop telegram plugin finished") + self.logger.debug(f"Plugin '{self.get_fullname()}': stop method finished") + + def remove_all_events(self): + while not self._queue.empty(): + self._queue.get_nowait() # Entfernt das Event aus der Queue + self._queue.task_done() # Markiert die Aufgabe als erledigt + self.logger.debug("all events removed") + def _start_loop(self): + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + self.logger.debug("Starting event loop") + self._loop.run_forever() + async def run_coros(self): """ This method run multiple coroutines concurrently using asyncio @@ -220,37 +263,41 @@ async def connect(self): """ if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("connect method called") - try: - await self._application.initialize() - await self._application.start() - self._updater = self._application.updater - - q = await self._updater.start_polling(timeout=self._long_polling_timeout, error_callback=self.error_handler) + while self.alive: + try: + await self._application.initialize() + await self._application.start() + self._updater = self._application.updater - if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug(f"started polling the updater, Queue is {q}") + q = await self._updater.start_polling(timeout=self._long_polling_timeout, error_callback=self.error_handler) - self._bot = self._updater.bot - self.logger.info(f"Telegram bot is listening: {await self._updater.bot.getMe()}") - if self._welcome_msg: if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug(f"sent welcome message {self._welcome_msg}") - cids = [key for key, value in self._chat_ids_item().items() if value == 1] - self.msg_broadcast(self._welcome_msg, chat_id=cids) + self.logger.debug(f"started polling the updater, Queue is {q}") - except TelegramError as e: - # catch Unauthorized errors due to an invalid token - self.logger.error(f"Unable to start up Telegram conversation. Maybe an invalid token? {e}") - return False + self._bot = self._updater.bot + self.logger.info(f"Telegram bot is listening: {await self._updater.bot.getMe()}") + if self._welcome_msg: + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"sent welcome message {self._welcome_msg}") + cids = [key for key, value in self._chat_ids_item().items() if value == 1] + self.msg_broadcast(self._welcome_msg, chat_id=cids) + + # If no exception occurred, break the loop and exit the function + break + + except TelegramError as e: + self.logger.error(f"Unable to start up Telegram conversation. {e}") + await asyncio.sleep(60) # Wait for 60 seconds before retrying - while + if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug("connect method end") - def error_handler(self, update, context): + def error_handler(self, error): """ Just logs an error in case of a problem """ try: - self.logger.warning(f'Update {update} caused error {context.error}') + self.logger.error(f'Update caused error {error}') except Exception: pass @@ -330,6 +377,14 @@ def parse_item(self, item): Default plugin parse_item method. Is called when the plugin is initialized. :param item: The item to process. """ + + # check for pause item + if item.property.path == self._pause_item_path: + self.logger.debug(f'pause item {item.property.path} registered') + self._pause_item = item + self.add_item(item, updating=True) + return self.update_item + if self.has_iattr(item.conf, ITEM_ATTR_CHAT_IDS): if self._chat_ids_item: self.logger.warning(f"Item: {item.property.path} declares chat_id for telegram plugin which are already defined, aborting!") @@ -360,7 +415,8 @@ def parse_item(self, item): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"Register new command '{key}', add item '{item}' and register a handler") # add a handler for each info-attribute - self._application.add_handler(CommandHandler(key, self.cHandler_info_attr)) + self._cHandlers.append(CommandHandler(key, self.cHandler_info_attr)) + #self._application.add_handler(CommandHandler(key, self.cHandler_info_attr)) return self.update_item else: self.logger.error(f"Command '{key}' chosen for item '{item}' is invalid for telegram botfather") @@ -411,7 +467,8 @@ def parse_item(self, item): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"Register new command '{key}', add item '{item}' and register a handler") # add a handler for each control-attribute - self._application.add_handler(CommandHandler(key, self.cHandler_control_attr)) + #self._application.add_handler(CommandHandler(key, self.cHandler_control_attr)) + self._cHandlers.append(CommandHandler(key, self.cHandler_control_attr)) return self.update_item return None @@ -433,6 +490,21 @@ def update_item(self, item, caller=None, source=None, dest=None): """ Called each time an item changed in SmartHomeNG """ + + # check for pause item + if item is self._pause_item: + if caller != self.get_shortname(): + self.logger.debug(f'pause item changed to {item()}') + if item() and self.alive: + self.stop() + elif not item() and not self.alive: + self.run() + return + + if not self.alive: + self.logger.info('Plugin is not alive, data will not be written') + return + if caller != self.get_fullname(): self.logger.info(f"update item: {item.property.path}") @@ -527,7 +599,7 @@ async def async_msg_broadcast(self, msg, chat_id=None, reply_markup=None, parse_ for cid in self.get_chat_id_list(chat_id): try: - response = await self._bot.send_message(chat_id=cid, text=msg, reply_markup=reply_markup, parse_mode=parse_mode) + response = await self._bot.sendMessage(chat_id=cid, text=msg, reply_markup=reply_markup, parse_mode=parse_mode) if response: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"Message sent:[{msg}] to Chat_ID:[{cid}] Bot:[{self._bot.bot}] response:[{response}]") @@ -630,7 +702,7 @@ def has_access_right(self, user_id): if user_id in self._chat_ids_item(): return True else: - self._bot.send_message(chat_id=user_id, text=self._no_access_msg) + self._bot.sendMessage(chat_id=user_id, text=self._no_access_msg) return False @@ -642,7 +714,7 @@ def has_write_access_right(self, user_id): if user_id in self._chat_ids_item(): return self._chat_ids_item()[user_id] else: - self._bot.send_message(chat_id=user_id, text=self._no_write_access_msg) + self._bot.sendMessage(chat_id=user_id, text=self._no_write_access_msg) return False @@ -685,7 +757,7 @@ def has_write_access_right(self, user_id): it contains the following objects: args - bot context.bot is the target for send_message() function + bot context.bot is the target for sendMessage() function bot_data chat_data dispatcher @@ -707,7 +779,7 @@ async def eHandler(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> Just logs an error in case of a problem """ try: - self.logger.warning(f'Update {update} caused error {context.error}') + self.logger.error(f'Update {update} caused error {context.error}') except Exception: pass @@ -740,26 +812,26 @@ async def mHandler(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> if dicCtl['type'] == 'onoff': item = dicCtl['item'] msg = f"{dicCtl['name']} \n change to:On(True)" - self._bot.sendMessage(chat_id=update.message.chat.id, text=msg) + await self._bot.sendMessage(chat_id=update.message.chat.id, text=msg) item(True) self._waitAnswer = None - self._bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) + await self._bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) elif text == 'Off': if dicCtl['type'] == 'onoff': item = dicCtl['item'] msg = f"{dicCtl['name']} \n change to:Off(False)" - self._bot.sendMessage(chat_id=update.message.chat.id, text=msg) + await self._bot.sendMessage(chat_id=update.message.chat.id, text=msg) item(False) self._waitAnswer = None - self._bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) + await self._bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) elif text == 'Yes': if self.scheduler_get('telegram_change_item_timeout'): self.scheduler_remove('telegram_change_item_timeout') dicCtlCopy = dicCtl.copy() dicCtlCopy['question'] = '' - self.change_item(update, context, dicCtlCopy['name'], dicCtlCopy) + await self.change_item(update, context, dicCtlCopy['name'], dicCtlCopy) self._waitAnswer = None - self._bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) + await self._bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) elif dicCtl['type'] == 'num': if type(text) == int or float: if self.logger.isEnabledFor(logging.DEBUG): @@ -787,7 +859,7 @@ async def mHandler(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> msg = f"{dicCtl['name']} \n out off range" await context.bot.sendMessage(chat_id=update.message.chat.id, text=msg) else: - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()}) self._waitAnswer = None except Exception as e: if self.logger.isEnabledFor(logging.DEBUG): @@ -800,7 +872,7 @@ async def cHandler_time(self, update: Update, context: ContextTypes.DEFAULT_TYPE if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"/time: return server time for update={update}, chat_id={update.message.chat.id} and context={dir(context)}") if self.has_access_right(update.message.chat.id): - await context.bot.send_message(chat_id=update.message.chat.id, text=str(datetime.datetime.now())) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=str(datetime.datetime.now())) async def cHandler_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -809,7 +881,7 @@ async def cHandler_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"/help: show available commands as keyboard for update={update}, chat_id={update.message.chat.id} and context={dir(context)}") if self.has_access_right(update.message.chat.id): - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("choose"), reply_markup={"keyboard": [["/hide","/start"], ["/time","/list"], ["/lo","/info"], ["/control", "/tr "]]}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("choose"), reply_markup={"keyboard": [["/hide","/start"], ["/time","/list"], ["/lo","/info"], ["/control", "/tr "]]}) async def cHandler_hide(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -819,7 +891,7 @@ async def cHandler_hide(self, update: Update, context: ContextTypes.DEFAULT_TYPE self.logger.debug(f"/hide: hide keyboard for bot={context.bot} and chat_id={update.message.chat.id}") if self.has_access_right(update.message.chat.id): hide_keyboard = {'hide_keyboard': True} - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("I'll hide the keyboard"), reply_markup=hide_keyboard) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("I'll hide the keyboard"), reply_markup=hide_keyboard) async def cHandler_list(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -828,8 +900,7 @@ async def cHandler_list(self, update: Update, context: ContextTypes.DEFAULT_TYPE if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"/list: show registered items and value for chat_id={update.message.chat.id}") if self.has_access_right(update.message.chat.id): - await context.bot.send_message(chat_id=update.message.chat.id, text=self.list_items()) - #self.list_items(update.message.chat.id) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.list_items()) async def cHandler_info(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -839,9 +910,9 @@ async def cHandler_info(self, update: Update, context: ContextTypes.DEFAULT_TYPE self.logger.debug(f"/info: show item-menu with registered items with specific attribute for chat_id={update.message.chat.id}") if self.has_access_right(update.message.chat.id): if len(self._items_info) > 0: - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("Infos from the items:"), reply_markup={"keyboard": self.create_info_reply_markup()}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Infos from the items:"), reply_markup={"keyboard": self.create_info_reply_markup()}) else: - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("no items have attribute telegram_info!"), reply_markup={"keyboard": self.create_info_reply_markup()}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("no items have attribute telegram_info!"), reply_markup={"keyboard": self.create_info_reply_markup()}) async def cHandler_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -866,7 +937,7 @@ async def cHandler_start(self, update: Update, context: ContextTypes.DEFAULT_TYP else: self.logger.warning('No chat_ids defined') - await context.bot.send_message(chat_id=update.message.chat.id, text=text) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=text) async def cHandler_info_attr(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -882,7 +953,6 @@ async def cHandler_info_attr(self, update: Update, context: ContextTypes.DEFAULT if c_key in self._items_info: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug(f"info-command: {c_key}") - #self.list_items_info(update.message.chat.id, c_key) await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.list_items_info(c_key)) else: await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("unknown command %s") % c_key) @@ -901,7 +971,6 @@ async def cHandler_lo(self, update: Update, context: ContextTypes.DEFAULT_TYPE) for logic in sorted(self.logics.return_defined_logics()): # list with the names of all logics that are currently loaded data = [] info = self.logics.get_logic_info(logic) - # self.logger.debug(f"logic_info: {info}") if len(info) == 0 or not info['enabled']: data.append("disabled") if 'next_exec' in info: @@ -936,11 +1005,10 @@ async def cHandler_control(self, update: Update, context: ContextTypes.DEFAULT_T self.logger.debug(f"/control: show item-menu with registered items with specific attribute for chat_id={update.message.chat.id}") if self.has_write_access_right(update.message.chat.id): if len(self._items_control) > 0: - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) - await context.bot.send_message(chat_id=update.message.chat.id, text=self.list_items_control()) - #self.list_items_control(update.message.chat.id) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard":self.create_control_reply_markup()}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.list_items_control()) else: - await context.bot.send_message(chat_id=update.message.chat.id, text=self.translate("no items have attribute telegram_control!"), reply_markup={"keyboard": self.create_control_reply_markup()}) + await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("no items have attribute telegram_control!"), reply_markup={"keyboard": self.create_control_reply_markup()}) async def cHandler_control_attr(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """ @@ -1035,6 +1103,7 @@ def list_items_control(self): """ Show registered items and value with specific attribute ITEM_ATTR_CONTROL """ + text = "" for key, value in sorted(self._items_control.items()): # {'type':type,'item':item} item = value['item'] if item.type(): @@ -1135,16 +1204,14 @@ async def change_item(self, update, context, name, dicCtl): text = self.translate("no items found with the attribute %s") % ITEM_ATTR_CONTROL await context.bot.sendMessage(chat_id=chat_id, text=text) - async def telegram_change_item_timeout(self, **kwargs): + def telegram_change_item_timeout(self, **kwargs): update = None - context = None if 'update' in kwargs: update = kwargs['update'] - if 'context' in kwargs: - context = kwargs['context'] - if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug(f"Answer control_item timeout update:{update} context:{context}") - if self._waitAnswer is not None: - self._waitAnswer = None - # self._bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()}) - await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()}) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"Answer control_item timeout update:{update}") + if self._waitAnswer is not None: + self._waitAnswer = None + self.msg_broadcast(msg=self.translate("Control/Change item-values:"), chat_id=update.message.chat.id, reply_markup={"keyboard": self.create_control_reply_markup()} ) + elif self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug(f"telegram_change_item_timeout: update argument missing") diff --git a/telegram/plugin.yaml b/telegram/plugin.yaml index 09d9dfc1a..5bf572ecc 100755 --- a/telegram/plugin.yaml +++ b/telegram/plugin.yaml @@ -11,10 +11,10 @@ plugin: keywords: telegram chat messenger photo support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1548691-support-thread-für-das-telegram-plugin - version: 2.0.1 # Plugin version - sh_minversion: '1.9.5' # minimum shNG version to use this plugin + version: 2.0.3 # Plugin version + sh_minversion: '1.10' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) - # py_minversion: 3.6 # minimum Python version to use for this plugin + py_minversion: '3.6' # minimum Python version to use for this plugin # py_maxversion: # maximum Python version to use for this plugin (leave empty if latest) multi_instance: True # plugin supports multi instance restartable: True @@ -92,6 +92,14 @@ parameters: de: 'Telegram Threads zur leichteren Unterscheidung in der Thread Liste umbenennen' en: 'Rename Telegram threads for easier distinction in thread list' + pause_item: + type: str + default: '' + description: + de: 'Item, um die Ausführung des Plugins zu steuern' + en: 'item for controlling plugin execution' + + item_attributes: # Definition of item attributes defined by this plugin telegram_message: diff --git a/telegram/user_doc.rst b/telegram/user_doc.rst index e1b8890a8..25dfdd892 100755 --- a/telegram/user_doc.rst +++ b/telegram/user_doc.rst @@ -599,6 +599,12 @@ dargestellt und die entsprechenden Aktionen ausgeführt. if msg != '': telegram_plugin.msg_broadcast(msg, message_chat_id, reply_markup, parse_mode) +Changelog +--------- +V2.0.3 Plugin mit stop/run/pause_item steuerbar +V2.0.2 Fehler beim Kommando ``/control`` behoben +V2.0.0 Umbau auf neues Telegram Paket (V20.2+) mit async + Web Interface ============= diff --git a/uzsu/plugin.yaml b/uzsu/plugin.yaml index 12c7eb150..f03cf0f8a 100755 --- a/uzsu/plugin.yaml +++ b/uzsu/plugin.yaml @@ -149,6 +149,7 @@ item_structs: visu_acl: ro eval: sh...planned() eval_trigger: .. + initial_value: "{'value': '-', 'next': '-'}" value: remark: The next value @@ -156,7 +157,6 @@ item_structs: visu_acl: ro eval: sh...property.value['value'] eval_trigger: .. - initial_value: '-' time: remark: The next time @@ -164,7 +164,6 @@ item_structs: visu_acl: ro eval: sh...property.value['next'] eval_trigger: .. - initial_value: '-' logic_parameters: NONE # Definition of logic parameters defined by this plugin diff --git a/visu_smartvisu/__init__.py b/visu_smartvisu/__init__.py index 504d35b83..feaaa1d9f 100755 --- a/visu_smartvisu/__init__.py +++ b/visu_smartvisu/__init__.py @@ -41,10 +41,9 @@ ######################################################################### class SmartVisu(SmartPlugin): - PLUGIN_VERSION="1.3.4" + PLUGIN_VERSION = "1.3.4" ALLOW_MULTIINSTANCE = True - def my_to_bool(self, value, attr='', default=False): try: result = self.to_bool(value) @@ -53,15 +52,14 @@ def my_to_bool(self, value, attr='', default=False): self.logger.error("smartVISU: Invalid value '"+str(value)+"' configured for attribute "+attr+" in plugin.conf, using '"+str(result)+"' instead") return result - - def __init__(self, smarthome, smartvisu_dir='', generate_pages='True', overwrite_templates='Yes', visu_style = 'std', handle_widgets='True' ): + def __init__(self, smarthome, smartvisu_dir='', generate_pages='True', overwrite_templates=True, visu_style='std', handle_widgets='True'): self.logger = logging.getLogger(__name__) self._sh = smarthome self.smartvisu_dir = str(smartvisu_dir) self._generate_pages = self.my_to_bool(generate_pages, 'generate_pages', True) self.overwrite_templates = self.my_to_bool(overwrite_templates, 'overwrite_templates', True) - if visu_style.lower() in ['std','blk']: + if visu_style.lower() in ['std', 'blk']: self.visu_style = visu_style.lower() else: self.visu_style = 'std' @@ -74,7 +72,6 @@ def __init__(self, smarthome, smartvisu_dir='', generate_pages='True', overwrite # else: # self.logger.log(logging.WARNING, "Handling for smartVISU v{} in directory {}".format(self.smartvisu_version, self.smartvisu_dir)) - def run(self): self.alive = True if self.smartvisu_dir != '': @@ -97,11 +94,9 @@ def run(self): self.logger.exception("SmartVisuGenerator: Exception: {}".format(e)) self.logger.info("Finished smartVISU v{} handling".format(self.smartvisu_version)) - def stop(self): self.alive = False - def parse_item(self, item): # Relative path support (release 1.3 and up) item.expand_relativepathes('sv_widget', "'", "'") @@ -109,15 +104,12 @@ def parse_item(self, item): item.expand_relativepathes('sv_nav_aside', "'", "'") item.expand_relativepathes('sv_nav_aside2', "'", "'") - def parse_logic(self, logic): pass - def update_item(self, item, caller=None, source=None, dest=None): pass - def get_smartvisu_version(self): """ Determine which smartVISU version is installed in 'smartvisu_dir' @@ -147,7 +139,7 @@ def get_smartvisu_version(self): class SmartVisuGenerator: - def __init__(self, smarthome, smartvisu_dir='', overwrite_templates='Yes', visu_style='std', smartvisu_version=''): + def __init__(self, smarthome, smartvisu_dir='', overwrite_templates=True, visu_style='std', smartvisu_version=''): self.logger = logging.getLogger(__name__) self._sh = smarthome self.items = Items.get_instance() @@ -156,9 +148,9 @@ def __init__(self, smarthome, smartvisu_dir='', overwrite_templates='Yes', visu_ self.smartvisu_version = smartvisu_version self.overwrite_templates = overwrite_templates self.visu_style = visu_style.lower() - if not self.visu_style in ['std','blk']: + if self.visu_style not in ['std', 'blk']: self.visu_style = 'std' - self.logger.warning("SmartVisuGenerator: visu_style '{}' unknown, using visu_style '{1}'".format(visu_style, self.visu_style)) + self.logger.warning("SmartVisuGenerator: visu_style '{0}' unknown, using visu_style '{1}'".format(visu_style, self.visu_style)) self.logger.info("Generating pages for smartVISU v{}".format(self.smartvisu_version)) @@ -174,7 +166,6 @@ def __init__(self, smarthome, smartvisu_dir='', overwrite_templates='Yes', visu_ self.pages() self.logger.info("Generating pages for smartVISU v{} End".format(self.smartvisu_version)) - def handle_heading_attributes(self, room): if 'sv_heading_right' in room.conf: heading_right = room.conf['sv_heading_right'] @@ -194,7 +185,6 @@ def handle_heading_attributes(self, room): heading = '' return heading - def get_widgetblocksize(self, item): """ Returns the blocksize for the block in which the item is to be displayed. @@ -206,13 +196,12 @@ def get_widgetblocksize(self, item): """ if 'sv_blocksize' in item.conf: blocksize = item.conf['sv_blocksize'] - if not blocksize in ['1','2','3']: + if blocksize not in ['1', '2', '3']: blocksize = '2' else: blocksize = '2' return blocksize - def get_attribute(self, attr, item): if attr in item.conf: attrvalue = item.conf[attr] @@ -220,14 +209,12 @@ def get_attribute(self, attr, item): attrvalue = '' return attrvalue - def room(self, room): """ Interpretation of the room-specific item-attributes. This routine is called once per 'sv_page'. :param room: Items (with room configuration) - :param tpldir: Directory where the template files are stored (within smartVISU) :return: html code to be included in the visu file for the room :rtype: str @@ -292,7 +279,6 @@ def room(self, room): r = self.parse_tpl('roomlite.html', [('{{ visu_name }}', str(room)), ('{{ visu_widgets }}', widgets), ('{{ visu_img }}', rimg), ('{{ visu_heading }}', heading)]) return r - def pages(self): if not self.remove_oldpages(): return @@ -341,7 +327,6 @@ def pages(self): lite_lis += self.parse_tpl('navi.html', [('{{ visu_page }}', item.property.path), ('{{ visu_name }}', str(item)), ('{{ visu_img }}', img), ('{{ visu_aside }}', nav_aside), ('{{ visu_aside2 }}', nav_aside2), ('item.name', str(item)), ("'item", "'" + item.property.path)]) self.write_parseresult(item.property.path+'.html', r) - nav = self.parse_tpl('navigation.html', [('{{ visu_navis }}', nav_lis)]) self.write_parseresult('room_nav.html', nav) @@ -351,7 +336,6 @@ def pages(self): nav = self.parse_tpl('navigation.html', [('{{ visu_navis }}', lite_lis)]) self.write_parseresult('roomlite_nav.html', nav) - self.copy_tpl('rooms.html') self.copy_tpl('roomslite.html') self.copy_tpl('category.html') @@ -373,7 +357,6 @@ def parse_tpl(self, template, replace): tpl = tpl.replace(s, r) return tpl - def write_parseresult(self, htmlfile, parseresult): try: with open(self.outdir + '/' + htmlfile, 'w') as f: @@ -381,14 +364,13 @@ def write_parseresult(self, htmlfile, parseresult): except Exception as e: self.logger.warning("Could not write to {0}/{1}: {2}".format(self.outdir, htmlfile, e)) - def copy_tpl(self, tplname, destname=''): if destname == '': destname = tplname try: shutil.copy(self.tpldir + '/' + tplname, self.outdir + '/' + destname) except Exception as e: - self.logger.error("Could not copy {0} from {1} to {2}".format(tplname, tpldir, destdir)) + self.logger.error("Could not copy {0} from {1} to {2}".format(tplname, self.tpldir, self.outdir)) ######################################################################### @@ -435,9 +417,9 @@ def copy_templates(self): if self.smartvisu_version == '2.9': for fn in os.listdir(srcdir): - if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.tpldir, fn)) ): + if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.tpldir, fn))): self.logger.debug("copy_templates: Copying template '{}' from plugin to smartVISU v{}".format(fn, self.smartvisu_version)) - shutil.copy2( os.path.join(srcdir, fn), self.tpldir ) + shutil.copy2(os.path.join(srcdir, fn), self.tpldir) else: # create output directory @@ -450,10 +432,10 @@ def copy_templates(self): # Open file for twig import statements (for root.html) for fn in os.listdir(srcdir): - if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.tpldir, fn)) ): + if (self.overwrite_templates) or (not os.path.isfile(os.path.join(self.tpldir, fn))): self.logger.debug("copy_templates: Copying template '{}' from plugin to smartVISU v{}".format(fn, self.smartvisu_version)) try: - shutil.copy2( os.path.join(srcdir, fn), self.tpldir ) + shutil.copy2(os.path.join(srcdir, fn), self.tpldir) except Exception as e: self.logger.error("Could not copy {0} from {1} to {2}".format(fn, srcdir, self.tpldir)) return @@ -487,17 +469,16 @@ def __init__(self, smarthome, smartvisu_dir='', smartvisu_version=''): self.install_widgets(self._sh) - def install_widgets(self, smarthome): if not self.remove_oldfiles(): return if self.smartvisu_version == '2.7' or self.smartvisu_version == '2.8': # make a backup copy of root.html if it doesn't exist (for full integeration) - if not os.path.isfile( self.pgbdir + '/root_master.html' ): - self.logger.warning( "install_widgets: Creating a copy of root.html" ) + if not os.path.isfile(self.pgbdir + '/root_master.html'): + self.logger.warning("install_widgets: Creating a copy of root.html") try: - shutil.copy2( self.pgbdir + '/root.html', self.pgbdir + '/root_master.html' ) + shutil.copy2(self.pgbdir + '/root.html', self.pgbdir + '/root_master.html') except Exception as e: self.logger.error("Could not copy {} from {} to {}".format('root.html', self.pgbdir, self.pgbdir + '/root_master.html')) return @@ -506,17 +487,17 @@ def install_widgets(self, smarthome): f_root = open(self.pgbdir + '/root_master.html', "r") root_contents = f_root.readlines() f_root.close() - self.logger.debug( "root_contents: {0}".format(root_contents) ) + self.logger.debug("root_contents: {0}".format(root_contents)) # find insert points in original root.html - iln_html = self.findinsertline( root_contents, '{% import "plot.html" as plot %}' ) - iln_js = self.findinsertline( root_contents, "{% if isfile('pages/'~config_pages~'/visu.js') %}" ) - iln_css = self.findinsertline( root_contents, "{% if isfile('pages/'~config_pages~'/visu.css') %}" ) + iln_html = self.findinsertline(root_contents, '{% import "plot.html" as plot %}') + iln_js = self.findinsertline(root_contents, "{% if isfile('pages/'~config_pages~'/visu.js') %}") + iln_css = self.findinsertline(root_contents, "{% if isfile('pages/'~config_pages~'/visu.css') %}") # copy widgets from plugin directories of configured plugins # read plungin.conf _conf = lib.config.parse(smarthome._plugin_conf) - self.logger.debug( "install_widgets: _conf = {}".format(str(_conf)) ) + self.logger.debug("install_widgets: _conf = {}".format(str(_conf))) mypluginlist = [] for plugin in _conf: self.logger.debug("install_widgets: Plugin section '{}', class_path = '{}', plugin_name = '{}'".format(plugin, str(_conf[plugin].get('class_path', '')), str(_conf[plugin].get('plugin_name', '')))) @@ -525,15 +506,15 @@ def install_widgets(self, smarthome): plgdir = 'plugins.' + _conf[plugin].get('plugin_name', '') if plgdir not in mypluginlist: # process each plugin only once - mypluginlist.append( plgdir ) + mypluginlist.append(plgdir) if self.smartvisu_version == '2.7' or self.smartvisu_version == '2.8': - self.copy_widgets( plgdir.replace('.', '/'), root_contents, iln_html, iln_js, iln_css ) + self.copy_widgets(plgdir.replace('.', '/'), root_contents, iln_html, iln_js, iln_css) else: - self.copy_widgets( plgdir.replace('.', '/') ) + self.copy_widgets(plgdir.replace('.', '/')) if self.smartvisu_version == '2.7' or self.smartvisu_version == '2.8': # write root.html with additions for widgets - self.logger.info( "Adding import statements to root.html" ) + self.logger.info("Adding import statements to root.html") f_root = open(self.pgbdir + '/root.html', "w") root_contents = "".join(root_contents) f_root.write(root_contents) @@ -542,16 +523,15 @@ def install_widgets(self, smarthome): ######################################################################### - def findinsertline(self, root_contents, searchstring ): + def findinsertline(self, root_contents, searchstring): # look for insert point in root.html: find and return line that contains the searchstring iln = '' for ln in root_contents: - if ln.find( searchstring ) != -1: + if ln.find(searchstring) != -1: iln = ln if iln == '': self.logger.warning("findinsertline: No insert point for pattern {0}".format(searchstring)) - return( iln ) - + return(iln) def copy_widgets(self, plgdir, root_contents='', iln_html='', iln_js='', iln_css=''): wdgdir = 'sv_widgets' @@ -569,47 +549,45 @@ def copy_widgets(self, plgdir, root_contents='', iln_html='', iln_js='', iln_css if self.smartvisu_version == '2.9': if os.path.splitext(fn)[1] == '.png': # copy icons to the icons directory - shutil.copy2( os.path.join(srcdir, fn), self.icndir ) + shutil.copy2(os.path.join(srcdir, fn), self.icndir) else: # the rest to the widgets directory & strip 'widgets_' from name if fn.startswith('widget_'): dn = fn[len('widget_'):] - shutil.copy2( os.path.join(srcdir, fn), os.path.join(self.outdir, dn) ) + shutil.copy2(os.path.join(srcdir, fn), os.path.join(self.outdir, dn)) else: - shutil.copy2( os.path.join(srcdir, fn), self.outdir ) + shutil.copy2(os.path.join(srcdir, fn), self.outdir) else: # v2.7 & v2.8 - shutil.copy2( srcdir + '/' + fn, self.outdir ) + shutil.copy2(srcdir + '/' + fn, self.outdir) if self.smartvisu_version == '2.7' or self.smartvisu_version == '2.8': if (fn[0:7] == "widget_") and (fn[-5:] == ".html"): self.logger.info("- Installing for SV v{} from '{}': {}".format(self.smartvisu_version, plgdir, '\t' + fn)) if iln_html != '': - self.create_htmlinclude(fn, fn[7:-5] , root_contents, iln_html) + self.create_htmlinclude(fn, fn[7:-5], root_contents, iln_html) if (fn[0:7] == "widget_") and (fn[-3:] == ".js"): if iln_js != '': - self.create_jsinclude(fn, fn[7:-3] , root_contents, iln_js) + self.create_jsinclude(fn, fn[7:-3], root_contents, iln_js) if (fn[0:7] == "widget_") and (fn[-4:] == ".css"): if iln_css != '': - self.create_cssinclude(fn, fn[7:-4] , root_contents, iln_css) + self.create_cssinclude(fn, fn[7:-4], root_contents, iln_css) return - def create_htmlinclude(self, filename, classname, root_contents, iln_html): insertln = root_contents.index(iln_html) +1 # Insert widget statements to root_contents if insertln != 0: - self.logger.debug( "create_htmlinclude: Inserting in root.html at line {0} after '{1}'".format(insertln, iln_html) ) + self.logger.debug("create_htmlinclude: Inserting in root.html at line {0} after '{1}'".format(insertln, iln_html)) twig_statement = '\t{% import "' + self.shwdgdir + '/' + filename + '" as ' + classname + ' %}' root_contents.insert(insertln, twig_statement+'\n') - def create_jsinclude(self, filename, classname, root_contents, iln_js): insertln = root_contents.index(iln_js) # Insert widget statements to root_contents if insertln > -1: - self.logger.debug( "create_jsinclude: Inserting in root.html at line {0} before '{1}'".format(insertln, iln_js) ) + self.logger.debug("create_jsinclude: Inserting in root.html at line {0} before '{1}'".format(insertln, iln_js)) twig_statement1 = "\t{% if isfile('widgets/sh_widgets/" + filename + "') %}" twig_statement2 = '\t\t{% endif %}' self.logger.debug('create_jsinclude: {0}'.format(twig_statement1)) @@ -617,12 +595,11 @@ def create_jsinclude(self, filename, classname, root_contents, iln_js): root_contents.insert(insertln, twig_statement2+'\n') root_contents.insert(insertln, twig_statement1+'\n') - def create_cssinclude(self, filename, classname, root_contents, iln_css): insertln = root_contents.index(iln_css) # Insert widget statements to root_contents if insertln > -1: - self.logger.debug( "create_jsinclude: Inserting in root.html at line {0} before '{1}'".format(insertln, iln_css) ) + self.logger.debug("create_jsinclude: Inserting in root.html at line {0} before '{1}'".format(insertln, iln_css)) twig_statement1 = "\t{% if isfile('widgets/sh_widgets/" + filename + "') %}" twig_statement2 = '\t\t{% endif %}' self.logger.debug('create_cssinclude: {0}'.format(twig_statement1)) @@ -630,7 +607,6 @@ def create_cssinclude(self, filename, classname, root_contents, iln_css): root_contents.insert(insertln, twig_statement2+'\n') root_contents.insert(insertln, twig_statement1+'\n') - def remove_oldfiles(self): # clear temp directory if not os.path.isdir(self.tmpdir): @@ -668,4 +644,3 @@ def remove_oldfiles(self): self.logger.warning("Could not delete file {0}: {1}".format(fp, e)) return True - diff --git a/withings_health/user_doc.rst b/withings_health/user_doc.rst index 5cada8c16..98395b516 100644 --- a/withings_health/user_doc.rst +++ b/withings_health/user_doc.rst @@ -23,7 +23,8 @@ Vorbereitung Dieses Plugin benötigt die withings-api. Sie müssen sich unter `Withings Account `_ registrieren und im Dashboard -eine Applikation anlegen. Der Name ist frei wählbar, die (lokale) Callback-URL wird über die Weboberfläche des Plugins angezeigt: http://:/plugin/withings_health. +eine Applikation anlegen. Der Name ist frei wählbar, die (lokale) Callback-URL wird über die Weboberfläche des Plugins +angezeigt: http://:/plugin/withings_health (Die IP sollte die von SmartHomeNG sein!). Wenn Sie sich bei der `Withings App `_ einloggen, kann die achtstellige Zahl in der URL ausgelesen und in der Pluginkonfiguration als user_id angegeben werden. diff --git a/yamahayxc/__init__.py b/yamahayxc/__init__.py index a2ec97899..35975be9f 100755 --- a/yamahayxc/__init__.py +++ b/yamahayxc/__init__.py @@ -29,7 +29,6 @@ # - parse zone().range_step -> read range/step for vol / eq -import logging import requests import socket import json @@ -53,20 +52,20 @@ class YamahaYXC(SmartPlugin): """ This is the main plugin class YamahaYXC to control YXC-compatible devices. """ - PLUGIN_VERSION = "1.0.6" + PLUGIN_VERSION = "1.0.7" ALLOW_MULTIINSTANCE = False # # public functions # - def __init__(self, smarthome): + def __init__(self, smarthome, **kwargs): """ Default init function """ - self.logger = logging.getLogger(__name__) - self.logger.info("Init YamahaYXC") + super().__init__(**kwargs) self._sh = smarthome + self.logger.info("Init YamahaYXC") # valid commands for use in item configuration 'yamahayxc_cmd = ...' self._yamaha_cmds = ['state', 'power', 'input', 'playback', 'preset', @@ -79,7 +78,7 @@ def __init__(self, smarthome): self._yamaha_ignore_cmds_upd = ['state', 'preset', 'alarm_on', 'alarm_time', 'alarm_beep'] # store items in 2D-array: - # _yamaha_dev [host] [cmd] = item + # _yamaha_dev [host] [cmd] = item # also see parse_item()... self._yamaha_dev = {} # store host addresses of devices @@ -106,13 +105,13 @@ def run(self): data, addr = self.sock.recvfrom(self.srv_buffer) try: host, port = addr - except: + except Exception as e: self.logger.warn( - "Error receiving data - host/port not readable") + f"Error receiving data - host/port not readable: {e}") return if host not in list(self._yamaha_dev.keys()): self.logger.debug( - "Received notify from unknown host {}".format(host)) + f"Received notify from unknown host {host}") else: # connected device sends updates every second for # about 10 minutes without further interaction @@ -128,11 +127,11 @@ def run(self): data_flat = {} try: data_flat.update(data['main']) - except: + except Exception: pass try: data_flat.update(data['netusb']) - except: + except Exception: pass # try all known command words... @@ -145,7 +144,7 @@ def run(self): notify_val = self._convert_value_yxc_to_plugin(data_flat[cmd], cmd, host) item = self._yamaha_dev[host][cmd] item(notify_val, "YamahaYXC") - except: + except Exception: pass # device told us new info is available? @@ -171,7 +170,7 @@ def stop(self): self.alive = False try: self.sock.shutdown(socket.SHUT_RDWR) - except: + except Exception: pass def parse_item(self, item): @@ -352,7 +351,7 @@ def _update_alarm_state(self, yamaha_host, update_items=True): alarm = state["alarm"] except KeyError: return - + alarm_on = alarm["alarm_on"] alarm_time = alarm["oneday"]["time"] alarm_beep = alarm["oneday"]["beep"] diff --git a/yamahayxc/plugin.yaml b/yamahayxc/plugin.yaml index 9da7b64bf..57eff09f8 100755 --- a/yamahayxc/plugin.yaml +++ b/yamahayxc/plugin.yaml @@ -64,7 +64,7 @@ plugin: # documentation: https://github.com/smarthomeNG/smarthome/wiki/CLI-Plugin # url of documentation (wiki) page support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1174064-plugin-yamaha-musiccast-geräte-neuere-generation - version: 1.0.6 # Plugin version + version: 1.0.7 # Plugin version sh_minversion: '1.3' # minimum shNG version to use this plugin # sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) multi_instance: False # plugin supports multi instance