Local LAN connection

This commit is contained in:
Devmel Apps
2022-02-19 22:26:13 +01:00
parent 7962a336a7
commit 40e2dfac42
7 changed files with 165 additions and 56 deletions
+4 -16
View File
@@ -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
+6 -4
View File
@@ -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
+70 -16
View File
@@ -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,24 +20,56 @@ 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 = ""
async_add_entities([entity]) 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])
return 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): 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,30 +110,47 @@ 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
try: if self._addons_url and self._spurl:
response = get(url, headers=headers) headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
status_code = response.status_code try:
except: response = post(self._addons_url+"airsend/transfer", headers=headers, data=payload, timeout=6)
pass 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: if status_code == 200:
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))
+1 -1
View File
@@ -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": [],
+81 -17
View File
@@ -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,24 +20,62 @@ 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 = ""
async_add_entities([entity]) 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])
return 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): 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,27 +115,48 @@ 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
try: if self._addons_url and self._spurl:
response = get(url, headers=headers) headers = {"Authorization": "Bearer "+self._spurl, "content-type": "application/json", "User-Agent": "hass_airsend"}
status_code = response.status_code try:
except: response = post(self._addons_url+"airsend/transfer", headers=headers, data=payload, timeout=6)
pass 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: if status_code == 200:
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))
+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/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 -1
View File
@@ -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