Deactivate +5v signal when there is not input signal

This commit is contained in:
Pierre committed 2026-10-06 22:47:29 +02:00
1 parent a734b5f780
commit 6d2fd26f66
1 file changed
+70 -67
+70 -67
View File
@@ -15,9 +15,10 @@ class Vertex2(SmartObject):
self._stop_interrupt_listener()
self._interrupt_stop = threading.Event()
self._status_requested = threading.Event()
self._tx0_recovery_attempted = False
self._tx0_recovery_restore_handle = None
self._plus5_off_handle = None
self._plus5_active_index = None
self._input_signal_present = False
self.plus5_signal_timeout = max(0, float(self.args.get('plus5_signal_timeout', 60)))
self.video_tx_signal_sensor = self.create_entity(
self.args['video_tx_signal_sensor'],
state='unavailable',
@@ -175,21 +176,12 @@ class Vertex2(SmartObject):
buffer = ''
status = {}
status_started = None
interrupt_generation = 0
last_interrupt_at = None
last_interrupt_interval = None
request_generation = 0
request_interval = None
previous_snapshot = None
previous_snapshot_generation = None
while not interrupt_stop.is_set():
if status_started is None and self._status_requested.is_set():
self._status_requested.clear()
status = {}
status_started = time.monotonic()
request_generation = interrupt_generation
request_interval = last_interrupt_interval
for command in commands:
connection.sendall((command + '\r').encode('ascii'))
@@ -214,10 +206,6 @@ class Vertex2(SmartObject):
line = line.strip()
if line == 'int':
self.log_info('Vertex2 IP INTERRUPT received')
now = time.monotonic()
last_interrupt_interval = None if last_interrupt_at is None else now - last_interrupt_at
last_interrupt_at = now
interrupt_generation += 1
self._status_requested.set()
elif status_started is not None:
if line.startswith('insel '):
@@ -236,23 +224,6 @@ class Vertex2(SmartObject):
tx0=snapshot['tx0'], tx1=snapshot['tx1'],
audout=snapshot['audout'], insel=snapshot['insel'])
status_started = None
consecutive = (previous_snapshot_generation is not None
and request_generation == previous_snapshot_generation + 1)
unchanged = previous_snapshot == snapshot
rx_has_signal = any(
snapshot[field].strip().lower() not in ('', 'no signal', 'unavailable')
for field in ('rx0', 'rx1')
)
if (unchanged and consecutive and request_interval is not None
and 2 <= request_interval < 3 and not rx_has_signal):
if not self._tx0_recovery_attempted:
self.run_in(self.trigger_tx0_stuck_workaround, 0,
interrupt_interval=request_interval,
snapshot=snapshot)
elif not unchanged:
self._tx0_recovery_attempted = False
previous_snapshot = snapshot
previous_snapshot_generation = request_generation
if status_started is not None and time.monotonic() - status_started > 5:
raise OSError('Vertex2 TCP status response timed out')
@@ -271,30 +242,6 @@ class Vertex2(SmartObject):
self.log_warning(f'Vertex2 IP INTERRUPT unavailable: {error}')
interrupt_stop.wait(5)
def trigger_tx0_stuck_workaround(self, kwargs):
if self._tx0_recovery_attempted:
return
if self.output_selected_sensor.get_state() != self.get_output_config(0)['name']:
return
if self.unreachable_sensor.get_state() != 'off':
return
self._tx0_recovery_attempted = True
snapshot_summary = ', '.join(f'{key}={value!r}' for key, value in kwargs['snapshot'].items())
self.log_warning(
f"Vertex2 TX0 +5V workaround triggered: identical complete TCP status after "
f"an IP INTERRUPT interval of {kwargs['interrupt_interval']:.2f}s "
f"({snapshot_summary}); cycling TX0 +5V FORCE"
)
self.force_tx0_plus5(False)
self._tx0_recovery_restore_handle = self.run_in(self.restore_tx0_plus5_after_recovery, 1)
def restore_tx0_plus5_after_recovery(self, kwargs):
self._tx0_recovery_restore_handle = None
if self.output_selected_sensor.get_state() == self.get_output_config(0)['name']:
self.log_info('Vertex2 TX0 +5V workaround: restoring TX0 +5V FORCE')
self.force_tx0_plus5(True)
def _stop_interrupt_listener(self):
interrupt_stop = getattr(self, '_interrupt_stop', None)
if interrupt_stop is not None:
@@ -306,13 +253,15 @@ class Vertex2(SmartObject):
if interrupt_thread.is_alive():
raise RuntimeError('Previous Vertex2 IP INTERRUPT listener did not stop')
self._interrupt_thread = None
restore_handle = getattr(self, '_tx0_recovery_restore_handle', None)
if restore_handle is not None:
self.cancel_timer(restore_handle)
self._tx0_recovery_restore_handle = None
plus5_off_handle = getattr(self, '_plus5_off_handle', None)
if plus5_off_handle is not None:
self.cancel_timer(plus5_off_handle)
self._plus5_off_handle = None
def terminate(self):
self._stop_interrupt_listener()
if getattr(self, '_plus5_active_index', None) is not None:
self._set_active_output_plus5(None)
super().terminate()
def generate_input_selected_sensor_name(self,i):
@@ -394,13 +343,10 @@ class Vertex2(SmartObject):
self.log_info(f"Selecting TX{output_index} as the active output")
self.apply_output_audio_settings(output_index)
if output_index == 0:
self.force_tx1_plus5(False)
self.set_edid_video_flags_to_tx0()
self.force_tx0_plus5(True)
else:
self.force_tx0_plus5(False)
self.set_edid_video_flags_to_tx1()
self.force_tx1_plus5(True)
self._set_active_output_plus5(output_index if self._input_signal_present else None)
def disable_output_selection(self):
default_output_config = self.get_output_config(None)
@@ -409,8 +355,60 @@ class Vertex2(SmartObject):
attributes={"icon": default_output_config["icon"]},
)
self._refresh_selected_output_signal()
self.force_tx0_plus5(False)
self.force_tx1_plus5(False)
self._set_active_output_plus5(None)
def _set_active_output_plus5(self, output_index):
if self._plus5_off_handle is not None:
self.cancel_timer(self._plus5_off_handle)
self._plus5_off_handle = None
previous_index = self._plus5_active_index
if previous_index == output_index:
return
if previous_index == 0:
self.force_tx0_plus5(False)
elif previous_index == 1:
self.force_tx1_plus5(False)
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):
if result == SmartCondition.Result.Succeeded:
@@ -646,6 +644,11 @@ class Vertex2(SmartObject):
selected_output_index = self.output_names.get(self.output_selected_sensor.get_state())
self._refresh_selected_input_sensor(selected_output_index)
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:
self.audio_out_signal_sensor.set_state(state=audio_out_signal)
self.unreachable_sensor.set_state(state='off')