Vertex2 tweaking

This commit is contained in:
Pierre committed 2026-10-09 22:47:26 +02:00
1 parent 6d2fd26f66
commit dd4335c811
4 files changed
+76 -76

No files matched your search

+9
View File
@@ -154,6 +154,9 @@ vertex_2:
priority: 5 priority: 5
virtual_sensors: virtual_sensors:
default_values:
binary_sensor.is_tv_on: false
binary_sensor.vertex2_has_input_signal: false
sensors: sensors:
binary_sensor.projector_screen_down: cover.living_room_projection_screen.current_position < 90 binary_sensor.projector_screen_down: cover.living_room_projection_screen.current_position < 90
@@ -183,10 +186,14 @@ vertex_2:
name: TV name: TV
icon: mdi:television icon: mdi:television
use_audio_out_audio_flags: input_boolean.use_bravia_theater_for_the_frame use_audio_out_audio_flags: input_boolean.use_bravia_theater_for_the_frame
force_plus5:
trigger_conditions: binary_sensor.is_tv_on or binary_sensor.vertex2_has_input_signal
blocking_conditions: not switch.the_frame_power_plug
1: 1:
name: Projector name: Projector
icon: mdi:projector icon: mdi:projector
use_audio_out_audio_flags: True use_audio_out_audio_flags: True
force_plus5: switch.living_room_projector_plug
# vertex2_power_plug: # vertex2_power_plug:
# entity: switch.fibaro_vertex_plug_51 # entity: switch.fibaro_vertex_plug_51
@@ -219,6 +226,8 @@ vertex_2:
output_selected_sensor: sensor.vertex2_output_selected output_selected_sensor: sensor.vertex2_output_selected
input_selected_sensor: sensor.vertex2_input_selected input_selected_sensor: sensor.vertex2_input_selected
unreachable_sensor: binary_sensor.vertex2_unreachable unreachable_sensor: binary_sensor.vertex2_unreachable
command_processing_sensor: binary_sensor.vertex2_command_processing
command_processing_timeout: 20
use_bravia_theater_for_the_frame: use_bravia_theater_for_the_frame:
module: smartswitch module: smartswitch
+2 -2
View File
@@ -110,10 +110,10 @@ virtual_sensors:
binary_sensor.is_playstation_on: switch.ps5_136_power binary_sensor.is_playstation_on: switch.ps5_136_power
sensor.vertex2_input_selected_icon: sensor.vertex2_input_selected.icon sensor.vertex2_input_selected_icon: sensor.vertex2_input_selected.icon
retain_condition.vertex2_has_input_signal: retain_condition.vertex2_has_input_signal:
retain_time: 20 retain_time: 10
conditions: conditions:
trigger_conditions: (sensor.vertex2_video_signal and sensor.vertex2_video_signal != 'no signal') or sensor.vertex2_audio_signal trigger_conditions: (sensor.vertex2_video_signal and sensor.vertex2_video_signal != 'no signal') or sensor.vertex2_audio_signal
disable_conditions: binary_sensor.vertex2_unreachable disable_conditions: binary_sensor.vertex2_unreachable or binary_sensor.vertex2_command_processing
sensor_attributes: sensor_attributes:
icon: sensor.vertex2_input_selected_icon icon: sensor.vertex2_input_selected_icon
binary_sensor.is_using_apple_tv: binary_sensor.is_apple_tv_on and binary_sensor.vertex2_has_input_signal and sensor.vertex2_input_selected == 'AppleTV' binary_sensor.is_using_apple_tv: binary_sensor.is_apple_tv_on and binary_sensor.vertex2_has_input_signal and sensor.vertex2_input_selected == 'AppleTV'
+64 -73
View File
@@ -15,10 +15,16 @@ class Vertex2(SmartObject):
self._stop_interrupt_listener() self._stop_interrupt_listener()
self._interrupt_stop = threading.Event() self._interrupt_stop = threading.Event()
self._status_requested = threading.Event() self._status_requested = threading.Event()
self._plus5_off_handle = None self._command_processing_lock = threading.Lock()
self._plus5_active_index = None self._command_processing_handle = None
self._input_signal_present = False self._command_processing_deadline = None
self.plus5_signal_timeout = max(0, float(self.args.get('plus5_signal_timeout', 60))) self.command_processing_timeout = max(0, float(self.args.get('command_processing_timeout', 30)))
self.command_processing_sensor = None
if self.args.get('command_processing_sensor'):
self.command_processing_sensor = self.create_entity(
self.args['command_processing_sensor'],
state='off',
)
self.video_tx_signal_sensor = self.create_entity( self.video_tx_signal_sensor = self.create_entity(
self.args['video_tx_signal_sensor'], self.args['video_tx_signal_sensor'],
state='unavailable', state='unavailable',
@@ -112,17 +118,23 @@ class Vertex2(SmartObject):
output_index = int(output_key) output_index = int(output_key)
except (TypeError, ValueError): except (TypeError, ValueError):
continue continue
for condition_key in ('use_audio_out_audio_flags',): for condition_key in ('use_audio_out_audio_flags', 'force_plus5'):
condition_config = config.get(condition_key) condition_config = config.get(condition_key)
if not condition_config: if condition_config is None:
continue continue
callback_options = (
{'on_change_cb': self.on_output_plus5_condition_change,
'trigger_callback_on_activation': True}
if condition_key == 'force_plus5'
else {'on_update_cb': self.on_output_config_conditions_update,
'trigger_callback_on_activation': False}
)
self.output_config_conditions.setdefault(output_index, {})[condition_key] = SmartCondition.Evaluator( self.output_config_conditions.setdefault(output_index, {})[condition_key] = SmartCondition.Evaluator(
self, self,
condition_config, condition_config,
condition_name=f'output_config_tx{output_index}_{condition_key}', condition_name=f'output_config_tx{output_index}_{condition_key}',
on_update_cb=self.on_output_config_conditions_update,
pass_condition_name_to_cb=str(output_index), pass_condition_name_to_cb=str(output_index),
trigger_callback_on_activation=False, **callback_options,
) )
self.output_selection_evaluators = [] self.output_selection_evaluators = []
@@ -205,6 +217,7 @@ class Vertex2(SmartObject):
line, buffer = buffer.split('\n', 1) line, buffer = buffer.split('\n', 1)
line = line.strip() line = line.strip()
if line == 'int': if line == 'int':
self._refresh_command_processing()
self.log_info('Vertex2 IP INTERRUPT received') self.log_info('Vertex2 IP INTERRUPT received')
self._status_requested.set() self._status_requested.set()
elif status_started is not None: elif status_started is not None:
@@ -253,15 +266,16 @@ class Vertex2(SmartObject):
if interrupt_thread.is_alive(): if interrupt_thread.is_alive():
raise RuntimeError('Previous Vertex2 IP INTERRUPT listener did not stop') raise RuntimeError('Previous Vertex2 IP INTERRUPT listener did not stop')
self._interrupt_thread = None self._interrupt_thread = None
plus5_off_handle = getattr(self, '_plus5_off_handle', None) processing_lock = getattr(self, '_command_processing_lock', None)
if plus5_off_handle is not None: if processing_lock is not None:
self.cancel_timer(plus5_off_handle) with processing_lock:
self._plus5_off_handle = None self._command_processing_deadline = None
if self._command_processing_handle is not None:
self.cancel_timer(self._command_processing_handle)
self._command_processing_handle = None
def terminate(self): def terminate(self):
self._stop_interrupt_listener() self._stop_interrupt_listener()
if getattr(self, '_plus5_active_index', None) is not None:
self._set_active_output_plus5(None)
super().terminate() super().terminate()
def generate_input_selected_sensor_name(self,i): def generate_input_selected_sensor_name(self,i):
@@ -309,7 +323,7 @@ class Vertex2(SmartObject):
break break
if output_selected is None: if output_selected is None:
self.log_warning("No output_selection condition succeeded; disabling TX0 and TX1 +5V FORCE") self.log_warning("No output_selection condition succeeded; clearing output selection")
self.disable_output_selection() self.disable_output_selection()
return return
@@ -346,7 +360,6 @@ class Vertex2(SmartObject):
self.set_edid_video_flags_to_tx0() self.set_edid_video_flags_to_tx0()
else: else:
self.set_edid_video_flags_to_tx1() self.set_edid_video_flags_to_tx1()
self._set_active_output_plus5(output_index if self._input_signal_present else None)
def disable_output_selection(self): def disable_output_selection(self):
default_output_config = self.get_output_config(None) default_output_config = self.get_output_config(None)
@@ -355,60 +368,15 @@ class Vertex2(SmartObject):
attributes={"icon": default_output_config["icon"]}, attributes={"icon": default_output_config["icon"]},
) )
self._refresh_selected_output_signal() self._refresh_selected_output_signal()
self._set_active_output_plus5(None)
def _set_active_output_plus5(self, output_index): def on_output_plus5_condition_change(self, output_index, previous_result, result):
if self._plus5_off_handle is not None: if result == SmartCondition.Result.Disabled:
self.cancel_timer(self._plus5_off_handle)
self._plus5_off_handle = None
previous_index = self._plus5_active_index
if previous_index == output_index:
return return
enabled = result == SmartCondition.Result.Succeeded
if previous_index == 0: if int(output_index) == 0:
self.force_tx0_plus5(False) self.force_tx0_plus5(enabled)
elif previous_index == 1: elif int(output_index) == 1:
self.force_tx1_plus5(False) self.force_tx1_plus5(enabled)
self._plus5_active_index = output_index
if output_index == 0:
# At startup the previous hardware state is unknown; disable the
# other output only after a signal confirms the selected TX.
self.force_tx1_plus5(False)
self.force_tx0_plus5(True)
elif output_index == 1:
self.force_tx0_plus5(False)
self.force_tx1_plus5(True)
def _update_plus5_from_input_signal(self, has_signal):
self._input_signal_present = has_signal
selected_name = self.output_selected_sensor.get_state()
selected_index = self.output_names.get(selected_name)
if has_signal:
self._set_active_output_plus5(selected_index)
elif selected_index in (0, 1) and self._plus5_off_handle is None:
self.log_info(
f"No input signal; turning selected TX +5V FORCE off in {self.plus5_signal_timeout:g}s"
)
self._plus5_off_handle = self.run_in(
self._turn_off_plus5_after_signal_loss,
self.plus5_signal_timeout,
)
def _turn_off_plus5_after_signal_loss(self, kwargs):
self._plus5_off_handle = None
if self._input_signal_present:
return
self.log_info("Input signal absent for configured timeout; turning selected TX +5V FORCE off")
selected_index = self.output_names.get(self.output_selected_sensor.get_state())
if selected_index == 0:
self.force_tx0_plus5(False)
elif selected_index == 1:
self.force_tx1_plus5(False)
if self._plus5_active_index == selected_index:
self._plus5_active_index = None
def on_filmmaker_mode_conditions_change(self,prev_result,result): def on_filmmaker_mode_conditions_change(self,prev_result,result):
if result == SmartCondition.Result.Succeeded: if result == SmartCondition.Result.Succeeded:
@@ -422,8 +390,35 @@ class Vertex2(SmartObject):
else: else:
self.turn_off(self.power_plug_entity) self.turn_off(self.power_plug_entity)
def _refresh_command_processing(self, start=False):
if self.command_processing_sensor is None:
return
with self._command_processing_lock:
if not start and self.command_processing_sensor.get_state() != 'on':
return
if self._command_processing_handle is not None:
self.cancel_timer(self._command_processing_handle)
self._command_processing_deadline = time.monotonic() + self.command_processing_timeout
if self.command_processing_sensor.get_state() != 'on':
self.command_processing_sensor.set_state(state='on')
self._command_processing_handle = self.run_in(
self._on_command_processing_timeout,
self.command_processing_timeout,
deadline=self._command_processing_deadline,
)
def _on_command_processing_timeout(self, kwargs):
with self._command_processing_lock:
# Ignore a callback already queued before a timer reset or shutdown.
if kwargs['deadline'] != self._command_processing_deadline:
return
self._command_processing_handle = None
self._command_processing_deadline = None
self.command_processing_sensor.set_state(state='off')
def send_cmd(self,cmd,value = ''): def send_cmd(self,cmd,value = ''):
request = f'{self.args["vertex2_address"]}/cmd?{cmd}={value}' request = f'{self.args["vertex2_address"]}/cmd?{cmd}={value}'
self._refresh_command_processing(start=True)
try: try:
r = get(request, timeout=5) r = get(request, timeout=5)
if cmd == 'edid' and value.startswith(('audiotx', 'audioout')): if cmd == 'edid' and value.startswith(('audiotx', 'audioout')):
@@ -535,6 +530,7 @@ class Vertex2(SmartObject):
"name": config.get("name", default_config["name"]), "name": config.get("name", default_config["name"]),
"icon": config.get("icon", default_config["icon"]), "icon": config.get("icon", default_config["icon"]),
"use_audio_out_audio_flags": config.get("use_audio_out_audio_flags"), "use_audio_out_audio_flags": config.get("use_audio_out_audio_flags"),
"force_plus5": config.get("force_plus5"),
} }
return {"name": config, "icon": None} return {"name": config, "icon": None}
@@ -644,11 +640,6 @@ class Vertex2(SmartObject):
selected_output_index = self.output_names.get(self.output_selected_sensor.get_state()) selected_output_index = self.output_names.get(self.output_selected_sensor.get_state())
self._refresh_selected_input_sensor(selected_output_index) self._refresh_selected_input_sensor(selected_output_index)
self._update_output_video_sensors(kwargs['rx0'], kwargs['rx1']) self._update_output_video_sensors(kwargs['rx0'], kwargs['rx1'])
has_input_signal = any(
signal.strip().lower() not in ('', 'no signal', 'unavailable')
for signal in (kwargs['rx0'], kwargs['rx1'])
)
self._update_plus5_from_input_signal(has_input_signal)
if self.audio_out_signal_sensor.get_state() != audio_out_signal: if self.audio_out_signal_sensor.get_state() != audio_out_signal:
self.audio_out_signal_sensor.set_state(state=audio_out_signal) self.audio_out_signal_sensor.set_state(state=audio_out_signal)
self.unreachable_sensor.set_state(state='off') self.unreachable_sensor.set_state(state='off')