From df1e19ec49c3ee512de425e32a8327cdf2113d21 Mon Sep 17 00:00:00 2001 From: Devmel Apps Date: Fri, 4 Aug 2023 15:01:45 +0200 Subject: [PATCH] external machine --- README.md | 9 ++ addons/airsend/Dockerfile | 2 + addons/airsend/callback.php | 159 +++---------------------------- addons/airsend/hassapi.class.php | 155 ++++++++++++++++++++++++++++++ addons/airsend/run.sh | 9 +- 5 files changed, 184 insertions(+), 150 deletions(-) create mode 100644 addons/airsend/hassapi.class.php diff --git a/README.md b/README.md index ee2b3a7..051b0bf 100644 --- a/README.md +++ b/README.md @@ -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 -` 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 + + +## 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` diff --git a/addons/airsend/Dockerfile b/addons/airsend/Dockerfile index 3dca49f..0d5c78c 100644 --- a/addons/airsend/Dockerfile +++ b/addons/airsend/Dockerfile @@ -2,6 +2,7 @@ ARG BUILD_FROM FROM $BUILD_FROM # Copy data for add-on +COPY hassapi.class.php /home COPY callback.php /home COPY poststate.sh /home 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 # Start AirSendWebService CMD [ "/run.sh" ] +EXPOSE 33863 diff --git a/addons/airsend/callback.php b/addons/airsend/callback.php index e2d027a..2bd6891 100644 --- a/addons/airsend/callback.php +++ b/addons/airsend/callback.php @@ -1,156 +1,21 @@ isAuthorized()){ 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'); $data = json_decode($raw, true, 512, JSON_BIGINT_AS_STRING); if (is_array($data) && isset($data['events'])) { diff --git a/addons/airsend/hassapi.class.php b/addons/airsend/hassapi.class.php new file mode 100644 index 0000000..81d31aa --- /dev/null +++ b/addons/airsend/hassapi.class.php @@ -0,0 +1,155 @@ +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; + } +} + +?> \ No newline at end of file diff --git a/addons/airsend/run.sh b/addons/airsend/run.sh index b5ae97f..6748729 100644 --- a/addons/airsend/run.sh +++ b/addons/airsend/run.sh @@ -1,5 +1,6 @@ #!/usr/bin/with-contenv bashio +cd /home arch="$(apk --print-arch)" case "$arch" in \ aarch64) arch='arm64' ;; \ @@ -10,13 +11,15 @@ case "$arch" in \ esac; echo "AirSendWebService arch: ${arch}" 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 +else + echo "Not running on Home Assistant machine..." fi -cd /home ulimit -n 4096 ./bin/unix/${arch}/AirSendWebService 99399 php -S 127.0.0.1:80 callback.php