diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Alarm/AlarmEditor.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Alarm/AlarmEditor.vue
index 1b5e44720..bacb7c8a6 100644
--- a/platypush/backend/http/webapp/src/components/panels/Entities/Alarm/AlarmEditor.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Entities/Alarm/AlarmEditor.vue
@@ -138,8 +138,8 @@
- 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.
@@ -150,6 +150,26 @@
+
@@ -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
diff --git a/platypush/entities/alarm.py b/platypush/entities/alarm.py
index 6a93c3345..7b2698a23 100644
--- a/platypush/entities/alarm.py
+++ b/platypush/entities/alarm.py
@@ -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)
diff --git a/platypush/plugins/alarm/__init__.py b/platypush/plugins/alarm/__init__.py
index 7aaa66f07..d0aef9b07 100644
--- a/platypush/plugins/alarm/__init__.py
+++ b/platypush/plugins/alarm/__init__.py
@@ -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",
diff --git a/platypush/plugins/alarm/_model.py b/platypush/plugins/alarm/_model.py
index 406b0c058..eddb93c04 100644
--- a/platypush/plugins/alarm/_model.py
+++ b/platypush/plugins/alarm/_model.py
@@ -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,
diff --git a/platypush/plugins/media/vlc/__init__.py b/platypush/plugins/media/vlc/__init__.py
index 0965c9e7f..a80a7bef4 100644
--- a/platypush/plugins/media/vlc/__init__.py
+++ b/platypush/plugins/media/vlc/__init__.py
@@ -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()