external machine
This commit is contained in:
@@ -14,3 +14,12 @@ Home Assistant Addon to control an AirSend device in a local network.
|
|||||||
1. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend-addon/master/install | bash -`
|
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).
|
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
|
2. In Supervisor -> Add-on store -> Local add-ons, refresh, install and start AirSend addon
|
||||||
|
|
||||||
|
|
||||||
|
## 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,6 +2,7 @@ ARG BUILD_FROM
|
|||||||
FROM $BUILD_FROM
|
FROM $BUILD_FROM
|
||||||
|
|
||||||
# Copy data for add-on
|
# Copy data for add-on
|
||||||
|
COPY hassapi.class.php /home
|
||||||
COPY callback.php /home
|
COPY callback.php /home
|
||||||
COPY poststate.sh /home
|
COPY poststate.sh /home
|
||||||
COPY run.sh /
|
COPY run.sh /
|
||||||
@@ -14,3 +15,4 @@ RUN mkdir -p /home && cd /home && wget http://devmel.com/dl/AirSendWebService.tg
|
|||||||
RUN apk add php php-curl
|
RUN apk add php php-curl
|
||||||
# Start AirSendWebService
|
# Start AirSendWebService
|
||||||
CMD [ "/run.sh" ]
|
CMD [ "/run.sh" ]
|
||||||
|
EXPOSE 33863
|
||||||
|
|||||||
+12
-147
@@ -1,156 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once dirname(__FILE__) . "/hassapi.class.php";
|
||||||
|
|
||||||
$BASE_HASS_API = "http://supervisor/core/api";
|
$BASE_HASS_API = "http://supervisor/core/api";
|
||||||
$SUPERVISOR_TOKEN = @file_get_contents('hass_api.token');
|
$HASS_API_TOKEN = @file_get_contents('hass_api.token');
|
||||||
|
|
||||||
if(empty($SUPERVISOR_TOKEN)){
|
//On an external machine, replace with your ha values
|
||||||
|
//$BASE_HASS_API = "http://homeassistant.local:8123/api";
|
||||||
|
//$HASS_API_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3YTJjYjE2M2VjZjQ0MGM0OGUwYzdkOTc2MjM4YWY5MCIsImlhdCI6MTY2ODg5NzA4OCwiZXhwIjoxOTg0MjU3MDg4fQ.gyDg_jYbD561OdQ0IngAMga-4LE3DTsd6bEIGkITGTc';
|
||||||
|
|
||||||
|
$api = new HassAPI($BASE_HASS_API, $HASS_API_TOKEN);
|
||||||
|
|
||||||
|
if(!$api->isAuthorized()){
|
||||||
header("HTTP/1.1 401 Unauthorized");
|
header("HTTP/1.1 401 Unauthorized");
|
||||||
exit;
|
trigger_error("Unauthorized", E_USER_ERROR);
|
||||||
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
class API{
|
|
||||||
var $states = null;
|
|
||||||
|
|
||||||
function __construct($BASE_HASS_API, $SUPERVISOR_TOKEN) {
|
|
||||||
$this->BASE_HASS_API = $BASE_HASS_API;
|
|
||||||
$this->SUPERVISOR_TOKEN = $SUPERVISOR_TOKEN;
|
|
||||||
$this->loadStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
static function request($url, $data = null, $method = 'GET', $token = null) {
|
|
||||||
$result = false;
|
|
||||||
if (extension_loaded('curl') === true){
|
|
||||||
$curl = curl_init();
|
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($curl, CURLOPT_AUTOREFERER, true);
|
|
||||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
if (preg_match('~^(?:DELETE|GET|POST|PUT)$~i', $method) > 0){
|
|
||||||
if (preg_match('~^(?:POST|PUT)$~i', $method) > 0){
|
|
||||||
if (is_array($data) === true){
|
|
||||||
foreach (preg_grep('~^@~', $data) as $key => $value){
|
|
||||||
$data[$key] = sprintf('@%s', rtrim(str_replace('\\', '/', realpath(ltrim($value, '@'))), '/') . (is_dir(ltrim($value, '@')) ? '/' : ''));
|
|
||||||
}
|
|
||||||
if (count($data) != count($data, COUNT_RECURSIVE)){
|
|
||||||
$data = http_build_query($data, '', '&');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curl_setopt($curl, CURLOPT_POST, true);
|
|
||||||
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
|
||||||
}
|
|
||||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
|
|
||||||
if (isset($token)){
|
|
||||||
$token = str_replace('"', "", $token);
|
|
||||||
$options = array(CURLOPT_HTTPHEADER => array('Content-Type: application/json' , "Authorization: Bearer ".$token));
|
|
||||||
curl_setopt_array($curl, $options);
|
|
||||||
}
|
|
||||||
$result = array();
|
|
||||||
$result['data'] = curl_exec($curl);
|
|
||||||
$result['info'] = curl_getinfo($curl);
|
|
||||||
if (curl_errno($curl)) {
|
|
||||||
$result['error'] = curl_error($curl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curl_close($curl);
|
|
||||||
}
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
function loadStates() {
|
|
||||||
$res = API::request($this->BASE_HASS_API."/states", null, 'GET', $this->SUPERVISOR_TOKEN);
|
|
||||||
if($res !== false && isset($res) && is_array($res) && isset($res['data'])){
|
|
||||||
$this->states = json_decode($res['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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getState($entity_id){
|
|
||||||
if($this->states){
|
|
||||||
foreach ($this->states as $value) {
|
|
||||||
if($value['entity_id'] == $entity_id){
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function postState($entity_id, $content){
|
|
||||||
$json = json_encode($content, JSON_FORCE_OBJECT);
|
|
||||||
if(strpos($this->BASE_HASS_API, "supervisor") !== false){
|
|
||||||
//Bug in supervisor proxy => use bash
|
|
||||||
exec('./poststate.sh "'.$entity_id.'" "'.base64_encode($json).'"', $output, $retval);
|
|
||||||
trigger_error("postState ".($retval)." ".print_r($output, true), E_USER_WARNING);
|
|
||||||
}else{
|
|
||||||
$res = API::request($this->BASE_HASS_API."/states/".$entity_id, $json, 'POST', $this->SUPERVISOR_TOKEN);
|
|
||||||
trigger_error("postState ".json_encode($res), E_USER_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchEntityId($uid) {
|
|
||||||
if($this->states){
|
|
||||||
foreach ($this->states as $value) {
|
|
||||||
if($value['entity_uid_sha256'] == $uid){
|
|
||||||
return $value['entity_id'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setState($entity_id, $state) {
|
|
||||||
$content = $this->getState($entity_id);
|
|
||||||
if($content != null){
|
|
||||||
//Update state
|
|
||||||
$content['state'] = $state;
|
|
||||||
if(($content['attributes']['supported_features']&0x4) == 0x4){
|
|
||||||
$position = 50;
|
|
||||||
if($state == 'open'){
|
|
||||||
$position = 100;
|
|
||||||
}else if($state == 'closed'){
|
|
||||||
$position = 0;
|
|
||||||
}
|
|
||||||
$content['attributes']['current_position'] = $position;
|
|
||||||
}
|
|
||||||
unset($content['entity_uid_sha256']);
|
|
||||||
unset($content['last_changed']);
|
|
||||||
unset($content['last_updated']);
|
|
||||||
$this->postState($entity_id, $content);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPosition($entity_id, $position) {
|
|
||||||
$content = $this->getState($entity_id);
|
|
||||||
if($content != null){
|
|
||||||
$is_cover = (stripos($entity_id, "cover.") !== false) ? true : false;
|
|
||||||
//Update position
|
|
||||||
$state = 'stop';
|
|
||||||
if(($content['attributes']['supported_features']&0x4) == 0x4){
|
|
||||||
$content['attributes']['current_position'] = $position;
|
|
||||||
$state = $position.'%';
|
|
||||||
}
|
|
||||||
if($position == 100){
|
|
||||||
$state = $is_cover ? 'open': 'on';
|
|
||||||
}else if($position == 0){
|
|
||||||
$state = $is_cover ? 'closed': 'off';
|
|
||||||
}
|
|
||||||
$content['state'] = $state;
|
|
||||||
unset($content['entity_uid_sha256']);
|
|
||||||
unset($content['last_changed']);
|
|
||||||
unset($content['last_updated']);
|
|
||||||
$this->postState($entity_id, $content);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$api = new API($BASE_HASS_API, $SUPERVISOR_TOKEN);
|
|
||||||
$raw = file_get_contents('php://input');
|
$raw = file_get_contents('php://input');
|
||||||
$data = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
|
$data = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING);
|
||||||
if (is_array($data) && isset($data['events'])) {
|
if (is_array($data) && isset($data['events'])) {
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class HassAPI{
|
||||||
|
var $states = null;
|
||||||
|
|
||||||
|
function __construct($BASE_HASS_API, $HASS_API_TOKEN) {
|
||||||
|
$this->BASE_HASS_API = $BASE_HASS_API;
|
||||||
|
$this->HASS_API_TOKEN = $HASS_API_TOKEN;
|
||||||
|
if($this->isAuthorized()){
|
||||||
|
$this->loadStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAuthorized() {
|
||||||
|
if(!empty($this->BASE_HASS_API) && !empty($this->HASS_API_TOKEN)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadStates() {
|
||||||
|
$res = HassAPI::request($this->BASE_HASS_API."/states", null, 'GET', $this->HASS_API_TOKEN);
|
||||||
|
if($res !== false && isset($res) && is_array($res) && isset($res['data'])){
|
||||||
|
$this->states = json_decode($res['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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getState($entity_id){
|
||||||
|
if($this->states){
|
||||||
|
foreach ($this->states as $value) {
|
||||||
|
if($value['entity_id'] == $entity_id){
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchEntityId($uid) {
|
||||||
|
if($this->states){
|
||||||
|
foreach ($this->states as $value) {
|
||||||
|
if($value['entity_uid_sha256'] == $uid){
|
||||||
|
return $value['entity_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setState($entity_id, $state) {
|
||||||
|
$content = $this->getState($entity_id);
|
||||||
|
if($content != null){
|
||||||
|
//Update state
|
||||||
|
$content['state'] = $state;
|
||||||
|
if(($content['attributes']['supported_features']&0x4) == 0x4){
|
||||||
|
$position = 50;
|
||||||
|
if($state == 'open'){
|
||||||
|
$position = 100;
|
||||||
|
}else if($state == 'closed'){
|
||||||
|
$position = 0;
|
||||||
|
}
|
||||||
|
$content['attributes']['current_position'] = $position;
|
||||||
|
}
|
||||||
|
unset($content['entity_uid_sha256']);
|
||||||
|
unset($content['last_changed']);
|
||||||
|
unset($content['last_updated']);
|
||||||
|
$this->postState($entity_id, $content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPosition($entity_id, $position) {
|
||||||
|
$content = $this->getState($entity_id);
|
||||||
|
if($content != null){
|
||||||
|
$is_cover = (stripos($entity_id, "cover.") !== false) ? true : false;
|
||||||
|
//Update position
|
||||||
|
$state = 'stop';
|
||||||
|
if(($content['attributes']['supported_features']&0x4) == 0x4){
|
||||||
|
$content['attributes']['current_position'] = $position;
|
||||||
|
$state = $position.'%';
|
||||||
|
}
|
||||||
|
if($position == 100){
|
||||||
|
$state = $is_cover ? 'open': 'on';
|
||||||
|
}else if($position == 0){
|
||||||
|
$state = $is_cover ? 'closed': 'off';
|
||||||
|
}
|
||||||
|
$content['state'] = $state;
|
||||||
|
unset($content['entity_uid_sha256']);
|
||||||
|
unset($content['last_changed']);
|
||||||
|
unset($content['last_updated']);
|
||||||
|
$this->postState($entity_id, $content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function postState($entity_id, $content){
|
||||||
|
$json = json_encode($content, JSON_FORCE_OBJECT);
|
||||||
|
if(strpos($this->BASE_HASS_API, "supervisor") !== false){
|
||||||
|
//Bug in supervisor proxy => use bash
|
||||||
|
exec('./poststate.sh "'.$entity_id.'" "'.base64_encode($json).'"', $output, $retval);
|
||||||
|
}else{
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function request($url, $data = null, $method = 'GET', $token = null) {
|
||||||
|
$result = false;
|
||||||
|
if (extension_loaded('curl') === true){
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($curl, CURLOPT_AUTOREFERER, true);
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
if (preg_match('~^(?:DELETE|GET|POST|PUT)$~i', $method) > 0){
|
||||||
|
if (preg_match('~^(?:POST|PUT)$~i', $method) > 0){
|
||||||
|
if (is_array($data) === true){
|
||||||
|
foreach (preg_grep('~^@~', $data) as $key => $value){
|
||||||
|
$data[$key] = sprintf('@%s', rtrim(str_replace('\\', '/', realpath(ltrim($value, '@'))), '/') . (is_dir(ltrim($value, '@')) ? '/' : ''));
|
||||||
|
}
|
||||||
|
if (count($data) != count($data, COUNT_RECURSIVE)){
|
||||||
|
$data = http_build_query($data, '', '&');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl_setopt($curl, CURLOPT_POST, true);
|
||||||
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
||||||
|
}
|
||||||
|
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
|
||||||
|
if (isset($token)){
|
||||||
|
$token = str_replace('"', "", $token);
|
||||||
|
$options = array(CURLOPT_HTTPHEADER => array('Content-Type: application/json' , "Authorization: Bearer ".$token));
|
||||||
|
curl_setopt_array($curl, $options);
|
||||||
|
}
|
||||||
|
$result = array();
|
||||||
|
$result['data'] = curl_exec($curl);
|
||||||
|
$result['info'] = curl_getinfo($curl);
|
||||||
|
if (curl_errno($curl)) {
|
||||||
|
$result['error'] = curl_error($curl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl_close($curl);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/with-contenv bashio
|
#!/usr/bin/with-contenv bashio
|
||||||
|
|
||||||
|
cd /home
|
||||||
arch="$(apk --print-arch)"
|
arch="$(apk --print-arch)"
|
||||||
case "$arch" in \
|
case "$arch" in \
|
||||||
aarch64) arch='arm64' ;; \
|
aarch64) arch='arm64' ;; \
|
||||||
@@ -10,13 +11,15 @@ case "$arch" in \
|
|||||||
esac;
|
esac;
|
||||||
echo "AirSendWebService arch: ${arch}"
|
echo "AirSendWebService arch: ${arch}"
|
||||||
hname="$(hostname -i)"
|
hname="$(hostname -i)"
|
||||||
echo "internal_url: http://${hname}:33863"
|
echo "internal_url: http://${hname}:33863/"
|
||||||
|
|
||||||
if [ -n "${SUPERVISOR_TOKEN:-}" ]; then
|
if [ -n "${SUPERVISOR_TOKEN:-}" ]
|
||||||
|
then
|
||||||
echo ${SUPERVISOR_TOKEN} > hass_api.token
|
echo ${SUPERVISOR_TOKEN} > hass_api.token
|
||||||
|
else
|
||||||
|
echo "Not running on Home Assistant machine..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd /home
|
|
||||||
ulimit -n 4096
|
ulimit -n 4096
|
||||||
./bin/unix/${arch}/AirSendWebService 99399
|
./bin/unix/${arch}/AirSendWebService 99399
|
||||||
php -S 127.0.0.1:80 callback.php
|
php -S 127.0.0.1:80 callback.php
|
||||||
|
|||||||
Reference in New Issue
Block a user