Skip to content
This repository has been archived by the owner on Apr 30, 2022. It is now read-only.

Migrate to MQTT from HTTPS polling #16

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e71d06e
Use the Glow App ID rather than prompting for one
unlobito Aug 12, 2020
bacb33a
glow: add retrieve_devices method
unlobito Aug 12, 2020
c453f1e
Merge branch 'master' into mqtt
unlobito May 15, 2021
b17515f
move InvalidAuth handling out of sensor
unlobito May 16, 2021
24bf8d7
calculate MQTT topic based on CAD ID
unlobito May 16, 2021
9f9c2f6
connect to MQTT server with discovered CAD ID
unlobito May 16, 2021
dd104d8
persist credentials in Glow object
unlobito May 17, 2021
3e7432a
WIP: route MQTT data to sensor object
unlobito May 17, 2021
84939dd
migrate to paho-mqtt
unlobito May 17, 2021
61408de
parse more MQTTPayload data
unlobito May 17, 2021
610f5de
MQTTPayload: docstrings
unlobito May 17, 2021
71af3ba
attempt gas readings
unlobito May 17, 2021
68d34e7
typo in supply_status key
unlobito May 17, 2021
db42193
subscribe to HILD and DCAD topics
unlobito May 17, 2021
6bdd399
address mypy errors
unlobito May 17, 2021
367d610
manifest: version 0.1.1
unlobito Jun 5, 2021
8ef0a46
use MQTT wildcard for topic
unlobito Jun 5, 2021
ce7bb34
chore: device_info() returns DeviceInfo
unlobito Jun 15, 2021
2e7c3bd
search for the Glow Display deviceTypeId
unlobito Jun 15, 2021
17562bb
call async_write_ha_state with a job
unlobito Sep 8, 2021
d4b9ae3
emit log messages for known setup failures
unlobito Sep 11, 2021
ae6ec9a
README: MQTT access
unlobito Oct 4, 2021
6bdf84c
Initial WIP potential fix for twos complement calculation of consumpt…
danstreeter Oct 14, 2021
ab265bd
Check hex length before dec conversion
unlobito Oct 26, 2021
e0ad1fe
introduce Glowdata for generic access from sensors
unlobito Dec 19, 2021
1ca3602
Determine sensor availability at runtime
unlobito Dec 21, 2021
845bc48
README.md: archived
unlobito Apr 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: python
python:
- "3.8"
- "3.9"
install:
# Attempt to work around Home Assistant not being declared through PEP 561
# Details: https://github.com/home-assistant/core/pull/28866#pullrequestreview-319309922
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# Archived
This repository was archived on 30 April 2022 (see https://github.com/unlobito/ha-hildebrandglow/discussions/55 for details).

[HandyHat/ha-hildebrandglow-dcc](https://github.com/HandyHat/ha-hildebrandglow-dcc) is maintained as a fork for DCC users.

# 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.
You'll need to have an active Glow account (usable through the Bright app), [a Consumer Access Device](https://www.hildebrand.co.uk/our-products/), and MQTT access enabled before using this integration. If you haven't been given MQTT connection details by Hildebrand, you'll need to contact them and request MQTT access be enabled for your account.

This integration will currently emit one sensor for the current usage of each detected smart meter.

Expand Down
93 changes: 73 additions & 20 deletions custom_components/hildebrandglow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
import asyncio
from typing import Any, Dict

import async_timeout
import voluptuous as vol
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
InvalidStateError,
)

from .const import DOMAIN
from .glow import Glow
from .const import APP_ID, DOMAIN, GLOW_SESSION, LOGGER
from .glow import CannotConnect, Glow, InvalidAuth, NoCADAvailable

CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)

PLATFORMS = ["sensor"]
PLATFORMS = (SENSOR_DOMAIN,)


