├── .github ├── FUNDING.yml └── workflows │ └── validate.yaml ├── LICENSE ├── README.md ├── custom_components └── hass_agent_notifier │ ├── __init__.py │ ├── manifest.json │ └── notify.py └── hacs.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: LAB02-Admin 2 | ko_fi: lab02research 3 | -------------------------------------------------------------------------------- /.github/workflows/validate.yaml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - name: HACS validation 15 | uses: "hacs/action@main" 16 | with: 17 | category: "integration" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) LAB02 Research 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Validate](https://github.com/LAB02-Research/HASS.Agent-Notifier/workflows/Validate/badge.svg)](https://github.com/LAB02-Research/HASS.Agent-Notifier/actions?query=workflow:"Validate") 2 | [![GitHub release](https://img.shields.io/github/release/LAB02-Research/HASS.Agent-Notifier?include_prereleases=&sort=semver&color=blue)](https://github.com/LAB02-Research/HASS.Agent-Notifier/releases/) 3 | [![License](https://img.shields.io/badge/License-MIT-blue)](#license) 4 | [![buymeacoffee](https://img.shields.io/badge/BuyMeACoffee-Donate-blue.svg)](https://www.buymeacoffee.com/lab02research) 5 | [![Discord](https://img.shields.io/badge/dynamic/json?color=blue&label=Discord&logo=discord&logoColor=white&query=presence_count&suffix=%20Online&url=https://discordapp.com/api/guilds/932957721622360074/widget.json)](https://discord.gg/nMvqzwrVBU) 6 | 7 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg)](https://github.com/hacs/integration) 8 | 9 | 10 | # HASS.Agent Notifier 11 | 12 | ⚠️❗**YOU DON'T NEED THIS INTEGRATION IF YOU'RE USING THE NEW [HASS.AGENT](https://github.com/LAB02-Research/HASS.Agent-Integration) INTEGRATION!** ❗⚠️ 13 | 14 | ---- 15 | 16 | Note: this integrations has been replaced by the [new integration](https://github.com/LAB02-Research/HASS.Agent-Integration). It will keep working, but for new functionality, consider switching to the new one. 17 | 18 | ---- 19 | 20 | This Home Assistant integration allows you to send notifications to HASS.Agent, a Windows-based Home Assistant client. 21 | 22 | Need help? Check [the documentation](https://hassagent.readthedocs.io/), visit the dedicated HA forum thread or join on Discord. 23 | 24 | Note: it won't be of much use if you don't have HASS.Agent installed & configured on at least one PC (or Windows based device). 25 | 26 | ---- 27 | 28 | ### Contents 29 | 30 | * [Functionality](#functionality) 31 | * [Installation](#installation) 32 | * [Configuration](#configuration) 33 | * [Installation and Configuration Summary](#installation-and-configuration-summary) 34 | * [Usage](#usage) 35 | * [Help](#help) 36 | * [Debugging](#debugging) 37 | * [License](#license) 38 | 39 | ---- 40 | 41 | ### Functionality 42 | 43 | Send normal (text-based) and image notifications, and configure them to be *actionable* to directly interact with Home Assistant. 44 | 45 | ---- 46 | 47 | ### Installation 48 | 49 | The easiest way to install is to use HACS. Simply search for **HASS.Agent Notifier**, install and restart Home Assistant. 50 | 51 | If you want to manually install, copy the `hass_agent_notifier` folder into the `config\custom_components` folder of your Home Assistant instance, and restart. 52 | 53 | ---- 54 | 55 | ### Configuration 56 | 57 | This integration exposes itself as a notifications integration, and can be configured by adding this snippet in your `configuration.yaml` file: 58 | 59 | ```yaml 60 | notify: 61 | name: "hass agent test device" 62 | platform: hass_agent_notifier 63 | resource: http://{device_ip}:5115/notify 64 | ``` 65 | 66 | Replace `{device_ip}` with the IP of the device that has an HASS.Agent instance running. To find your IP, run `ipconfig` in a command prompt on your PC. Look for the value after `IPv4 Address`. Optionally replace `5115` if you've configured a different port, normally you shouldn't have to. 67 | 68 | Restart Home Assistant to load your configuration. 69 | 70 | The port needs to be open on the target device. HASS.Agent will offer to do this for you during the onboarding process. 71 | To do so manually, you can run this command in an elevated prompt: 72 | 73 | `netsh advfirewall firewall add rule name="HASS.Agent Notifier" dir=in action=allow protocol=TCP localport=5115` 74 | 75 | ---- 76 | 77 | ### Installation and Configuration Summary 78 | 79 | Quick summary to get things working: 80 | 81 | - Install **HASS.Agent-Notifier** integration, either through HACS or manually 82 | - Reboot Home Assistant 83 | - Create a `notify` entity, make sure you enter the right IP for your PC 84 | - Reboot Home Assistant 85 | - Start adding the new entity to your automations & scripts :) 86 | 87 | ---- 88 | 89 | ### Usage 90 | 91 | #### General 92 | 93 | Currently, there are four variables you can set: 94 | 95 | * `message`: the message you want to show 96 | * `title`: the title of your popup [optional] 97 | * `image`: http(s) url containing the location of an image [optional] 98 | * `duration`: duration (in seconds) for which the popup will be shown [optional] 99 | 100 | #### Text notification 101 | 102 | ```yaml 103 | action: 104 | - service: notify.hass_agent_test_device 105 | data: 106 | message: "This is a test message." 107 | ``` 108 | 109 | #### Text notification with title and duration 110 | 111 | ```yaml 112 | action: 113 | - service: notify.hass_agent_test_device 114 | data: 115 | message: "This is a test message with title and 3 sec duration." 116 | title: "HASS.Agent Test" 117 | data: 118 | duration: 3 119 | ``` 120 | 121 | #### Image notification 122 | 123 | ```yaml 124 | action: 125 | - service: notify.hass_agent_test_device 126 | data: 127 | message: "This is a test message with an image." 128 | data: 129 | image: "http://10.0.0.6:1234/jpeg/image.jpg" 130 | ``` 131 | 132 | #### Camera Proxy 133 | 134 | As pointed out by [@brianhanifin]( https://github.com/brianhanifin ) in this issue, you can also use Home Assistant's camera proxy. This way you don't have to share the credentials etc. of your camera. Home Assistant will provide a token that's valid for 5 minutes, so it's safe to use. 135 | 136 | Example script: 137 | 138 | ```yaml 139 | notification_test: 140 | alias: Notification Test 141 | variables: 142 | image: | 143 | {%- set image = "http://hass.local:8123" + state_attr("camera.garden","entity_picture") %} 144 | {{ image }} 145 | sequence: 146 | - service: notify.hass_agent_test 147 | data: 148 | title: Test 149 | message: "This is a test message with an image." 150 | data: 151 | image: "{{ image }}" 152 | mode: single 153 | icon: mdi:bell 154 | ``` 155 | 156 | Optionally change `hass.local` to the mDNS/IP of your Home Assistant instance, and change `garden` to the name of your camera - or use another variable as provided in the linked issue. 157 | 158 | #### Script GUI examples 159 | 160 | This is the sequence part of a test script to send a text-only message, created through the Home Assistant GUI: 161 | 162 | ![Script Test Notification](https://raw.githubusercontent.com/LAB02-Research/HASS.Agent/main/images/notifier_script_example.png) 163 | 164 | This is the same script, but with an image added to the notification: 165 | 166 | ![Script Test Image Notification](https://raw.githubusercontent.com/LAB02-Research/HASS.Agent/main/images/notifier_script_image_example.png) 167 | 168 | You can use the new Button Card to trigger your test scripts. 169 | 170 | ---- 171 | 172 | ### Help 173 | 174 | There's a section dedicated to notification support in [the documentation](https://hassagent.readthedocs.io/). It'll help you troubleshoot common problems, and provide some examples. 175 | 176 | ---- 177 | 178 | ### Debugging 179 | 180 | **Note: make sure you check [the documentation](https://hassagent.readthedocs.io/) for common troubleshooting help.** 181 | 182 | If something's not working as it should, while everything's configured and HASS.Agent isn't showing any errors in its logs, browse to the following URL from another PC on the same network as HASS.Agent: `http://{hass_agent_ip}:5115`. Make sure to change `{hass_agent_ip}` to the IP of the PC where HASS.Agent's installed. 183 | 184 | If HASS.Agent is configured and the firewall rule's active, you'll see: `HASS.Agent Active`. 185 | 186 | If not, something is blocking access to HASS.Agent. Add the following snippet to your configuration.yaml to enable debug logging for the integration: 187 | 188 | 189 | ```yaml 190 | logger: 191 | default: warning 192 | logs: 193 | custom_components.hass_agent_notifier: debug 194 | ``` 195 | 196 | Reboot Home Assistant. Whenever you send a message, this should show up in your logs: 197 | 198 | ![Debug Output](https://raw.githubusercontent.com/LAB02-Research/HASS.Agent/main/images/notifier_debug_logging.png) 199 | 200 | If not, please open a ticket and post your log output. 201 | 202 | ---- 203 | 204 | ### License 205 | 206 | HASS.Agent Notifier and HASS.Agent are released under the MIT license. 207 | -------------------------------------------------------------------------------- /custom_components/hass_agent_notifier/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /custom_components/hass_agent_notifier/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "hass_agent_notifier", 3 | "name": "HASS.Agent Notifier", 4 | "documentation": "https://github.com/LAB02-Research/HASS.Agent-Notifier", 5 | "issue_tracker": "https://github.com/LAB02-Research/HASS.Agent-Notifier/issues", 6 | "dependencies": [], 7 | "codeowners": ["@LAB02-Admin"], 8 | "version": "2022.10.25.1", 9 | "requirements": [], 10 | "iot_class": "local_push" 11 | } 12 | -------------------------------------------------------------------------------- /custom_components/hass_agent_notifier/notify.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom component for Home Assistant to enable sending messages via HASS Agent. 3 | 4 | 5 | Example configuration.yaml entry: 6 | 7 | notify: 8 | - name: hass notifier 9 | platform: hass_agent_notifier 10 | resource: http://192.168.0.1:5115/notify 11 | 12 | With this custom component loaded, you can send messaged to a HASS Agent. 13 | """ 14 | 15 | import logging 16 | from typing import Any 17 | from contextlib import suppress 18 | 19 | import requests 20 | import voluptuous as vol 21 | import re 22 | 23 | from homeassistant.components.notify import ( 24 | ATTR_TITLE_DEFAULT, 25 | ATTR_TITLE, 26 | ATTR_DATA, 27 | PLATFORM_SCHEMA, 28 | BaseNotificationService, 29 | ) 30 | 31 | from homeassistant.components import media_source 32 | 33 | from homeassistant.helpers.network import NoURLAvailableError, get_url 34 | 35 | from http import HTTPStatus 36 | 37 | from homeassistant.const import ( 38 | CONF_RESOURCE, 39 | ) 40 | import homeassistant.helpers.config_validation as cv 41 | 42 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_RESOURCE): cv.url}) 43 | 44 | _LOGGER = logging.getLogger(__name__) 45 | 46 | CAMERA_PROXY_REGEX = re.compile(r"\/api\/camera_proxy\/camera\.(.*)") 47 | 48 | 49 | def get_service(hass, config, discovery_info=None): 50 | """Get the HASS Agent notification service.""" 51 | resource = config.get(CONF_RESOURCE) 52 | 53 | _LOGGER.info("Service created") 54 | 55 | return HassAgentNotificationService(hass, resource) 56 | 57 | 58 | class HassAgentNotificationService(BaseNotificationService): 59 | """Implementation of the HASS Agent notification service""" 60 | 61 | def __init__(self, hass, resource): 62 | """Initialize the service.""" 63 | self._resource = resource 64 | self._hass = hass 65 | 66 | def send_request(self, url, data): 67 | """Sends the json request""" 68 | return requests.post(url, json=data, timeout=10) 69 | 70 | async def async_send_message(self, message: str, **kwargs: Any): 71 | """Send the message to the provided resource.""" 72 | _LOGGER.debug("Preparing notification") 73 | 74 | title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) 75 | data = kwargs.get(ATTR_DATA, None) 76 | 77 | if data is None: 78 | data = dict() 79 | 80 | image = data.get("image", None) 81 | 82 | if image is not None: 83 | new_url = None 84 | 85 | camera_proxy_match = CAMERA_PROXY_REGEX.match(image) 86 | 87 | if camera_proxy_match is not None: 88 | camera = self.hass.states.get(f"camera.{camera_proxy_match.group(1)}") 89 | 90 | if camera is not None: 91 | external_url = None 92 | with suppress(NoURLAvailableError): # external_url not configured 93 | external_url = get_url(self.hass, allow_internal=False) 94 | 95 | if external_url is not None: 96 | access_token = camera.attributes["access_token"] 97 | new_url = f"{external_url}{image}?token={access_token}" 98 | 99 | elif media_source.is_media_source_id(image): 100 | sourced_media = await media_source.async_resolve_media(self.hass, image) 101 | sourced_media = media_source.async_process_play_media_url( 102 | self.hass, sourced_media.url 103 | ) 104 | new_url = sourced_media 105 | 106 | if new_url is not None: 107 | data.update({"image": new_url}) 108 | 109 | payload = {"message": message, "title": title, "data": data} 110 | 111 | _LOGGER.debug("Sending notification") 112 | 113 | try: 114 | 115 | response = await self.hass.async_add_executor_job( 116 | self.send_request, self._resource, payload 117 | ) 118 | 119 | _LOGGER.debug("Checking result") 120 | 121 | if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: 122 | _LOGGER.error( 123 | "Server error. Response %d: %s", 124 | response.status_code, 125 | response.reason, 126 | ) 127 | elif response.status_code == HTTPStatus.BAD_REQUEST: 128 | _LOGGER.error( 129 | "Client error (bad request). Response %d: %s", 130 | response.status_code, 131 | response.reason, 132 | ) 133 | elif response.status_code == HTTPStatus.NOT_FOUND: 134 | _LOGGER.debug( 135 | "Server error (not found). Response %d: %s", 136 | response.status_code, 137 | response.reason, 138 | ) 139 | elif response.status_code == HTTPStatus.METHOD_NOT_ALLOWED: 140 | _LOGGER.error( 141 | "Server error (method not allowed). Response %d", 142 | response.status_code, 143 | ) 144 | elif response.status_code == HTTPStatus.REQUEST_TIMEOUT: 145 | _LOGGER.debug( 146 | "Server error (request timeout). Response %d: %s", 147 | response.status_code, 148 | response.reason, 149 | ) 150 | elif response.status_code == HTTPStatus.NOT_IMPLEMENTED: 151 | _LOGGER.error( 152 | "Server error (not implemented). Response %d: %s", 153 | response.status_code, 154 | response.reason, 155 | ) 156 | elif response.status_code == HTTPStatus.SERVICE_UNAVAILABLE: 157 | _LOGGER.error( 158 | "Server error (service unavailable). Response %d", 159 | response.status_code, 160 | ) 161 | elif response.status_code == HTTPStatus.GATEWAY_TIMEOUT: 162 | _LOGGER.error( 163 | "Network error (gateway timeout). Response %d: %s", 164 | response.status_code, 165 | response.reason, 166 | ) 167 | elif response.status_code == HTTPStatus.OK: 168 | _LOGGER.debug( 169 | "Success. Response %d: %s", response.status_code, response.reason 170 | ) 171 | else: 172 | _LOGGER.debug( 173 | "Unknown response %d: %s", response.status_code, response.reason 174 | ) 175 | 176 | except Exception as ex: 177 | _LOGGER.debug("Error sending message: %s", ex) 178 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HASS.Agent Notifier", 3 | "homeassistant": "2021.4", 4 | "render_readme": true 5 | } 6 | --------------------------------------------------------------------------------