[alarm] Added `dismiss_interval` configuration.

This commit is contained in:
Fabio Manganiello 2023-12-18 03:01:27 +01:00
parent 250858fe99
commit 52fd64a162
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
5 changed files with 110 additions and 20 deletions

View File

@ -138,8 +138,8 @@
<br />
<span class="subtext">
<span class="text">
How long the interval should be paused after being triggered and
snoozed.
How long the alarm should be paused after being triggered and
manually snoozed.
</span>
</span>
</div>
@ -150,6 +150,26 @@
</div>
</div>
<div class="row item">
<div class="name">
<label>
<i class="icon fas fa-xmark" />
Dismiss timeout
</label>
<br />
<span class="subtext">
<span class="text">
How long the alarm should run before being automatically dismissed.
</span>
</span>
</div>
<div class="value">
<TimeInterval :value="editForm.dismiss_interval"
@input="editForm.dismiss_interval = $event" />
</div>
</div>
<div class="row item">
<div class="name">
<label>
@ -247,6 +267,7 @@ export default {
'media_plugin',
'name',
'snooze_interval',
'dismiss_interval',
'when',
].forEach(key => {
if (this.editForm[key] !== this.value[key])
@ -258,6 +279,17 @@ export default {
},
methods: {
actionsToArgs(actions) {
return actions?.map(action => {
if (action.name) {
action.action = action.name
delete action.name
}
return action
}) ?? []
},
onWhenInput(value, type) {
if (value == null)
return
@ -302,7 +334,8 @@ export default {
media_plugin: this.editForm.media_plugin,
audio_volume: this.editForm.audio_volume,
snooze_interval: this.editForm.snooze_interval,
actions: this.editForm.actions,
dismiss_interval: this.editForm.dismiss_interval,
actions: this.actionsToArgs(this.editForm.actions),
}
} else {
action = 'alarm.edit'
@ -311,6 +344,9 @@ export default {
...this.changes,
}
if (this.changes.actions)
args.actions = this.actionsToArgs(this.changes.actions)
if (this.changes.name != null) {
args.name = this.value.name
args.new_name = this.changes.name

View File

@ -26,6 +26,7 @@ if not is_defined('alarm'):
media_plugin = Column(String, nullable=True)
audio_volume = Column(Integer, nullable=True)
snooze_interval = Column(Integer, nullable=True)
dismiss_interval = Column(Integer, nullable=True)
actions = Column(JSON, nullable=True)
static = Column(Boolean, nullable=False, default=False)
condition_type = Column(String, nullable=False)

View File

@ -77,14 +77,15 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
def __init__(
self,
alarms: Optional[Union[list, Dict[str, Any]]] = None,
alarms: Optional[Union[List[dict], Dict[str, dict]]] = None,
media_plugin: Optional[str] = None,
poll_interval: Optional[float] = 5.0,
poll_interval: Optional[float] = 2.0,
snooze_interval: float = 300.0,
dismiss_interval: float = 300.0,
**kwargs,
):
"""
:param alarms: List or name->value dict with the configured alarms. Example:
:param alarms: List or name->value dict with the configured alarms.
:param media_plugin: Media plugin (instance of
:class:`platypush.plugins.media.MediaPlugin`) that will be used to
play the alarm audio. It needs to be a supported local media
@ -92,11 +93,18 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
``media.gstreamer``, ``sound``, etc. If not specified, the first
available configured local media plugin will be used. This only
applies to alarms that are configured to play an audio resource.
:param poll_interval: Poll interval in seconds (default: 5).
:param snooze_interval: Default snooze interval in seconds (default: 300).
:param poll_interval: (Internal) poll interval, in seconds (default: 2).
:param snooze_interval: Default snooze interval in seconds. This
specifies how long to wait between alarm runs when an alarm is
dismissed (default: 300).
:param dismiss_interval: Default dismiss interval in seconds. This
specifies how long an alarm should run without being manually
snoozed/dismissed before being automatically dismissed (default:
300).
"""
super().__init__(poll_interval=poll_interval, **kwargs)
self.snooze_interval = snooze_interval
self.dismiss_interval = dismiss_interval
self._db_lock = RLock()
alarms = alarms or []
if isinstance(alarms, dict):
@ -259,6 +267,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
audio_volume: Optional[Union[int, float]] = None,
enabled: bool = True,
snooze_interval: Optional[float] = None,
dismiss_interval: Optional[float] = None,
) -> Alarm:
alarm = Alarm(
when=when,
@ -269,6 +278,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
media_plugin=media_plugin or self.media_plugin,
audio_volume=audio_volume,
snooze_interval=snooze_interval or self.snooze_interval,
dismiss_interval=dismiss_interval or self.dismiss_interval,
stop_event=self._should_stop,
on_change=self._on_alarm_update,
)
@ -316,6 +326,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
audio_volume: Optional[Union[int, float]] = None,
enabled: bool = True,
snooze_interval: Optional[float] = None,
dismiss_interval: Optional[float] = None,
) -> dict:
"""
Add a new alarm.
@ -332,6 +343,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
:param audio_volume: Volume of the audio.
:param enabled: Whether the new alarm should be enabled (default: True).
:param snooze_interval: Snooze seconds before playing the alarm again.
:param dismiss_interval: Dismiss seconds before stopping the alarm.
:return: The newly created alarm.
"""
if audio_file:
@ -349,6 +361,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
enabled=enabled,
audio_volume=audio_volume,
snooze_interval=snooze_interval,
dismiss_interval=dismiss_interval,
).to_dict()
@action
@ -363,6 +376,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
audio_volume: Optional[Union[int, float]] = None,
enabled: Optional[bool] = None,
snooze_interval: Optional[float] = None,
dismiss_interval: Optional[float] = None,
) -> dict:
"""
Edit an existing alarm.
@ -383,6 +397,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
:param audio_volume: Volume of the audio.
:param enabled: Whether the new alarm should be enabled.
:param snooze_interval: Snooze seconds before playing the alarm again.
:param dismiss_interval: Dismiss seconds before stopping the alarm.
:return: The modified alarm.
"""
alarm = self._get_alarm(name)
@ -410,6 +425,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
if audio_volume is not None
else alarm.audio_volume,
snooze_interval=snooze_interval or alarm.snooze_interval,
dismiss_interval=dismiss_interval or alarm.dismiss_interval,
).to_dict()
@action
@ -419,15 +435,25 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
:param name: Alarm name.
"""
alarm = self._get_alarm(name)
try:
alarm = self._get_alarm(name)
except AssertionError:
self.logger.warning('Alarm %s does not exist', name)
return
assert not alarm.static, (
f'Alarm {name} is statically defined in the configuration, '
'cannot overwrite it programmatically'
)
alarm.stop()
with self._db.get_session() as session:
db_alarm = session.query(DbAlarm).filter_by(name=name).first()
assert db_alarm, f'Alarm {name} does not exist'
if not db_alarm:
self.logger.warning('Alarm %s does not exist', name)
return
self._clear_alarm(db_alarm, session)
@action
@ -507,6 +533,7 @@ class AlarmPlugin(RunnablePlugin, EntityManager):
"media_plugin": "media.vlc",
"audio_volume": 10,
"snooze_interval": 300,
"dismiss_interval": 300,
"actions": [
{
"action": "tts.say",

View File

@ -61,7 +61,8 @@ class Alarm:
media_plugin: Optional[str] = None,
audio_volume: Optional[Union[int, float]] = None,
snooze_interval: float = 300,
poll_interval: float = 5,
dismiss_interval: float = 300,
poll_interval: float = 2,
enabled: bool = True,
static: bool = False,
stop_event: Optional[threading.Event] = None,
@ -75,6 +76,7 @@ class Alarm:
self.media_plugin = media_plugin
self.audio_volume = audio_volume
self.snooze_interval = snooze_interval
self.dismiss_interval = dismiss_interval
self.state = AlarmState.UNKNOWN
self.timer: Optional[threading.Timer] = None
self.static = static
@ -91,6 +93,7 @@ class Alarm:
self.stop_event = stop_event or threading.Event()
self.poll_interval = poll_interval
self.on_change = on_change
self._dismiss_timer: Optional[threading.Timer] = None
def _on_change(self):
if self.on_change:
@ -209,6 +212,7 @@ class Alarm:
def dismiss(self):
self.state = AlarmState.DISMISSED
self.stop_audio()
self._clear_dismiss_timer()
get_bus().post(AlarmDismissedEvent(name=self.name))
self._on_change()
@ -216,6 +220,7 @@ class Alarm:
self._runtime_snooze_interval = interval or self.snooze_interval
self.state = AlarmState.SNOOZED
self.stop_audio()
self._clear_dismiss_timer()
get_bus().post(
AlarmSnoozedEvent(name=self.name, interval=self._runtime_snooze_interval)
)
@ -230,19 +235,27 @@ class Alarm:
return
interval = next_run - time.time()
self.state = AlarmState.WAITING
self.timer = threading.Timer(interval, self.alarm_callback)
self.timer.start()
self.state = AlarmState.WAITING
self._clear_dismiss_timer()
self._on_change()
def stop(self):
self.state = AlarmState.SHUTDOWN
self.stop_audio()
if self.timer:
self.timer.cancel()
self.timer = None
self._on_change()
def _clear_dismiss_timer(self):
if self._dismiss_timer:
self._dismiss_timer.cancel()
self._dismiss_timer = None
def _get_media_plugin(self) -> MediaPlugin:
plugin = get_plugin(self.media_plugin)
assert plugin and isinstance(plugin, MediaPlugin), (
@ -265,16 +278,23 @@ class Alarm:
def stop_audio(self):
self._get_media_plugin().stop()
def _on_start(self):
if self.state != AlarmState.RUNNING:
self._dismiss_timer = threading.Timer(self.dismiss_interval, self.dismiss)
self._dismiss_timer.start()
self.state = AlarmState.RUNNING
get_bus().post(AlarmStartedEvent(name=self.name))
self._on_change()
if self.media_plugin and self.media:
self.play_audio()
self.actions.execute()
def alarm_callback(self):
while not self.should_stop():
if self.is_enabled():
self.state = AlarmState.RUNNING
get_bus().post(AlarmStartedEvent(name=self.name))
self._on_change()
if self.media_plugin and self.media:
self.play_audio()
self.actions.execute()
self._on_start()
elif self.state != AlarmState.WAITING:
self.state = AlarmState.WAITING
self._on_change()
@ -339,6 +359,7 @@ class Alarm:
'media_plugin': self.media_plugin,
'audio_volume': self.audio_volume,
'snooze_interval': self.snooze_interval,
'dismiss_interval': self.dismiss_interval,
'actions': self.actions.requests,
'static': self.static,
'condition_type': self.condition_type.value,
@ -354,6 +375,7 @@ class Alarm:
audio_volume=alarm.audio_volume, # type: ignore
actions=alarm.actions, # type: ignore
snooze_interval=alarm.snooze_interval, # type: ignore
dismiss_interval=alarm.dismiss_interval, # type: ignore
enabled=bool(alarm.enabled),
static=bool(alarm.static),
state=getattr(AlarmState, str(alarm.state)),
@ -375,6 +397,7 @@ class Alarm:
for req in self.actions.requests
],
snooze_interval=self.snooze_interval,
dismiss_interval=self.dismiss_interval,
enabled=self.is_enabled(),
static=self.static,
condition_type=self.condition_type.value,

View File

@ -238,7 +238,10 @@ class MediaVlcPlugin(MediaPlugin):
def quit(self, *_, **__):
"""Quit the player (same as `stop`)"""
with self._stop_lock:
assert self._player, 'No vlc instance is running'
if not self._player:
self.logger.warning('No vlc instance is running')
return self.status()
self._player.stop()
self._on_stop_event.wait(timeout=5)
self._reset_state()