Skip to content

Commit

Permalink
Merge pull request #951 from onkelandy/denon
Browse files Browse the repository at this point in the history
Denon Plugin: some fixes and update to use resend feature
  • Loading branch information
onkelandy authored Aug 17, 2024
2 parents 28beff4 + 06df538 commit 0e2c273
Show file tree
Hide file tree
Showing 4 changed files with 3,041 additions and 3,052 deletions.
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

0 comments on commit 0e2c273

Please sign in to comment.