Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: andrew-codechimp/HA-Hive-Local-Thermostat
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.1.9
Choose a base ref
...
head repository: andrew-codechimp/HA-Hive-Local-Thermostat
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
32 changes: 24 additions & 8 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
{
"name": "andrew-codechimp/hive-local-thermostat",
"image": "mcr.microsoft.com/vscode/devcontainers/python:3.12-bullseye",
"image": "mcr.microsoft.com/devcontainers/python:dev-3.13",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {
"installDirectlyFromGitHubRelease": true,
"version": "latest"
},
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "lts"
},
"ghcr.io/devcontainers-contrib/features/poetry:2": {
"version": "latest"
},
"ghcr.io/devcontainers/features/rust:1": {}
},
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
@@ -17,27 +31,29 @@
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
"ms-python.vscode-pylance",
"charliermarsh.ruff"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.formatting.provider": "black",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
}
}
},
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers/features/rust:1": {}
}
"remoteUser": "vscode"
}
6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -6,21 +6,21 @@ on:
- '**.py' # Run if pushed commits include a change to a Python (.py) file.
- '.github/workflows/lint.yml' # Run if pushed commits include a change to a github actions workflow file.
- 'requirements.txt' # Run if pushed commits include a change to the Python requirements.txt file.
- '.ruff.toml' # Run if ruff configuration file changes.
- '.pyprogject.toml' # Run if project configuration file changes.
pull_request:
paths:
- '**.py' # Run if pushed commits include a change to a Python (.py) file.
- '.github/workflows/lint.yml' # Run if pushed commits include a change to a github actions workflow file.
- 'requirements.txt' # Run if pushed commits include a change to the Python requirements.txt file.
- '.ruff.toml' # Run if ruff configuration file changes.
- '.pyprogject.toml' # Run if project configuration file changes.
workflow_dispatch:

jobs:
build:
runs-on: "ubuntu-latest"
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.13"]
steps:
- name: Checkout repo
uses: actions/checkout@v4
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -30,6 +30,6 @@ jobs:
zip hive_local_thermostat.zip -r ./
- name: "Upload the ZIP file to the release"
uses: softprops/action-gh-release@v2.0.4
uses: softprops/action-gh-release@v2.2.1
with:
files: ${{ github.workspace }}/custom_components/hive_local_thermostat/hive_local_thermostat.zip
48 changes: 0 additions & 48 deletions .ruff.toml

This file was deleted.

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Andrew Jackson
Copyright (c) 2024-2025 Andrew Jackson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -9,8 +9,6 @@

Local Hive Thermostat MQTT integration for Home Assistant

**Early beta**

*Please :star: this repo if you find it useful*
*If you want to show your support please*

