├── .gitignore ├── .vscode └── settings.json ├── custom_components └── hive │ ├── climate.py │ ├── entity.py │ ├── light.py │ ├── switch.py │ ├── config_flow.py │ ├── binary_sensor.py │ ├── water_heater.py │ ├── manifest.json │ ├── icons.json │ ├── const.py │ ├── services.yaml │ ├── translations │ └── en.json │ ├── __init__.py │ ├── strings.json │ └── sensor.py ├── setup.cfg ├── hacs.json ├── .github ├── workflows │ ├── delete_beta.yml │ ├── hassfest.yaml │ └── stale.yml ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── SECURITY.md ├── info.md ├── LICENSE ├── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .venv -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black" 3 | } -------------------------------------------------------------------------------- /custom_components/hive/climate.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.climate import * -------------------------------------------------------------------------------- /custom_components/hive/entity.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.entity import * 2 | -------------------------------------------------------------------------------- /custom_components/hive/light.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.light import * 2 | -------------------------------------------------------------------------------- /custom_components/hive/switch.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.switch import * 2 | -------------------------------------------------------------------------------- /custom_components/hive/config_flow.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.config_flow import * -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | known_third_party = aiohttp,homeassistant,pyhiveapi,voluptuous 3 | -------------------------------------------------------------------------------- /custom_components/hive/binary_sensor.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.binary_sensor import * -------------------------------------------------------------------------------- /custom_components/hive/water_heater.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.hive.water_heater import * 2 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hive Custom Component", 3 | "render_readme": true, 4 | "content_in_root": false 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/delete_beta.yml: -------------------------------------------------------------------------------- 1 | name: Delete Beta Releases 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: dev-drprasad/delete-older-releases@v0.2.0 11 | with: 12 | keep_latest: 1 13 | delete_tags: true 14 | delete_tag_pattern: _b 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | validate-hacs: 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - uses: "actions/checkout@v3" 15 | - name: HACS validation 16 | uses: "hacs/action@main" 17 | with: 18 | category: "integration" 19 | ignore: "brands" 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull Request 3 | about: Create PR to update HA Custom Component 4 | title: "[PR]" 5 | labels: PR 6 | assignees: KJonline 7 | 8 | --- 9 | **Related Issues** 10 | PLease tag any related issues this PR fixes 11 | 12 | **Describe the change proposed** 13 | A clear and concise description of what the PR is. 14 | 15 | **Additional context** 16 | Add any other context about the problem here. 17 | -------------------------------------------------------------------------------- /custom_components/hive/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "hive", 3 | "name": "Hive", 4 | "config_flow": true, 5 | "documentation": "https://www.home-assistant.io/integrations/hive", 6 | "issue_tracker": "https://github.com/Pyhass/Hive-Custom-Component/issues", 7 | "version": "2025.11.2", 8 | "homekit": { 9 | "models": ["HHKBridge*"] 10 | }, 11 | "requirements": ["pyhive-integration==1.0.7"], 12 | "codeowners": ["@KJonline"], 13 | "iot_class": "cloud_polling", 14 | "loggers": ["apyhiveapi"] 15 | } 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We support the below versions of the Hive Custom Component. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | > 2025.1.x | :white_check_mark: | | 10 | | < 2025 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | To report a vulnerability, please raise a bug with a label of vulnerability. 15 | 16 | [**>> Report vulnerability here <<**](https://github.com/Pyhive/HA-Hive-Custom-Component/issues) 17 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # [Hive Custom Component](https://github.com/Rendili/hive-for-home-assistant) for Home Assistant 2 | 3 | This is a custom component to allow control of Hive devices in [Homeassistant](https://home-assistant.io)using the Hive website API. 4 | 5 | ## Hive Custom Component Supports 6 | 7 | - Hive Heating 8 | - Hive Hot Water 9 | - Hive Lights 10 | - Hive Plugs 11 | - Hive Sensors 12 | - Hive Hub 13 | - Hive Attributes 14 | 15 | ## Useful links 16 | 17 | - [General documentation](https://github.com/Rendili/hive-for-home-assistant/blob/master/README.md) 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: KJonline 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /custom_components/hive/icons.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity": { 3 | "binary_sensor": { 4 | "heating": { 5 | "default": "mdi:radiator" 6 | }, 7 | "hot_water": { 8 | "default": "mdi:hand-water" 9 | } 10 | }, 11 | "sensor": { 12 | "heating": { 13 | "default": "mdi:radiator" 14 | }, 15 | "hot_water": { 16 | "default": "mdi:hand-water" 17 | } 18 | } 19 | }, 20 | "services": { 21 | "boost_heating_on": { 22 | "service": "mdi:radiator" 23 | }, 24 | "boost_heating_off": { 25 | "service": "mdi:radiator-off" 26 | }, 27 | "boost_hot_water": { 28 | "service": "mdi:water-boiler" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: KJonline 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behaviour** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /custom_components/hive/const.py: -------------------------------------------------------------------------------- 1 | """Constants for Hive.""" 2 | from homeassistant.const import Platform 3 | 4 | ATTR_MODE = "mode" 5 | ATTR_TIME_PERIOD = "time_period" 6 | ATTR_ONOFF = "on_off" 7 | CONF_CODE = "2fa" 8 | CONF_DEVICE_NAME = "device_name" 9 | CONFIG_ENTRY_VERSION = 1 10 | DEFAULT_NAME = "Hive" 11 | DOMAIN = "hive" 12 | PLATFORMS = [ 13 | Platform.BINARY_SENSOR, 14 | Platform.CLIMATE, 15 | Platform.LIGHT, 16 | Platform.SENSOR, 17 | Platform.SWITCH, 18 | Platform.WATER_HEATER, 19 | ] 20 | PLATFORM_LOOKUP = { 21 | Platform.BINARY_SENSOR: "binary_sensor", 22 | Platform.CLIMATE: "climate", 23 | Platform.LIGHT: "light", 24 | Platform.SENSOR: "sensor", 25 | Platform.SWITCH: "switch", 26 | Platform.WATER_HEATER: "water_heater", 27 | } 28 | SERVICE_BOOST_HOT_WATER = "boost_hot_water" 29 | SERVICE_BOOST_HEATING_ON = "boost_heating_on" 30 | SERVICE_BOOST_HEATING_OFF = "boost_heating_off" 31 | WATER_HEATER_MODES = ["on", "off"] 32 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '29 5 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v5 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | stale-issue-message: 'There has been no activity on this Issue. Issue will be closed in 30 Days' 25 | stale-pr-message: 'There has been no activity on this PR. PR will be closed in 14 Days' 26 | stale-issue-label: 'no-issue-activity' 27 | stale-pr-label: 'no-pr-activity' 28 | days-before-close: 30 29 | days-before-pr-close: 14 30 | exempt-issue-labels: 'enhancement, question' 31 | -------------------------------------------------------------------------------- /custom_components/hive/services.yaml: -------------------------------------------------------------------------------- 1 | boost_heating_on: 2 | target: 3 | entity: 4 | integration: hive 5 | domain: climate 6 | fields: 7 | time_period: 8 | required: true 9 | example: 01:30:00 10 | selector: 11 | time: 12 | temperature: 13 | default: 25.0 14 | selector: 15 | number: 16 | min: 7 17 | max: 35 18 | step: 0.5 19 | unit_of_measurement: ° 20 | boost_heating_off: 21 | fields: 22 | entity_id: 23 | required: true 24 | selector: 25 | entity: 26 | integration: hive 27 | domain: climate 28 | boost_hot_water: 29 | fields: 30 | entity_id: 31 | required: true 32 | selector: 33 | entity: 34 | integration: hive 35 | domain: water_heater 36 | time_period: 37 | required: true 38 | example: 01:30:00 39 | selector: 40 | time: 41 | on_off: 42 | required: true 43 | selector: 44 | select: 45 | options: 46 | - "on" 47 | - "off" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pyhive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /custom_components/hive/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "Account is already configured", 5 | "reauth_successful": "Re-authentication was successful", 6 | "unknown_entry": "Unable to find existing entry." 7 | }, 8 | "error": { 9 | "invalid_code": "Failed to sign into Hive. Your two-factor authentication code was incorrect.", 10 | "invalid_password": "Failed to sign into Hive. Incorrect password please try again.", 11 | "invalid_username": "Failed to sign into Hive. Your email address is not recognised.", 12 | "no_internet_available": "An internet connection is required to connect to Hive.", 13 | "unknown": "Unexpected error" 14 | }, 15 | "step": { 16 | "2fa": { 17 | "data": { 18 | "2fa": "Two-factor code" 19 | }, 20 | "description": "Enter your Hive authentication code. \n \n Please enter code 0000 to request another code.", 21 | "title": "Hive Two-factor Authentication." 22 | }, 23 | "configuration": { 24 | "data": { 25 | "device_name": "Device Name" 26 | }, 27 | "description": "Enter your Hive configuration ", 28 | "title": "Hive Configuration." 29 | }, 30 | "reauth": { 31 | "data": { 32 | "password": "Password", 33 | "username": "Username" 34 | }, 35 | "description": "Re-enter your Hive login information.", 36 | "title": "Hive Login" 37 | }, 38 | "user": { 39 | "data": { 40 | "password": "Password", 41 | "scan_interval": "Scan Interval (seconds)", 42 | "username": "Username" 43 | }, 44 | "description": "Enter your Hive login information.", 45 | "title": "Hive Login" 46 | } 47 | } 48 | }, 49 | "options": { 50 | "step": { 51 | "user": { 52 | "data": { 53 | "scan_interval": "Scan Interval (seconds)" 54 | }, 55 | "description": "Update the scan interval to poll for data more often.", 56 | "title": "Options for Hive" 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hive Custom Component 2 | 3 | ![Pylint](https://github.com/Pyhive/HA-Hive-Custom-Component/workflows/Pylint/badge.svg) 4 | ![GitHub Release](https://img.shields.io/github/v/release/Pyhive/HA-Hive-Custom-Component?display_name=tag&logo=Github) 5 | 6 | 7 | 8 | This is a custom version of the Home Assistant Hive 9 | component. This version will setup the Home Assistant 10 | Hive component, but will also take device attributes 11 | within the Hive system and set them up as individual 12 | sensors e.g `Living Room Lamp Battery Level` 13 | 14 | * Additional Sensor types: 15 | * Availability - Online/Offline 16 | * Mode - Manual/Schedule 17 | * Battery Level 18 | * Current Temperature - Heating/Hotwater only 19 | * Target Temperature - Heating/Hotwater only 20 | * Current State - Heating/Hotwater only 21 | * Boost State - Heating/Hotwater only 22 | 23 | For details on what the Home Assistant Hive component supports, please see below: 24 | 25 | [**>> Hive Component Documentation <<**](https://www.home-assistant.io/integrations/hive/) 26 | 27 | Issues and trouble reports should be reported in 28 | the issues tab: 29 | 30 | [**>> Report issues here <<**](https://github.com/Pyhive/HA-Hive-Custom-Component/issues) 31 | 32 | ## Install 33 | 34 | There are two ways to install the Hive custom component see below. 35 | 36 | ### HACS 37 | 38 | The recommended way to is to install via [HACS](https://hacs.xyz/). 39 | Once HACS has been installed and setup. You just need to navigate to the HACS panel and choose integrations. 40 | This will show and option to add an integration click it and search for Hive in the presented popup. 41 | 42 | Once installation is complete please follow the [setup section](#setup) to set the Hive integration up. 43 | 44 | ### Manally 45 | 46 | To install the Hive integration manually you need to download the [latest version](https://github.com/Pyhive/HA-Hive-Custom-Component/releases/latest). 47 | Once downloaded you will need to copy the Hive folder into the custom_components folder within your home assistant configuration, if this does not exist then the folder will need creating. 48 | 49 | Once installation is complete please follow the [setup section](#setup) to set the Hive integration up. 50 | 51 | ## Setup 52 | 53 | ### UI 54 | 55 | The component can be setup in the Home assistant UI by doing the following actions. 56 | 57 | * 1 - Navigate to the Home Assistant [Configuration](https://my.home-assistant.io/redirect/config/). 58 | * 2 - Open the [integrations](https://my.home-assistant.io/redirect/integrations/) panel. 59 | * 3 - Click the add integrations option. 60 | * 4 - Search for [Hive](https://my.home-assistant.io/redirect/config_flow_start/?domain=hive) 61 | * 5 - Follow onboarding instructions to setup Hive. 62 | 63 | ## Options 64 | 65 | Once the integration is installed and configured with Home Assistant you will be able to 66 | change the below options from the Home Assistant integration page. 67 | 68 | * 1 - **Scan Interval** 69 | This determines how often the integration should communicate with Hive to retrieve new data. 70 | The default configuration is 120 seconds but can be reduced to as low as 30 seconds. 71 | 72 | ## Update 73 | 74 | Update instructions based on installation method. 75 | 76 | ### HACS Update 77 | 78 | Hacs will auto notify you within the HACS panel that there is a pending update. 79 | 80 | ### Manual Update 81 | 82 | To update to the next version download the [latest version](https://github.com/Pyhive/HA-Hive-Custom-Component/releases/latest) again 83 | and replace the current hive folder with the newly downloaded one. 84 | 85 | 86 | :warning: **Setting up this custom version will overwrite the default integration.** 87 | -------------------------------------------------------------------------------- /custom_components/hive/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for the Hive devices and services.""" 2 | 3 | from __future__ import annotations 4 | 5 | from collections.abc import Awaitable, Callable, Coroutine 6 | from functools import wraps 7 | import logging 8 | from typing import Any, Concatenate 9 | 10 | from aiohttp.web_exceptions import HTTPException 11 | from apyhiveapi import Auth, Hive 12 | from apyhiveapi.helper.hive_exceptions import HiveReauthRequired 13 | 14 | from homeassistant.config_entries import ConfigEntry 15 | from homeassistant.const import CONF_SCAN_INTERVAL 16 | from homeassistant.core import HomeAssistant 17 | from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady 18 | from homeassistant.helpers import aiohttp_client, device_registry as dr 19 | from homeassistant.helpers.dispatcher import async_dispatcher_send 20 | 21 | from .const import DOMAIN, PLATFORM_LOOKUP, PLATFORMS 22 | from .entity import HiveEntity 23 | 24 | _LOGGER = logging.getLogger(__name__) 25 | 26 | type HiveConfigEntry = ConfigEntry[Hive] 27 | 28 | 29 | async def async_setup_entry(hass: HomeAssistant, entry: HiveConfigEntry) -> bool: 30 | """Set up Hive from a config entry.""" 31 | web_session = aiohttp_client.async_get_clientsession(hass) 32 | hive_config = dict(entry.data) 33 | hive = Hive(web_session) 34 | 35 | hive_config["options"] = {} 36 | hive_config["options"].update( 37 | {CONF_SCAN_INTERVAL: dict(entry.options).get(CONF_SCAN_INTERVAL, 120)} 38 | ) 39 | entry.runtime_data = hive 40 | 41 | try: 42 | devices = await hive.session.startSession(hive_config) 43 | except HTTPException as error: 44 | _LOGGER.error("Could not connect to the internet: %s", error) 45 | raise ConfigEntryNotReady from error 46 | except HiveReauthRequired as err: 47 | raise ConfigEntryAuthFailed from err 48 | 49 | device_registry = dr.async_get(hass) 50 | device_registry.async_get_or_create( 51 | config_entry_id=entry.entry_id, 52 | identifiers={(DOMAIN, devices["parent"][0]["device_id"])}, 53 | name=devices["parent"][0]["hiveName"], 54 | model=devices["parent"][0]["deviceData"]["model"], 55 | sw_version=devices["parent"][0]["deviceData"]["version"], 56 | manufacturer=devices["parent"][0]["deviceData"]["manufacturer"], 57 | ) 58 | 59 | await hass.config_entries.async_forward_entry_setups( 60 | entry, 61 | [ 62 | ha_type 63 | for ha_type, hive_type in PLATFORM_LOOKUP.items() 64 | if devices.get(hive_type) 65 | ], 66 | ) 67 | 68 | return True 69 | 70 | 71 | async def async_unload_entry(hass: HomeAssistant, entry: HiveConfigEntry) -> bool: 72 | """Unload a config entry.""" 73 | return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 74 | 75 | 76 | async def async_remove_entry(hass: HomeAssistant, entry: HiveConfigEntry) -> None: 77 | """Remove a config entry.""" 78 | hive = Auth(entry.data["username"], entry.data["password"]) 79 | await hive.forget_device( 80 | entry.data["tokens"]["AuthenticationResult"]["AccessToken"], 81 | entry.data["device_data"][1], 82 | ) 83 | 84 | 85 | async def async_remove_config_entry_device( 86 | hass: HomeAssistant, config_entry: HiveConfigEntry, device_entry: dr.DeviceEntry 87 | ) -> bool: 88 | """Remove a config entry from a device.""" 89 | return True 90 | 91 | 92 | def refresh_system[_HiveEntityT: HiveEntity, **_P]( 93 | func: Callable[Concatenate[_HiveEntityT, _P], Awaitable[Any]], 94 | ) -> Callable[Concatenate[_HiveEntityT, _P], Coroutine[Any, Any, None]]: 95 | """Force update all entities after state change.""" 96 | 97 | @wraps(func) 98 | async def wrapper(self: _HiveEntityT, *args: _P.args, **kwargs: _P.kwargs) -> None: 99 | await func(self, *args, **kwargs) 100 | async_dispatcher_send(self.hass, DOMAIN) 101 | 102 | return wrapper 103 | -------------------------------------------------------------------------------- /custom_components/hive/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "title": "Hive login", 6 | "description": "Enter your Hive login information.", 7 | "data": { 8 | "username": "[%key:common::config_flow::data::username%]", 9 | "password": "[%key:common::config_flow::data::password%]", 10 | "scan_interval": "Scan interval (seconds)" 11 | } 12 | }, 13 | "2fa": { 14 | "title": "Hive two-factor authentication", 15 | "description": "Enter your Hive authentication code.\n\nPlease enter code 0000 to request another code.", 16 | "data": { 17 | "2fa": "Two-factor code" 18 | } 19 | }, 20 | "configuration": { 21 | "title": "Hive configuration", 22 | "description": "Enter your Hive configuration.", 23 | "data": { 24 | "device_name": "Device name" 25 | } 26 | }, 27 | "reauth": { 28 | "title": "[%key:component::hive::config::step::user::title%]", 29 | "description": "Re-enter your Hive login information.", 30 | "data": { 31 | "username": "[%key:common::config_flow::data::username%]", 32 | "password": "[%key:common::config_flow::data::password%]" 33 | } 34 | } 35 | }, 36 | "error": { 37 | "invalid_username": "Failed to sign in to Hive. Your email address is not recognized.", 38 | "invalid_password": "Failed to sign in to Hive. Incorrect password, please try again.", 39 | "invalid_code": "Failed to sign in to Hive. Your two-factor authentication code was incorrect.", 40 | "no_internet_available": "An Internet connection is required to connect to Hive.", 41 | "unknown": "[%key:common::config_flow::error::unknown%]" 42 | }, 43 | "abort": { 44 | "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", 45 | "unknown_entry": "Unable to find existing entry.", 46 | "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" 47 | } 48 | }, 49 | "options": { 50 | "step": { 51 | "user": { 52 | "title": "Options for Hive", 53 | "description": "Update the scan interval to poll for data more often.", 54 | "data": { 55 | "scan_interval": "Scan interval (seconds)" 56 | } 57 | } 58 | } 59 | }, 60 | "services": { 61 | "boost_heating_on": { 62 | "name": "Boost heating on", 63 | "description": "Sets the boost mode ON, defining the period of time and the desired target temperature for the boost.", 64 | "fields": { 65 | "time_period": { 66 | "name": "[%key:component::hive::services::boost_hot_water::fields::time_period::name%]", 67 | "description": "[%key:component::hive::services::boost_hot_water::fields::time_period::description%]" 68 | }, 69 | "temperature": { 70 | "name": "Temperature", 71 | "description": "The target temperature for the boost period." 72 | } 73 | } 74 | }, 75 | "boost_heating_off": { 76 | "name": "Boost heating off", 77 | "description": "Sets the boost mode OFF.", 78 | "fields": { 79 | "entity_id": { 80 | "name": "Entity ID", 81 | "description": "The entity ID to turn boost off." 82 | } 83 | } 84 | }, 85 | "boost_hot_water": { 86 | "name": "Boost hotwater", 87 | "description": "Sets the boost mode ON or OFF, defining the period of time for the boost.", 88 | "fields": { 89 | "entity_id": { 90 | "name": "Entity ID", 91 | "description": "The entity ID to boost." 92 | }, 93 | "time_period": { 94 | "name": "Time period", 95 | "description": "The time period for the boost." 96 | }, 97 | "on_off": { 98 | "name": "[%key:common::config_flow::data::mode%]", 99 | "description": "Set the boost function on or off." 100 | } 101 | } 102 | } 103 | }, 104 | "entity": { 105 | "sensor": { 106 | "heating": { 107 | "state": { 108 | "manual": "[%key:common::state::manual%]", 109 | "off": "[%key:common::state::off%]", 110 | "schedule": "Schedule" 111 | } 112 | }, 113 | "hot_water": { 114 | "state": { 115 | "on": "[%key:common::state::on%]", 116 | "off": "[%key:common::state::off%]", 117 | "schedule": "[%key:component::hive::entity::sensor::heating::state::schedule%]" 118 | } 119 | } 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /custom_components/hive/sensor.py: -------------------------------------------------------------------------------- 1 | """Support for the Hive sensors.""" 2 | 3 | from datetime import timedelta 4 | 5 | from homeassistant.components.sensor import ( 6 | SensorDeviceClass, 7 | SensorEntity, 8 | SensorEntityDescription, 9 | SensorStateClass, 10 | ) 11 | from homeassistant.const import ( 12 | PERCENTAGE, 13 | EntityCategory, 14 | UnitOfPower, 15 | UnitOfTemperature, 16 | ) 17 | from homeassistant.core import HomeAssistant 18 | from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback 19 | 20 | from . import HiveConfigEntry 21 | from .entity import HiveEntity 22 | 23 | PARALLEL_UPDATES = 0 24 | SCAN_INTERVAL = timedelta(seconds=15) 25 | 26 | SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( 27 | SensorEntityDescription( 28 | key="Battery", 29 | native_unit_of_measurement=PERCENTAGE, 30 | device_class=SensorDeviceClass.BATTERY, 31 | entity_category=EntityCategory.DIAGNOSTIC, 32 | ), 33 | SensorEntityDescription( 34 | key="Power", 35 | native_unit_of_measurement=UnitOfPower.WATT, 36 | state_class=SensorStateClass.MEASUREMENT, 37 | device_class=SensorDeviceClass.POWER, 38 | entity_category=EntityCategory.DIAGNOSTIC, 39 | ), 40 | SensorEntityDescription( 41 | key="Heating_Current_Temperature", 42 | device_class=SensorDeviceClass.TEMPERATURE, 43 | state_class=SensorStateClass.MEASUREMENT, 44 | native_unit_of_measurement=UnitOfTemperature.CELSIUS, 45 | icon="mdi:thermometer", 46 | ), 47 | SensorEntityDescription( 48 | key="Heating_Target_Temperature", 49 | device_class=SensorDeviceClass.TEMPERATURE, 50 | state_class=SensorStateClass.MEASUREMENT, 51 | native_unit_of_measurement=UnitOfTemperature.CELSIUS, 52 | icon="mdi:thermometer", 53 | ), 54 | SensorEntityDescription( 55 | key="Heating_State", 56 | icon="mdi:radiator", 57 | ), 58 | SensorEntityDescription( 59 | key="Heating_Mode", 60 | icon="mdi:radiator", 61 | ), 62 | SensorEntityDescription( 63 | key="Heating_Boost", 64 | icon="mdi:radiator", 65 | ), 66 | SensorEntityDescription( 67 | key="Hotwater_State", 68 | icon="mdi:water-pump", 69 | ), 70 | SensorEntityDescription( 71 | key="Hotwater_Mode", 72 | icon="mdi:water-pump", 73 | ), 74 | SensorEntityDescription( 75 | key="Hotwater_Boost", 76 | icon="mdi:water-pump", 77 | ), 78 | SensorEntityDescription( 79 | key="Mode", 80 | icon="mdi:eye", 81 | ), 82 | SensorEntityDescription(key="Availability", icon="mdi:check-circle"), 83 | ) 84 | 85 | 86 | async def async_setup_entry( 87 | hass: HomeAssistant, 88 | entry: HiveConfigEntry, 89 | async_add_entities: AddConfigEntryEntitiesCallback, 90 | ) -> None: 91 | """Set up Hive thermostat based on a config entry.""" 92 | hive = entry.runtime_data 93 | devices = hive.session.deviceList.get("sensor") 94 | if not devices: 95 | return 96 | async_add_entities( 97 | ( 98 | HiveSensorEntity(hive, dev, description) 99 | for dev in devices 100 | for description in SENSOR_TYPES 101 | if dev["hiveType"] == description.key 102 | ), 103 | True, 104 | ) 105 | 106 | 107 | class HiveSensorEntity(HiveEntity, SensorEntity): 108 | """Hive Sensor Entity.""" 109 | 110 | def __init__(self, hive, hive_device, entity_description): 111 | """Initialise hive sensor.""" 112 | super().__init__(hive, hive_device) 113 | self.entity_description = entity_description 114 | 115 | async def async_update(self): 116 | """Update all Node data from Hive.""" 117 | await self.hive.session.updateData(self.device) 118 | self.device = await self.hive.sensor.getSensor(self.device) 119 | 120 | if self.device["hiveType"] == "CurrentTemperature": 121 | self._attr_extra_state_attributes = await self.get_current_temp_sa() 122 | elif self.device["hiveType"] == "Heating_State": 123 | self._attr_extra_state_attributes = await self.get_heating_state_sa() 124 | elif self.device["hiveType"] == "Heating_Mode": 125 | self._attr_extra_state_attributes = await self.get_heating_state_sa() 126 | elif self.device["hiveType"] == "Heating_Boost": 127 | s_a = {} 128 | if await self.hive.heating.getBoostStatus(self.device) == "ON": 129 | minsend = await self.hive.heating.getBoostTime(self.device) 130 | s_a.update({"Boost ends in": (str(minsend) + " minutes")}) 131 | self._attr_extra_state_attributes = s_a 132 | elif self.device["hiveType"] == "Hotwater_State": 133 | self._attr_extra_state_attributes = await self.get_hotwater_state_sa() 134 | elif self.device["hiveType"] == "Hotwater_Mode": 135 | self._attr_extra_state_attributes = await self.get_hotwater_state_sa() 136 | elif self.device["hiveType"] == "Hotwater_Boost": 137 | s_a = {} 138 | if await self.hive.hotwater.getBoost(self.device) == "ON": 139 | endsin = await self.hive.hotwater.getBoostTime(self.device) 140 | s_a.update({"Boost ends in": (str(endsin) + " minutes")}) 141 | self._attr_extra_state_attributes = s_a 142 | 143 | if self.device["hiveType"] not in ("sense", "Availability"): 144 | self._attr_available = self.device.get("deviceData", {}).get("online", True) 145 | else: 146 | self._attr_available = True 147 | 148 | if self._attr_available: 149 | self._attr_native_value = self.device["status"]["state"] 150 | 151 | async def get_current_temp_sa(self): 152 | """Get current heating temperature state attributes.""" 153 | s_a = {} 154 | temp_current = 0 155 | temperature_target = 0 156 | temperature_difference = 0 157 | 158 | minmax_temps = await self.hive.heating.minmaxTemperature(self.device) 159 | if minmax_temps is not None: 160 | s_a.update( 161 | { 162 | "Today Min / Max": str(minmax_temps["TodayMin"]) 163 | + " °C" 164 | + " / " 165 | + str(minmax_temps["TodayMax"]) 166 | + " °C" 167 | } 168 | ) 169 | 170 | s_a.update( 171 | { 172 | "Restart Min / Max": str(minmax_temps["RestartMin"]) 173 | + " °C" 174 | + " / " 175 | + str(minmax_temps["RestartMax"]) 176 | + " °C" 177 | } 178 | ) 179 | 180 | temp_current = await self.hive.heating.currentTemperature(self.device) 181 | temperature_target = await self.hive.heating.targetTemperature(self.device) 182 | 183 | if temperature_target > temp_current: 184 | temperature_difference = temperature_target - temp_current 185 | temperature_difference = round(temperature_difference, 2) 186 | 187 | s_a.update({"Current Temperature": temp_current}) 188 | s_a.update({"Target Temperature": temperature_target}) 189 | s_a.update({"Temperature Difference": temperature_difference}) 190 | 191 | return s_a 192 | 193 | async def get_heating_state_sa(self): 194 | """Get current heating state, state attributes.""" 195 | s_a = {} 196 | 197 | snan = await self.hive.heating.getScheduleNowNextLater(self.device) 198 | if snan is not None: 199 | if "now" in snan: 200 | if ( 201 | "value" in snan["now"] 202 | and "start" in snan["now"] 203 | and "Start_DateTime" in snan["now"] 204 | and "End_DateTime" in snan["now"] 205 | and "target" in snan["now"]["value"] 206 | ): 207 | now_target = str(snan["now"]["value"]["target"]) + " °C" 208 | nstrt = snan["now"]["Start_DateTime"].strftime("%H:%M") 209 | now_end = snan["now"]["End_DateTime"].strftime("%H:%M") 210 | 211 | sa_string = now_target + " : " + nstrt + " - " + now_end 212 | s_a.update({"Now": sa_string}) 213 | 214 | if "next" in snan: 215 | if ( 216 | "value" in snan["next"] 217 | and "start" in snan["next"] 218 | and "Start_DateTime" in snan["next"] 219 | and "End_DateTime" in snan["next"] 220 | and "target" in snan["next"]["value"] 221 | ): 222 | next_target = str(snan["next"]["value"]["target"]) + " °C" 223 | nxtstrt = snan["next"]["Start_DateTime"].strftime("%H:%M") 224 | next_end = snan["next"]["End_DateTime"].strftime("%H:%M") 225 | 226 | sa_string = next_target + " : " + nxtstrt + " - " + next_end 227 | s_a.update({"Next": sa_string}) 228 | 229 | if "later" in snan: 230 | if ( 231 | "value" in snan["later"] 232 | and "start" in snan["later"] 233 | and "Start_DateTime" in snan["later"] 234 | and "End_DateTime" in snan["later"] 235 | and "target" in snan["later"]["value"] 236 | ): 237 | ltarg = str(snan["later"]["value"]["target"]) + " °C" 238 | lstrt = snan["later"]["Start_DateTime"].strftime("%H:%M") 239 | lend = snan["later"]["End_DateTime"].strftime("%H:%M") 240 | 241 | sa_string = ltarg + " : " + lstrt + " - " + lend 242 | s_a.update({"Later": sa_string}) 243 | else: 244 | s_a.update({"Schedule not active": ""}) 245 | 246 | return s_a 247 | 248 | async def get_hotwater_state_sa(self): 249 | """Get current hotwater state, state attributes.""" 250 | s_a = {} 251 | 252 | snan = await self.hive.hotwater.getScheduleNowNextLater(self.device) 253 | if snan is not None: 254 | if "now" in snan: 255 | if ( 256 | "value" in snan["now"] 257 | and "start" in snan["now"] 258 | and "Start_DateTime" in snan["now"] 259 | and "End_DateTime" in snan["now"] 260 | and "status" in snan["now"]["value"] 261 | ): 262 | now_status = snan["now"]["value"]["status"] 263 | now_start = snan["now"]["Start_DateTime"].strftime("%H:%M") 264 | now_end = snan["now"]["End_DateTime"].strftime("%H:%M") 265 | 266 | sa_string = now_status + " : " + now_start + " - " + now_end 267 | s_a.update({"Now": sa_string}) 268 | 269 | if "next" in snan: 270 | if ( 271 | "value" in snan["next"] 272 | and "start" in snan["next"] 273 | and "Start_DateTime" in snan["next"] 274 | and "End_DateTime" in snan["next"] 275 | and "status" in snan["next"]["value"] 276 | ): 277 | next_status = snan["next"]["value"]["status"] 278 | nxtstrt = snan["next"]["Start_DateTime"].strftime("%H:%M") 279 | next_end = snan["next"]["End_DateTime"].strftime("%H:%M") 280 | 281 | sa_string = next_status + " : " + nxtstrt + " - " + next_end 282 | s_a.update({"Next": sa_string}) 283 | if "later" in snan: 284 | if ( 285 | "value" in snan["later"] 286 | and "start" in snan["later"] 287 | and "Start_DateTime" in snan["later"] 288 | and "End_DateTime" in snan["later"] 289 | and "status" in snan["later"]["value"] 290 | ): 291 | later_status = snan["later"]["value"]["status"] 292 | later_start = snan["later"]["Start_DateTime"].strftime("%H:%M") 293 | later_end = snan["later"]["End_DateTime"].strftime("%H:%M") 294 | 295 | sa_string = later_status + " : " + later_start + " - " + later_end 296 | s_a.update({"Later": sa_string}) 297 | else: 298 | s_a.update({"Schedule not active": ""}) 299 | 300 | return s_a 301 | --------------------------------------------------------------------------------