Skip to content

Commit

Permalink
Merge pull request #975 from sisamiwe/dev-sonos
Browse files Browse the repository at this point in the history
Sonos Plugin: improve dpt3 handling, make plugin restartable
  • Loading branch information
Morg42 authored Dec 8, 2024
2 parents 1d7c7a6 + 0d99188 commit 75550cf
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 41 deletions.
89 changes: 53 additions & 36 deletions sonos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2994,7 +2994,7 @@ class Sonos(SmartPlugin):
"""
Main class of the Plugin. Does all plugin specific stuff
"""
PLUGIN_VERSION = "1.8.8"
PLUGIN_VERSION = "1.8.9"

def __init__(self, sh):
"""Initializes the plugin."""
Expand All @@ -3007,14 +3007,14 @@ def __init__(self, sh):
self._tts = self.get_parameter_value("tts")
self._snippet_duration_offset = float(self.get_parameter_value("snippet_duration_offset"))
self._discover_cycle = self.get_parameter_value("discover_cycle")
self.webif_pagelength = self.get_parameter_value('webif_pagelength')
local_webservice_path = self.get_parameter_value("local_webservice_path")
local_webservice_path_snippet = self.get_parameter_value("local_webservice_path_snippet")
webservice_ip = self.get_parameter_value("webservice_ip")
webservice_port = self.get_parameter_value("webservice_port")
speaker_ips = self.get_parameter_value("speaker_ips")
self._pause_item_path = self.get_parameter_value('pause_item')
except KeyError as e:
self.logger.critical(f"Plugin '{self.get_shortname()}': Inconsistent plugin (invalid metadata definition: {e} not defined)")
self.logger.critical(f"Plugin '{self.get_fullname()}': Inconsistent plugin (invalid metadata definition: {e} not defined)")
self._init_complete = False
return

Expand Down Expand Up @@ -3054,6 +3054,10 @@ def __init__(self, sh):

def run(self):
self.logger.debug("Run method called")

# let the plugin change the state of pause_item
if self._pause_item:
self._pause_item(False, self.get_fullname())

# do initial speaker discovery and set scheduler
self._discover()
Expand All @@ -3063,13 +3067,16 @@ def run(self):
self.alive = True

def stop(self):
self.logger.debug("Stop method called")
self.logger.dbghigh(self.translate("Methode '{method}' aufgerufen", {'method': 'stop()'}))

# let the plugin change the state of pause_item
if self._pause_item:
self._pause_item(True, self.get_fullname())

if self.webservice:
self.webservice.stop()

if self.scheduler_get('sonos_discover_scheduler'):
self.scheduler_remove('sonos_discover_scheduler')
self.scheduler_remove_all()

