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: suggested resend feature #937

Closed
wants to merge 11 commits into from
94 changes: 89 additions & 5 deletions denon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import builtins
import os
import sys
import threading
import time
import datetime
from lib.shtime import Shtime

if __name__ == '__main__':
builtins.SDP_standalone = True
Expand Down Expand Up @@ -56,12 +60,37 @@ class denon(SmartDevicePlugin):
PLUGIN_VERSION = '1.0.1'

def on_connect(self, by=None):
self.logger.debug(f"Connected.. Cycle for retry: {self._sendretry_cycle}")
if self._send_retries >= 1:
self.scheduler_add('resend', self._resend, cycle=self._sendretry_cycle)
self.logger.debug("Checking for custom input names.")
self.send_command('general.custom_inputnames')
if self.scheduler_get('read_initial_values'):
return
elif self._initial_value_read_delay > 0:
self.logger.dbghigh(f"On connect reading initial values after {self._initial_value_read_delay} seconds.")
self.scheduler_add('read_initial_values', self._read_initial_values, value={'force': True}, next=self.shtime.now() + datetime.timedelta(seconds=self._initial_value_read_delay))
else:
self._read_initial_values(True)

def _set_device_defaults(self):
def _on_suspend(self):
for scheduler in self.scheduler_get_all():
self.scheduler_remove(scheduler)

def _on_resume(self):
if self.scheduler_get('resend'):
self.scheduler_remove('resend')
self.logger.debug(f"Resuming.. Cycle for retry: {self._sendretry_cycle}")
if self._send_retries >= 1:
self.scheduler_add('resend', self._resend, cycle=self._sendretry_cycle)

def _set_device_defaults(self):
self._use_callbacks = True
self._custom_inputnames = {}
self._sending = {}
self._sending_lock = threading.Lock()
self._send_retries = self.get_parameter_value('send_retries')
self._sendretry_cycle = int(self.get_parameter_value('sendretry_cycle'))

# set our own preferences concerning connections
if PLUGIN_ATTR_NET_HOST in self._parameters and self._parameters[PLUGIN_ATTR_NET_HOST]:
Expand All @@ -80,15 +109,59 @@ def _set_device_defaults(self):
# 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 _send(self, data_dict, resend_info=None):
if resend_info is None:
resend_info = {}
if resend_info.get('returnvalue') is not None:
self._sending.update({resend_info.get('command'): resend_info})
return self._connection.send(data_dict)

def _resend(self):
if not self.alive or self.suspended:
return
self._sending_lock.acquire(True, 2)
_remove_commands = []
for command in list(self._sending.keys()):
_retry = self._sending[command].get("retry") or 0
_sent = True
if _retry is not None and _retry < self._send_retries:
self.logger.debug(f'Re-sending {command}, retry {_retry}.')
_sent = self.send_command(command, self._sending[command].get("returnvalue"), return_result=True, retry=_retry + 1)
elif _retry is not None and _retry >= self._send_retries:
_sent = False
if _sent is False:
_remove_commands.append(command)
self.logger.info(f"Giving up re-sending {command} after {_retry} retries.")
for command in _remove_commands:
self._sending.pop(command)
if self._sending_lock.locked():
self._sending_lock.release()

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 _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')

def on_data_received(self, by, data, command=None):

commands = None
Expand Down Expand Up @@ -132,7 +205,18 @@ def on_data_received(self, by, data, command=None):
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.logger.debug(f'received data "{data}" for command {command} converted to value {value}.')
if command in self._sending:
self._sending_lock.acquire(True, 2)
_retry = self._sending[command].get("retry") or 0
_compare = self._sending[command].get('returnvalue')
if self._sending[command].get('returntype')(value) == _compare:
self._sending.pop(command)
self.logger.debug(f'Correct answer for {command}, removing from send. Sending {self._sending}')
elif _retry is not None and _retry <= self._send_retries:
self.logger.debug(f'Should send again {self._sending}...')
if self._sending_lock.locked():
self._sending_lock.release()
self._dispatch_callback(command, value, by)

self._process_additional_data(base_command, data, value, custom, by)
Expand All @@ -145,4 +229,4 @@ def _check_for_custominputs(self, command, data):
self._custom_inputnames[src] = name

if __name__ == '__main__':
s = Standalone(lms, sys.argv[0])
s = Standalone(denon, sys.argv[0])
4 changes: 2 additions & 2 deletions denon/commands.py
Original file line number Diff line number Diff line change
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"
}
}
}
Loading
Loading