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
|
||||
|
||||
1. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend-addon/master/install | bash -`
|
||||
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).
|
||||
2. In Supervisor -> Add-on store -> Local add-ons, refresh, install and start AirSend addon
|
||||
#### 1. Install the Add-on
|
||||
Run the following command in your terminal:
|
||||
```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
|
||||
1. Clone repository
|
||||
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:
|
||||
#### 1. Clone the Repository
|
||||
```bash
|
||||
git clone https://github.com/devmel/hass_airsend-addon
|
||||
cd hass_airsend-addon/addons/airsend
|
||||
```
|
||||
|
||||
#### 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 HASS_HOST='homeassistant.local:8123' \
|
||||
-e HASS_TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiZDg1MWJlNjE1NmM0Zjc4YTRiNzhjZjRlMDc4MWZiMCIsImlhdCI6MTY5MTIyOTg5MCwiZXhwIjoyMDA2NTg5ODkwfQ._ECys-uPkM8fn1W2wvjzIJ9HLJcI7RHmZmix_2C9QgU' \
|
||||
-e HASS_AUTOINCLUDE=0 \
|
||||
hass_airsend-addon
|
||||
-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.
|
||||
|
||||
@@ -4,10 +4,13 @@ FROM $BUILD_FROM
|
||||
ENV HASS_HOST=
|
||||
ENV HASS_TOKEN=
|
||||
ENV HASS_AUTOINCLUDE=0
|
||||
ENV HTTPS=1
|
||||
|
||||
# Copy data for add-on
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY hassapi.class.php /home
|
||||
COPY hass_cb.php /home
|
||||
COPY sync_hass_states.php /home
|
||||
COPY state_post.sh /home
|
||||
COPY states_get.sh /home
|
||||
COPY run.sh /
|
||||
@@ -18,7 +21,16 @@ RUN chmod a+x /run.sh
|
||||
# Install AirSendWebService
|
||||
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
|
||||
CMD [ "/run.sh" ]
|
||||
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;
|
||||
if($this->states){
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-4
@@ -9,11 +9,8 @@ case "$arch" in \
|
||||
amd64) arch='x86_64' ;; \
|
||||
i386) arch='x86' ;; \
|
||||
esac;
|
||||
echo "AirSendWebService arch: ${arch}"
|
||||
|
||||
# Start AirSendWebService
|
||||
ulimit -n 4096
|
||||
./bin/unix/${arch}/AirSendWebService 99399
|
||||
echo "AirSendWebService arch: ${arch}"
|
||||
|
||||
CALLBACK_PID=
|
||||
if [ -n "${SUPERVISOR_TOKEN:-}" ]
|
||||
@@ -21,20 +18,53 @@ then
|
||||
hname="$(hostname -i)"
|
||||
echo "internal_url: http://${hname}:33863/"
|
||||
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
|
||||
# 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
|
||||
|
||||
# Give services time to start
|
||||
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
|
||||
ASW_PID=$(cat AirSendWebService.lock)
|
||||
while true; do
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
|
||||
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
|
||||
|
||||
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