move api communication to external lib

This commit is contained in:
Robin Wohlers-Reichel
2019-04-09 16:08:09 +10:00
parent 1c41458660
commit 76eb6fb8a6
4 changed files with 30 additions and 124 deletions

View 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": []
}

View File

@@ -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):

View File

@@ -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

View File

@@ -124,6 +124,7 @@ TEST_REQUIREMENTS = (
'simplisafe-python', 'simplisafe-python',
'sleepyq', 'sleepyq',
'smhi-pkg', 'smhi-pkg',
'solax'
'somecomfort', 'somecomfort',
'sqlalchemy', 'sqlalchemy',
'srpenergy', 'srpenergy',