Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
unlobito committed Jan 3, 2020
0 parents commit 3c19f7e
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 0 deletions.
19 changes: 19 additions & 0 deletions LICENSE.md
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.
20 changes: 20 additions & 0 deletions README.md
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.
23 changes: 23 additions & 0 deletions custom_components/hildebrandglow/.translations/en.json
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"
}
}
51 changes: 51 additions & 0 deletions custom_components/hildebrandglow/__init__.py
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
52 changes: 52 additions & 0 deletions custom_components/hildebrandglow/config_flow.py
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
)
3 changes: 3 additions & 0 deletions custom_components/hildebrandglow/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the Hildebrand Glow integration."""

DOMAIN = "hildebrandglow"
67 changes: 67 additions & 0 deletions custom_components/hildebrandglow/glow.py
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."""
14 changes: 14 additions & 0 deletions custom_components/hildebrandglow/manifest.json
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"
]
}
96 changes: 96 additions & 0 deletions custom_components/hildebrandglow/sensor.py
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'])

23 changes: 23 additions & 0 deletions custom_components/hildebrandglow/strings.json
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"
}
}
}

0 comments on commit 3c19f7e

Please sign in to comment.