mirror of
https://github.com/home-assistant/core.git
synced 2025-08-03 20:55:10 +02:00
move api communication to external lib
This commit is contained in:
11
homeassistant/components/solax/manifest.json
Normal file
11
homeassistant/components/solax/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"domain": "solax",
|
||||||
|
"name": "Solax Inverter",
|
||||||
|
"documentation": "https://www.home-assistant.io/components/solax",
|
||||||
|
"requirements": [
|
||||||
|
"solax==0.0.3"
|
||||||
|
],
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": []
|
||||||
|
}
|
||||||
|
|
@@ -5,8 +5,6 @@ import json
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import async_timeout
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -22,141 +20,37 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# key: name of sensor
|
|
||||||
# value.0: index
|
|
||||||
# value.1: unit (String) or None
|
|
||||||
# from https://github.com/GitHobi/solax/wiki/direct-data-retrieval
|
|
||||||
INVERTER_SENSORS = {
|
|
||||||
'PV1 Current': (0, 'A'),
|
|
||||||
'PV2 Current': (1, 'A'),
|
|
||||||
'PV1 Voltage': (2, 'V'),
|
|
||||||
'PV2 Voltage': (3, 'V'),
|
|
||||||
|
|
||||||
'Output Current': (4, 'A'),
|
|
||||||
'Network Voltage': (5, 'V'),
|
|
||||||
'Power Now': (6, 'W'),
|
|
||||||
|
|
||||||
'Inverter Temperature': (7, TEMP_CELSIUS),
|
|
||||||
'Today\'s Energy': (8, 'kWh'),
|
|
||||||
'Total Energy': (9, 'kWh'),
|
|
||||||
'Exported Power': (10, 'W'),
|
|
||||||
'PV1 Power': (11, 'W'),
|
|
||||||
'PV2 Power': (12, 'W'),
|
|
||||||
|
|
||||||
'Battery Voltage': (13, 'V'),
|
|
||||||
'Battery Current': (14, 'A'),
|
|
||||||
'Battery Power': (15, 'W'),
|
|
||||||
'Battery Temperature': (16, TEMP_CELSIUS),
|
|
||||||
'Battery Remaining Capacity': (17, '%'),
|
|
||||||
|
|
||||||
'Battery Energy': (19, 'kWh'),
|
|
||||||
|
|
||||||
'Grid Frequency': (50, 'Hz'),
|
|
||||||
'EPS Voltage': (53, 'V'),
|
|
||||||
'EPS Current': (54, 'A'),
|
|
||||||
'EPS Power': (55, 'W'),
|
|
||||||
'EPS Frequency': (56, 'Hz'),
|
|
||||||
}
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_IP_ADDRESS): cv.string,
|
vol.Required(CONF_IP_ADDRESS): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=30)
|
SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
REQUEST_TIMEOUT = 5
|
|
||||||
|
|
||||||
REAL_TIME_DATA_ENDPOINT = 'http://{ip_address}/api/realTimeData.htm'
|
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema(
|
|
||||||
vol.All([vol.Coerce(float)], vol.Length(min=68, max=68))
|
|
||||||
)
|
|
||||||
|
|
||||||
REAL_TIME_DATA_SCHEMA = vol.Schema({
|
|
||||||
vol.Required('method'): cv.string,
|
|
||||||
vol.Required('version'): cv.string,
|
|
||||||
vol.Required('type'): cv.string,
|
|
||||||
vol.Required('SN'): cv.string,
|
|
||||||
vol.Required('Data'): DATA_SCHEMA,
|
|
||||||
vol.Required('Status'): cv.positive_int,
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
|
||||||
|
|
||||||
|
|
||||||
class SolaxRequestError(Exception):
|
|
||||||
"""Error to indicate a Solax API request has failed."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities,
|
async def async_setup_platform(hass, config, async_add_entities,
|
||||||
discovery_info=None):
|
discovery_info=None):
|
||||||
"""Platform setup."""
|
"""Platform setup."""
|
||||||
endpoint = RealTimeDataEndpoint(hass, config.get(CONF_IP_ADDRESS))
|
import solax
|
||||||
|
|
||||||
|
api = solax.solax.RealTimeAPI(config.get(CONF_IP_ADDRESS))
|
||||||
|
endpoint = RealTimeDataEndpoint(hass, api)
|
||||||
hass.async_add_job(endpoint.async_refresh)
|
hass.async_add_job(endpoint.async_refresh)
|
||||||
async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL)
|
async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL)
|
||||||
devices = []
|
devices = []
|
||||||
for sensor in INVERTER_SENSORS:
|
for sensor in solax.INVERTER_SENSORS:
|
||||||
devices.append(Inverter(sensor))
|
unit = solax.INVERTER_SENSORS[sensor][1]
|
||||||
|
if unit == 'C':
|
||||||
|
unit = TEMP_CELSIUS
|
||||||
|
devices.append(Inverter(sensor, unit))
|
||||||
endpoint.sensors = devices
|
endpoint.sensors = devices
|
||||||
async_add_entities(devices)
|
async_add_entities(devices)
|
||||||
|
|
||||||
|
|
||||||
async def async_solax_real_time_request(hass, schema, ip_address, retry,
|
|
||||||
t_wait=0):
|
|
||||||
"""Make call to inverter endpoint."""
|
|
||||||
if t_wait > 0:
|
|
||||||
msg = "Timeout connecting to Solax inverter, waiting %d to retry."
|
|
||||||
_LOGGER.error(msg, t_wait)
|
|
||||||
asyncio.sleep(t_wait)
|
|
||||||
new_wait = (t_wait*2)+5
|
|
||||||
retry = retry - 1
|
|
||||||
try:
|
|
||||||
session = async_get_clientsession(hass)
|
|
||||||
|
|
||||||
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
|
|
||||||
url = REAL_TIME_DATA_ENDPOINT.format(ip_address=ip_address)
|
|
||||||
req = await session.get(url)
|
|
||||||
garbage = await req.read()
|
|
||||||
formatted = garbage.decode("utf-8")
|
|
||||||
formatted = formatted.replace(",,", ",0.0,").replace(",,", ",0.0,")
|
|
||||||
json_response = json.loads(formatted)
|
|
||||||
return schema(json_response)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
if retry > 0:
|
|
||||||
return await async_solax_real_time_request(hass,
|
|
||||||
schema,
|
|
||||||
ip_address,
|
|
||||||
retry,
|
|
||||||
new_wait)
|
|
||||||
_LOGGER.error("Too many timeouts connecting to Solax.")
|
|
||||||
except (aiohttp.ClientError) as client_err:
|
|
||||||
_LOGGER.error("Could not connect to Solax API endpoint")
|
|
||||||
_LOGGER.error(client_err)
|
|
||||||
except ValueError:
|
|
||||||
_LOGGER.error("Received non-JSON data from Solax API endpoint")
|
|
||||||
except vol.Invalid as err:
|
|
||||||
_LOGGER.error("Received unexpected JSON from Solax"
|
|
||||||
" API endpoint: %s", err)
|
|
||||||
_LOGGER.error(json_response)
|
|
||||||
raise SolaxRequestError
|
|
||||||
|
|
||||||
|
|
||||||
def parse_solax_battery_response(response):
|
|
||||||
"""Manipulate the response from solax endpoint."""
|
|
||||||
data_list = response['Data']
|
|
||||||
result = {}
|
|
||||||
for name, index in INVERTER_SENSORS.items():
|
|
||||||
response_index = index[0]
|
|
||||||
result[name] = data_list[response_index]
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class RealTimeDataEndpoint:
|
class RealTimeDataEndpoint:
|
||||||
"""Representation of a Sensor."""
|
"""Representation of a Sensor."""
|
||||||
|
|
||||||
def __init__(self, hass, ip_address):
|
def __init__(self, hass, api):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.ip_address = ip_address
|
self.api = api
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.ready = asyncio.Event()
|
self.ready = asyncio.Event()
|
||||||
self.sensors = []
|
self.sensors = []
|
||||||
@@ -167,11 +61,7 @@ class RealTimeDataEndpoint:
|
|||||||
This is the only method that should fetch new data for Home Assistant.
|
This is the only method that should fetch new data for Home Assistant.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
resp = await async_solax_real_time_request(self.hass,
|
self.data = await self.api.get_data()
|
||||||
REAL_TIME_DATA_SCHEMA,
|
|
||||||
self.ip_address,
|
|
||||||
3)
|
|
||||||
self.data = parse_solax_battery_response(resp)
|
|
||||||
self.ready.set()
|
self.ready.set()
|
||||||
except SolaxRequestError:
|
except SolaxRequestError:
|
||||||
if now is not None:
|
if now is not None:
|
||||||
@@ -187,10 +77,11 @@ class RealTimeDataEndpoint:
|
|||||||
class Inverter(Entity):
|
class Inverter(Entity):
|
||||||
"""Class for a sensor."""
|
"""Class for a sensor."""
|
||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key, unit):
|
||||||
"""Initialize an inverter sensor."""
|
"""Initialize an inverter sensor."""
|
||||||
self.key = key
|
self.key = key
|
||||||
self.value = None
|
self.value = None
|
||||||
|
self.unit = unit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@@ -205,7 +96,7 @@ class Inverter(Entity):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
return INVERTER_SENSORS[self.key][1]
|
return self.unit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
|
@@ -1595,6 +1595,9 @@ socialbladeclient==0.2
|
|||||||
# homeassistant.components.solaredge
|
# homeassistant.components.solaredge
|
||||||
solaredge==0.0.2
|
solaredge==0.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.solax
|
||||||
|
solax==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.honeywell
|
# homeassistant.components.honeywell
|
||||||
somecomfort==0.5.2
|
somecomfort==0.5.2
|
||||||
|
|
||||||
|
@@ -124,6 +124,7 @@ TEST_REQUIREMENTS = (
|
|||||||
'simplisafe-python',
|
'simplisafe-python',
|
||||||
'sleepyq',
|
'sleepyq',
|
||||||
'smhi-pkg',
|
'smhi-pkg',
|
||||||
|
'solax'
|
||||||
'somecomfort',
|
'somecomfort',
|
||||||
'sqlalchemy',
|
'sqlalchemy',
|
||||||
'srpenergy',
|
'srpenergy',
|
||||||
|
Reference in New Issue
Block a user