Local LAN connection
This commit is contained in:
@@ -9,7 +9,8 @@ Component for sending radio commands (433-434Mhz) through the AirSend device.
|
|||||||
1. Add `airsend:` to your HA configuration (see configuration below).
|
1. Add `airsend:` to your HA configuration (see configuration below).
|
||||||
2. 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. Restart Home Assistant
|
3. To allow a local LAN connection please install and start [hass_airsend-addon](https://github.com/devmel/hass_airsend-addon).
|
||||||
|
4. Restart Home Assistant
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -17,21 +18,8 @@ Component for sending radio commands (433-434Mhz) through the AirSend device.
|
|||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
Simple example:
|
#### 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`.
|
||||||
```yaml
|
|
||||||
# Example configuration.yaml entry
|
|
||||||
airsend:
|
|
||||||
devices:
|
|
||||||
light:
|
|
||||||
id: 9000
|
|
||||||
type: 4098
|
|
||||||
apiKey: !secret asKey
|
|
||||||
prise somfy:
|
|
||||||
id: 9010
|
|
||||||
type: 4097
|
|
||||||
apiKey: !secret asKey
|
|
||||||
```
|
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ default_config:
|
|||||||
|
|
||||||
airsend:
|
airsend:
|
||||||
devices:
|
devices:
|
||||||
lumière:
|
volet cuisine:
|
||||||
id: 9000
|
id: 9000
|
||||||
type: 4098
|
type: 4098
|
||||||
apiKey: !secret asKey
|
apiKey: !secret apiKey
|
||||||
prise somfy:
|
prise somfy:
|
||||||
id: 9010
|
|
||||||
type: 4097
|
type: 4097
|
||||||
apiKey: !secret asKey
|
spurl: !secret spurl
|
||||||
|
channel:
|
||||||
|
id: 13920
|
||||||
|
source: 19259
|
||||||
|
|
||||||
|
|
||||||
group: !include groups.yaml
|
group: !include groups.yaml
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
"""AirSend switches."""
|
"""AirSend switches."""
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from requests import get
|
from requests import get, post
|
||||||
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.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 (
|
from homeassistant.components.cover import (
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
)
|
)
|
||||||
@@ -15,11 +20,40 @@ from . import (
|
|||||||
_LOGGER = logging.getLogger(DOMAIN)
|
_LOGGER = logging.getLogger(DOMAIN)
|
||||||
|
|
||||||
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
||||||
|
addons_url = ""
|
||||||
|
try:
|
||||||
|
addon_info: dict = await async_get_addon_info(hass, 'local_airsend')
|
||||||
|
ip = addon_info["ip_address"]
|
||||||
|
if ip:
|
||||||
|
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.items():
|
||||||
if options['type'] == 4098:
|
if options['type'] == 4098:
|
||||||
entity = AirSendCover(hass, name, options['id'], options['type'], options['apiKey'])
|
id = ""
|
||||||
|
apiKey = ""
|
||||||
|
spurl = ""
|
||||||
|
channel = {}
|
||||||
|
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
|
return
|
||||||
|
|
||||||
@@ -27,12 +61,15 @@ async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_
|
|||||||
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):
|
def __init__(self, hass : HomeAssistant, name : str, id: str, type : int, apikey : str, addons_url : str, spurl : str, channel : dict):
|
||||||
"""Initialize a cover device."""
|
"""Initialize a cover device."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._id = id
|
self._id = id
|
||||||
self._type = type
|
self._type = type
|
||||||
self._apikey = apikey
|
self._apikey = apikey
|
||||||
|
self._addons_url = addons_url
|
||||||
|
self._spurl = spurl
|
||||||
|
self._channel = channel
|
||||||
uname = DOMAIN+name
|
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
|
||||||
@@ -73,24 +110,41 @@ class AirSendCover(CoverEntity):
|
|||||||
|
|
||||||
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"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/5/"
|
url = "https://airsend.cloud/device/"+str(self._id)+"/command/5/"
|
||||||
self._call_cloud(url, True)
|
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
||||||
|
self._action(url, payload, False)
|
||||||
|
|
||||||
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"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/4/"
|
url = "https://airsend.cloud/device/"+str(self._id)+"/command/4/"
|
||||||
self._call_cloud(url, False)
|
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
||||||
|
self._action(url, payload, False)
|
||||||
|
|
||||||
def stop_cover(self, **kwargs):
|
def stop_cover(self, **kwargs):
|
||||||
"""Stop the cover."""
|
"""Stop the cover."""
|
||||||
|
note = {"method":1,"type":0,"value": "STOP"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/3/"
|
url = "https://airsend.cloud/device/"+str(self._id)+"/command/3/"
|
||||||
self._call_cloud(url, False)
|
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
||||||
|
self._action(url, payload, False)
|
||||||
|
|
||||||
def _call_cloud(self, url : str, new_state : bool):
|
def _action(self, cloud_url : str, payload : str, new_state : bool):
|
||||||
headers = {"Authorization": "Bearer "+self._apikey, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
status_code = 404
|
status_code = 404
|
||||||
|
if self._addons_url and self._spurl:
|
||||||
|
headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
||||||
try:
|
try:
|
||||||
response = get(url, headers=headers)
|
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
|
status_code = response.status_code
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -98,5 +152,5 @@ class AirSendCover(CoverEntity):
|
|||||||
self._state = new_state
|
self._state = new_state
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("airsend.cloud error : "+str(status_code))
|
_LOGGER.error("action error : "+str(status_code))
|
||||||
raise Exception("airsend.cloud error : "+str(status_code))
|
raise Exception("action error : "+str(status_code))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"domain": "airsend",
|
"domain": "airsend",
|
||||||
"name": "AirSend",
|
"name": "AirSend",
|
||||||
"documentation": "https://github.com/devmel/hass_airsend",
|
"documentation": "https://github.com/devmel/hass_airsend",
|
||||||
"dependencies": [],
|
"dependencies": ["http"],
|
||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"codeowners": ["@devmel"],
|
"codeowners": ["@devmel"],
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
"""AirSend switches."""
|
"""AirSend switches."""
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from requests import get
|
from requests import get, post
|
||||||
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.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 (
|
from homeassistant.components.switch import (
|
||||||
SwitchEntity,
|
SwitchEntity,
|
||||||
)
|
)
|
||||||
@@ -15,11 +20,45 @@ from . import (
|
|||||||
_LOGGER = logging.getLogger(DOMAIN)
|
_LOGGER = logging.getLogger(DOMAIN)
|
||||||
|
|
||||||
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_add_entities, discovery_info=None):
|
||||||
|
addons_url = ""
|
||||||
|
try:
|
||||||
|
addon_info: dict = await async_get_addon_info(hass, 'local_airsend')
|
||||||
|
ip = addon_info["ip_address"]
|
||||||
|
if ip:
|
||||||
|
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.items():
|
||||||
if options['type'] == 4096 or options['type'] == 4097:
|
if options['type'] == 4096 or options['type'] == 4097:
|
||||||
entity = AirSendSwitch(hass, name, options['id'], options['type'], options['apiKey'])
|
id = ""
|
||||||
|
apiKey = ""
|
||||||
|
spurl = ""
|
||||||
|
channel = {}
|
||||||
|
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
|
return
|
||||||
|
|
||||||
@@ -27,12 +66,16 @@ async def async_setup_platform(hass : HomeAssistant, config : ConfigType, async_
|
|||||||
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):
|
def __init__(self, hass : HomeAssistant, name : str, id: str, type : int, apikey : str, addons_url : str, spurl : str, channel : dict, note : dict):
|
||||||
"""Initialize a switch or light device."""
|
"""Initialize a switch or light device."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._id = id
|
self._id = id
|
||||||
self._type = type
|
self._type = type
|
||||||
self._apikey = apikey
|
self._apikey = apikey
|
||||||
|
self._addons_url = addons_url
|
||||||
|
self._spurl = spurl
|
||||||
|
self._channel = channel
|
||||||
|
self._note = note
|
||||||
uname = DOMAIN+name
|
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
|
||||||
@@ -72,21 +115,42 @@ 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" if self._type == 4097 else "6"
|
command = "6"
|
||||||
|
note = self._note
|
||||||
|
if self._type == 4097:
|
||||||
|
command = "1"
|
||||||
|
note = {"method":1,"type":0,"value": "ON"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
||||||
self._call_cloud(url, True)
|
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
||||||
|
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" if self._type == 4097 else "6"
|
command = "6"
|
||||||
|
note = self._note
|
||||||
|
if self._type == 4097:
|
||||||
|
command = "0"
|
||||||
|
note = {"method":1,"type":0,"value": "OFF"}
|
||||||
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
url = "https://airsend.cloud/device/"+str(self._id)+"/command/"+command+"/"
|
||||||
self._call_cloud(url, False)
|
payload = '{"wait": true, "channel":'+json.dumps(self._channel)+', "thingnotes":{"notes":['+json.dumps(note)+']}}'
|
||||||
|
self._action(url, payload, False)
|
||||||
|
|
||||||
def _call_cloud(self, url : str, new_state : bool):
|
def _action(self, cloud_url : str, payload : str, new_state : bool):
|
||||||
headers = {"Authorization": "Bearer "+self._apikey, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
|
||||||
status_code = 404
|
status_code = 404
|
||||||
|
if self._addons_url and self._spurl:
|
||||||
|
headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
|
||||||
try:
|
try:
|
||||||
response = get(url, headers=headers)
|
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
|
status_code = response.status_code
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -94,5 +158,5 @@ class AirSendSwitch(SwitchEntity):
|
|||||||
self._state = new_state
|
self._state = new_state
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("airsend.cloud error : "+str(status_code))
|
_LOGGER.error("action error : "+str(status_code))
|
||||||
raise Exception("airsend.cloud error : "+str(status_code))
|
raise Exception("action error : "+str(status_code))
|
||||||
|
|||||||
@@ -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/cloud/hass_airsend.zip"
|
wget "https://github.com/devmel/hass_airsend/releases/download/1.0/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..."
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
# Use this file to store secrets like usernames and passwords.
|
# Use this file to store secrets like usernames and passwords.
|
||||||
# Learn more at https://www.home-assistant.io/docs/configuration/secrets/
|
# Learn more at https://www.home-assistant.io/docs/configuration/secrets/
|
||||||
some_password: welcome
|
some_password: welcome
|
||||||
asKey: 18xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx26
|
apiKey: 18xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx26
|
||||||
|
spurl: sp://airsend_password@192.168.0.2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user