From c5f99dae9bdd6ab2e6c1276765296e90ed904f4e Mon Sep 17 00:00:00 2001 From: Devmel Apps Date: Mon, 6 Oct 2025 10:49:59 +0200 Subject: [PATCH] Standalone version --- CHANGELOG.md | 4 ++ README.md | 16 ++++-- addons/airsend/Dockerfile | 6 ++- addons/airsend/build.yaml | 10 ++-- addons/airsend/config.yaml | 2 +- addons/airsend/{callback.php => hass_cb.php} | 29 ++++++---- addons/airsend/hassapi.class.php | 34 +++++++++--- addons/airsend/run.sh | 56 +++++++++++++++----- 8 files changed, 117 insertions(+), 40 deletions(-) rename addons/airsend/{callback.php => hass_cb.php} (68%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd102d7..176e2e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.3 + +- Standalone version + ## 1.2 - RF listening and states managements on callback diff --git a/README.md b/README.md index 051b0bf..85b38b2 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,14 @@ Home Assistant Addon to control an AirSend device in a local network. ## Install on an external machine 1. Clone repository -2. Go to addons/airsend folder -3. In DockerFile : replace `ARG BUILD_FROM FROM $BUILD_FROM` with your machine architecture build, example : `FROM ghcr.io/home-assistant/amd64-base:3.18` -4. In callback.php : replace $BASE_HASS_API and $HASS_API_TOKEN with your home automation machine values -5. In terminal, run `docker build -t hass_airsend-addon .` -6. In terminal, run `docker run -dp 33863:33863 hass_airsend-addon` +2. Generate your API key: [link to your profile](https://my.home-assistant.io/redirect/profile/) (security tab, long-lived access tokens) +3. Go to addons/airsend folder +4. Depending on your machine architecture, run in a terminal. Example with amd64: `docker build --build-arg "BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.22" -t hass_airsend-addon .` +5. Depending on your home assistant server and your token, run in a terminal. Example with homeassistant.local:8123: +```bash +docker run -dp 33863:33863 \ +-e HASS_HOST='homeassistant.local:8123' \ +-e HASS_TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiZDg1MWJlNjE1NmM0Zjc4YTRiNzhjZjRlMDc4MWZiMCIsImlhdCI6MTY5MTIyOTg5MCwiZXhwIjoyMDA2NTg5ODkwfQ._ECys-uPkM8fn1W2wvjzIJ9HLJcI7RHmZmix_2C9QgU' \ +-e HASS_AUTOINCLUDE=0 \ +hass_airsend-addon +``` diff --git a/addons/airsend/Dockerfile b/addons/airsend/Dockerfile index 887949a..f47433e 100644 --- a/addons/airsend/Dockerfile +++ b/addons/airsend/Dockerfile @@ -1,9 +1,13 @@ ARG BUILD_FROM FROM $BUILD_FROM +ENV HASS_HOST= +ENV HASS_TOKEN= +ENV HASS_AUTOINCLUDE=0 + # Copy data for add-on COPY hassapi.class.php /home -COPY callback.php /home +COPY hass_cb.php /home COPY state_post.sh /home COPY states_get.sh /home COPY run.sh / diff --git a/addons/airsend/build.yaml b/addons/airsend/build.yaml index 27f6ef1..3a12d60 100644 --- a/addons/airsend/build.yaml +++ b/addons/airsend/build.yaml @@ -1,9 +1,9 @@ build_from: - aarch64: ghcr.io/home-assistant/aarch64-base:3.18 - amd64: ghcr.io/home-assistant/amd64-base:3.18 - armhf: ghcr.io/home-assistant/armhf-base:3.18 - armv7: ghcr.io/home-assistant/armv7-base:3.18 - i386: ghcr.io/home-assistant/i386-base:3.18 + aarch64: ghcr.io/home-assistant/aarch64-base:3.22 + amd64: ghcr.io/home-assistant/amd64-base:3.22 + armhf: ghcr.io/home-assistant/armhf-base:3.22 + armv7: ghcr.io/home-assistant/armv7-base:3.22 + i386: ghcr.io/home-assistant/i386-base:3.22 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io diff --git a/addons/airsend/config.yaml b/addons/airsend/config.yaml index 4586fc0..daa1cc1 100644 --- a/addons/airsend/config.yaml +++ b/addons/airsend/config.yaml @@ -1,6 +1,6 @@ name: "AirSend" description: "AirSend Web Service (local connection, no internet, no cloud)" -version: "1.2" +version: "1.3" slug: "airsend" url: https://github.com/devmel/hass_airsend-addon codenotary: notary@home-assistant.io diff --git a/addons/airsend/callback.php b/addons/airsend/hass_cb.php similarity index 68% rename from addons/airsend/callback.php rename to addons/airsend/hass_cb.php index a7a488a..74b4d09 100644 --- a/addons/airsend/callback.php +++ b/addons/airsend/hass_cb.php @@ -1,14 +1,25 @@ isAuthorized()){ header("HTTP/1.1 401 Unauthorized"); @@ -28,10 +39,10 @@ if (is_array($data) && isset($data['events'])) { if($val['type'] == 3 || $val['type'] == 2 || $val['type'] == 1){ $states = $api->convertNotesToStates($val['thingnotes']['notes']); foreach ($states as $j => $state) { - $api->setState($entity_id, $state[0], $state[1], $val['timestamp']); + $api->setState($entity_id, $state[0], $state[1], $val['timestamp'], $val['channel']); } }else{ - $api->setState($entity_id, 'error', 'error_'.$val['type'], $val['timestamp']); + $api->setState($entity_id, 'error', 'error_'.$val['type'], $val['timestamp'], $val['channel']); } } //Interrupt event @@ -47,7 +58,7 @@ if (is_array($data) && isset($data['events'])) { //Search entity and update $entities = $api->searchEntitiesFromChannelAndType($val['channel'], $state[0]); foreach ($entities as $k => $entity_id) { - $api->setState($entity_id, $state[0], $state[1], $val['timestamp']); + $api->setState($entity_id, $state[0], $state[1], $val['timestamp'], $val['channel']); } //Creates if not exists if(count($entities) == 0){ diff --git a/addons/airsend/hassapi.class.php b/addons/airsend/hassapi.class.php index 0f0a17d..5851c8f 100644 --- a/addons/airsend/hassapi.class.php +++ b/addons/airsend/hassapi.class.php @@ -3,11 +3,10 @@ class HassAPI{ var $states = null; - function __construct($BASE_HASS_API, $HASS_API_TOKEN) { + function __construct($BASE_HASS_API, $HASS_API_TOKEN, $auto_include = false) { $this->BASE_HASS_API = $BASE_HASS_API; $this->HASS_API_TOKEN = $HASS_API_TOKEN; - $fbvalue = @file_get_contents('auto_include.cfg'); - $this->auto_include = filter_var($fbvalue, FILTER_VALIDATE_BOOLEAN); + $this->auto_include = $auto_include; } public function isAuthorized() { @@ -64,6 +63,11 @@ class HassAPI{ function setState($entity_id, $type, $state, $timestamp_ms, $channel = null) { $reloadcache = false; $attributes = null; + if(isset($channel) && ((int)$channel["id"]) == 1){ + if($type === 'data'){ + return; //accept AirSend state only + } + } if($entity_id == null && isset($channel)){ $reloadcache = true; //New entity @@ -148,6 +152,24 @@ class HassAPI{ return null; } + public static function extractHostAndPort($input, $default_port = 8123){ + $hostWithPort = preg_replace('#^https?://#', '', $input); + $hostWithPort = explode('/', $hostWithPort)[0]; + if (strpos($hostWithPort, ':') !== false) { + list($hostname, $port) = explode(':', $hostWithPort, 2); + $port = (int)$port; + } else { + $hostname = $hostWithPort; + } + if(empty($port) || ($port <= 0 && $port >= 0x1000)){ + $port = $default_port; + } + return [ + 'hostname' => $hostname, + 'port' => $port + ]; + } + public static function convertNotesToStates($notes){ $ret = array(); if(is_array($notes) && count($notes) > 0){ @@ -332,10 +354,8 @@ class HassAPI{ } curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method)); if (isset($token)){ - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER); - curl_setopt($curl,CURLOPT_XOAUTH2_BEARER, $token); - $options = array(CURLOPT_HTTPHEADER => array('Content-Type: application/json')); - curl_setopt_array($curl, $options); + $options = array('Content-Type: application/json', 'Authorization: Bearer ' . $token); + curl_setopt($curl, CURLOPT_HTTPHEADER, $options); } $result = array(); $result['data'] = curl_exec($curl); diff --git a/addons/airsend/run.sh b/addons/airsend/run.sh index cbb542f..9a90b05 100644 --- a/addons/airsend/run.sh +++ b/addons/airsend/run.sh @@ -10,18 +10,50 @@ case "$arch" in \ i386) arch='x86' ;; \ esac; echo "AirSendWebService arch: ${arch}" -hname="$(hostname -i)" -echo "internal_url: http://${hname}:33863/" - -if [ -n "${SUPERVISOR_TOKEN:-}" ] -then - echo ${SUPERVISOR_TOKEN} > hass_api.token - echo $(bashio::config 'auto_include') > auto_include.cfg -else - echo "Not running on Home Assistant machine..." -fi +# Start AirSendWebService ulimit -n 4096 ./bin/unix/${arch}/AirSendWebService 99399 -php -S 127.0.0.1:80 callback.php -sleep infinity + +CALLBACK_PID= +if [ -n "${SUPERVISOR_TOKEN:-}" ] +then + hname="$(hostname -i)" + echo "internal_url: http://${hname}:33863/" + echo $(bashio::config 'auto_include') > auto_include.cfg + # Start hass callback service + php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$! +elif [ -n "${HASS_HOST:-}" ] && [ -n "${HASS_TOKEN:-}" ] +then + echo "internal_url: http://{YOUR_DOCKER_MACHINE_IP}:33863/" + # Start hass callback service + php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$! +else + echo "Running the AirSendWebService only..." +fi + +# Give services time to start +sleep 5 + +# Monitor services +ASW_PID=$(cat AirSendWebService.lock) +while true; do + sleep 30 + + # Check if AirSendWebService is still running + if ! kill -0 "$ASW_PID" 2>/dev/null; then + echo "ERROR: AirSendWebService (PID: $ASW_PID) died..." >&2 + break + fi + + # Check if Callback service exists and if is still running + if [[ -n "$CALLBACK_PID" && "$CALLBACK_PID" =~ ^[0-9]+$ ]] && ! kill -0 "$CALLBACK_PID" 2>/dev/null; then + echo "ERROR: Callback Service (PID: $CALLBACK_PID) died..." >&2 + break + fi +done + +kill $ASW_PID 2>/dev/null || true +kill $CALLBACK_PID 2>/dev/null || true +echo "exiting..." +exit 1