for uid, speaker in sonos_speaker.items():
speaker.dispose()
Expand All @@ -3084,6 +3091,12 @@ def parse_item(self, item: Items) -> object:
:param item: item to parse
:return: update function or None
"""

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

item_config = dict()

Expand All @@ -3100,7 +3113,7 @@ def parse_item(self, item: Items) -> object:

if self.has_iattr(item.conf, 'sonos_recv'):
# create Speaker instance if not exists
_initialize_speaker(uid, self.logger, self.get_shortname())
_initialize_speaker(uid, self.logger, self.get_fullname())

# to make code smaller, map sonos_cmd value to the Speaker property by name
item_attribute = self.get_iattr_value(item.conf, 'sonos_recv')
Expand Down Expand Up @@ -3147,22 +3160,22 @@ def parse_item(self, item: Items) -> object:
self.logger.warning("volume_dpt3 item has no volume parent item. Ignoring!")
return

# make sure there is a child helper item
child_helper = None
# make sure there is a child volume helper item
volume_helper_item = None
for child in item.return_children():
if self.has_iattr(child.conf, 'sonos_attrib'):
if self.get_iattr_value(child.conf, 'sonos_attrib').lower() == 'dpt3_helper':
child_helper = child
volume_helper_item = child
break

if child_helper is None:
self.logger.warning("volume_dpt3 item has no helper item. Ignoring!")
if volume_helper_item is None:
self.logger.warning("volume_dpt3 item has no volume helper item. Ignoring!")
return

dpt3_step = self.get_iattr_value(item.conf, 'sonos_dpt3_step')
dpt3_time = self.get_iattr_value(item.conf, 'sonos_dpt3_time')

item_config.update({'volume_item': parent_item, 'helper': child_helper, 'dpt3_step': dpt3_step, 'dpt3_time': dpt3_time})
item_config.update({'volume_item': parent_item, 'helper_item': volume_helper_item, 'dpt3_step': dpt3_step, 'dpt3_time': dpt3_time})
self.add_item(item, config_data_dict=item_config, updating=True)
return self._handle_dpt3

Expand Down Expand Up @@ -3226,36 +3239,31 @@ def play_alert_all_speakers(self, alert_uri, speaker_list=[], alert_volume=20, a
zone.snap.restore(fade=fade_back)

def _handle_dpt3(self, item, caller=None, source=None, dest=None):
if caller != self.get_shortname():
"""Handle relative volumen change via a received relative dim command (dpt3) by making use of internal fadeing"""

if caller != self.get_fullname():
item_config = self.get_item_config(item)
volume_item = item_config['volume_item']
volume_helper = item_config['helper']
volume_helper_item = item_config['helper']
vol_step = item_config['dpt3_step']
vol_time = item_config['dpt3_time']
vol_max = self._resolve_max_volume_command(item)

if vol_max < 0:
vol_max = 100

current_volume = int(volume_item())
if current_volume < 0:
current_volume = 0
if current_volume > 100:
current_volume = 100

volume_helper(current_volume)
vol_max = max(0, self._resolve_max_volume_command(item)) or 100
_current_volume = max(0, min(100, int(volume_item())))
volume_helper_item(_current_volume, self.get_fullname())

if item()[1] == 1:
self.logger.debug(f"Starte relative Lautstärkeänderung.")
if item()[0] == 1:
# up
volume_helper.fade(vol_max, vol_step, vol_time)
self.logger.debug(f"erhöhe Lautstärke mit {vol_step} Stufe(n) pro {vol_time}s")
volume_helper_item.fade(vol_max, vol_step, vol_time)
else:
# down
volume_helper.fade(0 - vol_step, vol_step, vol_time)
self.logger.debug(f"reduziere Lautstärke mit {vol_step} Stufe(n) pro {vol_time}s")
volume_helper_item.fade(0 - vol_step, vol_step, vol_time)
else:
volume_helper(int(volume_helper() + 1))
volume_helper(int(volume_helper() - 1))
self.logger.debug(f"Stoppe relative Lautstärkeänderung.")
volume_helper_item(int(volume_helper_item()), self.get_fullname())

def _check_webservice_ip(self, webservice_ip: str) -> bool:
if not webservice_ip == '' and not webservice_ip == '0.0.0.0':
Expand Down Expand Up @@ -3283,7 +3291,7 @@ def _check_webservice_port(self, webservice_port: int) -> bool:
else:
self.logger.error(f"Your webservice_port parameter is invalid. '{webservice_port}' is not within port range 1024-65535. TTS disabled!")
return False

return True

def _check_local_webservice_path(self, local_webservice_path: str) -> bool:
Expand Down Expand Up @@ -3457,10 +3465,19 @@ def update_item(self, item: Items, caller: object, source: object, dest: object)
:param source: if given it represents the source
:param dest: if given it represents the dest
"""

if self.alive and caller != self.get_fullname():

self.logger.debug(f"update_item called for {item.path()} with value {item()}")
# check for pause item
if item is self._pause_item and caller != self.get_fullname():
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

# check for sonos item
if self.alive and caller != self.get_fullname():
self.logger.debug(f"update_item called for {item.path()} with value {item()} {caller=}")
item_config = self.get_item_config(item)
command = item_config.get('sonos_send', '').lower()
uid = item_config.get('uid')
Expand Down Expand Up @@ -3780,7 +3797,7 @@ def _discover(self, force: bool = False) -> None:
# sonos_speaker[uid].check_subscriptions()
else:
self.logger.warning(f"Initializing new speaker with uid={uid} and ip={zone.ip_address}")
_initialize_speaker(uid, self.logger, self.get_shortname())
_initialize_speaker(uid, self.logger, self.get_fullname())
sonos_speaker[uid].soco = zone

sonos_speaker[uid].is_initialized = True
Expand Down
17 changes: 12 additions & 5 deletions sonos/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ plugin:
documentation: https://github.com/smarthomeNG/plugins/blob/master/sonos/README.md
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/25151-sonos-anbindung

version: 1.8.8 # Plugin version
sh_minversion: '1.5.1' # minimum shNG version to use this plugin
py_minversion: '3.8' # minimum Python version to use for this plugin
version: 1.8.9 # Plugin version
sh_minversion: '1.10.0.3' # minimum shNG version to use this plugin
py_minversion: '3.9' # minimum Python version to use for this plugin
multi_instance: False # plugin supports multi instance
restartable: unknown
restartable: yes
classname: Sonos # class containing the plugin

parameters:
Expand Down Expand Up @@ -61,7 +61,7 @@ parameters:
If "tts" is enabled and this option is not set, the value "local_webservice_path" is used for the audio snippet path.'

webservice_ip:
type: ip
type: ipv4
description:
de: '(optional) Für TTS und die Audio-Snippet-Funktionalität wird ein simpler Webservice gestartet. \n
Die IP-Adresse wird per default automatisch ermittelt, kann hier aber manuell gesetzt werden.'
Expand All @@ -85,6 +85,13 @@ parameters:
de: "(optional) Verlängert die Dauer von Snippet Audio Dateien um einen festen Offset in Sekunden."
en: "(optional) Extend snippet duration by a fixed offset specified in seconds"

pause_item:
type: str
default: ''
description:
de: 'Item, um die Ausführung des Plugins zu steuern'
en: 'item for controlling plugin execution'

item_attributes:
sonos_uid:
type: str
Expand Down

0 comments on commit 75550cf

Please sign in to comment.