AirSend duo
This commit is contained in:
@@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
# AirSend Home Assistant
|
# AirSend Home Assistant
|
||||||
|
|
||||||
Component for sending radio commands (433-434Mhz) through the AirSend device.
|
Component for sending radio commands through the AirSend (RF433) or AirSend duo (RF433 & RF868).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Add `airsend:` to your HA configuration (see configuration below).
|
1. Into the terminal, run `wget -q -O - https://raw.githubusercontent.com/devmel/hass_airsend/master/install | bash -`
|
||||||
2. 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).
|
||||||
3. To allow a local LAN connection please install and start [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon).
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,33 @@
|
|||||||
from homeassistant.core import HomeAssistant
|
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.const import (
|
from homeassistant.components.hassio import (
|
||||||
CONF_DEVICES,
|
async_get_addon_info,
|
||||||
)
|
)
|
||||||
|
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:
|
||||||
return True
|
return True
|
||||||
|
internalurl = ""
|
||||||
|
try:
|
||||||
|
internalurl = config[DOMAIN][CONF_INTERNAL_URL]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if internalurl == "":
|
||||||
|
try:
|
||||||
|
addon_info: dict = await async_get_addon_info(hass, "local_airsend")
|
||||||
|
ip = addon_info["ip_address"]
|
||||||
|
if ip:
|
||||||
|
internalurl = "http://" + str(ip) + ":33863/"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
config[DOMAIN][CONF_INTERNAL_URL] = internalurl
|
||||||
for plateform in AS_TYPE:
|
for plateform in AS_TYPE:
|
||||||
discovery.load_platform(hass, plateform, DOMAIN, config[DOMAIN][CONF_DEVICES].copy(), config)
|
discovery.load_platform(hass, plateform, DOMAIN, config[DOMAIN].copy(), config)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,84 +1,44 @@
|
|||||||
"""AirSend buttons."""
|
"""AirSend buttons."""
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from requests import get, post
|
|
||||||
|
from .device import Device
|
||||||
|
|
||||||
|
from homeassistant.components.button import ButtonEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
|
||||||
from homeassistant.components.hassio import (
|
|
||||||
async_get_addon_discovery_info,
|
|
||||||
async_get_addon_info,
|
|
||||||
)
|
|
||||||
from homeassistant.components.button import (
|
|
||||||
ButtonEntity,
|
|
||||||
)
|
|
||||||
from . import (
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(DOMAIN)
|
from homeassistant.const import CONF_DEVICES, CONF_INTERNAL_URL
|
||||||
|
|
||||||
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
from . import DOMAIN
|
||||||
addons_url = ""
|
|
||||||
try:
|
|
||||||
addon_info: dict = await async_get_addon_info(hass, 'local_airsend')
|
async def async_setup_platform(
|
||||||
ip = addon_info["ip_address"]
|
hass: HomeAssistant, config: ConfigType, async_add_entities, discovery_info=None
|
||||||
if ip:
|
) -> None:
|
||||||
addons_url = "http://"+str(ip)+":33863/"
|
|
||||||
# _LOGGER.warning("Addon '%s'", addon_info)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
for name, options in discovery_info.items():
|
for name, options in discovery_info[CONF_DEVICES].items():
|
||||||
if options['type'] == 4096 :
|
device = Device(name, options, discovery_info[CONF_INTERNAL_URL])
|
||||||
id = ""
|
if device.is_button:
|
||||||
apiKey = ""
|
entity = AirSendButton(
|
||||||
spurl = ""
|
hass,
|
||||||
channel = {}
|
device,
|
||||||
note = {"method":1,"type":0,"value": "TOGGLE"}
|
)
|
||||||
try:
|
|
||||||
id = options['id']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
apiKey = options['apiKey']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
spurl = options['spurl']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
channel = options['channel']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
note = options['note']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
entity = AirSendButton(hass, name, id, options['type'], apiKey, addons_url, spurl, channel, note)
|
|
||||||
async_add_entities([entity])
|
async_add_entities([entity])
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class AirSendButton(ButtonEntity):
|
class AirSendButton(ButtonEntity):
|
||||||
"""Representation of an AirSend Button."""
|
"""Representation of an AirSend Button."""
|
||||||
|
|
||||||
def __init__(self, hass : HomeAssistant, name : str, id: str, type : int, apikey : str, addons_url : str, spurl : str, channel : dict, note : dict):
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: Device,
|
||||||
|
) -> None:
|
||||||
"""Initialize a button."""
|
"""Initialize a button."""
|
||||||
self._name = name
|
self._device = device
|
||||||
self._id = id
|
uname = DOMAIN + device.name
|
||||||
self._type = type
|
|
||||||
self._apikey = apikey
|
|
||||||
self._addons_url = addons_url
|
|
||||||
self._spurl = spurl
|
|
||||||
self._channel = channel
|
|
||||||
self._note = note
|
|
||||||
uname = DOMAIN+name
|
|
||||||
self._unique_id = "_".join(x for x in uname)
|
self._unique_id = "_".join(x for x in uname)
|
||||||
self._state = False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
@@ -97,7 +57,7 @@ class AirSendButton(ButtonEntity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device if any."""
|
"""Return the name of the device if any."""
|
||||||
return self._name
|
return self._device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
@@ -109,37 +69,8 @@ class AirSendButton(ButtonEntity):
|
|||||||
"""Return true if unable to access real state of entity."""
|
"""Return true if unable to access real state of entity."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def press(self) -> None:
|
def press(self, **kwargs: Any) -> None:
|
||||||
"""Handle the button press."""
|
"""Handle the button press."""
|
||||||
command = "6"
|
note = {"method": 1, "type": 0, "value": "TOGGLE"}
|
||||||
note = self._note
|
if self._device.transfer(note):
|
||||||
url = "https://airsend.cloud/device/" + str(self._id) + "/command/" + command + "/"
|
|
||||||
payload = '{"wait": true, "channel":' + json.dumps(self._channel) + ', "thingnotes":{"notes":[' + json.dumps(
|
|
||||||
note) + ']}}'
|
|
||||||
self._action(url, payload, True)
|
|
||||||
|
|
||||||
def _action(self, cloud_url : str, payload : str, new_state : bool):
|
|
||||||
status_code = 404
|
|
||||||
if self._addons_url and self._spurl:
|
|
||||||
headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
try:
|
|
||||||
response = post(self._addons_url+"airsend/transfer", headers=headers, data=payload, timeout=6)
|
|
||||||
status_code = 500
|
|
||||||
jdata = json.loads(response.text)
|
|
||||||
if jdata["type"] < 0x100:
|
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code != 200 and self._apikey and cloud_url:
|
|
||||||
headers = {"Authorization": "Bearer "+self._apikey, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
try:
|
|
||||||
response = get(cloud_url, headers=headers, timeout=10)
|
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code == 200:
|
|
||||||
self._state = new_state
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
else:
|
|
||||||
_LOGGER.error("action error : "+str(status_code))
|
|
||||||
raise Exception("action error : "+str(status_code))
|
|
||||||
|
|||||||
@@ -1,78 +1,47 @@
|
|||||||
"""AirSend switches."""
|
"""AirSend switches."""
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from requests import get, post
|
|
||||||
|
from .device import Device
|
||||||
|
|
||||||
|
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.helpers.entity import async_generate_entity_id
|
|
||||||
from homeassistant.components.hassio import (
|
|
||||||
async_get_addon_discovery_info,
|
|
||||||
async_get_addon_info,
|
|
||||||
)
|
|
||||||
from homeassistant.components.cover import (
|
|
||||||
CoverEntity,
|
|
||||||
)
|
|
||||||
from . import (
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(DOMAIN)
|
from homeassistant.const import CONF_DEVICES, CONF_INTERNAL_URL
|
||||||
|
|
||||||
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
from . import DOMAIN
|
||||||
addons_url = ""
|
|
||||||
try:
|
|
||||||
addon_info: dict = await async_get_addon_info(hass, 'local_airsend')
|
async def async_setup_platform(
|
||||||
ip = addon_info["ip_address"]
|
hass: HomeAssistant, config: ConfigType, async_add_entities, discovery_info=None
|
||||||
if ip:
|
) -> None:
|
||||||
addons_url = "http://"+str(ip)+":33863/"
|
|
||||||
# _LOGGER.warning("Addon '%s'", addon_info)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
for name, options in discovery_info.items():
|
for name, options in discovery_info[CONF_DEVICES].items():
|
||||||
if options['type'] == 4098:
|
device = Device(name, options, discovery_info[CONF_INTERNAL_URL])
|
||||||
id = ""
|
if device.is_cover:
|
||||||
apiKey = ""
|
entity = AirSendCover(
|
||||||
spurl = ""
|
hass,
|
||||||
channel = {}
|
device,
|
||||||
try:
|
)
|
||||||
id = options['id']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
apiKey = options['apiKey']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
spurl = options['spurl']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
channel = options['channel']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
entity = AirSendCover(hass, name, id, options['type'], apiKey, addons_url, spurl, channel)
|
|
||||||
async_add_entities([entity])
|
async_add_entities([entity])
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class AirSendCover(CoverEntity):
|
class AirSendCover(CoverEntity):
|
||||||
"""Representation of an AirSend Cover."""
|
"""Representation of an AirSend Cover."""
|
||||||
|
|
||||||
def __init__(self, hass : HomeAssistant, name : str, id: str, type : int, apikey : str, addons_url : str, spurl : str, channel : dict):
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: Device,
|
||||||
|
) -> None:
|
||||||
"""Initialize a cover device."""
|
"""Initialize a cover device."""
|
||||||
self._name = name
|
self._device = device
|
||||||
self._id = id
|
uname = DOMAIN + device.name
|
||||||
self._type = type
|
|
||||||
self._apikey = apikey
|
|
||||||
self._addons_url = addons_url
|
|
||||||
self._spurl = spurl
|
|
||||||
self._channel = channel
|
|
||||||
uname = DOMAIN+name
|
|
||||||
self._unique_id = "_".join(x for x in uname)
|
self._unique_id = "_".join(x for x in uname)
|
||||||
self._state = False
|
self._closed = False
|
||||||
|
if device.is_cover_with_position:
|
||||||
|
self._attr_current_cover_position = 50
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
@@ -91,7 +60,7 @@ class AirSendCover(CoverEntity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device if any."""
|
"""Return the name of the device if any."""
|
||||||
return self._name
|
return self._device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
@@ -106,51 +75,42 @@ 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._state
|
return not 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"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/5/"
|
if self._device.transfer(note):
|
||||||
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
self._closed = False
|
||||||
self._action(url, payload, False)
|
if self._device.is_cover_with_position:
|
||||||
|
self._attr_current_cover_position = 100
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
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"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/4/"
|
if self._device.transfer(note):
|
||||||
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
self._closed = True
|
||||||
self._action(url, payload, False)
|
if self._device.is_cover_with_position:
|
||||||
|
self._attr_current_cover_position = 0
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
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"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/3/"
|
if self._device.transfer(note):
|
||||||
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
self._closed = False
|
||||||
self._action(url, payload, False)
|
if self._device.is_cover_with_position:
|
||||||
|
self._attr_current_cover_position = 50
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def _action(self, cloud_url : str, payload : str, new_state : bool):
|
def set_cover_position(self, **kwargs):
|
||||||
status_code = 404
|
"""Move the cover to a specific position."""
|
||||||
if self._addons_url and self._spurl:
|
position = int(kwargs["position"])
|
||||||
headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
note = {"method": 1, "type": 9, "value": position}
|
||||||
try:
|
if self._device.transfer(note):
|
||||||
response = post(self._addons_url+"airsend/transfer", headers=headers, data=payload, timeout=6)
|
self._attr_current_cover_position = position
|
||||||
status_code = 500
|
self._closed = False
|
||||||
jdata = json.loads(response.text)
|
if self._attr_current_cover_position == 0:
|
||||||
if jdata["type"] < 0x100:
|
self._closed = True
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code != 200 and self._apikey and cloud_url:
|
|
||||||
headers = {"Authorization": "Bearer "+self._apikey, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
try:
|
|
||||||
response = get(cloud_url, headers=headers, timeout=10)
|
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code == 200:
|
|
||||||
self._state = new_state
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
else:
|
|
||||||
_LOGGER.error("action error : "+str(status_code))
|
|
||||||
raise Exception("action error : "+str(status_code))
|
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
"""AirSend device."""
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from requests import get, post, exceptions
|
||||||
|
from . import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
class Device:
|
||||||
|
"""Representation of a Device."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
options,
|
||||||
|
serviceurl: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a device."""
|
||||||
|
self._name = name
|
||||||
|
self._serviceurl = serviceurl
|
||||||
|
self._uid = None
|
||||||
|
self._rtype = None
|
||||||
|
self._apikey = None
|
||||||
|
self._spurl = None
|
||||||
|
self._channel = {}
|
||||||
|
self._note = None
|
||||||
|
try:
|
||||||
|
self._uid = options["id"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._rtype = options["type"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._apikey = options["apiKey"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._spurl = options["spurl"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._channel = options["channel"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._note = options["note"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_button(self) -> bool:
|
||||||
|
"""Return if is a button."""
|
||||||
|
if self._rtype == 4096:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_cover(self) -> bool:
|
||||||
|
"""Return if is a cover."""
|
||||||
|
if self._rtype in (4098, 4099):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_cover_with_position(self) -> bool:
|
||||||
|
"""Return if is a cover with position."""
|
||||||
|
if self._rtype == 4099:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_switch(self) -> bool:
|
||||||
|
"""Return if is a switch."""
|
||||||
|
if self._rtype == 4097:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def transfer(self, note) -> bool:
|
||||||
|
"""Send a command."""
|
||||||
|
status_code = 404
|
||||||
|
if self._serviceurl and self._spurl:
|
||||||
|
jnote = json.dumps(note)
|
||||||
|
if (
|
||||||
|
self._note is not None
|
||||||
|
and "method" in self._note
|
||||||
|
and "type" in self._note
|
||||||
|
and "value" in self._note
|
||||||
|
and "method" in note.keys()
|
||||||
|
and "type" in note.keys()
|
||||||
|
and "value" in note.keys()
|
||||||
|
and note["method"] == 1 and note["type"] == 0
|
||||||
|
and (note["value"] == "TOGGLE" or note["value"] == 6)
|
||||||
|
):
|
||||||
|
jnote = json.dumps(self._note)
|
||||||
|
payload = (
|
||||||
|
'{"wait": true, "channel":'
|
||||||
|
+ json.dumps(self._channel)
|
||||||
|
+ ', "thingnotes":{"notes":['
|
||||||
|
+ jnote
|
||||||
|
+ "]}}"
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer " + self._spurl,
|
||||||
|
"content-type": "application/json",
|
||||||
|
"User-Agent": "hass_airsend",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = post(
|
||||||
|
self._serviceurl + "airsend/transfer",
|
||||||
|
headers=headers,
|
||||||
|
data=payload,
|
||||||
|
timeout=6,
|
||||||
|
)
|
||||||
|
status_code = 500
|
||||||
|
jdata = json.loads(response.text)
|
||||||
|
if jdata["type"] < 0x100:
|
||||||
|
status_code = response.status_code
|
||||||
|
except exceptions.RequestException:
|
||||||
|
pass
|
||||||
|
if status_code != 200 and self._apikey:
|
||||||
|
action = "command"
|
||||||
|
value = 6
|
||||||
|
if (
|
||||||
|
"method" in note.keys()
|
||||||
|
and "type" in note.keys()
|
||||||
|
and "value" in note.keys()
|
||||||
|
):
|
||||||
|
if note["method"] == 1 and note["type"] == 0:
|
||||||
|
if note["value"] == "OFF":
|
||||||
|
value = 0
|
||||||
|
if note["value"] == "ON":
|
||||||
|
value = 1
|
||||||
|
if note["value"] == "STOP":
|
||||||
|
value = 3
|
||||||
|
if note["value"] == "DOWN":
|
||||||
|
value = 4
|
||||||
|
if note["value"] == "UP":
|
||||||
|
value = 5
|
||||||
|
if note["method"] == 1 and note["type"] == 9:
|
||||||
|
action = "level"
|
||||||
|
value = int(note["value"])
|
||||||
|
cloud_url = (
|
||||||
|
"https://airsend.cloud/device/"
|
||||||
|
+ str(self._uid)
|
||||||
|
+ "/"
|
||||||
|
+ action
|
||||||
|
+ "/"
|
||||||
|
+ str(value)
|
||||||
|
+ "/"
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer " + self._apikey,
|
||||||
|
"content-type": "application/json",
|
||||||
|
"User-Agent": "hass_airsend",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = get(cloud_url, headers=headers, timeout=10)
|
||||||
|
status_code = response.status_code
|
||||||
|
except exceptions.RequestException:
|
||||||
|
pass
|
||||||
|
if status_code == 200:
|
||||||
|
return True
|
||||||
|
_LOGGER.error("Transfer error '%s' : '%s'", self.name, status_code)
|
||||||
|
raise Exception("Transfer error " + self.name + " : " + str(status_code))
|
||||||
@@ -1,82 +1,43 @@
|
|||||||
"""AirSend switches."""
|
"""AirSend switches."""
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from requests import get, post
|
|
||||||
|
from .device import Device
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.helpers.entity import async_generate_entity_id
|
|
||||||
from homeassistant.components.hassio import (
|
|
||||||
async_get_addon_discovery_info,
|
|
||||||
async_get_addon_info,
|
|
||||||
)
|
|
||||||
from homeassistant.components.switch import (
|
|
||||||
SwitchEntity,
|
|
||||||
)
|
|
||||||
from . import (
|
|
||||||
DOMAIN,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(DOMAIN)
|
from homeassistant.const import CONF_DEVICES, CONF_INTERNAL_URL
|
||||||
|
|
||||||
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
from . import DOMAIN
|
||||||
addons_url = ""
|
|
||||||
try:
|
|
||||||
addon_info: dict = await async_get_addon_info(hass, 'local_airsend')
|
async def async_setup_platform(
|
||||||
ip = addon_info["ip_address"]
|
hass: HomeAssistant, config: ConfigType, async_add_entities, discovery_info=None
|
||||||
if ip:
|
) -> None:
|
||||||
addons_url = "http://"+str(ip)+":33863/"
|
|
||||||
# _LOGGER.warning("Addon '%s'", addon_info)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
for name, options in discovery_info.items():
|
for name, options in discovery_info[CONF_DEVICES].items():
|
||||||
if options['type'] == 4097:
|
device = Device(name, options, discovery_info[CONF_INTERNAL_URL])
|
||||||
id = ""
|
if device.is_switch:
|
||||||
apiKey = ""
|
entity = AirSendSwitch(
|
||||||
spurl = ""
|
hass,
|
||||||
channel = {}
|
device,
|
||||||
note = {"method":1,"type":0,"value": "TOGGLE"}
|
)
|
||||||
try:
|
|
||||||
id = options['id']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
apiKey = options['apiKey']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
spurl = options['spurl']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
channel = options['channel']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
note = options['note']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
entity = AirSendSwitch(hass, name, id, options['type'], apiKey, addons_url, spurl, channel, note)
|
|
||||||
async_add_entities([entity])
|
async_add_entities([entity])
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class AirSendSwitch(SwitchEntity):
|
class AirSendSwitch(SwitchEntity):
|
||||||
"""Representation of an AirSend Switch."""
|
"""Representation of an AirSend Switch."""
|
||||||
|
|
||||||
def __init__(self, hass : HomeAssistant, name : str, id: str, type : int, apikey : str, addons_url : str, spurl : str, channel : dict, note : dict):
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device: Device,
|
||||||
|
) -> None:
|
||||||
"""Initialize a switch or light device."""
|
"""Initialize a switch or light device."""
|
||||||
self._name = name
|
self._device = device
|
||||||
self._id = id
|
uname = DOMAIN + device.name
|
||||||
self._type = type
|
|
||||||
self._apikey = apikey
|
|
||||||
self._addons_url = addons_url
|
|
||||||
self._spurl = spurl
|
|
||||||
self._channel = channel
|
|
||||||
self._note = note
|
|
||||||
uname = DOMAIN+name
|
|
||||||
self._unique_id = "_".join(x for x in uname)
|
self._unique_id = "_".join(x for x in uname)
|
||||||
self._state = False
|
self._state = False
|
||||||
|
|
||||||
@@ -97,7 +58,7 @@ class AirSendSwitch(SwitchEntity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device if any."""
|
"""Return the name of the device if any."""
|
||||||
return self._name
|
return self._device.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
@@ -115,42 +76,14 @@ 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."""
|
||||||
command = "1"
|
note = {"method": 1, "type": 0, "value": "ON"}
|
||||||
note = {"method":1,"type":0,"value": "ON"}
|
if self._device.transfer(note):
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
self._state = True
|
||||||
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
self.schedule_update_ha_state()
|
||||||
self._action(url, payload, True)
|
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
def turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
command = "0"
|
note = {"method": 1, "type": 0, "value": "OFF"}
|
||||||
note = {"method":1,"type":0,"value": "OFF"}
|
if self._device.transfer(note):
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
self._state = False
|
||||||
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
|
||||||
self._action(url, payload, False)
|
|
||||||
|
|
||||||
def _action(self, cloud_url : str, payload : str, new_state : bool):
|
|
||||||
status_code = 404
|
|
||||||
if self._addons_url and self._spurl:
|
|
||||||
headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
try:
|
|
||||||
response = post(self._addons_url+"airsend/transfer", headers=headers, data=payload, timeout=6)
|
|
||||||
status_code = 500
|
|
||||||
jdata = json.loads(response.text)
|
|
||||||
if jdata["type"] < 0x100:
|
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code != 200 and self._apikey and cloud_url:
|
|
||||||
headers = {"Authorization": "Bearer "+self._apikey, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
try:
|
|
||||||
response = get(cloud_url, headers=headers, timeout=10)
|
|
||||||
status_code = response.status_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if status_code == 200:
|
|
||||||
self._state = new_state
|
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
else:
|
|
||||||
_LOGGER.error("action error : "+str(status_code))
|
|
||||||
raise Exception("action error : "+str(status_code))
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 22 KiB |
Reference in New Issue
Block a user