8 Commits
12 changed files with 368 additions and 66 deletions
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## 1.3
- Standalone version
## 1.2 ## 1.2
- RF listening and states managements on callback - RF listening and states managements on callback
+54 -9
View File
@@ -11,15 +11,60 @@ Home Assistant Addon to control an AirSend device in a local network.
## Manual Installation ## Manual Installation
1. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend-addon/master/install | bash -` #### 1. Install the Add-on
OR copy the `airsend` folder into your [addon folder](https://developers.home-assistant.io/docs/creating_integration_file_structure/#where-home-assistant-looks-for-integrations). Run the following command in your terminal:
2. In Supervisor -> Add-on store -> Local add-ons, refresh, install and start AirSend addon ```bash
wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend-addon/master/install bash -
```
**OR**
Manually copy the `airsend` folder into your [Home Assistant addons directory](https://developers.home-assistant.io/docs/creating_integration_file_structure/#where-home-assistant-looks-for-integrations).
#### 2. Start the Add-on
1. Go to **Supervisor****Add-on Store****Local add-ons**.
2. Refresh the list, then install and start the **AirSend** add-on.
## Install on an external machine ## Install on an external machine
1. Clone repository #### 1. Clone the Repository
2. Go to addons/airsend folder ```bash
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` git clone https://github.com/devmel/hass_airsend-addon
4. In callback.php : replace $BASE_HASS_API and $HASS_API_TOKEN with your home automation machine values cd hass_airsend-addon/addons/airsend
5. In terminal, run `docker build -t hass_airsend-addon .` ```
6. In terminal, run `docker run -dp 33863:33863 hass_airsend-addon`
#### 2. Generate a Home Assistant API Key
1. Go to your [Home Assistant profile](https://my.home-assistant.io/redirect/profile/).
2. Navigate to the **Security** tab.
3. Generate a **long-lived access token** (use this as `HASS_TOKEN`).
---
#### 3. Build the Docker Image
Replace `amd64` with your machine's architecture if needed (`armhf`, `armv7`, `aarch64`, etc.):
```bash
docker build --build-arg "BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.22" -t hass_airsend-addon .
```
---
#### 4. Run the Docker Container
Adjust the environment variables according to your setup:
```bash
docker run -dp 33863:33863 \
-e HTTPS=1 \
-e HASS_HOST='homeassistant.local:8123' \
-e HASS_TOKEN='your_token_here' \
-e HASS_AUTOINCLUDE=0 \
hass_airsend-addon
```
##### Environment Variables:
- HTTPS=1: Enables HTTPS. Use HTTPS=0 if you cannot handle self-signed certificates.
- HASS_HOST: Your Home Assistant server address and port (e.g., homeassistant.local:8123).
- HASS_TOKEN: The long-lived access token you generated.
- HASS_AUTOINCLUDE: Set to 0 to disable auto-inclusion of the add-on in Home Assistant.
---
### Important Notes
- **Security**: Never share your `HASS_TOKEN`.
- **HTTPS**: If `HTTPS=0`, ensure your network is secure.
+22 -4
View File
@@ -1,18 +1,36 @@
ARG BUILD_FROM ARG BUILD_FROM
FROM $BUILD_FROM FROM $BUILD_FROM
ENV HASS_HOST=
ENV HASS_TOKEN=
ENV HASS_AUTOINCLUDE=0
ENV HTTPS=1
# Copy data for add-on # Copy data for add-on
COPY nginx.conf /etc/nginx/nginx.conf
COPY hassapi.class.php /home COPY hassapi.class.php /home
COPY callback.php /home COPY hass_cb.php /home
COPY poststate.sh /home COPY sync_hass_states.php /home
COPY state_post.sh /home
COPY states_get.sh /home
COPY run.sh / COPY run.sh /
RUN chmod a+x /home/poststate.sh RUN chmod a+x /home/state_post.sh
RUN chmod a+x /home/states_get.sh
RUN chmod a+x /run.sh RUN chmod a+x /run.sh
# Install AirSendWebService # Install AirSendWebService
RUN mkdir -p /home && cd /home && wget http://devmel.com/dl/AirSendWebService.tgz && tar -zxvf AirSendWebService.tgz && chmod -R 777 bin RUN mkdir -p /home && cd /home && wget http://devmel.com/dl/AirSendWebService.tgz && tar -zxvf AirSendWebService.tgz && chmod -R 777 bin
RUN apk add php php-curl # Install Services
RUN apk add php php-curl nginx openssl
# Generate self-signed certificate
RUN mkdir -p /etc/nginx/ssl
RUN openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-subj "/C=FR/ST=France/L=Paris/O=AirSendWebService/CN=localhost" \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt
# Start AirSendWebService # Start AirSendWebService
CMD [ "/run.sh" ] CMD [ "/run.sh" ]
EXPOSE 33863 EXPOSE 33863
+5 -5
View File
@@ -1,9 +1,9 @@
build_from: build_from:
aarch64: ghcr.io/home-assistant/aarch64-base:3.18 aarch64: ghcr.io/home-assistant/aarch64-base:3.22
amd64: ghcr.io/home-assistant/amd64-base:3.18 amd64: ghcr.io/home-assistant/amd64-base:3.22
armhf: ghcr.io/home-assistant/armhf-base:3.18 armhf: ghcr.io/home-assistant/armhf-base:3.22
armv7: ghcr.io/home-assistant/armv7-base:3.18 armv7: ghcr.io/home-assistant/armv7-base:3.22
i386: ghcr.io/home-assistant/i386-base:3.18 i386: ghcr.io/home-assistant/i386-base:3.22
codenotary: codenotary:
signer: notary@home-assistant.io signer: notary@home-assistant.io
base_image: notary@home-assistant.io base_image: notary@home-assistant.io
+1 -1
View File
@@ -1,6 +1,6 @@
name: "AirSend" name: "AirSend"
description: "AirSend Web Service (local connection, no internet, no cloud)" description: "AirSend Web Service (local connection, no internet, no cloud)"
version: "1.2" version: "1.3"
slug: "airsend" slug: "airsend"
url: https://github.com/devmel/hass_airsend-addon url: https://github.com/devmel/hass_airsend-addon
codenotary: notary@home-assistant.io codenotary: notary@home-assistant.io
@@ -1,14 +1,25 @@
<?php <?php
require_once dirname(__FILE__) . "/hassapi.class.php"; require_once dirname(__FILE__) . "/hassapi.class.php";
$BASE_HASS_API = "http://supervisor/core/api"; $SUPERVISOR_TOKEN = @getenv("SUPERVISOR_TOKEN");
$HASS_API_TOKEN = @file_get_contents('hass_api.token'); $HASS_HOST= @getenv("HASS_HOST");
$HASS_TOKEN= @getenv("HASS_TOKEN");
//On an external machine, replace with your ha values $auto_include = false;
//$BASE_HASS_API = "http://homeassistant.local:8123/api"; if(!empty($HASS_HOST) && !empty($HASS_TOKEN)){
//$HASS_API_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3YTJjYjE2M2VjZjQ0MGM0OGUwYzdkOTc2MjM4YWY5MCIsImlhdCI6MTY2ODg5NzA4OCwiZXhwIjoxOTg0MjU3MDg4fQ.gyDg_jYbD561OdQ0IngAMga-4LE3DTsd6bEIGkITGTc'; $machine = HassAPI::extractHostAndPort($HASS_HOST);
$HASS_API_URL = "http://".$machine['hostname'].":".$machine['port']."/api";
$HASS_API_TOKEN = $HASS_TOKEN;
$HASS_AUTOINCLUDE = @getenv("HASS_AUTOINCLUDE");
$auto_include = filter_var($HASS_AUTOINCLUDE, FILTER_VALIDATE_BOOLEAN);
}else if(!empty($SUPERVISOR_TOKEN)){
$HASS_API_URL = "http://supervisor/core/api";
$HASS_API_TOKEN = $SUPERVISOR_TOKEN;
$fbvalue = @file_get_contents('auto_include.cfg');
$auto_include = filter_var($fbvalue, FILTER_VALIDATE_BOOLEAN);
}
$api = new HassAPI($BASE_HASS_API, $HASS_API_TOKEN); $api = new HassAPI($HASS_API_URL, $HASS_API_TOKEN, $auto_include);
if(!$api->isAuthorized()){ if(!$api->isAuthorized()){
header("HTTP/1.1 401 Unauthorized"); 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){ if($val['type'] == 3 || $val['type'] == 2 || $val['type'] == 1){
$states = $api->convertNotesToStates($val['thingnotes']['notes']); $states = $api->convertNotesToStates($val['thingnotes']['notes']);
foreach ($states as $j => $state) { 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{ }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 //Interrupt event
@@ -47,7 +58,7 @@ if (is_array($data) && isset($data['events'])) {
//Search entity and update //Search entity and update
$entities = $api->searchEntitiesFromChannelAndType($val['channel'], $state[0]); $entities = $api->searchEntitiesFromChannelAndType($val['channel'], $state[0]);
foreach ($entities as $k => $entity_id) { 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 //Creates if not exists
if(count($entities) == 0){ if(count($entities) == 0){
+113 -29
View File
@@ -3,15 +3,10 @@
class HassAPI{ class HassAPI{
var $states = null; 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->BASE_HASS_API = $BASE_HASS_API;
$this->HASS_API_TOKEN = $HASS_API_TOKEN; $this->HASS_API_TOKEN = $HASS_API_TOKEN;
$this->auto_include = false; $this->auto_include = $auto_include;
if($this->isAuthorized()){
$fbvalue = @file_get_contents('auto_include.cfg');
$this->auto_include = filter_var($fbvalue, FILTER_VALIDATE_BOOLEAN);
$this->loadStates();
}
} }
public function isAuthorized() { public function isAuthorized() {
@@ -24,7 +19,7 @@ class HassAPI{
return $this->auto_include; return $this->auto_include;
} }
function searchEntityId($uid) { function searchEntityId($uid) {
if($this->states){ if($this->loadStates()){
foreach ($this->states as $value) { foreach ($this->states as $value) {
if($value['entity_uid_sha256'] == $uid){ if($value['entity_uid_sha256'] == $uid){
return $value['entity_id']; return $value['entity_id'];
@@ -36,7 +31,7 @@ class HassAPI{
function searchEntitiesFromChannelAndType($channel, $type) { function searchEntitiesFromChannelAndType($channel, $type) {
$ret = array(); $ret = array();
if(isset($channel) && $this->states){ if(isset($channel) && $this->loadStates()){
foreach ($this->states as $value) { foreach ($this->states as $value) {
if(array_key_exists('attributes', $value) && array_key_exists('channel', $value['attributes'])){ if(array_key_exists('attributes', $value) && array_key_exists('channel', $value['attributes'])){
if(self::isChannelCompatible($channel, $value['attributes']['channel'])){ if(self::isChannelCompatible($channel, $value['attributes']['channel'])){
@@ -66,8 +61,15 @@ class HassAPI{
} }
function setState($entity_id, $type, $state, $timestamp_ms, $channel = null) { function setState($entity_id, $type, $state, $timestamp_ms, $channel = null) {
$reloadcache = false;
$attributes = null; $attributes = null;
if(isset($channel) && ((int)$channel["id"]) == 1){
if($type === 'data'){
return; //accept AirSend state only
}
}
if($entity_id == null && isset($channel)){ if($entity_id == null && isset($channel)){
$reloadcache = true;
//New entity //New entity
$entity_id = "sensor.".self::toUniqueChannelName($channel); $entity_id = "sensor.".self::toUniqueChannelName($channel);
$attributes = array('channel' => $channel); $attributes = array('channel' => $channel);
@@ -104,11 +106,13 @@ class HassAPI{
if(array_key_exists('state', $content) && $content['state'] == $state){ if(array_key_exists('state', $content) && $content['state'] == $state){
$state .= '+'; $state .= '+';
} }
$reloadcache = true;
} }
if($type === 'toggle'){ if($type === 'toggle'){
if(array_key_exists('state', $content) && $content['state'] == 'pressed'){ if(array_key_exists('state', $content) && $content['state'] == 'pressed'){
$state .= '+'; $state .= '+';
} }
$reloadcache = true;
} }
if($type === 'level'){ if($type === 'level'){
$is_cover = (stripos($entity_id, "cover.") !== false) ? true : false; $is_cover = (stripos($entity_id, "cover.") !== false) ? true : false;
@@ -131,14 +135,14 @@ class HassAPI{
unset($content['last_updated']); unset($content['last_updated']);
//$dti = DateTimeImmutable::createFromFormat('U.u', ($timestamp_ms/1000)); //$dti = DateTimeImmutable::createFromFormat('U.u', ($timestamp_ms/1000));
//$content['last_updated'] = $dti->format('Y-m-d').'T'.$dti->format('H:i:s').'+00:00'; //$content['last_updated'] = $dti->format('Y-m-d').'T'.$dti->format('H:i:s').'+00:00';
$this->postState($entity_id, $content); $this->state_post($entity_id, $content, $reloadcache);
return true; return true;
} }
return false; return false;
} }
function getState($entity_id){ function getState($entity_id){
if($this->states && isset($entity_id)){ if($this->loadStates() && isset($entity_id)){
foreach ($this->states as $value) { foreach ($this->states as $value) {
if($value['entity_id'] == $entity_id){ if($value['entity_id'] == $entity_id){
return $value; return $value;
@@ -148,6 +152,24 @@ class HassAPI{
return null; 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){ public static function convertNotesToStates($notes){
$ret = array(); $ret = array();
if(is_array($notes) && count($notes) > 0){ if(is_array($notes) && count($notes) > 0){
@@ -253,31 +275,94 @@ class HassAPI{
return $result; return $result;
} }
private function postState($entity_id, $content){ private function state_post($entity_id, $content, $reloadcache = false){
$json = json_encode($content, JSON_FORCE_OBJECT); $json = json_encode($content, JSON_FORCE_OBJECT);
if(strpos($this->BASE_HASS_API, "supervisor") !== false){ if(strpos($this->BASE_HASS_API, "supervisor") !== false){
//Bug in supervisor proxy => use bash //Bug in supervisor proxy => use bash
exec('./poststate.sh "'.$entity_id.'" "'.base64_encode($json).'"', $output, $retval); exec('./state_post.sh "'.$entity_id.'" "'.base64_encode($json).'"', $output, $retval);
}else{ }else{
$res = HassAPI::request($this->BASE_HASS_API."/states/".$entity_id, $json, 'POST', $this->HASS_API_TOKEN); $res = HassAPI::request($this->BASE_HASS_API."/states/".$entity_id, $json, 'POST', $this->HASS_API_TOKEN);
} }
trigger_error("postState ".$entity_id." ".$content['state'], E_USER_NOTICE); trigger_error("state_post ".$reloadcache." : ".$entity_id." ".$content['state'], E_USER_NOTICE);
if($reloadcache){
$this->states_get_cache();
}
} }
private function states_get_cache(){
private function loadStates() { if(strpos($this->BASE_HASS_API, "supervisor") !== false){
$res = HassAPI::request($this->BASE_HASS_API."/states", null, 'GET', $this->HASS_API_TOKEN); //Bug in supervisor proxy => use bash
if($res !== false && isset($res) && is_array($res) && isset($res['data'])){ exec('./states_get.sh', $output, $retval);
$this->states = json_decode($res['data'], true); }else{
if($this->states){ $res = HassAPI::request($this->BASE_HASS_API."/states", null, 'GET', $this->HASS_API_TOKEN);
foreach ($this->states as &$value) { if($res !== false && isset($res) && is_array($res) && isset($res['data'])){
$h = str_split(hash('sha256', $value['entity_id']),12)[0]; file_put_contents("states.json", $res['data']);
$val = intval($h, 16);
$value['entity_uid_sha256'] = $val;
}
} }
} }
} }
public function loadStatesForSync() {
$ret = false;
if($this->states){
$ret = true;
}else{
if($this->isAuthorized()){
$loaded = false;
$data_age = @(time() - filemtime('states.json'));
if($data_age > 100){
$this->states_get_cache();
}
$data = @file_get_contents('states.json');
if(isset($data)){
$this->states = json_decode($data, true);
if($this->states){
foreach ($this->states as &$value) {
$h = str_split(hash('sha256', $value['entity_id']),12)[0];
$val = intval($h, 16);
$value['entity_uid_sha256'] = $val;
}
$loaded = true;
$ret = true;
}
}
if($loaded == false){
trigger_error("API GET states wrong response", E_USER_ERROR);
}
}
}
return $this->states;
}
Private function loadStates() {
$ret = false;
if($this->states){
$ret = true;
}else{
if($this->isAuthorized()){
$loaded = false;
$data_age = @(time() - filemtime('states.json'));
if($data_age > 100){
$this->states_get_cache();
}
$data = @file_get_contents('states.json');
if(isset($data)){
$this->states = json_decode($data, true);
if($this->states){
foreach ($this->states as &$value) {
$h = str_split(hash('sha256', $value['entity_id']),12)[0];
$val = intval($h, 16);
$value['entity_uid_sha256'] = $val;
}
$loaded = true;
$ret = true;
}
}
if($loaded == false){
trigger_error("API GET states wrong response", E_USER_ERROR);
}
}
}
return $ret;
}
private static function request($url, $data = null, $method = 'GET', $token = null) { private static function request($url, $data = null, $method = 'GET', $token = null) {
$result = false; $result = false;
@@ -301,9 +386,8 @@ class HassAPI{
} }
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method)); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
if (isset($token)){ if (isset($token)){
$token = str_replace('"', "", $token); $options = array('Content-Type: application/json', 'Authorization: Bearer ' . $token);
$options = array(CURLOPT_HTTPHEADER => array('Content-Type: application/json' , "Authorization: Bearer ".$token)); curl_setopt($curl, CURLOPT_HTTPHEADER, $options);
curl_setopt_array($curl, $options);
} }
$result = array(); $result = array();
$result['data'] = curl_exec($curl); $result['data'] = curl_exec($curl);
+16
View File
@@ -0,0 +1,16 @@
events {}
http {
server {
listen 33863 ssl;
server_name _;
ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
location / {
proxy_pass http://127.0.0.1:33864;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
+70 -8
View File
@@ -9,19 +9,81 @@ case "$arch" in \
amd64) arch='x86_64' ;; \ amd64) arch='x86_64' ;; \
i386) arch='x86' ;; \ i386) arch='x86' ;; \
esac; esac;
ulimit -n 4096
echo "AirSendWebService arch: ${arch}" echo "AirSendWebService arch: ${arch}"
hname="$(hostname -i)"
echo "internal_url: http://${hname}:33863/"
CALLBACK_PID=
if [ -n "${SUPERVISOR_TOKEN:-}" ] if [ -n "${SUPERVISOR_TOKEN:-}" ]
then then
echo ${SUPERVISOR_TOKEN} > hass_api.token hname="$(hostname -i)"
echo "internal_url: http://${hname}:33863/"
echo $(bashio::config 'auto_include') > auto_include.cfg echo $(bashio::config 'auto_include') > auto_include.cfg
# Start AirSendWebService
./bin/unix/${arch}/AirSendWebService 99399
# Start hass callback service
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
elif [ -n "${HASS_HOST:-}" ] && [ -n "${HASS_TOKEN:-}" ]
then
# Start AirSendWebService
if [ "${HTTPS:-0}" -eq 1 ]; then
./bin/unix/${arch}/AirSendWebService 33864
nginx
echo "internal_url: https://{YOUR_DOCKER_MACHINE_IP}:33863/"
else
./bin/unix/${arch}/AirSendWebService 99399
echo "internal_url: http://{YOUR_DOCKER_MACHINE_IP}:33863/"
fi
# Start hass callback service
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
else else
echo "Not running on Home Assistant machine..." # Start AirSendWebService
if [ "${HTTPS:-0}" -eq 1 ]; then
./bin/unix/${arch}/AirSendWebService 33864
nginx
echo "example: curl -k https://{YOUR_DOCKER_MACHINE_IP}:33863/"
else
./bin/unix/${arch}/AirSendWebService 99399
echo "example: curl http://{YOUR_DOCKER_MACHINE_IP}:33863/"
fi
echo "Running the AirSendWebService only..."
fi fi
ulimit -n 4096 # Give services time to start
./bin/unix/${arch}/AirSendWebService 99399 sleep 5
php -S 127.0.0.1:80 callback.php
sleep infinity # Lancement du synchro en arrière-plan toutes les X secondes
# Cela garantit que même si un équipement est sur un "autre canal",
# il sera interrogé et mis à jour car on boucle sur la liste globale.
php -r "while(true) { include 'sync_hass_states.php'; sleep(10); }" & SYNC_PID=$!
# Surveillance du processus de synchronisation
while true; do
sleep 60
if [[ -n "$SYNC_PID" && "$SYNC_PID" =~ ^[0-9]+$ ]] && ! kill -0 $SYNC_PID 2>/dev/null; then
echo "ERROR: Sync Service died..." >&2
break
fi
done
# 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
@@ -2,4 +2,4 @@
payload=`echo $2 | base64 -d` payload=`echo $2 | base64 -d`
echo -en $payload | curl -s --data-binary @- -X POST -H "Authorization: Bearer ${SUPERVISOR_TOKEN}" -H "Content-Type: application/json" http://supervisor/core/api/states/$1 echo -en $payload | curl -s --data-binary @- -X POST -H "Authorization: Bearer ${HASS_TOKEN}" -H "Content-Type: application/json" http://${HASS_HOST}/api/states/$1
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bashio
curl -o states.json -s -X GET -H "Authorization: Bearer ${HASS_TOKEN}" -H "Content-Type: application/json" http://${HASS_HOST}/api/states/$1
+59
View File
@@ -0,0 +1,59 @@
<?php
require_once dirname(__FILE__) . "/hassapi.class.php";
// --- Configuration (Identique à votre setup actuel) ---
$SUPERVISOR_TOKEN = @getenv("SUPERVISOR_TOKEN");
$HASS_HOST = @getenv("HASS_HOST");
$HASS_TOKEN = @getenv("HASS_TOKEN");
$auto_include = false;
if(!empty($HASS_HOST) && !empty($HASS_TOKEN)){
$machine = HassAPI::extractHostAndPort($HASS_HOST);
$HASS_API_URL = "http://".$machine['hostname'].":".$machine['port']."/api";
$HASS_API_TOKEN = $HASS_TOKEN;
$HASS_AUTOINCLUDE = @getenv("HASS_AUTOINCLUDE");
$auto_include = filter_var($HASS_AUTOINCLUDE, FILTER_VALIDATE_BOOLEAN);
}else if(!empty($SUPERVISOR_TOKEN)){
$HASS_API_URL = "http://supervisor/core/api";
$HASS_API_TOKEN = $SUPERVISOR_TOKEN;
$fbvalue = @file_get_contents('auto_include.cfg');
$auto_include = filter_var($fbvalue, FILTER_VALIDATE_BOOLEAN);
}
$api = new HassAPI($HASS_API_URL, $HASS_API_TOKEN, $auto_include);
if ($api->isAuthorized()) {
// On récupère la liste des entités que Home Assistant connaît
// Cela nous sert de "liste de courses" pour savoir ce qu'on doit mettre à jour.
$all_entities = $api->loadStatesForSync();
foreach ($all_entities as $entity_data) {
$id = $entity_data['entity_id'];
$details = $api->getState($id); // Récupère le type (temp, humidity, etc.) et l'état actuel.
if ($details) {
/**
* ÉTAPE CRUCIALE : Ici, au lieu d'attendre un événement AirSend,
* vous devez appeler une fonction qui interroge directement
* votre service AirSendWebService pour obtenir la valeur physique.
*
* Si vous avez une méthode ou un endpoint dans AirSend pour "récupérer tout",
* utilisez-le ici pour remplir $current_physical_value.
*/
$current_physical_value = $details['state']; // Par défaut, on garde l'ancienne si pas de changement
// Exemple logique si vous interrogez le service AirSend directement :
// $raw_airsend = \AirSendService::fetchStatus($entity_data['some_id']);
// if ($raw_airsend) { $current_physical_value = $raw_airsend; }
// On force la mise à jour vers Home Assistant
$api->setState(
$id,
$details['type'] ?? 'state',
$current_physical_value,
time() * 1000, // Timestamp actuel pour forcer le "last_updated" dans HA
$entity_data['attributes']['channel'] ?? null
);
}
}
}