async states
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 ip:
|
if "_airsend" in name:
|
||||||
internalurl = "http://" + str(ip) + ":33863/"
|
ip = options["ip_address"]
|
||||||
except KeyError:
|
if ip:
|
||||||
|
internalurl = "http://" + str(ip) + ":33863/"
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,9 +130,14 @@ class Device:
|
|||||||
data=payload,
|
data=payload,
|
||||||
timeout=6,
|
timeout=6,
|
||||||
)
|
)
|
||||||
status_code = 500
|
if self._wait == True:
|
||||||
jdata = json.loads(response.text)
|
ret = True
|
||||||
if jdata["type"] < 0x100:
|
status_code = 500
|
||||||
|
jdata = json.loads(response.text)
|
||||||
|
if jdata["type"] < 0x100:
|
||||||
|
status_code = response.status_code
|
||||||
|
else:
|
||||||
|
ret = None
|
||||||
status_code = response.status_code
|
status_code = response.status_code
|
||||||
except exceptions.RequestException:
|
except exceptions.RequestException:
|
||||||
pass
|
pass
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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..."
|
||||||
|
|||||||
Reference in New Issue
Block a user