@@ -20,7 +18,7 @@ To use this integration your Hive thermostat receiver must be added to [Zigbee2M

Zigbee2MQTT will expose the native sensors but Hive requires specific message structures to be sent for setting modes and a combination of sensor values to determine the modes, this integration creates controls and sensors that correctly interface with the native Hive values/methods.

SLR1x and SLR2x thermostats are supported, though this has only been tested with an SLR2c. If you have thoroughly tested a different model please let me know and I'll add it to the list of confirmed devices.
SLR1x and SLR2x thermostats are supported, this has been tested with an SLR2c and an SLR1c. If you have thoroughly tested a different model please let me know and I'll add it to the list of confirmed devices. As long as you have one of these receivers this integration will work with either the Hive mini or regular controller.

Once you have the thermostat receiver added to Zigbee2MQTT, add a device via this integration and specify a friendly name, the Zibee2MQTT topic which should look something like this `zigbee2mqtt/HiveReceiver` (note this is case sensitive).

@@ -30,7 +28,9 @@ You can optionally hide/disable the native Hive device created by Zigbee2MQTT wi

The integration supports native boost and native schedules. With schedules you can switch on/off schedule mode but you cannot modify the schedule details via the integration. You can of course ignore the schedule mode and setup automations within Home Assistant to control when heating/water is on or off and set a temperature for heating.

The numeric entities allow you to set defaults for boost times, temperatures and also frost protection. Frost protection should be set to match what you have set on the Hive thermostat for an accurate display.
The numeric entities allow you to set defaults for boost times, heating boost temperature and also frost protection. Frost protection should be set to match what you have set on the Hive thermostat for an accurate display.

Two actions are provided to natively boost the Heating `hive_local_thermostat.boost_heating` and Water `hive_local_thermostat.boost_water` (SLR2 only), these can optionally take a duration and temperature (heating only), these actions allow you to make custom buttons/scripts/automations to add additional control over the default boost buttons.

![Hive Screenshot](https://github.com/andrew-codechimp/HA-Hive-Local-Thermostat/blob/main/images/screenshot.png "Hive Controls")

56 changes: 37 additions & 19 deletions custom_components/hive_local_thermostat/__init__.py
Original file line number Diff line number Diff line change
@@ -6,34 +6,42 @@

from __future__ import annotations

from awesomeversion.awesomeversion import AwesomeVersion

import json
from asyncio import sleep

from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.components.mqtt import client as mqtt_client
from homeassistant.components.mqtt.models import ReceiveMessage
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import CONF_ENTITIES, Platform
from homeassistant.const import __version__ as HA_VERSION # noqa: N812
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers import config_validation as cv
from homeassistant.const import __version__ as HA_VERSION # noqa: N812
from homeassistant.components.mqtt import client as mqtt_client
from homeassistant.components.mqtt.models import ReceiveMessage

from homeassistant.const import CONF_ENTITIES
from homeassistant.helpers.typing import ConfigType

from .const import (
CONF_MODEL,
CONF_MQTT_TOPIC,
DOMAIN,
LOGGER,
MIN_HA_VERSION,
CONF_MQTT_TOPIC,
MODEL_SLR2,
)
from .services import setup_services

PLATFORMS_SLR1: list[Platform] = [
Platform.SENSOR, Platform.CLIMATE, Platform.NUMBER, Platform.BUTTON, Platform.BINARY_SENSOR
]

PLATFORMS: list[Platform] = [
Platform.SENSOR, Platform.CLIMATE, Platform.NUMBER, Platform.SELECT, Platform.BUTTON,
PLATFORMS_SLR2: list[Platform] = [
Platform.SENSOR, Platform.CLIMATE, Platform.NUMBER, Platform.SELECT, Platform.BUTTON, Platform.BINARY_SENSOR
]

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

def get_platforms(model: str) -> list[Platform]:
"""Return platforms for model."""
return PLATFORMS_SLR2 if model == MODEL_SLR2 else PLATFORMS_SLR1

async def async_setup(
hass: HomeAssistant, # pylint: disable=unused-argument
@@ -50,8 +58,9 @@ async def async_setup(
LOGGER.critical(msg)
return False

return True
setup_services(hass)

return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up this integration using UI."""
@@ -60,24 +69,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN][entry.entry_id] = {}
hass.data[DOMAIN][entry.entry_id][CONF_ENTITIES] = []

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await hass.config_entries.async_forward_entry_setups(
entry,
get_platforms(entry.options[CONF_MODEL]))

entry.async_on_unload(entry.add_update_listener(async_reload_entry))

@callback
async def mqtt_message_received(message: ReceiveMessage):
async def mqtt_message_received(message: ReceiveMessage) -> None:
"""Handle received MQTT message."""
topic = message.topic
payload = message.payload
LOGGER.debug("Received message: %s", topic)
LOGGER.debug(" Payload: %s", payload)
LOGGER.debug("Payload: %s", payload)

parsed_data = json.loads(payload)

if entry.entry_id not in hass.data[DOMAIN]:
return

for platform in PLATFORMS:
for platform in get_platforms(entry.options[CONF_MODEL]):
for entity in hass.data[DOMAIN][entry.entry_id][platform]:
entity.process_update(parsed_data)

@@ -87,12 +98,20 @@ async def mqtt_message_received(message: ReceiveMessage):
hass, topic, mqtt_message_received, 1
)

# Send an initial message to get the current state
await sleep(2)
payload = r'{"system_mode":""}'
LOGGER.debug("Sending to %s/get message %s", topic, payload)
await mqtt_client.async_publish(hass, topic + "/get", payload)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
if unloaded := await hass.config_entries.async_unload_platforms(
entry,
get_platforms(entry.options[CONF_MODEL])):
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded

@@ -107,4 +126,3 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update options."""
await hass.config_entries.async_reload(entry.entry_id)

Loading