async states

This commit is contained in:
Devmel Apps
2023-08-04 16:41:19 +02:00
parent bb072fd573
commit 055cf05b48
7 changed files with 51 additions and 35 deletions
+7 -10
View File
@@ -6,21 +6,18 @@ Component for sending radio commands through the AirSend (RF433) or AirSend duo
## Installation ## Installation
1. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend/master/install | bash -` 1. Install and start [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon).
2. Go to `airsend.cloud -> import/export -> Export YAML` and copy the airsend.yaml file to the folder `config`
2. Add `airsend: !include airsend.yaml` at the end of your `configuration.yaml` file
3. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend/master/install | bash -`
OR copy the `airsend` folder into your [custom_components folder](https://developers.home-assistant.io/docs/creating_integration_file_structure/#where-home-assistant-looks-for-integrations). OR copy the `airsend` folder into your [custom_components folder](https://developers.home-assistant.io/docs/creating_integration_file_structure/#where-home-assistant-looks-for-integrations).
2. To allow a local LAN connection please install and start [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon). 4. Restart Home Assistant
3. Restart Home Assistant
4. Add `airsend:` to your HA configuration (see configuration below).
5. Restart Home Assistant
## Configuration ## Configuration
### YAML
To integrate `airsend` into Home Assistant, go to `airsend.cloud -> import/export -> Export YAML` and add the contents of the downloaded file into your HA configuration `configuration.yaml`.
#### Local LAN connection #### Local LAN connection
The configuration allows to use the local mode (if [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon) is started) by adding the field `spurl: !secret spurl` in each device. In this mode you must modify the file `secrets.yaml` by adding the local url of the AirSend with its local ipv4 (ex: 192.168.x.x so `spurl: sp://airsend_password@192.168.x.x`), the local ipv6 `fe80::` does not work because of virtualization. You can also remove fields `apiKey`. The configuration allows to use the local mode by adding the field `spurl: !secret spurl` in each device. In this mode you must modify the file `secrets.yaml` by adding the local url of the AirSend with its local ipv4 (ex: 192.168.x.x so `spurl: sp://airsend_password@192.168.x.x`), the local ipv6 `fe80::` does not work because of virtualization. You can also remove fields `apiKey`.
The local mode requires the execution of [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon), if it is not on the same machine it is possible to add the field `internal_url: http://x.x.x.x:33863/` in airsend.conf
## Preview ## Preview
+8 -5
View File
@@ -3,14 +3,13 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.components.hassio import ( from homeassistant.components.hassio import (
async_get_addon_info, get_addons_info,
) )
from homeassistant.const import CONF_INTERNAL_URL from homeassistant.const import CONF_INTERNAL_URL
DOMAIN = "airsend" DOMAIN = "airsend"
AS_TYPE = ["switch", "cover", "button"] AS_TYPE = ["switch", "cover", "button"]
async def async_setup(hass: HomeAssistant, config: ConfigType): async def async_setup(hass: HomeAssistant, config: ConfigType):
"""Set up the AirSend component.""" """Set up the AirSend component."""
if DOMAIN not in config: if DOMAIN not in config:
@@ -22,12 +21,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType):
pass pass
if internalurl == "": if internalurl == "":
try: try:
addon_info: dict = await async_get_addon_info(hass, "local_airsend") addons_info = get_addons_info(hass)
ip = addon_info["ip_address"] for name, options in addons_info.items():
if "_airsend" in name:
ip = options["ip_address"]
if ip: if ip:
internalurl = "http://" + str(ip) + ":33863/" internalurl = "http://" + str(ip) + ":33863/"
except KeyError: except:
pass pass
if internalurl != "" and not internalurl.endswith('/'):
internalurl += "/"
config[DOMAIN][CONF_INTERNAL_URL] = internalurl config[DOMAIN][CONF_INTERNAL_URL] = internalurl
for plateform in AS_TYPE: for plateform in AS_TYPE:
discovery.load_platform(hass, plateform, DOMAIN, config[DOMAIN].copy(), config) discovery.load_platform(hass, plateform, DOMAIN, config[DOMAIN].copy(), config)
+1 -1
View File
@@ -72,5 +72,5 @@ class AirSendButton(ButtonEntity):
def press(self, **kwargs: Any) -> None: def press(self, **kwargs: Any) -> None:
"""Handle the button press.""" """Handle the button press."""
note = {"method": 1, "type": 0, "value": "TOGGLE"} note = {"method": 1, "type": 0, "value": "TOGGLE"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self.schedule_update_ha_state() self.schedule_update_ha_state()
+5 -6
View File
@@ -6,7 +6,6 @@ from .device import Device
from homeassistant.components.cover import CoverEntity from homeassistant.components.cover import CoverEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.const import CONF_DEVICES, CONF_INTERNAL_URL from homeassistant.const import CONF_DEVICES, CONF_INTERNAL_URL
from . import DOMAIN from . import DOMAIN
@@ -75,12 +74,12 @@ class AirSendCover(CoverEntity):
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed.""" """Return if the cover is closed."""
return not self._closed return self._closed
def open_cover(self, **kwargs: Any) -> None: def open_cover(self, **kwargs: Any) -> None:
"""Open the cover.""" """Open the cover."""
note = {"method": 1, "type": 0, "value": "UP"} note = {"method": 1, "type": 0, "value": "UP"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._closed = False self._closed = False
if self._device.is_cover_with_position: if self._device.is_cover_with_position:
self._attr_current_cover_position = 100 self._attr_current_cover_position = 100
@@ -89,7 +88,7 @@ class AirSendCover(CoverEntity):
def close_cover(self, **kwargs: Any) -> None: def close_cover(self, **kwargs: Any) -> None:
"""Close cover.""" """Close cover."""
note = {"method": 1, "type": 0, "value": "DOWN"} note = {"method": 1, "type": 0, "value": "DOWN"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._closed = True self._closed = True
if self._device.is_cover_with_position: if self._device.is_cover_with_position:
self._attr_current_cover_position = 0 self._attr_current_cover_position = 0
@@ -98,7 +97,7 @@ class AirSendCover(CoverEntity):
def stop_cover(self, **kwargs): def stop_cover(self, **kwargs):
"""Stop the cover.""" """Stop the cover."""
note = {"method": 1, "type": 0, "value": "STOP"} note = {"method": 1, "type": 0, "value": "STOP"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._closed = False self._closed = False
if self._device.is_cover_with_position: if self._device.is_cover_with_position:
self._attr_current_cover_position = 50 self._attr_current_cover_position = 50
@@ -108,7 +107,7 @@ class AirSendCover(CoverEntity):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
position = int(kwargs["position"]) position = int(kwargs["position"])
note = {"method": 1, "type": 9, "value": position} note = {"method": 1, "type": 9, "value": position}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._attr_current_cover_position = position self._attr_current_cover_position = position
self._closed = False self._closed = False
if self._attr_current_cover_position == 0: if self._attr_current_cover_position == 0:
+22 -5
View File
@@ -1,6 +1,7 @@
"""AirSend device.""" """AirSend device."""
import logging import logging
import json import json
import hashlib
from requests import get, post, exceptions from requests import get, post, exceptions
from . import DOMAIN from . import DOMAIN
@@ -23,6 +24,7 @@ class Device:
self._rtype = None self._rtype = None
self._apikey = None self._apikey = None
self._spurl = None self._spurl = None
self._wait = False
self._channel = {} self._channel = {}
self._note = None self._note = None
try: try:
@@ -41,6 +43,10 @@ class Device:
self._spurl = options["spurl"] self._spurl = options["spurl"]
except KeyError: except KeyError:
pass pass
try:
self._wait = eval(str(options["wait"]))
except KeyError:
pass
try: try:
self._channel = options["channel"] self._channel = options["channel"]
except KeyError: except KeyError:
@@ -83,10 +89,15 @@ class Device:
return True return True
return False return False
def transfer(self, note) -> bool: def transfer(self, note, entity_id = None) -> bool:
"""Send a command.""" """Send a command."""
status_code = 404 status_code = 404
if self._serviceurl and self._spurl: ret = False
wait = 'false, "callback":"http://127.0.0.1/"'
if self._wait == True:
wait = 'true'
if self._serviceurl and self._spurl and entity_id is not None:
uid = hashlib.sha256(entity_id.encode('utf-8')).hexdigest()[:12]
jnote = json.dumps(note) jnote = json.dumps(note)
if ( if (
self._note is not None self._note is not None
@@ -101,9 +112,9 @@ class Device:
): ):
jnote = json.dumps(self._note) jnote = json.dumps(self._note)
payload = ( payload = (
'{"wait": true, "channel":' '{"wait": '+wait+', "channel":'
+ json.dumps(self._channel) + json.dumps(self._channel)
+ ', "thingnotes":{"notes":[' + ', "thingnotes":{"uid":"0x'+uid+'", "notes":['
+ jnote + jnote
+ "]}}" + "]}}"
) )
@@ -119,10 +130,15 @@ class Device:
data=payload, data=payload,
timeout=6, timeout=6,
) )
if self._wait == True:
ret = True
status_code = 500 status_code = 500
jdata = json.loads(response.text) jdata = json.loads(response.text)
if jdata["type"] < 0x100: if jdata["type"] < 0x100:
status_code = response.status_code status_code = response.status_code
else:
ret = None
status_code = response.status_code
except exceptions.RequestException: except exceptions.RequestException:
pass pass
if status_code != 200 and self._apikey: if status_code != 200 and self._apikey:
@@ -164,9 +180,10 @@ class Device:
try: try:
response = get(cloud_url, headers=headers, timeout=10) response = get(cloud_url, headers=headers, timeout=10)
status_code = response.status_code status_code = response.status_code
ret = True
except exceptions.RequestException: except exceptions.RequestException:
pass pass
if status_code == 200: if status_code == 200:
return True return ret
_LOGGER.error("Transfer error '%s' : '%s'", self.name, status_code) _LOGGER.error("Transfer error '%s' : '%s'", self.name, status_code)
raise Exception("Transfer error " + self.name + " : " + str(status_code)) raise Exception("Transfer error " + self.name + " : " + str(status_code))
+2 -2
View File
@@ -77,13 +77,13 @@ class AirSendSwitch(SwitchEntity):
def turn_on(self, **kwargs: Any) -> None: def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on.""" """Turn the device on."""
note = {"method": 1, "type": 0, "value": "ON"} note = {"method": 1, "type": 0, "value": "ON"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._state = True self._state = True
self.schedule_update_ha_state() self.schedule_update_ha_state()
def turn_off(self, **kwargs: Any) -> None: def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off.""" """Turn the device off."""
note = {"method": 1, "type": 0, "value": "OFF"} note = {"method": 1, "type": 0, "value": "OFF"}
if self._device.transfer(note): if self._device.transfer(note, self.entity_id) == True:
self._state = False self._state = False
self.schedule_update_ha_state() self.schedule_update_ha_state()
+1 -1
View File
@@ -56,7 +56,7 @@ if [ -n "$haPath" ]; then
cd "$haPath/custom_components" || error "Could not change path to $haPath/custom_components" cd "$haPath/custom_components" || error "Could not change path to $haPath/custom_components"
info "Downloading AirSend Home Assistant Component" info "Downloading AirSend Home Assistant Component"
wget "https://github.com/devmel/hass_airsend/releases/download/1.0/hass_airsend.zip" wget "https://github.com/devmel/hass_airsend/releases/download/latest/hass_airsend.zip"
if [ -d "$haPath/custom_components/hass_airsend" ]; then if [ -d "$haPath/custom_components/hass_airsend" ]; then
warn "airsend directory already exist, cleaning up..." warn "airsend directory already exist, cleaning up..."