forked from HandyHat/ha-hildebrandglow-dcc
-
Notifications
You must be signed in to change notification settings - Fork 1
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
0 parents
commit 3c19f7e
Showing
10 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
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,19 @@ | ||
Copyright (c) 2019 Harley Watson | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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,20 @@ | ||
# ha-hildebrandglow | ||
HomeAssistant integration for the [Hildebrand Glow](https://www.hildebrand.co.uk/our-products/) smart meter HAN for UK SMETS meters. | ||
|
||
Before using this integration, you'll need to have an active Glow account (usable through the Bright app) and API access enabled. If you haven't been given an API Application ID by Hildebrand, you'll need to contact them and request API access be enabled for your account. | ||
|
||
This integration will currently emit one sensor for the current usage of each detected smart meter. | ||
|
||
## Installation | ||
### Automated installation through HACS | ||
You can install this component through [HACS](https://hacs.xyz/) and receive automatic updates. | ||
|
||
After installing HACS, visit the HACS _Settings_ pane and add `https://github.com/unlobito/ha-hildebrandglow` as an `Integration`. You'll then be able to install it through the _Integrations_ pane. | ||
|
||
### Manual installation | ||
Copy the `custom_components/hildebrandglow/` directory and all of its files to your ` config/custom_components` directory. You'll then need to restart Home Assistant for it to detect the new integration. | ||
|
||
## Configuration | ||
Visit the _Integrations_ section within Home Assistant's _Configuration_ panel and click the _Add_ button in the bottom right corner. After searching for "Hildebrand Glow", you'll be asked for your application ID and Glow credentials. | ||
|
||
Once you've authenticated, the integration will automatically set up a sensor for each of the smart meters on your account. |
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,23 @@ | ||
{ | ||
"config": { | ||
"abort": { | ||
"already_configured": "Device is already configured" | ||
}, | ||
"error": { | ||
"cannot_connect": "Failed to connect, please try again", | ||
"invalid_auth": "Unable to authenticate.", | ||
"unknown": "Unexpected error" | ||
}, | ||
"step": { | ||
"user": { | ||
"data": { | ||
"app_id": "Application ID", | ||
"password": "Password", | ||
"username": "Username" | ||
}, | ||
"title": "Hildebrand Glow API access" | ||
} | ||
}, | ||
"title": "Hildebrand Glow" | ||
} | ||
} |
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,51 @@ | ||
"""The Hildebrand Glow integration.""" | ||
import asyncio | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.config_entries import ConfigEntry | ||
|
||
from .glow import Glow | ||
from .const import DOMAIN | ||
|
||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) | ||
|
||
PLATFORMS = ["sensor"] | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: dict): | ||
"""Set up the Hildebrand Glow component.""" | ||
hass.data[DOMAIN] = {} | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Set up Hildebrand Glow from a config entry.""" | ||
|
||
glow = Glow(entry.data['app_id'], entry.data['token']) | ||
hass.data[DOMAIN][entry.entry_id] = glow | ||
|
||
for component in PLATFORMS: | ||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, component) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Unload a config entry.""" | ||
unload_ok = all( | ||
await asyncio.gather( | ||
*[ | ||
hass.config_entries.async_forward_entry_unload(entry, component) | ||
for component in PLATFORMS | ||
] | ||
) | ||
) | ||
if unload_ok: | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
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,52 @@ | ||
"""Config flow for Octopus Energy integration.""" | ||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant import core, config_entries, exceptions | ||
|
||
from .const import DOMAIN # pylint:disable=unused-import | ||
|
||
from .glow import Glow, CannotConnect, InvalidAuth | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_SCHEMA = vol.Schema({"app_id": str, "username": str, "password": str}) | ||
|
||
async def validate_input(hass: core.HomeAssistant, data): | ||
"""Validate the user input allows us to connect. | ||
Data has the keys from DATA_SCHEMA with values provided by the user. | ||
""" | ||
|
||
glow = await Glow.authenticate(data['app_id'], data['username'], data['password']) | ||
|
||
# Return some info we want to store in the config entry. | ||
return {"name": glow["name"], "app_id": data['app_id'], "username": data['username'], 'password': data['password'], "token": glow["token"], "token_exp": glow["exp"]} | ||
|
||
|
||
class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Octopus Energy.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.SOURCE_USER | ||
|
||
async def async_step_user(self, user_input=None): | ||
"""Handle the initial step.""" | ||
errors = {} | ||
if user_input is not None: | ||
try: | ||
info = await validate_input(self.hass, user_input) | ||
|
||
return self.async_create_entry(title=info["name"], data=info) | ||
except CannotConnect: | ||
errors["base"] = "cannot_connect" | ||
except InvalidAuth: | ||
errors["base"] = "invalid_auth" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=DATA_SCHEMA, errors=errors | ||
) |
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,3 @@ | ||
"""Constants for the Hildebrand Glow integration.""" | ||
|
||
DOMAIN = "hildebrandglow" |
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,67 @@ | ||
import requests | ||
from homeassistant import exceptions | ||
from pprint import pprint | ||
|
||
class Glow: | ||
"""Bindings for the Hildebrand Glow Platform API""" | ||
|
||
BASE_URL = "https://api.glowmarkt.com/api/v0-1" | ||
|
||
def __init__(self, app_id, token): | ||
self.app_id = app_id | ||
self.token = token | ||
|
||
@classmethod | ||
async def authenticate(cls, app_id, username, password): | ||
url = f'{cls.BASE_URL}/auth' | ||
auth = {'username': username, 'password': password} | ||
headers = {'applicationId': app_id} | ||
|
||
try: | ||
response = requests.post(url, json=auth, headers=headers) | ||
except requests.Timeout: | ||
raise CannotConnect | ||
|
||
data = response.json() | ||
|
||
if data['valid']: | ||
return data | ||
else: | ||
pprint(data) | ||
raise InvalidAuth | ||
|
||
async def retrieve_resources(self): | ||
url = f'{self.BASE_URL}/resource' | ||
headers = {'applicationId': self.app_id, 'token': self.token} | ||
|
||
try: | ||
response = requests.get(url, headers=headers) | ||
except requests.Timeout: | ||
raise CannotConnect | ||
|
||
if response.status_code != 200: | ||
raise InvalidAuth | ||
|
||
data = response.json() | ||
return data | ||
|
||
async def current_usage(self, resource): | ||
url = f'{self.BASE_URL}/resource/{resource}/current' | ||
headers = {'applicationId': self.app_id, 'token': self.token} | ||
|
||
try: | ||
response = requests.get(url, headers=headers) | ||
except requests.Timeout: | ||
raise CannotConnect | ||
|
||
if response.status_code != 200: | ||
raise InvalidAuth | ||
|
||
data = response.json() | ||
return data | ||
|
||
class CannotConnect(exceptions.HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
|
||
class InvalidAuth(exceptions.HomeAssistantError): | ||
"""Error to indicate there is invalid auth.""" |
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,14 @@ | ||
{ | ||
"domain": "hildebrandglow", | ||
"name": "Hildebrand Glow", | ||
"config_flow": true, | ||
"documentation": "https://github.com/unlobito/ha-hildebrandglow", | ||
"requirements": ["requests"], | ||
"ssdp": [], | ||
"zeroconf": [], | ||
"homekit": {}, | ||
"dependencies": [], | ||
"codeowners": [ | ||
"@unlobito" | ||
] | ||
} |
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,96 @@ | ||
"""Platform for sensor integration.""" | ||
from homeassistant.const import POWER_WATT, DEVICE_CLASS_POWER | ||
from homeassistant.helpers.entity import Entity | ||
|
||
from .const import DOMAIN | ||
|
||
async def async_setup_entry(hass, config, async_add_entities): | ||
"""Set up the sensor platform.""" | ||
|
||
new_entities = [] | ||
|
||
for entry in hass.data[DOMAIN]: | ||
glow = hass.data[DOMAIN][entry] | ||
|
||
resources = await glow.retrieve_resources() | ||
|
||
for resource in resources: | ||
if resource['resourceTypeId'] in GlowConsumptionCurrent.resourceTypeId: | ||
sensor = GlowConsumptionCurrent(glow, resource) | ||
new_entities.append(sensor) | ||
|
||
async_add_entities([sensor]) | ||
|
||
return True | ||
|
||
|
||
class GlowConsumptionCurrent(Entity): | ||
resourceTypeId = [ | ||
"ea02304a-2820-4ea0-8399-f1d1b430c3a0", # Smart Meter, electricity consumption | ||
"672b8071-44ff-4f23-bca2-f50c6a3ddd02" # Smart Meter, gas consumption | ||
] | ||
|
||
def __init__(self, glow, resource): | ||
"""Initialize the sensor.""" | ||
self._state = None | ||
self.glow = glow | ||
self.resource = resource | ||
|
||
@property | ||
def unique_id(self): | ||
return self.resource['resourceId'] | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the sensor.""" | ||
return self.resource['label'] | ||
|
||
@property | ||
def icon(self): | ||
"""Icon to use in the frontend, if any.""" | ||
if self.resource['dataSourceResourceTypeInfo']['type'] == 'ELEC': | ||
return "mdi:flash" | ||
elif self.resource['dataSourceResourceTypeInfo']['type'] == 'GAS': | ||
return "mdi:fire" | ||
|
||
@property | ||
def device_info(self): | ||
if self.resource['dataSourceResourceTypeInfo']['type'] == 'ELEC': | ||
human_type = 'electricity' | ||
elif self.resource['dataSourceResourceTypeInfo']['type'] == 'GAS': | ||
human_type = 'gas' | ||
|
||
return { | ||
'identifiers': { | ||
(DOMAIN, self.resource['dataSourceUnitInfo']['shid']) | ||
}, | ||
'name': f'Smart Meter, {human_type}' | ||
} | ||
|
||
@property | ||
def state(self): | ||
"""Return the state of the sensor.""" | ||
if self._state: | ||
return self._state['data'][0][1] | ||
else: | ||
return None | ||
|
||
@property | ||
def device_class(self): | ||
return DEVICE_CLASS_POWER | ||
|
||
@property | ||
def unit_of_measurement(self): | ||
"""Return the unit of measurement.""" | ||
if not self._state: | ||
return None | ||
elif self._state['units'] == "W": | ||
return POWER_WATT | ||
|
||
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. | ||
""" | ||
self._state = await self.glow.current_usage(self.resource['resourceId']) | ||
|
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,23 @@ | ||
{ | ||
"config": { | ||
"title": "Hildebrand Glow", | ||
"step": { | ||
"user": { | ||
"title": "Hildebrand Glow API access", | ||
"data": { | ||
"app_id": "Application ID", | ||
"username": "Username", | ||
"password": "Password" | ||
} | ||
} | ||
}, | ||
"error": { | ||
"cannot_connect": "Failed to connect, please try again", | ||
"invalid_auth": "Unable to authenticate.", | ||
"unknown": "Unexpected error" | ||
}, | ||
"abort": { | ||
"already_configured": "Device is already configured" | ||
} | ||
} | ||
} |