Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0c4d298ab | ||
|
|
6adf60a450 | ||
|
|
4c3ba13f6c | ||
|
|
4e55124837 | ||
|
|
0bea772784 | ||
|
|
04a0bef826 |
@@ -11,21 +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. Generate your API key: [link to your profile](https://my.home-assistant.io/redirect/profile/) (security tab, long-lived access tokens)
|
```bash
|
||||||
3. Go to addons/airsend folder
|
git clone https://github.com/devmel/hass_airsend-addon
|
||||||
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 .`
|
cd hass_airsend-addon/addons/airsend
|
||||||
5. Depending on your home assistant server and your token, run in a terminal. Example with homeassistant.local:8123:
|
```
|
||||||
|
|
||||||
|
#### 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
|
```bash
|
||||||
docker run -dp 33863:33863 \
|
docker run -dp 33863:33863 \
|
||||||
-e HASS_HOST='homeassistant.local:8123' \
|
-e HTTPS=1 \
|
||||||
-e HASS_TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiZDg1MWJlNjE1NmM0Zjc4YTRiNzhjZjRlMDc4MWZiMCIsImlhdCI6MTY5MTIyOTg5MCwiZXhwIjoyMDA2NTg5ODkwfQ._ECys-uPkM8fn1W2wvjzIJ9HLJcI7RHmZmix_2C9QgU' \
|
-e HASS_HOST='homeassistant.local:8123' \
|
||||||
-e HASS_AUTOINCLUDE=0 \
|
-e HASS_TOKEN='your_token_here' \
|
||||||
hass_airsend-addon
|
-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.
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ FROM $BUILD_FROM
|
|||||||
ENV HASS_HOST=
|
ENV HASS_HOST=
|
||||||
ENV HASS_TOKEN=
|
ENV HASS_TOKEN=
|
||||||
ENV HASS_AUTOINCLUDE=0
|
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 hass_cb.php /home
|
COPY hass_cb.php /home
|
||||||
|
COPY sync_hass_states.php /home
|
||||||
COPY state_post.sh /home
|
COPY state_post.sh /home
|
||||||
COPY states_get.sh /home
|
COPY states_get.sh /home
|
||||||
COPY run.sh /
|
COPY run.sh /
|
||||||
@@ -18,7 +21,16 @@ 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
|
||||||
|
|||||||
@@ -300,7 +300,39 @@ class HassAPI{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadStates() {
|
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;
|
$ret = false;
|
||||||
if($this->states){
|
if($this->states){
|
||||||
$ret = true;
|
$ret = true;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
-5
@@ -9,11 +9,8 @@ case "$arch" in \
|
|||||||
amd64) arch='x86_64' ;; \
|
amd64) arch='x86_64' ;; \
|
||||||
i386) arch='x86' ;; \
|
i386) arch='x86' ;; \
|
||||||
esac;
|
esac;
|
||||||
echo "AirSendWebService arch: ${arch}"
|
|
||||||
|
|
||||||
# Start AirSendWebService
|
|
||||||
ulimit -n 4096
|
ulimit -n 4096
|
||||||
./bin/unix/${arch}/AirSendWebService 99399
|
echo "AirSendWebService arch: ${arch}"
|
||||||
|
|
||||||
CALLBACK_PID=
|
CALLBACK_PID=
|
||||||
if [ -n "${SUPERVISOR_TOKEN:-}" ]
|
if [ -n "${SUPERVISOR_TOKEN:-}" ]
|
||||||
@@ -21,20 +18,53 @@ then
|
|||||||
hname="$(hostname -i)"
|
hname="$(hostname -i)"
|
||||||
echo "internal_url: http://${hname}:33863/"
|
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
|
# Start hass callback service
|
||||||
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
|
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
|
||||||
elif [ -n "${HASS_HOST:-}" ] && [ -n "${HASS_TOKEN:-}" ]
|
elif [ -n "${HASS_HOST:-}" ] && [ -n "${HASS_TOKEN:-}" ]
|
||||||
then
|
then
|
||||||
echo "internal_url: http://{YOUR_DOCKER_MACHINE_IP}:33863/"
|
# 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
|
# Start hass callback service
|
||||||
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
|
php -S 127.0.0.1:80 hass_cb.php & CALLBACK_PID=$!
|
||||||
else
|
else
|
||||||
|
# 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..."
|
echo "Running the AirSendWebService only..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Give services time to start
|
# Give services time to start
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
|
# 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
|
# Monitor services
|
||||||
ASW_PID=$(cat AirSendWebService.lock)
|
ASW_PID=$(cat AirSendWebService.lock)
|
||||||
while true; do
|
while true; do
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/with-contenv bashio
|
#!/usr/bin/with-contenv bashio
|
||||||
|
|
||||||
curl -o states.json -s -X GET -H "Authorization: Bearer ${SUPERVISOR_TOKEN}" -H "Content-Type: application/json" http://supervisor/core/api/states
|
curl -o states.json -s -X GET -H "Authorization: Bearer ${HASS_TOKEN}" -H "Content-Type: application/json" http://${HASS_HOST}/api/states/$1
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user