|
2 | 2 | from ast import literal_eval |
3 | 3 | from collections.abc import Iterable |
4 | 4 | from copy import deepcopy |
5 | | -from dataclasses import dataclass, field |
6 | 5 | from datetime import datetime, timedelta |
7 | 6 | from pathlib import Path |
8 | 7 | from typing import TYPE_CHECKING, Any, Callable, Literal, Type, overload |
|
15 | 14 | from appdaemon.models.notification.android import AndroidData |
16 | 15 | from appdaemon.models.notification.base import NotificationData |
17 | 16 | from appdaemon.models.notification.iOS import iOSData |
| 17 | +from appdaemon.plugins.hass.exceptions import ScriptNotFound |
18 | 18 | from appdaemon.plugins.hass.hassplugin import HassPlugin |
19 | 19 | from appdaemon.plugins.hass.notifications import AndroidNotification |
20 | | - |
| 20 | +from appdaemon.services import ServiceCallback |
21 | 21 |
|
22 | 22 | # Check if the module is being imported using the legacy method |
23 | 23 | if __name__ == Path(__file__).name: |
|
36 | 36 | from ...models.config.app import AppConfig |
37 | 37 |
|
38 | 38 |
|
39 | | -@dataclass |
40 | | -class ScriptNotFound(ade.AppDaemonException): |
41 | | - script_name: str |
42 | | - namespace: str |
43 | | - plugin_name: str |
44 | | - domain: str = field(init=False, default="script") |
45 | | - |
46 | | - def __str__(self): |
47 | | - res = f"'{self.script_name}' not found in plugin '{self.plugin_name}'" |
48 | | - if self.namespace != "default": |
49 | | - res += f" with namespace '{self.namespace}'" |
50 | | - return res |
51 | | - |
52 | | - |
53 | 39 | class Hass(ADBase, ADAPI): |
54 | 40 | """HASS API class for the users to inherit from. |
55 | 41 |
|
@@ -438,6 +424,105 @@ def constrain_input_select(self, value: str | Iterable[str]) -> bool: |
438 | 424 | # Helper functions for services |
439 | 425 | # |
440 | 426 |
|
| 427 | + @overload |
| 428 | + @utils.sync_decorator |
| 429 | + async def call_service( |
| 430 | + self, |
| 431 | + service: str, |
| 432 | + namespace: str | None = None, |
| 433 | + timeout: str | int | float | None = None, |
| 434 | + callback: ServiceCallback | None = None, |
| 435 | + hass_timeout: str | int | float | None = None, |
| 436 | + suppress_log_messages: bool = False, |
| 437 | + **data, |
| 438 | + ) -> Any: ... |
| 439 | + |
| 440 | + @utils.sync_decorator |
| 441 | + async def call_service(self, *args, **kwargs) -> Any: |
| 442 | + """Calls a Service within AppDaemon. |
| 443 | +
|
| 444 | + Services represent specific actions, and are generally registered by plugins or provided by AppDaemon itself. |
| 445 | + The app calls the service only by referencing the service with a string in the format ``<domain>/<service>``, so |
| 446 | + there is no direct coupling between apps and services. This allows any app to call any service, even ones from |
| 447 | + other plugins. |
| 448 | +
|
| 449 | + Services often require additional parameters, such as ``entity_id``, which AppDaemon will pass to the service |
| 450 | + call as appropriate, if used when calling this function. This allows arbitrary data to be passed to the service |
| 451 | + calls. |
| 452 | +
|
| 453 | + Apps can also register their own services using their ``self.regsiter_service`` method. |
| 454 | +
|
| 455 | + Args: |
| 456 | + service (str): The service name in the format `<domain>/<service>`. For example, `light/turn_on`. |
| 457 | + namespace (str, optional): It's safe to ignore this parameter in most cases because the default namespace |
| 458 | + will be used. However, if a `namespace` is provided, the service call will be made in that namespace. If |
| 459 | + there's a plugin associated with that namespace, it will do the service call. If no namespace is given, |
| 460 | + AppDaemon will use the app's namespace, which can be set using the ``self.set_namespace`` method. See |
| 461 | + the section on `namespaces <APPGUIDE.html#namespaces>`__ for more information. |
| 462 | + timeout (str | int | float, optional): The internal AppDaemon timeout for the service call. If no value is |
| 463 | + specified, the default timeout is 60s. The default value can be changed using the |
| 464 | + ``appdaemon.internal_function_timeout`` config setting. |
| 465 | + callback (callable): The non-async callback to be executed when complete. It should accept a single |
| 466 | + argument, which will be the result of the service call. This is the recommended method for calling |
| 467 | + services which might take a long time to complete. This effectively bypasses the ``timeout`` argument |
| 468 | + because it only applies to this function, which will return immediately instead of waiting for the |
| 469 | + result if a `callback` is specified. |
| 470 | + hass_timeout (str | int | float, optional): Only applicable to the Hass plugin. Sets the amount of time to |
| 471 | + wait for a response from Home Assistant. If no value is specified, the default timeout is 10s. The |
| 472 | + default value can be changed using the ``ws_timeout`` setting the in the Hass plugin configuration in |
| 473 | + ``appdaemon.yaml``. Even if no data is returned from the service call, Home Assistant will still send an |
| 474 | + acknowledgement back to AppDaemon, which this timeout applies to. Note that this is separate from the |
| 475 | + ``timeout``. If ``timeout`` is shorter than this one, it will trigger before this one does. |
| 476 | + suppress_log_messages (bool, optional): Only applicable to the Hass plugin. If this is set to ``True``, |
| 477 | + Appdaemon will suppress logging of warnings for service calls to Home Assistant, specifically timeouts |
| 478 | + and non OK statuses. Use this flag and set it to ``True`` to supress these log messages if you are |
| 479 | + performing your own error checking as described `here <APPGUIDE.html#some-notes-on-service-calls>`__ |
| 480 | + service_data (dict, optional): Used as an additional dictionary to pass arguments into the ``service_data`` |
| 481 | + field of the JSON that goes to Home Assistant. This is useful if you have a dictionary that you want to |
| 482 | + pass in that has a key like ``target`` which is otherwise used for the ``target`` argument. |
| 483 | + **data: Any other keyword arguments get passed to the service call as ``service_data``. Each service takes |
| 484 | + different parameters, so this will vary from service to service. For example, most services require |
| 485 | + ``entity_id``. The parameters for each service can be found in the actions tab of developer tools in |
| 486 | + the Home Assistant web interface. |
| 487 | +
|
| 488 | + Returns: |
| 489 | + Result of the `call_service` function if any, see |
| 490 | + `service call notes <APPGUIDE.html#some-notes-on-service-calls>`__ for more details. |
| 491 | +
|
| 492 | + Examples: |
| 493 | + HASS |
| 494 | + ^^^^ |
| 495 | +
|
| 496 | + >>> self.call_service("light/turn_on", entity_id="light.office_lamp", color_name="red") |
| 497 | + >>> self.call_service("notify/notify", title="Hello", message="Hello World") |
| 498 | + >>> events = self.call_service( |
| 499 | + "calendar/get_events", |
| 500 | + entity_id="calendar.home", |
| 501 | + start_date_time="2024-08-25 00:00:00", |
| 502 | + end_date_time="2024-08-27 00:00:00", |
| 503 | + )["result"]["response"]["calendar.home"]["events"] |
| 504 | +
|
| 505 | + MQTT |
| 506 | + ^^^^ |
| 507 | +
|
| 508 | + >>> self.call_service("mqtt/subscribe", topic="homeassistant/living_room/light", qos=2) |
| 509 | + >>> self.call_service("mqtt/publish", topic="homeassistant/living_room/light", payload="on") |
| 510 | +
|
| 511 | + Utility |
| 512 | + ^^^^^^^ |
| 513 | +
|
| 514 | + It's important that the ``namespace`` arg is set to ``admin`` for these services, as they do not exist |
| 515 | + within the default namespace, and apps cannot exist in the ``admin`` namespace. If the namespace is not |
| 516 | + specified, calling the method will raise an exception. |
| 517 | +
|
| 518 | + >>> self.call_service("app/restart", app="notify_app", namespace="admin") |
| 519 | + >>> self.call_service("app/stop", app="lights_app", namespace="admin") |
| 520 | + >>> self.call_service("app/reload", namespace="admin") |
| 521 | +
|
| 522 | + """ |
| 523 | + # We just wrap the ADAPI.call_service method here to add some additional arguments and docstrings |
| 524 | + return await super().call_service(*args, **kwargs) |
| 525 | + |
441 | 526 | def get_service_info(self, service: str) -> dict | None: |
442 | 527 | """Get some information about what kind of data the service expects to receive, which is helpful for debugging. |
443 | 528 |
|
|
0 commit comments