forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
37d75e8
commit 86533e3
Showing
13 changed files
with
926 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
"""The go-e Charger integration.""" | ||
from __future__ import annotations | ||
|
||
from datetime import timedelta | ||
import logging | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
from homeassistant.helpers import device_registry | ||
|
||
from .const import DOMAIN | ||
from .common import GoeChargerHub | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
PLATFORMS: list[str] = ["binary_sensor", "number", "select", "sensor"] | ||
|
||
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Set up the go-eCharger integration.""" | ||
|
||
hass.data[DOMAIN] = {} | ||
|
||
return True | ||
|
||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Set up go-e Charger from a config entry.""" | ||
|
||
async def async_update_data(): | ||
"""Fetch data from API endpoint.""" | ||
hub = GoeChargerHub(config_entry.data["host"]) | ||
|
||
try: | ||
data = await hub.get_data(hass, ["alw","acu","adi","sse","eto","ccw","rssi","lmo","amp","fna","car","err","cbl","wh","fwv","oem","typ","tma","nrg","modelStatus","var","fhz","ust","acs","frc","psm","loc"]) | ||
|
||
dr = await device_registry.async_get_registry(hass) | ||
dr.async_get_or_create( | ||
name=data["fna"], | ||
config_entry_id=config_entry.entry_id, | ||
#connections={(device_registry.CONNECTION_NETWORK_MAC, "11:22:33:44:55:66")}, | ||
identifiers={(DOMAIN, config_entry.data["serial"])}, | ||
manufacturer=data["oem"], | ||
model=data["typ"] + " (" + str(data["var"]) + "kW)", | ||
#suggested_area="Kitchen", | ||
sw_version=data["fwv"], | ||
) | ||
|
||
return data | ||
except Exception as e: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception %s", str(e)) | ||
return None | ||
|
||
coordinator = DataUpdateCoordinator( | ||
hass, | ||
_LOGGER, | ||
name="goe_charger_" + config_entry.data["serial"], | ||
update_method=async_update_data, | ||
update_interval=timedelta(seconds=5), | ||
) | ||
|
||
hass.data[DOMAIN][config_entry.entry_id] = coordinator | ||
|
||
await coordinator.async_config_entry_first_refresh() | ||
|
||
if coordinator.data is None: | ||
dr = await device_registry.async_get_registry(hass) | ||
dr.async_get_or_create( | ||
name="go-e_Charger_" + config_entry.data["serial"], | ||
config_entry_id=config_entry.entry_id, | ||
#connections={(device_registry.CONNECTION_NETWORK_MAC, "11:22:33:44:55:66")}, | ||
identifiers={(DOMAIN, config_entry.data["serial"])}, | ||
manufacturer="<unknown>", | ||
model="<unknown>", | ||
#suggested_area="Kitchen", | ||
sw_version="<unknown>", | ||
) | ||
|
||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
if not await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS): | ||
logging.warning('unload platforms failed') | ||
return False; | ||
|
||
hass.data[DOMAIN].pop(config_entry.entry_id) | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
"""Platform for number integration.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity, DataUpdateCoordinator | ||
from homeassistant.components.binary_sensor import BinarySensorEntity | ||
|
||
from .const import DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities) -> None: | ||
coordinator = hass.data[DOMAIN][config_entry.entry_id] | ||
serial = config_entry.data["serial"] | ||
|
||
async_add_entities([ | ||
GoeChargerBinary(coordinator, "Allow charging", serial, "allow_charging", "alw"), | ||
GoeChargerBinary(coordinator, "Adapter used", serial, "adapter_used", "adi"), | ||
]) | ||
|
||
class GoeChargerBinary(CoordinatorEntity, BinarySensorEntity): | ||
"""Representation of a Sensor.""" | ||
|
||
def __init__(self, coordinator: DataUpdateCoordinator, name: str, serial: str, unique_id: str, key: str): | ||
"""Pass coordinator to CoordinatorEntity.""" | ||
super().__init__(coordinator) | ||
self._name = name | ||
self._serial = serial | ||
self._unique_id = unique_id | ||
self._key = key | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the device.""" | ||
return self._name | ||
|
||
@property | ||
def unique_id(self): | ||
"""Return the unique id of the device.""" | ||
return "goe_charger_" + self._serial + "_" + self._unique_id | ||
|
||
@property | ||
def available(self) -> bool: | ||
"""Return True if entity is available.""" | ||
return self.coordinator.data is not None and self._key in self.coordinator.data | ||
|
||
@property | ||
def is_on(self) -> bool | None: | ||
"""Return the state of the sensor.""" | ||
return None if not self.available else self.coordinator.data[self._key] | ||
|
||
async def async_update(self): | ||
"""Fetch new state data for the sensor. | ||
This is the only method that should fetch new data for Home Assistant. | ||
""" | ||
await self.coordinator.async_request_refresh() | ||
|
||
@property | ||
def device_info(self): | ||
"""Get attributes about the device.""" | ||
return { | ||
"identifiers": {(DOMAIN, self._serial)}, | ||
#"name": self._device.label, | ||
#"model": self._device.device_type_name, | ||
#"manufacturer": "Unavailable", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
"""Common code go-e Charger integration.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Any | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
import aiohttp | ||
import asyncio | ||
import async_timeout | ||
import json | ||
import urllib.parse | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
class GoeChargerHub: | ||
def __init__(self, host: str) -> None: | ||
"""Initialize.""" | ||
self._host = host | ||
|
||
async def get_data(self, hass: HomeAssistant, keys: list[str]) -> bool: | ||
"""Get the data from the charger.""" | ||
|
||
url = 'http://' + self._host + '/api/status?filter=' + urllib.parse.quote_plus(','.join(keys)) | ||
|
||
session = async_get_clientsession(hass) | ||
|
||
try: | ||
with async_timeout.timeout(10): | ||
resp = await session.get(url) | ||
content = await resp.text() | ||
except asyncio.TimeoutError: | ||
_LOGGER.warning("Request timeout") | ||
raise TimeoutOccured(url) | ||
except aiohttp.ClientError: | ||
_LOGGER.warning("Request exception") | ||
raise CannotConnect(url) | ||
|
||
if resp.status != 200: | ||
_LOGGER.warning("Request invalid response %i %s", resp.status, content) | ||
raise InvalidRespStatus(resp.status, content) | ||
|
||
try: | ||
parsed = json.loads(content) | ||
except Exception as e: # pylint: disable=broad-except | ||
details = "Could not parse json " + str(e) | ||
_LOGGER.warning("%s %s", details, content) | ||
raise InvalidJson(details, content) | ||
|
||
if type(parsed) is not dict: | ||
details = "json is not a dict ({})".format(type(parsed).__name__) | ||
_LOGGER.warning("%s", details) | ||
raise InvalidJson(details, content) | ||
|
||
for key in keys: | ||
if key not in parsed: | ||
details = key + " not set in json object" | ||
_LOGGER.warning("%s", details) | ||
raise InvalidJson(details, content) | ||
|
||
_LOGGER.debug("Data received successfully for %s!", self._host) | ||
|
||
return parsed | ||
|
||
async def set_data(self, hass: HomeAssistant, data: dict[str, Any]) -> None: | ||
"""Set data to the charger.""" | ||
|
||
url = 'http://' + self._host + '/api/set?' | ||
for key, value in data.items(): | ||
url += urllib.parse.quote_plus(key) + '=' + urllib.parse.quote_plus(json.dumps(value)) + '&' | ||
|
||
session = async_get_clientsession(hass) | ||
|
||
try: | ||
with async_timeout.timeout(10): | ||
resp = await session.get(url) | ||
content = await resp.text() | ||
except asyncio.TimeoutError: | ||
_LOGGER.warning("Request timeout") | ||
raise TimeoutOccured(url) | ||
except aiohttp.ClientError: | ||
_LOGGER.warning("Request exception") | ||
raise CannotConnect(url) | ||
|
||
if resp.status != 200: | ||
_LOGGER.warning("Request invalid response %i %s", resp.status, content) | ||
raise InvalidRespStatus(resp.status, content) | ||
|
||
try: | ||
parsed = json.loads(content) | ||
except Exception as e: # pylint: disable=broad-except | ||
details = "Could not parse json " + str(e) | ||
_LOGGER.warning("%s %s", details, content) | ||
raise InvalidJson(details, content) | ||
|
||
if type(parsed) is not dict: | ||
details = "json is not a dict ({})".format(type(parsed).__name__) | ||
_LOGGER.warning("%s", details) | ||
raise InvalidJson(details, content) | ||
|
||
for key in data: | ||
if key not in parsed: | ||
details = key + " not set in json object" | ||
_LOGGER.warning("%s", details) | ||
raise InvalidJson(details, content) | ||
|
||
if parsed[key] != True: | ||
details = key + parsed[key] | ||
_LOGGER.warning("%s", details) | ||
raise InvalidJson(details, content) | ||
|
||
_LOGGER.debug("Data set successfully for %s!", self._host) | ||
|
||
class CannotConnect(HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
def __init__(self, url: str) -> None: | ||
"""Initialize.""" | ||
self.url = url | ||
|
||
class TimeoutOccured(HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
def __init__(self, url: str) -> None: | ||
"""Initialize.""" | ||
self.url = url | ||
|
||
class InvalidRespStatus(HomeAssistantError): | ||
"""Error to indicate we got an invalid response status.""" | ||
def __init__(self, status: int, response: str) -> None: | ||
"""Initialize.""" | ||
self.status = status | ||
self.response = response | ||
|
||
class InvalidJson(HomeAssistantError): | ||
"""Error to indicate we got an invalid json response.""" | ||
def __init__(self, details: str, response: str) -> None: | ||
"""Initialize.""" | ||
self.details = details | ||
self.response = response | ||
|
||
class NotImplemented(HomeAssistantError): | ||
"""Error to indicate that something is not yet implemented.""" |
Oops, something went wrong.