Shorter reconnection delay
This commit is contained in:
1 parent
8a8cb02296
commit
8cc2ae32df
4 files changed
+64
-25
No files matched your search
+22
-14
@@ -26,7 +26,6 @@ samsung_the_frame:
|
|||||||
trigger_conditions:
|
trigger_conditions:
|
||||||
- binary_sensor.is_everybody_sleeping or binary_sensor.is_living_room_sleeping or not binary_sensor.someone_home
|
- binary_sensor.is_everybody_sleeping or binary_sensor.is_living_room_sleeping or not binary_sensor.someone_home
|
||||||
- binary_sensor.projector_screen_down
|
- binary_sensor.projector_screen_down
|
||||||
#hdmi: binary_sensor.vertex2_tx0_plus5_for_a_while and binary_sensor.vertex2_has_input_signal and not binary_sensor.projector_screen_down
|
|
||||||
hdmi: binary_sensor.vertex2_has_input_signal and not binary_sensor.projector_screen_down
|
hdmi: binary_sensor.vertex2_has_input_signal and not binary_sensor.projector_screen_down
|
||||||
manual: binary_sensor.is_tv_on and not binary_sensor.tv_auto_shutdown
|
manual: binary_sensor.is_tv_on and not binary_sensor.tv_auto_shutdown
|
||||||
tv_off: not binary_sensor.living_room_occupancy
|
tv_off: not binary_sensor.living_room_occupancy
|
||||||
@@ -127,6 +126,14 @@ projection_screen_switch:
|
|||||||
class: SmartSwitch
|
class: SmartSwitch
|
||||||
entity: input_boolean.projection_screen
|
entity: input_boolean.projection_screen
|
||||||
|
|
||||||
|
virtual_sensors:
|
||||||
|
sensors:
|
||||||
|
continuous_condition.projection_screen_on_for_a_while:
|
||||||
|
time: 300
|
||||||
|
conditions: input_boolean.projection_screen
|
||||||
|
|
||||||
|
off_conditions: binary_sensor.projection_screen_on_for_a_while and binary_sensor.projector_off_for_a_while
|
||||||
|
|
||||||
toggle_events:
|
toggle_events:
|
||||||
apple_tv_mute_long_press:
|
apple_tv_mute_long_press:
|
||||||
event_name: state_changed
|
event_name: state_changed
|
||||||
@@ -153,6 +160,7 @@ vertex_2:
|
|||||||
virtual_sensors:
|
virtual_sensors:
|
||||||
default_values:
|
default_values:
|
||||||
binary_sensor.is_tv_on: false
|
binary_sensor.is_tv_on: false
|
||||||
|
binary_sensor.someone_home: false
|
||||||
binary_sensor.vertex2_has_input_signal: false
|
binary_sensor.vertex2_has_input_signal: false
|
||||||
sensors:
|
sensors:
|
||||||
binary_sensor.projector_screen_down: input_boolean.projection_screen
|
binary_sensor.projector_screen_down: input_boolean.projection_screen
|
||||||
@@ -164,7 +172,6 @@ vertex_2:
|
|||||||
0:
|
0:
|
||||||
name: AppleTV
|
name: AppleTV
|
||||||
icon: mdi:apple
|
icon: mdi:apple
|
||||||
#reboot_when_leaving: True
|
|
||||||
1:
|
1:
|
||||||
name: Xbox
|
name: Xbox
|
||||||
icon: mdi:microsoft-xbox
|
icon: mdi:microsoft-xbox
|
||||||
@@ -183,23 +190,20 @@ 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:
|
force_plus5: not binary_sensor.vertex2_no_signal_spam
|
||||||
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
|
#force_plus5: switch.living_room_projector_plug
|
||||||
|
|
||||||
|
vertex2_power_plug:
|
||||||
|
entity: switch.vertex2_power_plug
|
||||||
|
# power_consumption: sensor.fibaro_vertex_plug_51_power
|
||||||
|
conditions:
|
||||||
|
trigger_conditions: binary_sensor.someone_home #and (input_boolean.projection_screen or binary_sensor.the_frame_is_plugged_for_a_while)
|
||||||
|
blocking_conditions: (binary_sensor.is_everybody_sleeping or binary_sensor.is_living_room_sleeping) and not binary_sensor.vertex2_has_input_signal
|
||||||
|
|
||||||
# vertex2_power_plug:
|
|
||||||
# entity: switch.fibaro_vertex_plug_51
|
|
||||||
# # power_consumption: sensor.fibaro_vertex_plug_51_power
|
|
||||||
# conditions:
|
|
||||||
# trigger_conditions: binary_sensor.someone_home
|
|
||||||
# blocking_conditions:
|
|
||||||
# - binary_sensor.is_everybody_sleeping or binary_sensor.is_salon_sleeping
|
|
||||||
# - binary_sensor.vertex2_unreachable and not binary_sensor.is_tv_on and not binary_sensor.projector_on
|
|
||||||
|
|
||||||
#filmmaker_mode_conditions: sensor.vertex2_input_select_tx0 == 'AppleTV'
|
#filmmaker_mode_conditions: sensor.vertex2_input_select_tx0 == 'AppleTV'
|
||||||
|
|
||||||
@@ -216,12 +220,16 @@ vertex_2:
|
|||||||
|
|
||||||
vertex2_address: http://10.0.0.33
|
vertex2_address: http://10.0.0.33
|
||||||
vertex2_tcp_port: 2220
|
vertex2_tcp_port: 2220
|
||||||
|
startup_delay: 30 # seconds after the power plug turns on
|
||||||
|
|
||||||
video_tx_signal_sensor: sensor.vertex2_video_signal
|
video_tx_signal_sensor: sensor.vertex2_video_signal
|
||||||
allm_sensor: binary_sensor.vertex2_allm
|
allm_sensor: binary_sensor.vertex2_allm
|
||||||
|
#tx0_plus5_sensor: binary_sensor.vertex2_tx0_plus5
|
||||||
|
#tx1_plus5_sensor: binary_sensor.vertex2_tx1_plus5
|
||||||
audio_out_signal_sensor: sensor.vertex2_audio_signal
|
audio_out_signal_sensor: sensor.vertex2_audio_signal
|
||||||
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
|
||||||
|
no_signal_spam_sensor: binary_sensor.vertex2_no_signal_spam
|
||||||
unreachable_sensor: binary_sensor.vertex2_unreachable
|
unreachable_sensor: binary_sensor.vertex2_unreachable
|
||||||
command_processing_sensor: binary_sensor.vertex2_command_processing
|
command_processing_sensor: binary_sensor.vertex2_command_processing
|
||||||
command_processing_timeout: 20
|
command_processing_timeout: 20
|
||||||
|
|||||||
@@ -308,6 +308,8 @@ class SamsungTheFrame(SmartObject):
|
|||||||
if self._stopped:
|
if self._stopped:
|
||||||
return
|
return
|
||||||
self._generation += 1
|
self._generation += 1
|
||||||
|
# A fresh target must not inherit a long idle reconnection delay.
|
||||||
|
self._retry_at = min(self._retry_at, self._loop.time() + self._transition_retry_delay())
|
||||||
self.requested_state = None if state == 'manual' else state
|
self.requested_state = None if state == 'manual' else state
|
||||||
previous = self._target_task
|
previous = self._target_task
|
||||||
if previous is not None and not previous.cancelling():
|
if previous is not None and not previous.cancelling():
|
||||||
@@ -422,7 +424,10 @@ class SamsungTheFrame(SmartObject):
|
|||||||
await self._publish(*self._observed)
|
await self._publish(*self._observed)
|
||||||
except NETWORK_ERRORS as error:
|
except NETWORK_ERRORS as error:
|
||||||
self.log_info(f'TV refresh failed: {type(error).__name__}: {error}')
|
self.log_info(f'TV refresh failed: {type(error).__name__}: {error}')
|
||||||
self._retry_at = self._loop.time() + self._backoff
|
retry_delay = self._backoff
|
||||||
|
if self.requested_state is not None:
|
||||||
|
retry_delay = min(retry_delay, self._transition_retry_delay())
|
||||||
|
self._retry_at = self._loop.time() + retry_delay
|
||||||
self._backoff = min(60, self._backoff * 2)
|
self._backoff = min(60, self._backoff * 2)
|
||||||
# One refresh worker coalesces power events. WebSocket
|
# One refresh worker coalesces power events. WebSocket
|
||||||
# notifications wake it immediately; no network polling
|
# notifications wake it immediately; no network polling
|
||||||
@@ -490,6 +495,9 @@ class SamsungTheFrame(SmartObject):
|
|||||||
selected = random.choice(available)
|
selected = random.choice(available)
|
||||||
await self._client.art_request('select_image', content_id=selected['content_id'], show=True)
|
await self._client.art_request('select_image', content_id=selected['content_id'], show=True)
|
||||||
|
|
||||||
|
def _transition_retry_delay(self):
|
||||||
|
return max(0.2, float(self.args.get('transition_retry_delay', 2)))
|
||||||
|
|
||||||
async def _target_runner(self, previous, target, generation):
|
async def _target_runner(self, previous, target, generation):
|
||||||
if previous is not None:
|
if previous is not None:
|
||||||
with suppress(asyncio.CancelledError):
|
with suppress(asyncio.CancelledError):
|
||||||
@@ -510,7 +518,7 @@ class SamsungTheFrame(SmartObject):
|
|||||||
return
|
return
|
||||||
except NETWORK_ERRORS + (KeyError, TypeError) as error:
|
except NETWORK_ERRORS + (KeyError, TypeError) as error:
|
||||||
self.log_info(f'TV transition failed: {type(error).__name__}: {error}')
|
self.log_info(f'TV transition failed: {type(error).__name__}: {error}')
|
||||||
delay = 30
|
delay = self._transition_retry_delay()
|
||||||
# Cancellation interrupts both network waits and confirmation delays.
|
# Cancellation interrupts both network waits and confirmation delays.
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
self._refresh_at = 0
|
self._refresh_at = 0
|
||||||
@@ -519,17 +527,17 @@ class SamsungTheFrame(SmartObject):
|
|||||||
async def _transition(self, target):
|
async def _transition(self, target):
|
||||||
plugged, powered = await self._power()
|
plugged, powered = await self._power()
|
||||||
if plugged is None or powered is None:
|
if plugged is None or powered is None:
|
||||||
return 30
|
return self._transition_retry_delay()
|
||||||
if not plugged:
|
if not plugged:
|
||||||
if target == 'power_off':
|
if target == 'power_off':
|
||||||
return None
|
return None
|
||||||
await self.turn_on(self.power_plug)
|
await self.turn_on(self.power_plug)
|
||||||
return 60
|
return 5
|
||||||
if not powered:
|
if not powered:
|
||||||
if target == 'tv_off':
|
if target == 'tv_off':
|
||||||
return None
|
return None
|
||||||
await self._wake_up()
|
await self._wake_up()
|
||||||
return 60
|
return 10
|
||||||
if self._loop.time() < self._retry_at:
|
if self._loop.time() < self._retry_at:
|
||||||
return max(1, self._retry_at - self._loop.time())
|
return max(1, self._retry_at - self._loop.time())
|
||||||
# Always confirm the current state before a toggle. Serialize Art reads
|
# Always confirm the current state before a toggle. Serialize Art reads
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ lors de l'ajout de ce module.
|
|||||||
connexions. Une consommation indisponible n'est pas assimilée à un arrêt.
|
connexions. Une consommation indisponible n'est pas assimilée à un arrêt.
|
||||||
- Après un échec de lecture, reconnexion avec attente progressive : 5, 10, 20,
|
- Après un échec de lecture, reconnexion avec attente progressive : 5, 10, 20,
|
||||||
40, puis 60 secondes. Les événements de consommation ne contournent pas cette
|
40, puis 60 secondes. Les événements de consommation ne contournent pas cette
|
||||||
attente. Les transitions échouées sont revérifiées après 30 secondes.
|
attente en surveillance seule. Pendant une transition, cette attente est plafonnée
|
||||||
|
à 2 secondes, y compris après une erreur de transition. Une nouvelle consigne
|
||||||
|
raccourcit aussi une attente de reconnexion déjà en cours.
|
||||||
- Les cinq consignes sont conservées : `manual`, `art_mode`, `tv_off`, `hdmi`,
|
- Les cinq consignes sont conservées : `manual`, `art_mode`, `tv_off`, `hdmi`,
|
||||||
`power_off`. Une nouvelle consigne annule la transition précédente, y compris
|
`power_off`. Une nouvelle consigne annule la transition précédente, y compris
|
||||||
l'attente de 5 secondes avant coupure électrique. Une commande Power échouée
|
l'attente de 5 secondes avant coupure électrique. Une commande Power échouée
|
||||||
@@ -64,6 +66,7 @@ basculement :
|
|||||||
```yaml
|
```yaml
|
||||||
network_timeout: 2 # secondes, connexion/autorisation et requêtes TV
|
network_timeout: 2 # secondes, connexion/autorisation et requêtes TV
|
||||||
reconnect_delay: 5 # attente initiale, doublée jusqu'à 60 secondes
|
reconnect_delay: 5 # attente initiale, doublée jusqu'à 60 secondes
|
||||||
|
transition_retry_delay: 2 # attente entre tentatives pendant une transition
|
||||||
fallback_refresh: 60 # intervalle de lecture de secours, en secondes
|
fallback_refresh: 60 # intervalle de lecture de secours, en secondes
|
||||||
power_threshold: 35 # watts, même seuil que l'ancienne app
|
power_threshold: 35 # watts, même seuil que l'ancienne app
|
||||||
```
|
```
|
||||||
@@ -82,3 +85,7 @@ Les tests simulent les messages et sockets TV : persistance, notifications,
|
|||||||
concurrence, délais, reconnexion, transitions et annulation. Ils ne contactent pas
|
concurrence, délais, reconnexion, transitions et annulation. Ils ne contactent pas
|
||||||
la TV réelle. La réception des événements et les commandes doivent encore être
|
la TV réelle. La réception des événements et les commandes doivent encore être
|
||||||
validées sur le téléviseur lors du basculement manuel.
|
validées sur le téléviseur lors du basculement manuel.
|
||||||
|
|
||||||
|
Après allumage de la prise, la transition est revérifiée après 5 secondes ;
|
||||||
|
après une commande de réveil, après 10 secondes (au lieu de 60). Les délais
|
||||||
|
de confirmation après une touche et avant coupure électrique sont conservés.
|
||||||
@@ -11,7 +11,7 @@ import aiohttp
|
|||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'apps'))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'apps'))
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'apps' / 'ad_toolbox'))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'apps' / 'ad_toolbox'))
|
||||||
from SamsungTheFrameAsync import SamsungTheFrame, TVError, _TVConnection
|
from samsungtheframeasync import SamsungTheFrame, TVError, _TVConnection
|
||||||
|
|
||||||
|
|
||||||
def message(data):
|
def message(data):
|
||||||
@@ -231,6 +231,22 @@ class ApplicationTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
app.app_list = []
|
app.app_list = []
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
async def test_fresh_target_shortens_idle_reconnect_delay(self):
|
||||||
|
app = self.app()
|
||||||
|
app._retry_at = app._loop.time() + 60
|
||||||
|
app._transition = AsyncMock(return_value=None)
|
||||||
|
app._set_target('hdmi')
|
||||||
|
self.assertLessEqual(app._retry_at - app._loop.time(), 2)
|
||||||
|
await app._target_task
|
||||||
|
|
||||||
|
async def test_wake_rechecks_after_ten_seconds(self):
|
||||||
|
app = self.app()
|
||||||
|
app.args['wake_up_service'] = 'esphome/wake_tv'
|
||||||
|
app.get_state = AsyncMock(side_effect=lambda e: 'on' if e == app.power_plug else '0')
|
||||||
|
self.assertEqual(await app._transition('hdmi'), 10)
|
||||||
|
app.call_service.assert_awaited_once_with('esphome/wake_tv')
|
||||||
|
app._client.send_key.assert_not_awaited()
|
||||||
|
|
||||||
async def asyncTearDown(self):
|
async def asyncTearDown(self):
|
||||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
@@ -252,7 +268,7 @@ class ApplicationTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
app.listen_state = Mock()
|
app.listen_state = Mock()
|
||||||
app._post = Mock()
|
app._post = Mock()
|
||||||
app.create_task = Mock(side_effect=lambda coro: coro.close())
|
app.create_task = Mock(side_effect=lambda coro: coro.close())
|
||||||
with patch('SamsungTheFrameAsync.EventHandler') as handler:
|
with patch('samsungtheframeasync.EventHandler') as handler:
|
||||||
app.on_initialize_smart_object()
|
app.on_initialize_smart_object()
|
||||||
self.assertEqual(app.listen_state.call_count, 3)
|
self.assertEqual(app.listen_state.call_count, 3)
|
||||||
self.assertEqual(handler.call_count, 2)
|
self.assertEqual(handler.call_count, 2)
|
||||||
@@ -343,7 +359,7 @@ class ApplicationTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
app.get_state.side_effect = None
|
app.get_state.side_effect = None
|
||||||
self.assertIsNone(await app._transition('power_off'))
|
self.assertIsNone(await app._transition('power_off'))
|
||||||
app.turn_on.assert_not_called()
|
app.turn_on.assert_not_called()
|
||||||
self.assertEqual(await app._transition('art_mode'), 60)
|
self.assertEqual(await app._transition('art_mode'), 5)
|
||||||
app.turn_on.assert_awaited_once_with(app.power_plug)
|
app.turn_on.assert_awaited_once_with(app.power_plug)
|
||||||
app._client.art_request.assert_not_called()
|
app._client.art_request.assert_not_called()
|
||||||
|
|
||||||
@@ -379,7 +395,7 @@ class ApplicationTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
async def test_monitor_coalesces_sensor_bursts_and_shutdown_closes_client(self):
|
async def test_monitor_coalesces_sensor_bursts_and_shutdown_closes_client(self):
|
||||||
app = self.app()
|
app = self.app()
|
||||||
client = app._client
|
client = app._client
|
||||||
with patch('SamsungTheFrameAsync._TVConnection', return_value=client):
|
with patch('samsungtheframeasync._TVConnection', return_value=client):
|
||||||
task = asyncio.create_task(app._run())
|
task = asyncio.create_task(app._run())
|
||||||
await asyncio.sleep(.02)
|
await asyncio.sleep(.02)
|
||||||
for _ in range(30):
|
for _ in range(30):
|
||||||
@@ -402,7 +418,7 @@ class ApplicationTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
app.args['reconnect_delay'] = .5
|
app.args['reconnect_delay'] = .5
|
||||||
client.alive.return_value = False
|
client.alive.return_value = False
|
||||||
client.art_request.side_effect = TVError('offline')
|
client.art_request.side_effect = TVError('offline')
|
||||||
with patch('SamsungTheFrameAsync._TVConnection', return_value=client):
|
with patch('samsungtheframeasync._TVConnection', return_value=client):
|
||||||
task = asyncio.create_task(app._run())
|
task = asyncio.create_task(app._run())
|
||||||
await asyncio.sleep(.01)
|
await asyncio.sleep(.01)
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
|
|||||||
Reference in new issue
Block a user