Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Denon Plugin: some fixes and update to use resend feature #951

Merged
merged 20 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea4ef26
denon plugin: fix initial maxvolume check (it's not available)
onkelandy May 29, 2024
f8a4763
denon plugin: add readafterwrite parameter
onkelandy May 29, 2024
00daeb0
denon plugin: add resend options on plugin.yaml
onkelandy May 29, 2024
24b321e
denon plugin: query most relevant commands after powering on a zone
onkelandy May 29, 2024
a66d84d
denon plugin: add resend function
onkelandy May 29, 2024
c3a4494
denon plugin: fixes, updated plugin.yaml
onkelandy May 31, 2024
fdc0d48
denon plugin: add on_suspend/resume functions
onkelandy Jun 1, 2024
58d9567
denon plugin: implement delay for initial value read
onkelandy Jun 1, 2024
fabe5fd
denon plugin: fix resend_info
onkelandy Jul 3, 2024
8b847c3
denon plugin: code fix
onkelandy Jul 7, 2024
45c1352
denon plugin: add resume_initial_read and protocol (for resend)
onkelandy Aug 11, 2024
83dd11f
denon plugin: revert resend changes as it is now included as a protoc…
onkelandy Aug 11, 2024
d3afef8
denon plugin: remove reading of volumemax from structs (it is not ava…
onkelandy Aug 11, 2024
bc0633e
denon plugin: remove threading import
onkelandy Aug 11, 2024
9befb9f
denon plugin: remove protocol and command_class from plugin.yaml
onkelandy Aug 17, 2024
626deaf
denon plugin: auto set command class
onkelandy Aug 17, 2024
d52df4c
add sdp metadata
Morg42 Aug 17, 2024
4925eda
denon plugin: move custom inputnames to dataype
onkelandy Aug 17, 2024
eeca349
denon plugin: update plugin.yaml
onkelandy Aug 17, 2024
06df538
Merge branch 'develop' into denon
onkelandy Aug 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 25 additions & 63 deletions denon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import builtins
import os
import sys
import time

if __name__ == '__main__':
builtins.SDP_standalone = True
Expand All @@ -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

Expand All @@ -60,7 +62,7 @@ def on_connect(self, by=None):
self.send_command('general.custom_inputnames')

def _set_device_defaults(self):

self._use_callbacks = True
self._custom_inputnames = {}

# set our own preferences concerning connections
Expand All @@ -72,77 +74,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])
6 changes: 3 additions & 3 deletions denon/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
'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': {'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(.*)'},
Expand Down Expand Up @@ -106,7 +106,7 @@
'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})'},
'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}},
'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}},
Expand Down Expand Up @@ -424,7 +424,7 @@
'on_change': [".custom_name = '' if sh.....general.custom_inputnames() == {} else sh.....general.custom_inputnames()[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()[value]) if sh......general.custom_inputnames.reverse() != {} else None"
}
}
}
11 changes: 11 additions & 0 deletions denon/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading
Loading