diff --git a/apps/ad_toolbox b/apps/ad_toolbox index a387a13..191e69e 160000 --- a/apps/ad_toolbox +++ b/apps/ad_toolbox @@ -1 +1 @@ -Subproject commit a387a13fa27f06a8982bfa84ed8692bb7c4fb6a0 +Subproject commit 191e69eb1d82a1336645fb86b79cacd981466ab3 diff --git a/apps/global/homecinema.yaml b/apps/global/homecinema.yaml index 790f0fa..1df0e85 100644 --- a/apps/global/homecinema.yaml +++ b/apps/global/homecinema.yaml @@ -2,6 +2,11 @@ samsung_the_frame: module: samsungtheframe class: SamsungTheFrame + volume_up_events: + - the_frame_volume_up + volume_down_events: + - the_frame_volume_down + ip_address: 10.0.0.58 #broadcast_address: 10.0.0.255 #mac_address: 98:06:3C:85:93:38 @@ -230,4 +235,48 @@ apple_remote: virtual_events: toggle_use_bravia_theater_for_the_frame: event_name: toggle_use_bravia_theater_for_the_frame - event_condition: binary_sensor.is_tv_on and binary_sensor.ir_bridge_apple_tv_mute_button_short_press \ No newline at end of file + event_condition: binary_sensor.is_tv_on and binary_sensor.ir_bridge_apple_tv_mute_button_short_press + +# Volume buttons received by the IR bridge: Bravia > TV > Sonos. +homecinema_volume: + module: eventsforwarder + class: EventsForwarder + priority: 100 + + forwarders: + volume_up: + events_to_listen: + - esphome.ir_bridge_volume_up + events_to_send: + the_frame: + event_name: the_frame_volume_up + event_condition: not (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') and binary_sensor.is_tv_on == 'on' + services_to_call: + bravia: + service: media_player/volume_up + event_condition: (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') + data: + entity_id: media_player.bravia_theatre + sonos: + service: media_player/volume_up + event_condition: not (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') and binary_sensor.is_tv_on != 'on' + data: + entity_id: media_player.living_room_sonos + volume_down: + events_to_listen: + - esphome.ir_bridge_volume_down + events_to_send: + the_frame: + event_name: the_frame_volume_down + event_condition: not (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') and binary_sensor.is_tv_on == 'on' + services_to_call: + bravia: + service: media_player/volume_down + event_condition: (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') + data: + entity_id: media_player.bravia_theatre + sonos: + service: media_player/volume_down + event_condition: not (media_player.bravia_theatre == 'on' or media_player.bravia_theatre == 'playing' or media_player.bravia_theatre == 'paused' or media_player.bravia_theatre == 'idle') and binary_sensor.is_tv_on != 'on' + data: + entity_id: media_player.living_room_sonos diff --git a/apps/samsungtheframe.py b/apps/samsungtheframe.py index 3126fd4..a71b5a7 100644 --- a/apps/samsungtheframe.py +++ b/apps/samsungtheframe.py @@ -1,5 +1,6 @@ import appdaemon.plugins.hass.hassapi as hass from ad_toolbox.smartobject import SmartObject +from ad_toolbox.eventhandler import EventHandler import ad_toolbox.smartcondition as SmartCondition from samsungtvws import SamsungTVWS from samsungtvws import exceptions as tv_exceptions @@ -16,6 +17,7 @@ class SamsungTheFrame(SmartObject): self.log(f"Initializing TheFrame with ip {self.args['ip_address']}") #self.need_to_turn_off_when_hdmi_gone = False + self.volume_connection = None self.requested_state = None self.requested_state_confirm_cb_handle = None @@ -44,7 +46,14 @@ class SamsungTheFrame(SmartObject): self.listen_state(self.update_sensors_cb,self.power_plug) self.listen_state(self.update_sensors_cb,self.power_plug_power) - self.update_sensors() + self.update_sensors() + + self.volume_event_handlers = [] + for direction in ('up', 'down'): + config = self.args.get(f'volume_{direction}_events') + if config: + self.volume_event_handlers.append(EventHandler( + self, config, self.on_volume_event, direction)) def on_update_tv_states(self): for state_evaluator in self.tv_states_evaluators: @@ -58,9 +67,40 @@ class SamsungTheFrame(SmartObject): def on_update_tv_state_sensor(self, entity, attribute, old, new, kwargs): if new != old: self.request_tv_state(new) - def connect(self): + def connect(self, timeout=None): #self.log(f"Connecting to {self.args['ip_address']}") - return SamsungTVWS(self.args['ip_address'],port = 8002, token_file = os.path.join(str(self.AD.app_dir),"theframe_token.txt")) + return SamsungTVWS(self.args['ip_address'],port = 8002, timeout=timeout, token_file = os.path.join(str(self.AD.app_dir),"theframe_token.txt")) + + def close_volume_connection(self): + tv = self.volume_connection + self.volume_connection = None + if tv is not None: + try: + tv.close() + except (OSError, websocket.WebSocketException): + pass + + def terminate(self): + self.close_volume_connection() + super().terminate() + + def on_volume_event(self, event_name, event_data, direction): + if self.get_state(self.is_on_sensor) != 'on': + self.close_volume_connection() + return + try: + if self.volume_connection is None: + self.volume_connection = self.connect(timeout=1) + # ESPHome already limits repetitions to one per 300 ms. The + # library's default one-second delay would queue button events. + self.volume_connection.send_key( + 'KEY_VOLUP' if direction == 'up' else 'KEY_VOLDOWN', + key_press_delay=0) + except (OSError, tv_exceptions.ConnectionFailure, tv_exceptions.ResponseError, + websocket.WebSocketException) as error: + self.log(f"Volume {direction} failed: {error}", level="WARNING") + # Reconnect on the next press, without replaying a failed command. + self.close_volume_connection() def has_power(self): return self.get_state(self.power_plug) == 'on' and float(self.get_state(self.power_plug_power)) > 35 @@ -279,6 +319,9 @@ class SamsungTheFrame(SmartObject): self.log(f"Updating sensor: {self.is_art_mode_sensor} = '{is_art_mode}'") self.set_state(self.is_art_mode_sensor,state = is_art_mode) + if is_on == 'off': + self.close_volume_connection() + if self.get_state(self.is_on_sensor) != is_on: self.log(f"Updating sensor: {self.is_on_sensor} = '{is_on}'") self.set_state(self.is_on_sensor,state = is_on)