def service_toggle(self, kwargs):
self.workday = not self.workday
self.log("Service toggle test")
I see from the AD log that the service is registered, and it also appears in the Developer Tools in hass.io, but when I try to call it I just get a message that the service is not found:
@tormagj did you solve it? I’m having a similar issue.
I do not believe the self.fire_event() should be necessary. That function simply creates an “empty text service name” in HA, with no link back to AppDaemon. Looking at how HADashboard-widgets have done it, I have created a minimal example below, which does not work. Also, HADashboard doesn’t work for me neither, but that obviously worked at least at some point in time in the authors system based on the screenshots in the git repo.
Another interesting fact is that adding a listener to service_registered event in HA (devtools->events) shows no events being fired.
Thus, it seems like the services are created within AppDaemon, but never connected to HA. Specifically, everything up to the line linked below seems to execute correctly, but after that my knowledge in AppDaemon internals and asyncio is to shallow to grasp what’s going wrong.
I have tried to create a workaround based on self.fire_event(), and attaching a listener for the service call event, but since the service call is empty, no call is ever made from within HA.
Yes, I asked around on the Discord and was informed that the register_service is basically only for AD services and not HA services. Seems that HA does not support adding services this way, perhaps it should be? Anyone able to shed some light on the subject?
I was able to register services from AD and have them appear in HA by also firing a service registered event. See image below. The service also appears when using the list_services() method. However, when trying to call the service, HA just says “service not found”. So I concluded it is not possible, although it seems it could be…
One thing is adding some weird services, but what I was actually trying to achieve was having my AD app add for instance input_boolean or input_number on the fly in my code, and then use them for input in a custom Lovelace card. This didn’t work for some reason since the fields don’t seem to be “fully qualified” although they appeared in the States overview, they’re state changes were not registered properly. However, by following a thread I found on the topic, I was able to do what I wanted by implementing a listener in AD for those funky inputs that I added by code. So all in all it seems to be working fine.
I believe this was the post that helped me on my way:
Here is more or less what it boiled down to in my case:
def create_input_booleans(self):
self.input_booleans = [
{"entity_id" : "input_boolean.app_alarmclock_skipnext", "state":"off", "attr": {"editable": "true", "friendly_name": "AlarmClock skipnext", "icon": "mdi:debug-step-over"}},
{"entity_id" : "input_boolean.app_alarmclock_stop", "state":"off", "attr": {"editable": "true", "friendly_name": "AlarmClock stop", "icon": "mdi:stop"}},
]
for ib in self.input_booleans:
self.set_state(ib["entity_id"], state=ib["state"], attributes=ib["attr"])
self.listen_event(self.ib_change_state, event = "call_service")
self.listen_state(self.stop, "input_boolean.app_alarmclock_stop")
def stop(self, entity, attribute, old, new, kwargs={}):
if new == "on":
self.log("Stop requested")
self.switchoff()
self.call_service("input_boolean/turn_off", entity_id="input_boolean.app_alarmclock_stop")
def ib_change_state(self,event_name,data, kwargs):
entity_id = data.get("service_data").get("entity_id", None)
if entity_id is not None and any(entity_id in e["entity_id"] for e in self.input_booleans):
entity_data = self.get_state(entity_id, attribute="all")
if(data["service"] == "turn_off"):
self.log(entity_id + " switched off")
self.set_state(entity_id, state = "off", attributes=entity_data["attributes"])
if(data["service"] == "turn_on"):
self.log(entity_id + " switched on")
self.set_state(entity_id, state = "on", attributes=entity_data["attributes"])
if(data["service"] == "toggle"):
self.log(entity_id + " toggled")
togglestate = "on" if entity_data["state"] == "off" else "off"
self.set_state(entity_id, state = togglestate, attributes=entity_data["attributes"])
def createsensorsetup(self):
"""Create custom sensor"""
self.create_input_booleans()
So, in conclusion, I wasn’t able to register services freely, but I was able to achieve something similar by listening to changes. Hope it helps someone else looking around!
Yes, thank you. The docs definitely need some TLC in this area, in the appdaemon docs it doesn’t even define the callback args or mention that this isn’t a service that can be called from HA. You just saved me a bunch of time, thanks again.
Then, in my app, I listen to events that call that script. You can easily access the data that is passed to the script too, so you could choose what action your app takes depending on that data.
def initialize(self):
# Listen to the script being called which acts as the interface between
# HA and AppDaemon
self.listen_event(
self.app_service_call,
event="call_service",
service="ad_presence_light_controller",
)
def app_service_call(self, event_name, data, kwargs):
service_data = data["service_data"]
try:
assert "method" in service_data, "Missing data field: 'method'"
except AssertionError as exception:
self.log(f"Service data invalid: {exception}", level="ERROR")
return
if service_data["method"] == "reset_room":
self._reset_room()
Hope that helps! It would be great to have a direct way of creating services in HA, but I think this workaround is fairly clean.
Listening to events work.
For example I have an Appdaemon “service” that does TTS announce, optionally with an MP3 sound effect played before the text - all this as a HA event, basically like this:
class tts_announce(hass.Hass):
def initialize(self):
self.listen_event(self.tts_announce, "tts_announce")
def tts_announce(self, event, data, args):
if data is not None and 'message' in data:
text = data.get("message")
filename = None
if "filename" in data:
filename = data.get("filename")
...