diff --git a/addons/airsend/Dockerfile b/addons/airsend/Dockerfile index 68d4717..3dca49f 100644 --- a/addons/airsend/Dockerfile +++ b/addons/airsend/Dockerfile @@ -2,12 +2,15 @@ ARG BUILD_FROM FROM $BUILD_FROM # Copy data for add-on +COPY callback.php /home +COPY poststate.sh /home COPY run.sh / +RUN chmod a+x /home/poststate.sh RUN chmod a+x /run.sh # Install AirSendWebService -# RUN mkdir -p /home && cd /home && wget http://devmel.com/dl/AirSendWebService.tgz && (gzip -dc AirSendWebService.tgz | tar -xvzf -) && 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 # Start AirSendWebService CMD [ "/run.sh" ] diff --git a/addons/airsend/build.yaml b/addons/airsend/build.yaml index 9130add..82c9ff6 100644 --- a/addons/airsend/build.yaml +++ b/addons/airsend/build.yaml @@ -1,6 +1,9 @@ build_from: - aarch64: ghcr.io/home-assistant/aarch64-base:3.15 - amd64: ghcr.io/home-assistant/amd64-base:3.15 - armhf: ghcr.io/home-assistant/armhf-base:3.15 - armv7: ghcr.io/home-assistant/armv7-base:3.15 - i386: ghcr.io/home-assistant/i386-base:3.15 + aarch64: ghcr.io/home-assistant/aarch64-base:3.16 + amd64: ghcr.io/home-assistant/amd64-base:3.16 + armhf: ghcr.io/home-assistant/armhf-base:3.16 + armv7: ghcr.io/home-assistant/armv7-base:3.16 + i386: ghcr.io/home-assistant/i386-base:3.16 +codenotary: + signer: notary@home-assistant.io + base_image: notary@home-assistant.io diff --git a/addons/airsend/callback.php b/addons/airsend/callback.php new file mode 100644 index 0000000..e2d027a --- /dev/null +++ b/addons/airsend/callback.php @@ -0,0 +1,217 @@ +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'])) { + foreach ($data['events'] as $i => $val) { + if(isset($val['channel']) && isset($val['type']) && isset($val['thingnotes'])){ + $entity_id = $api->searchEntityId($val['thingnotes']['uid']); + if(isset($entity_id)){ + //Transfer event + if($val['type'] == 3 || $val['type'] == 2 || $val['type'] == 1){ + $notes = $val['thingnotes']['notes']; + if(is_array($notes) && count($notes) > 0){ + foreach ($notes as $i => $note) { + $ovalue = $note['value']; + if($note['type'] == 0){ //STATE + if(is_numeric($ovalue)){ + $ovalue = intval($ovalue); + $state = null; + $position = 0; + switch($ovalue){ + case 18: //TOGGLE + $state = 'pressed'; + break; + case 19: //OFF + $position = 0; + break; + case 20: //ON + $position = 100; + break; + case 17: //STOP + $state = 'stop'; + break; + case 33: //MIDDLE + case 38: //USERPOS + $state = 'user'; + break; + case 34: //DOWN + $position = 0; + break; + case 35: //UP + $position = 100; + break; + } + if($state){ + $api->setState($entity_id, $state); + }else{ + $api->setPosition($entity_id, $position); + } + } + }else if($note['type'] == 1){ //DATA + $api->setState($entity_id, 'pressed'); + }else if($note['type'] == 9){ //LEVEL + $api->setPosition($entity_id, intval($ovalue)); + } + } + } + }else{ + $api->setState($entity_id, 'error_'.$val['type']); + } + } + } + } +} + +?> diff --git a/addons/airsend/config.yaml b/addons/airsend/config.yaml index 6429e8c..620c160 100644 --- a/addons/airsend/config.yaml +++ b/addons/airsend/config.yaml @@ -1,14 +1,16 @@ name: "AirSend" description: "AirSend Web Service" -version: "1.0" +version: "1.1" slug: "airsend" url: https://github.com/devmel/hass_airsend-addon +codenotary: notary@home-assistant.io arch: - aarch64 - amd64 - armhf - armv7 - i386 +homeassistant_api: true startup: services init: false ports: diff --git a/addons/airsend/icon.png b/addons/airsend/icon.png index 7280984..28bf2c1 100644 Binary files a/addons/airsend/icon.png and b/addons/airsend/icon.png differ diff --git a/addons/airsend/poststate.sh b/addons/airsend/poststate.sh new file mode 100644 index 0000000..c2d03f5 --- /dev/null +++ b/addons/airsend/poststate.sh @@ -0,0 +1,4 @@ +#!/usr/bin/with-contenv bashio + +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 diff --git a/addons/airsend/run.sh b/addons/airsend/run.sh index 0a31134..d93c23f 100644 --- a/addons/airsend/run.sh +++ b/addons/airsend/run.sh @@ -10,5 +10,7 @@ case "$arch" in \ echo "AirSendWebService arch ${arch}" cd /home +echo ${SUPERVISOR_TOKEN} > hass_api.token ./bin/unix/${arch}/AirSendWebService 99399 +php -S 127.0.0.1:80 callback.php sleep infinity diff --git a/icons/icon.png b/icons/icon.png index 7280984..28bf2c1 100644 Binary files a/icons/icon.png and b/icons/icon.png differ