From 7f91fb3bd1881dd0d5617e70f6e5439b89d60f77 Mon Sep 17 00:00:00 2001 From: tlskinneriv Date: Sat, 20 Jul 2024 17:18:11 -0500 Subject: [PATCH] Fix for usage outside of HA OS (#16) - fixes for usage outside of HA OS - add service dependency, add meaningful logs --- .vscode/tasks.json | 13 ++++++++++ awnet/CHANGELOG.md | 15 +++++++++++ awnet/DOCS.md | 35 +++++++++++++++++++++++++ awnet/README.md | 6 ++++- awnet/build.yaml | 10 ++++---- awnet/config.yaml | 6 ++--- awnet/rootfs/awnet.py | 60 ++++++++++++++++++++++++++++++++++--------- awnet/rootfs/run.sh | 7 ++--- 8 files changed, 128 insertions(+), 24 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 171dde1..68c1707 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,6 +14,19 @@ "panel": "new" }, "problemMatcher": [] + }, + { + "label": "Monitor AWNET Logs", + "type": "shell", + "command": "while true; do docker logs -f addon_local_awnet_to_hass; sleep 1; done", + "group": { + "kind": "test", + }, + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] } ] } diff --git a/awnet/CHANGELOG.md b/awnet/CHANGELOG.md index 098f486..3034601 100644 --- a/awnet/CHANGELOG.md +++ b/awnet/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [1.1.2] - 2024-07-20 + +### Added + +- Meaningful log messages for when HA is unavailable or the Ambient Weather Station - Local integration is not set up. + +### Changed + +- HOUSEKEEPING: Updated base container to python3.12/alpine3.20. + +### Fixed + +- Update script to enable usage outside of HA OS. Closes [#15](https://github.com/tlskinneriv/hassio-addons/issues/15). +- Allow capital characters in MAC address override. + ## [1.1.1] - 2024-03-30 ### Changed diff --git a/awnet/DOCS.md b/awnet/DOCS.md index 270bc11..523086f 100644 --- a/awnet/DOCS.md +++ b/awnet/DOCS.md @@ -29,3 +29,38 @@ _These instructions are adapted from the Android app, but should be similar on o - Upload Interval: (user determined, how often to send data to Home Assistant) - Click "Save" at the bottom of the form - Use the "Finish" button in the upper right to complete configuration + +## Non-HA OS Options + +The following options have been minimally tested, but should work properly. By default, the container/script +will listen on all IP addresses on port 7080. + +### Container Setup without HA OS (e.g. HA Docker) + +Obtain the container from the GitHub registry for your platform: + +- armhf: ghcr.io/tlskinneriv/armhf-addon-awnet_to_hass +- armv7: ghcr.io/tlskinneriv/armv7-addon-awnet_to_hass +- aarch64: ghcr.io/tlskinneriv/aarch64-addon-awnet_to_hass +- amd64: ghcr.io/tlskinneriv/amd64-addon-awnet_to_hass +- i386: ghcr.io/tlskinneriv/i386-addon-awnet_to_hass + +Run the container with at least the following environment variables set: + +- HA_API_BASE_URL: The base URL to your HA instance's API; e.g. http://homeassistant.local:8123/api/ +- HA_API_AUTH_TOKEN: A long-lived access token for HA. Create one under user profile -> security. + +See the header comments in the [awnet.py][awnet.py] script for additional details and settings. + +### Script Setup for use with HA Core + +Obtain the [awnet.py][awnet.py] script. + +Run the script with at least the following environment variables set: + +- HA_API_BASE_URL: The base URL to your HA instance's API; e.g. http://homeassistant.local:8123/api/ +- HA_API_AUTH_TOKEN: A long-lived access token for HA. Create one under user profile -> security. + +See the header comments in the [awnet.py][awnet.py] script for additional details and settings. + +[awnet.py]: https://github.com/tlskinneriv/hassio-addons/blob/master/awnet/rootfs/awnet.py \ No newline at end of file diff --git a/awnet/README.md b/awnet/README.md index b7927b2..e35cc8c 100644 --- a/awnet/README.md +++ b/awnet/README.md @@ -18,4 +18,8 @@ This add-on and integration combo are a work in progress. Any feedback and/or co [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg [armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg -[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg \ No newline at end of file +[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg + +## Usage Without Home Assistant OS + +See the [documentation](https://github.com/tlskinneriv/hassio-addons/blob/master/awnet/DOCS.md#non-ha-os-options) about usage of the container and script within in the event you are running HA in Docker or HA Core. diff --git a/awnet/build.yaml b/awnet/build.yaml index 3d8c390..b1ceeae 100644 --- a/awnet/build.yaml +++ b/awnet/build.yaml @@ -1,10 +1,10 @@ # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-dockerfile build_from: - aarch64: "ghcr.io/home-assistant/aarch64-base-python:3.11-alpine3.18" - amd64: "ghcr.io/home-assistant/amd64-base-python:3.11-alpine3.18" - armhf: "ghcr.io/home-assistant/armhf-base-python:3.11-alpine3.18" - armv7: "ghcr.io/home-assistant/armv7-base-python:3.11-alpine3.18" - i386: "ghcr.io/home-assistant/i386-base-python:3.11-alpine3.18" + aarch64: "ghcr.io/home-assistant/aarch64-base-python:3.12-alpine3.20" + amd64: "ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.20" + armhf: "ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.20" + armv7: "ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.20" + i386: "ghcr.io/home-assistant/i386-base-python:3.12-alpine3.20" labels: org.opencontainers.image.title: "Home Assistant Add-on: AWNET to HASS" org.opencontainers.image.description: "Addon to capture local Ambient Weather data in Home Assistant" diff --git a/awnet/config.yaml b/awnet/config.yaml index c4f3a09..84c2ff1 100644 --- a/awnet/config.yaml +++ b/awnet/config.yaml @@ -1,5 +1,5 @@ name: AWNET to HASS -version: 1.1.1 +version: 1.1.2 slug: awnet_to_hass description: Addon to capture local Ambient Weather data in Home Assistant arch: @@ -13,8 +13,8 @@ options: log_level: WARNING schema: log_level: list(DEBUG|INFO|WARNING|ERROR) - passkey_override: "match(^(?:[a-f0-9]{2}[\\.\\-:]?){5}[a-f0-9]{2}$)?" + passkey_override: "match(^(?:[a-fA-F0-9]{2}[\\.\\-:]?){5}[a-fA-F0-9]{2}$)?" ports: - 80/tcp: 7080 + 7080/tcp: 7080 init: false image: "ghcr.io/tlskinneriv/{arch}-addon-awnet_to_hass" diff --git a/awnet/rootfs/awnet.py b/awnet/rootfs/awnet.py index 0da9a49..a08be3c 100644 --- a/awnet/rootfs/awnet.py +++ b/awnet/rootfs/awnet.py @@ -6,7 +6,22 @@ # original author: Austin of austinsnerdythings.com # publish date: 2021-03-20 -from urllib.parse import parse_qs +# In the event this script is to be used outside of this container configuration, the following +# environment variables must be set for proper operation: +# +# HA_API_BASE_URL: The base URL to your HA instance's API; e.g. http://homeassistant.local:8123/api/ +# HA_API_AUTH_TOKEN: A long-lived access token for HA. Create one under user profile -> security. +# +# Optional variables: +# +# HTTP_LISTEN_HOST: Default: "" (all IP addresses); the host that this script will listen on for updates from the AWNET device +# HTTP_LISTEN_PORT: Default: 7080; The port that this script should listen on for updates from the AWNET device +# HA_API_VALIDATE_CERTIFICATE: Default: True; if set to True, verifies the SSL certificate on HTTPS requests +# LOG_LEVEL: Default: WARNING; log level setting; one of: DEBUG, INFO, WARNING, ERROR, CRITICAL +# PASSKEY_OVERRIDE: Default: ""; In the event that the PASSKEY value for the device is not a MAC address, use this field to +# statically set it to your devices MAC address. + +from urllib.parse import parse_qs, urljoin from wsgiref.headers import Headers from wsgiref.simple_server import WSGIRequestHandler import re @@ -16,13 +31,19 @@ import logging import sys -# set vars -AUTH_TOKEN = os.getenv("SUPERVISOR_TOKEN", "test") - ATTR_PASSKEY = 'PASSKEY' _LOGGER = logging.getLogger(__name__) +# set vars +PASSKEY_OVERRIDE = os.environ.get("PASSKEY_OVERRIDE", "") +LOG_LEVEL = os.getenv("LOG_LEVEL", "WARNING") +HTTP_LISTEN_HOST = os.getenv("HTTP_LISTEN_HOST", "") +HTTP_LISTEN_PORT = int(os.getenv("HTTP_LISTEN_PORT", "7080")) +HA_API_AUTH_TOKEN = os.getenv("HA_API_AUTH_TOKEN", "test") +HA_API_BASE_URL = os.getenv("HA_API_BASE_URL", "http://supervisor/core/api/") +HA_API_VALIDATE_CERTIFICATE = os.getenv("HA_API_VALIDATE_CERTIFICATE", "True").lower() == 'true' + def get_headers(environ): """ Handles getting the headers from the environment, Content-Type is the special one @@ -40,19 +61,26 @@ def publish(payload): payload_json = json.dumps(payload) head = { - "Authorization": "Bearer " + AUTH_TOKEN, + "Authorization": "Bearer " + HA_API_AUTH_TOKEN, "content-type": "application/json", } good_responses = [200, 201] - url = "http://supervisor/core/api/services/awnet_local/update" + url = urljoin(HA_API_BASE_URL, "services/awnet_local/update") - response = requests.post(url, data=payload_json, headers=head) + response = requests.post(url, data=payload_json, headers=head, verify=HA_API_VALIDATE_CERTIFICATE) if response.status_code in good_responses: _LOGGER.info(f"Sent {payload_json}") else: - _LOGGER.error(f"Failed to send {payload_json} to {url} with headers {head}; {response.content}") + _LOGGER.error(f"Failed to send data to Home Assistant. HTTP error code: {response.status_code}.") + _LOGGER.info(f"Failed payload: {payload_json} to {url} with headers {head}; {response.content}") + if response.status_code == 400: + _LOGGER.warning("'Ambient Weather Station - Local' may not be set up. Please install it from HACS and set it up with your device's MAC address.") + elif response.status_code == 502: + _LOGGER.warning("Home Assistant may not be available to receive requests.") + else: + _LOGGER.warning("An unknown error occurred.") def handle_results(result): @@ -66,7 +94,7 @@ def handle_results(result): result[key] = result[key][0] else: _LOGGER.error('Unexpected list size for key %s', key) - m = re.search(pattern=r'(?:[a-f0-9]{2}[\.\-:]?){5}[a-f0-9]{2}', string=os.environ.get('PASSKEY_OVERRIDE', ''), flags=re.IGNORECASE) + m = re.search(pattern=r'(?:[a-f0-9]{2}[\.\-:]?){5}[a-f0-9]{2}', string=PASSKEY_OVERRIDE, flags=re.IGNORECASE) if m is not None: _LOGGER.debug('%s override: %s', ATTR_PASSKEY, m[0]) result[ATTR_PASSKEY] = m[0] @@ -151,10 +179,18 @@ def log_message(self, format, *args): logging.basicConfig(stream = sys.stdout, format = '[%(asctime)s] [%(levelname)-8s] %(message)s (%(filename)s:%(lineno)d)', - level = sys.argv[1]) + level = LOG_LEVEL) + + # log variable values for DEBUG + _LOGGER.info("LOG_LEVEL: %s", LOG_LEVEL) + _LOGGER.info("HTTP_LISTEN_HOST: %s", HTTP_LISTEN_HOST) + _LOGGER.info("HTTP_LISTEN_PORT: %s", HTTP_LISTEN_PORT) + _LOGGER.info("HA_API_AUTH_TOKEN: %s", HA_API_AUTH_TOKEN) + _LOGGER.info("HA_API_BASE_URL: %s", HA_API_BASE_URL) + _LOGGER.info("HA_API_VALIDATE_CERTIFICATE: %s", HA_API_VALIDATE_CERTIFICATE) # probably shouldn't run on port 80 but that's what I specified in the ambient weather console - httpd = make_server("", 80, application, handler_class=AWNETWSGIRequestHandler) - _LOGGER.info("Serving on http://localhost:80") + httpd = make_server(HTTP_LISTEN_HOST, HTTP_LISTEN_PORT, application, handler_class=AWNETWSGIRequestHandler) + _LOGGER.info(f"Serving on http://{HTTP_LISTEN_HOST}:{HTTP_LISTEN_PORT}") httpd.serve_forever() diff --git a/awnet/rootfs/run.sh b/awnet/rootfs/run.sh index 10e19d7..4b883fb 100644 --- a/awnet/rootfs/run.sh +++ b/awnet/rootfs/run.sh @@ -1,6 +1,7 @@ #!/usr/bin/with-contenv bashio -export PASSKEY_OVERRIDE=$(bashio::config 'passkey_override') +export PASSKEY_OVERRIDE=${PASSKEY_OVEFRRRIDE:-$(bashio::config 'passkey_override')} +export LOG_LEVEL=${LOG_LEVEL:-$(bashio::config 'log_level')} +export HA_API_AUTH_TOKEN=${HA_API_AUTH_TOKEN:-$SUPERVISOR_TOKEN} -LOG_LEVEL=$(bashio::config 'log_level') -python3 awnet.py $LOG_LEVEL \ No newline at end of file +python3 awnet.py