async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
Expand All @@ -23,28 +30,74 @@ async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Hildebrand Glow from a config entry."""
glow = Glow(entry.data["app_id"], entry.data["token"])
hass.data[DOMAIN][entry.entry_id] = glow
glow = Glow(
APP_ID,
entry.data["username"],
entry.data["password"],
)

try:
if not await async_connect_or_timeout(hass, glow):
return False
except CannotConnect as err:
raise ConfigEntryNotReady from err

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {GLOW_SESSION: glow}

for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)

if not entry.update_listeners:
entry.add_update_listener(async_update_options)

return True


async def async_connect_or_timeout(hass: HomeAssistant, glow: Glow) -> bool:
"""Connect from Glow."""
try:
async with async_timeout.timeout(10):
LOGGER.debug("Initialize connection from Glow")

await hass.async_add_executor_job(glow.authenticate)
await hass.async_add_executor_job(glow.retrieve_cad_hardwareId)
await hass.async_add_executor_job(glow.connect_mqtt)

while not glow.broker_active:
await asyncio.sleep(1)
except InvalidAuth as err:
LOGGER.error("Couldn't login with the provided username/password")
raise ConfigEntryAuthFailed from err

except NoCADAvailable as err:
LOGGER.error("Couldn't find any CAD devices (e.g. Glow Stick)")
raise InvalidStateError from err

except asyncio.TimeoutError as err:
await async_disconnect_or_timeout(hass, glow)
LOGGER.debug("Timeout expired: %s", err)
raise CannotConnect from err

return True


async def async_disconnect_or_timeout(hass: HomeAssistant, glow: Glow) -> bool:
"""Disconnect from Glow."""
LOGGER.debug("Disconnect from Glow")
async with async_timeout.timeout(3):
await hass.async_add_executor_job(glow.disconnect)
return True


async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update options."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
"""Unload Hildebrand Glow config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

coordinator = hass.data[DOMAIN].pop(entry.entry_id)
await coordinator[GLOW_SESSION].disconnect()
return unload_ok
19 changes: 9 additions & 10 deletions custom_components/hildebrandglow/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
"""Config flow for Hildebrand Glow integration."""
from __future__ import annotations

import logging
from typing import Any, Dict

import voluptuous as vol
from homeassistant import config_entries, core, data_entry_flow

from .const import DOMAIN # pylint:disable=unused-import
from .const import APP_ID, DOMAIN # pylint:disable=unused-import
from .glow import CannotConnect, Glow, InvalidAuth

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA = vol.Schema({"app_id": str, "username": str, "password": str})
DATA_SCHEMA = vol.Schema({"username": str, "password": str})


def config_object(data: dict, glow: Dict[str, Any]) -> Dict[str, Any]:
"""Prepare a ConfigEntity with authentication data and a temporary token."""
return {
"name": glow["name"],
"app_id": data["app_id"],
"username": data["username"],
"password": data["password"],
"token": glow["token"],
"token_exp": glow["exp"],
}


Expand All @@ -30,12 +29,12 @@ async def validate_input(hass: core.HomeAssistant, data: dict) -> Dict[str, Any]

Data has the keys from DATA_SCHEMA with values provided by the user.
"""
glow = await hass.async_add_executor_job(
Glow.authenticate, data["app_id"], data["username"], data["password"]
)
glow = Glow(APP_ID, data["username"], data["password"])

auth_data: Dict[str, Any] = await hass.async_add_executor_job(glow.authenticate)

# Return some info we want to store in the config entry.
return config_object(data, glow)
return config_object(data, auth_data)


class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
Expand All @@ -45,7 +44,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONNECTION_CLASS = config_entries.SOURCE_USER

async def async_step_user(
self, user_input: Dict = None
self, user_input: dict[str, Any] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the initial step."""
errors = {}
Expand Down
9 changes: 8 additions & 1 deletion custom_components/hildebrandglow/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
"""Constants for the Hildebrand Glow integration."""
import logging
from typing import Final

DOMAIN = "hildebrandglow"
DOMAIN: Final = "hildebrandglow"
APP_ID: Final = "b0f1b774-a586-4f72-9edd-27ead8aa7a8d"

LOGGER = logging.getLogger(__package__)

GLOW_SESSION: Final = "glow_session"
81 changes: 0 additions & 81 deletions custom_components/hildebrandglow/glow.py

This file was deleted.

Loading