Repository: AppDaemon/appdaemon

Issue:
add oneshot to listen_event
I noticed that `listen_state` has `oneshot` which seems very useful. I think it makes sense to implement the same in `listen_event`. What do you guys think?

I might give this a shot if I have time soon.


Discussion:
I saw someone asking for this just the other day. I think it would be useful.

Resolving patch:
diff --git a/appdaemon/adapi.py b/appdaemon/adapi.py
--- a/appdaemon/adapi.py
+++ b/appdaemon/adapi.py
@@ -1539,6 +1539,8 @@ async def listen_event(self, callback, event=None, **kwargs):
             **kwargs (optional): Zero or more keyword arguments.
 
         Keyword Args:
+            oneshot (bool, optional): If ``True``, the callback will be automatically cancelled
+                after the first state change that results in a callback.
             namespace(str, optional): Namespace to use for the call. See the section on
                 `namespaces <APPGUIDE.html#namespaces>`__ for a detailed description.
                 In most cases, it is safe to ignore this parameter. The value ``global``
diff --git a/appdaemon/events.py b/appdaemon/events.py
--- a/appdaemon/events.py
+++ b/appdaemon/events.py
@@ -259,6 +259,7 @@ async def process_event_callbacks(self, namespace, data):
                 self.logger.debug("Discarding event for loop avoidance")
                 return
 
+        removes = []
         for name in self.AD.callbacks.callbacks.keys():
             for uuid_ in self.AD.callbacks.callbacks[name]:
                 callback = self.AD.callbacks.callbacks[name][uuid_]
@@ -287,7 +288,7 @@ async def process_event_callbacks(self, namespace, data):
 
                         if _run:
                             if name in self.AD.app_management.objects:
-                                await self.AD.threading.dispatch_worker(name,
+                                executed = await self.AD.threading.dispatch_worker(name,
                                                                         {
                                                                             "id": uuid_,
                                                                             "name": name,
@@ -300,6 +301,15 @@ async def process_event_callbacks(self, namespace, data):
                                                                             "pin_thread": callback["pin_thread"],
                                                                             "kwargs": callback["kwargs"]
                                                                         })
+                                
+                                # Remove the callback if appropriate
+                                if executed is True:
+                                    remove = callback["kwargs"].get("oneshot", False)
+                                    if remove is True:
+                                        removes.append({"name": name, "uuid": uuid_})
+
+        for remove in removes:
+            await self.cancel_event_callback(remove["name"], remove["uuid"])
      
     async def event_services(self, namespace, domain, service, kwargs):
         if "event" in kwargs:


Test patch: