├── .gitignore ├── hacs.json ├── custom_components └── rika_firenet │ ├── manifest.json │ ├── translations │ ├── sensor.en.json │ ├── sensor.nl.json │ ├── sensor.de.json │ ├── sensor.fr.json │ ├── en.json │ ├── nl.json │ ├── de.json │ └── fr.json │ ├── const.py │ ├── entity.py │ ├── switch.py │ ├── __init__.py │ ├── climate.py │ ├── sensor.py │ ├── config_flow.py │ ├── number.py │ └── core.py ├── info.md ├── LICENSE ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea 3 | .DS_store 4 | *.iml 5 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rika Firenet", 3 | "hacs": "0.24.0", 4 | "domains": [ 5 | "climate", 6 | "sensor" 7 | ], 8 | "iot_class": "Cloud Polling", 9 | "homeassistant": "0.115.0" 10 | } 11 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "rika_firenet", 3 | "name": "Rika Firenet", 4 | "documentation": "", 5 | "version": "0.0.1", 6 | "dependencies": [], 7 | "codeowners": [], 8 | "config_flow": true, 9 | "requirements": [ 10 | "bs4", 11 | "requests" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/sensor.en.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": { 3 | "_": { 4 | "frost_protection": "Frost protection", 5 | "stove_off": "Stove off", 6 | "standby": "Standby", 7 | "external_request": "External request", 8 | "sub_state_unknown": "Substate unknown", 9 | "ignition_on": "Ignition on", 10 | "starting_up": "Starting up", 11 | "running": "Running", 12 | "big_clean": "Big clean", 13 | "clean": "Clean", 14 | "burn_off": "Burn off", 15 | "split_log_check": "Split log check", 16 | "split_log_mode": "Split log mode", 17 | "unknown": "Unknown" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/sensor.nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": { 3 | "_": { 4 | "frost_protection": "Vorst beveiliging", 5 | "stove_off": "Kachel uit", 6 | "standby": "Standby", 7 | "external_request": "Extern contact", 8 | "sub_state_unknown": "Substate onbekend", 9 | "ignition_on": "Ontsteken", 10 | "starting_up": "Opstarten", 11 | "running": "Pelletmodule", 12 | "big_clean": "Grote Reiniging", 13 | "clean": "Reiniging", 14 | "burn_off": "Uitdoven", 15 | "split_log_check": "Hout check", 16 | "split_log_mode": "Regeling Hout", 17 | "unknown": "Onbekend" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/sensor.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": { 3 | "_": { 4 | "frost_protection": "Frostschutz", 5 | "stove_off": "Ofen AUS", 6 | "standby": "Standby", 7 | "external_request": "Externe Anforderung", 8 | "sub_state_unknown": "Substate unbekannt", 9 | "ignition_on": "Zündung EIN", 10 | "starting_up": "Startphase", 11 | "running": "Backen", 12 | "big_clean": "Gr. Reinigung", 13 | "clean": "Reinigung", 14 | "burn_off": "Ausbrand", 15 | "split_log_check": "Scheitholzcheck", 16 | "split_log_mode": "Scheitholzbetrieb", 17 | "unknown": "Unbekanntes Problem" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/sensor.fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": { 3 | "_": { 4 | "frost_protection": "Mode hors gel", 5 | "stove_off": "Poêle éteint", 6 | "standby": "En veille", 7 | "external_request": "Demande externe", 8 | "sub_state_unknown": "Substate inconnu", 9 | "ignition_on": "Allumage", 10 | "starting_up": "Démarrage", 11 | "running": "Baking", 12 | "big_clean": "Nettoyage approfondi", 13 | "clean": "Nettoyage", 14 | "burn_off": "Fin de combustion", 15 | "split_log_check": "Contrôle de la présence du bois", 16 | "split_log_mode": "Mode bois", 17 | "unknown": "Inconnu" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | [![GitHub Release][releases-shield]][releases] 2 | [![GitHub Activity][commits-shield]][commits] 3 | [![Community Forum][forum-shield]][forum] 4 | 5 | _Component to integrate with [blueprint][blueprint]._ 6 | 7 | **This component will set up the following platforms.** 8 | 9 | Platform | Description 10 | -- | -- 11 | `climate` | ... 12 | `sensor` | ... 13 | 14 | {% if not installed %} 15 | ## Installation 16 | 17 | 1. Click install. 18 | 1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Rika Firenet". 19 | 20 | {% endif %} 21 | 22 | ## Configuration is done in the UI 23 | 24 | 25 | *** 26 | 27 | [rika_firenet]: https://github.com/Fockaert/rika-firenet-custom-component 28 | [commits]: https://github.com/Fockaert/rika-firenet-custom-component/commits/main 29 | [forum]: https://community.home-assistant.io/ 30 | [releases]: https://github.com/Fockaert/rika-firenet-custom-component/releases 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jan Fockaert 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/rika_firenet/const.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.climate.const import (PRESET_AWAY, 2 | PRESET_HOME) 3 | 4 | # Configuration 5 | CONF_ENABLED = "enabled" 6 | CONF_USERNAME = "username" 7 | CONF_PASSWORD = "password" 8 | CONF_DEFAULT_TEMPERATURE = "defaultTemperature" 9 | DATA = "data" 10 | UPDATE_TRACK = "update_track" 11 | 12 | # Platforms 13 | CLIMATE = "climate" 14 | SENSOR = "sensor" 15 | SWITCH = "switch" 16 | NUMBER = "number" 17 | PLATFORMS = [CLIMATE, SENSOR, SWITCH, NUMBER] 18 | 19 | # Types 20 | SUPPORT_PRESET = [PRESET_AWAY, PRESET_HOME] 21 | 22 | VERSION = "0.0.1" 23 | DOMAIN = "rika_firenet" 24 | 25 | UNIQUE_ID = "unique_id" 26 | 27 | DEFAULT_NAME = "Rika" 28 | NAME = "Rika Firenet" 29 | 30 | UPDATE_LISTENER = "update_listener" 31 | ISSUE_URL = "https://github.com/fockaert/rika-firenet-custom-component/issues" 32 | 33 | # Defaults 34 | STARTUP_MESSAGE = f""" 35 | ------------------------------------------------------------------- 36 | {NAME} 37 | Version: {VERSION} 38 | This is a custom integration! 39 | If you have any issues with this you need to open an issue here: 40 | {ISSUE_URL} 41 | ------------------------------------------------------------------- 42 | """ 43 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/entity.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 3 | 4 | from .const import DOMAIN, NAME, DEFAULT_NAME, VERSION 5 | from .core import RikaFirenetStove, RikaFirenetCoordinator 6 | 7 | _LOGGER = logging.getLogger(__name__) 8 | 9 | 10 | class RikaFirenetEntity(CoordinatorEntity): 11 | def __init__(self, config_entry, stove: RikaFirenetStove, coordinator: RikaFirenetCoordinator, suffix=None): 12 | super().__init__(coordinator) 13 | 14 | self._config_entry = config_entry 15 | self._stove = stove 16 | 17 | if suffix is not None: 18 | self._name = f"{stove.get_name()} {suffix}" 19 | self._unique_id = f"{suffix} {stove.get_name()}" 20 | else: 21 | self._name = stove.get_name() 22 | self._unique_id = stove.get_id() 23 | 24 | _LOGGER.info('RikaFirenetEntity creation with name: ' + self._name + ' unique_id: ' + self._unique_id) 25 | 26 | @property 27 | def unique_id(self): 28 | return self._unique_id 29 | 30 | @property 31 | def name(self): 32 | return self._name 33 | 34 | @property 35 | def device_info(self): 36 | return { 37 | "identifiers": {(DOMAIN, self.unique_id)}, 38 | "name": NAME, 39 | "model": VERSION, 40 | "manufacturer": DEFAULT_NAME, 41 | } 42 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "title": "Rika Firenet", 4 | "step": { 5 | "user": { 6 | "title": "Rika Firenet", 7 | "description": "Setup integration with Rika Firenet", 8 | "data": { 9 | "username": "Username", 10 | "password": "Password" 11 | } 12 | } 13 | }, 14 | "error": { 15 | "auth": "Username/Password is wrong." 16 | }, 17 | "abort": { 18 | "single_instance_allowed": "Only a single configuration of Rika Firenet is allowed." 19 | } 20 | }, 21 | "options": { 22 | "step": { 23 | "user": { 24 | "data": { 25 | "defaultTemperature": "Default temperature", 26 | "climate": "Climate enabled", 27 | "sensor": "Sensor enabled", 28 | "switch": "Switch enabled", 29 | "number": "Number enabled" 30 | } 31 | } 32 | } 33 | }, 34 | "state": { 35 | "_": { 36 | "frost_protection": "Frost protection", 37 | "stove_off": "Stove off", 38 | "standby": "Standby", 39 | "external_request": "External request", 40 | "sub_state_unknown": "Substate unknown", 41 | "ignition_on": "Ignition on", 42 | "starting_up": "Starting up", 43 | "running": "Running", 44 | "big_clean": "Big clean", 45 | "clean": "Clean", 46 | "burn_off": "Burn off", 47 | "split_log_check": "Split log check", 48 | "split_log_mode": "Split log mode", 49 | "unknown": "Unknown" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "title": "Rika Firenet", 4 | "step": { 5 | "user": { 6 | "title": "Rika Firenet", 7 | "description": "Setup integration with Rika Firenet", 8 | "data": { 9 | "username": "Username", 10 | "password": "Password" 11 | } 12 | } 13 | }, 14 | "error": { 15 | "auth": "Username/Password is wrong." 16 | }, 17 | "abort": { 18 | "single_instance_allowed": "Only a single configuration of Rika Firenet is allowed." 19 | } 20 | }, 21 | "options": { 22 | "step": { 23 | "user": { 24 | "data": { 25 | "defaultTemperature": "Default temperature", 26 | "climate": "Climate enabled", 27 | "sensor": "Sensor enabled", 28 | "switch": "Switch enabled", 29 | "number": "Number enabled" 30 | } 31 | } 32 | } 33 | }, 34 | "state": { 35 | "_": { 36 | "frost_protection": "Vorst beveiliging", 37 | "stove_off": "Kachel uit", 38 | "standby": "Standby", 39 | "external_request": "Extern contact", 40 | "sub_state_unknown": "Substate onbekend", 41 | "ignition_on": "Ontsteken", 42 | "starting_up": "Opstarten", 43 | "running": "Pelletmodule", 44 | "big_clean": "Grote Reiniging", 45 | "clean": "Reiniging", 46 | "burn_off": "Uitdoven", 47 | "split_log_check": "Hout check", 48 | "split_log_mode": "Regeling Hout", 49 | "unknown": "Onbekend" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "title": "Rika Firenet", 4 | "step": { 5 | "user": { 6 | "title": "Rika Firenet", 7 | "description": "Setup integration with Rika Firenet", 8 | "data": { 9 | "username": "Username", 10 | "password": "Password" 11 | } 12 | } 13 | }, 14 | "error": { 15 | "auth": "Username/Password is wrong." 16 | }, 17 | "abort": { 18 | "single_instance_allowed": "Only a single configuration of Rika Firenet is allowed." 19 | } 20 | }, 21 | "options": { 22 | "step": { 23 | "user": { 24 | "data": { 25 | "defaultTemperature": "Default temperature", 26 | "climate": "Climate enabled", 27 | "sensor": "Sensor enabled", 28 | "switch": "Switch enabled", 29 | "number": "Number enabled" 30 | } 31 | } 32 | } 33 | }, 34 | "state": { 35 | "_": { 36 | "frost_protection": "Frostschutz", 37 | "stove_off": "Ofen AUS", 38 | "standby": "Standby", 39 | "external_request": "Externe Anforderung", 40 | "sub_state_unknown": "Substate unbekannt", 41 | "ignition_on": "Zündung EIN", 42 | "starting_up": "Startphase", 43 | "running": "Backen", 44 | "big_clean": "Gr. Reinigung", 45 | "clean": "Reinigung", 46 | "burn_off": "Ausbrand", 47 | "split_log_check": "Scheitholzcheck", 48 | "split_log_mode": "Scheitholzbetrieb", 49 | "unknown": "Unbekanntes Problem" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /custom_components/rika_firenet/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "title": "Rika Firenet", 4 | "step": { 5 | "user": { 6 | "title": "Rika Firenet", 7 | "description": "Setup integration with Rika Firenet", 8 | "data": { 9 | "username": "Username", 10 | "password": "Password" 11 | } 12 | } 13 | }, 14 | "error": { 15 | "auth": "Username/Password is wrong." 16 | }, 17 | "abort": { 18 | "single_instance_allowed": "Only a single configuration of Rika Firenet is allowed." 19 | } 20 | }, 21 | "options": { 22 | "step": { 23 | "user": { 24 | "data": { 25 | "defaultTemperature": "Default temperature", 26 | "climate": "Climate enabled", 27 | "sensor": "Sensor enabled", 28 | "switch": "Switch enabled", 29 | "number": "Number enabled" 30 | } 31 | } 32 | } 33 | }, 34 | "state": { 35 | "_": { 36 | "frost_protection": "Mode hors gel", 37 | "stove_off": "Poêle éteint", 38 | "standby": "En veille", 39 | "external_request": "Demande externe", 40 | "sub_state_unknown": "Substate inconnu", 41 | "ignition_on": "Allumage", 42 | "starting_up": "Démarrage", 43 | "running": "Baking", 44 | "big_clean": "Nettoyage approfondi", 45 | "clean": "Nettoyage", 46 | "burn_off": "Fin de combustion", 47 | "split_log_check": "Contrôle de la présence du bois", 48 | "split_log_mode": "Mode bois", 49 | "unknown": "Inconnu" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Contributing to this project should be as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | 10 | ## Github is used for everything 11 | 12 | Github is used to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | Pull requests are the best way to propose changes to the codebase. 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 2. If you've changed something, update the documentation. 18 | 3. Make sure your code lints (using black). 19 | 4. Test you contribution. 20 | 5. Issue that pull request! 21 | 22 | ## Any contributions you make will be under the MIT Software License 23 | 24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's [issues](../../issues) 27 | 28 | GitHub issues are used to track public bugs. 29 | Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! 30 | 31 | ## Write bug reports with detail, background, and sample code 32 | 33 | **Great Bug Reports** tend to have: 34 | 35 | - A quick summary and/or background 36 | - Steps to reproduce 37 | - Be specific! 38 | - Give sample code if you can. 39 | - What you expected would happen 40 | - What actually happens 41 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 42 | 43 | People *love* thorough bug reports. I'm not even kidding. 44 | 45 | ## Use a Consistent Coding Style 46 | 47 | Use [black](https://github.com/ambv/black) to make sure the code follows the style. 48 | 49 | ## Test your code modification 50 | 51 | This custom component is based on [blueprint template](https://github.com/custom-components/blueprint). 52 | 53 | It comes with development environment in a container, easy to launch 54 | if you use Visual Studio Code. With this container you will have a stand alone 55 | Home Assistant instance running and already configured with the included 56 | [`.devcontainer/configuration.yaml`](https://github.com/oncleben31/ha-pool_pump/blob/master/.devcontainer/configuration.yaml) 57 | file. 58 | 59 | ## License 60 | 61 | By contributing, you agree that your contributions will be licensed under its MIT License. 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rika Firenet 2 | 3 | _Component to integrate with Rika Firenet [rikafirenet]._ 4 | 5 | **This component will set up the following platforms.** 6 | 7 | Platform | Description 8 | -- | -- 9 | `climate` | ... 10 | `sensor` | ... 11 | 12 | ## Planning 13 | * Add readme example graphs possible 14 | * Get the config flow working with update and platform selections 15 | * Support preset mode (in comment atm) 16 | * Support smart target temperature. e.g. Show base temperature when active 17 | * Support Rika stove without external thermostat (only tested with external thermostat) 18 | * ... Open for more stuff ... 19 | 20 | ## Installation 21 | 22 | 1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). 23 | 2. If you do not have a `custom_components` directory (folder) there, you need to create it. 24 | 3. In the `custom_components` directory (folder) create a new folder called `rika_firenet`. 25 | 4. Download _all_ the files from the `custom_components/rika_firenet/` directory (folder) in this repository. 26 | 5. Place the files you downloaded in the new directory (folder) you created. 27 | 6. Restart Home Assistant 28 | 7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Rika Firenet" 29 | 30 | ## Configuration is done in the UI 31 | 32 | Utility meters example: 33 | ```yaml 34 | utility_meter: 35 | hourly_stove_consumption: 36 | source: sensor._stove_consumption 37 | cycle: hourly 38 | daily_stove_consumption: 39 | source: sensor._stove_consumption 40 | cycle: daily 41 | weekly_stove_consumption: 42 | source: sensor._stove_consumption 43 | cycle: weekly 44 | monthly_stove_consumption: 45 | source: sensor._stove_consumption 46 | cycle: monthly 47 | 48 | hourly_stove_runtime: 49 | source: sensor._stove_runtime 50 | cycle: hourly 51 | daily_stove_runtime: 52 | source: sensor._stove_runtime 53 | cycle: daily 54 | weekly_stove_runtime: 55 | source: sensor._stove_runtime 56 | cycle: weekly 57 | monthly_stove_runtime: 58 | source: sensor._stove_runtime 59 | cycle: monthly 60 | ``` 61 | 62 | ## Contributions are welcome! 63 | 64 | If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md) 65 | 66 | *** 67 | 68 | [rikafirenet]: https://github.com/fockaert/rika-firenet-custom-component 69 | [forum]: https://community.home-assistant.io/ 70 | [releases]: https://github.com/fockaert/rika-firenet-custom-component/releases 71 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/switch.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.components.switch import SwitchEntity 4 | 5 | from .entity import RikaFirenetEntity 6 | 7 | from .const import ( 8 | DOMAIN 9 | ) 10 | from .core import RikaFirenetCoordinator 11 | from .core import RikaFirenetStove 12 | 13 | _LOGGER = logging.getLogger(__name__) 14 | 15 | DEVICE_SWITCH = [ 16 | "on off", 17 | "convection fan1", 18 | "convection fan2" 19 | ] 20 | 21 | 22 | async def async_setup_entry(hass, entry, async_add_entities): 23 | _LOGGER.info("setting up platform switches") 24 | coordinator: RikaFirenetCoordinator = hass.data[DOMAIN][entry.entry_id] 25 | 26 | stove_entities = [] 27 | 28 | # Create stove switches 29 | for stove in coordinator.get_stoves(): 30 | stove_entities.extend( 31 | [ 32 | RikaFirenetStoveBinarySwitch(entry, stove, coordinator, number) 33 | for number in DEVICE_SWITCH 34 | ] 35 | ) 36 | if stove_entities: 37 | async_add_entities(stove_entities, True) 38 | 39 | 40 | class RikaFirenetStoveBinarySwitch(RikaFirenetEntity, SwitchEntity): 41 | def __init__(self, config_entry, stove: RikaFirenetStove, coordinator: RikaFirenetCoordinator, number): 42 | super().__init__(config_entry, stove, coordinator, number) 43 | 44 | self._number = number 45 | 46 | def turn_on(self, **kwargs): # pylint: disable=unused-argument 47 | _LOGGER.info("turn_on " + self._number) 48 | 49 | if self._number == "on off": 50 | self._stove.turn_on() 51 | elif self._number == "convection fan1": 52 | self._stove.turn_convection_fan1_on() 53 | elif self._number == "convection fan2": 54 | self._stove.turn_convection_fan2_on() 55 | 56 | self.schedule_update_ha_state() 57 | 58 | def turn_off(self, **kwargs): # pylint: disable=unused-argument 59 | _LOGGER.info("turn_off " + self._number) 60 | 61 | if self._number == "on off": 62 | self._stove.turn_off() 63 | elif self._number == "convection fan1": 64 | self._stove.turn_convection_fan1_off() 65 | elif self._number == "convection fan2": 66 | self._stove.turn_convection_fan2_off() 67 | 68 | self.schedule_update_ha_state() 69 | 70 | @property 71 | def icon(self): 72 | return "hass:power" 73 | 74 | @property 75 | def is_on(self): 76 | if self._number == "on off": 77 | return self._stove.is_stove_on() 78 | elif self._number == "convection fan1": 79 | return self._stove.is_stove_convection_fan1_on() 80 | elif self._number == "convection fan2": 81 | return self._stove.is_stove_convection_fan2_on() 82 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import requests.exceptions 3 | from homeassistant.config_entries import ConfigEntry 4 | from homeassistant.core import HomeAssistant 5 | from homeassistant.exceptions import ConfigEntryNotReady 6 | 7 | from .const import (CONF_DEFAULT_TEMPERATURE, CONF_PASSWORD, 8 | CONF_USERNAME, DOMAIN, PLATFORMS, STARTUP_MESSAGE) 9 | from .core import RikaFirenetCoordinator 10 | 11 | _LOGGER = logging.getLogger(__name__) 12 | 13 | 14 | async def async_setup(hass: HomeAssistant, config: dict): 15 | _LOGGER.info('setup_platform()') 16 | return True 17 | 18 | 19 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 20 | _LOGGER.info('async_setup_entry():' + str(entry.entry_id)) 21 | 22 | if hass.data.get(DOMAIN) is None: 23 | hass.data.setdefault(DOMAIN, {}) 24 | _LOGGER.info(STARTUP_MESSAGE) 25 | 26 | username = entry.data.get(CONF_USERNAME) 27 | password = entry.data.get(CONF_PASSWORD) 28 | default_temperature = int(entry.options.get(CONF_DEFAULT_TEMPERATURE, 21)) 29 | 30 | coordinator = RikaFirenetCoordinator(hass, username, password, default_temperature) 31 | 32 | try: 33 | await hass.async_add_executor_job(coordinator.setup) 34 | except KeyError: 35 | _LOGGER.error("Failed to login to firenet") 36 | return False 37 | except RuntimeError as exc: 38 | _LOGGER.error("Failed to setup rika firenet: %s", exc) 39 | return ConfigEntryNotReady 40 | except requests.exceptions.Timeout as ex: 41 | raise ConfigEntryNotReady from ex 42 | except requests.exceptions.HTTPError as ex: 43 | if ex.response.status_code > 400 and ex.response.status_code < 500: 44 | _LOGGER.error("Failed to login to rika firenet: %s", ex) 45 | return False 46 | raise ConfigEntryNotReady from ex 47 | 48 | await coordinator.async_refresh() 49 | 50 | if not coordinator.last_update_success: 51 | raise ConfigEntryNotReady 52 | 53 | hass.data[DOMAIN][entry.entry_id] = coordinator 54 | 55 | coordinator.platforms = [p for p in PLATFORMS if entry.options.get(p, True)] 56 | await hass.config_entries.async_forward_entry_setups(entry, coordinator.platforms) 57 | 58 | entry.add_update_listener(async_reload_entry) 59 | 60 | return True 61 | 62 | 63 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): 64 | """Handle removal of an entry.""" 65 | coordinator = hass.data[DOMAIN][entry.entry_id] 66 | 67 | unloaded = await hass.config_entries.async_unload_platforms(entry, coordinator.platforms) 68 | 69 | if unloaded: 70 | hass.data[DOMAIN].pop(entry.entry_id) 71 | 72 | return unloaded 73 | 74 | 75 | async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry): 76 | """Reload config entry.""" 77 | await async_unload_entry(hass, entry) 78 | await async_setup_entry(hass, entry) 79 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/climate.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.components.climate import ClimateEntity 4 | from homeassistant.components.climate.const import HVACMode, ClimateEntityFeature 5 | 6 | from homeassistant.const import (ATTR_TEMPERATURE, UnitOfTemperature) 7 | 8 | from .const import (DOMAIN, SUPPORT_PRESET) 9 | from .core import RikaFirenetCoordinator 10 | from .entity import RikaFirenetEntity 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE # | SUPPORT_PRESET_MODE 15 | 16 | MIN_TEMP = 16 17 | MAX_TEMP = 30 18 | 19 | HVAC_MODES = [HVACMode.AUTO, HVACMode.HEAT, HVACMode.OFF] 20 | 21 | 22 | async def async_setup_entry(hass, entry, async_add_entities): 23 | """Set up platform.""" 24 | _LOGGER.info("setting up platform climate") 25 | coordinator: RikaFirenetCoordinator = hass.data[DOMAIN][entry.entry_id] 26 | 27 | stove_entities = [] 28 | 29 | # Create stove sensors 30 | for stove in coordinator.get_stoves(): 31 | stove_entities.append(RikaFirenetStoveClimate(entry, stove, coordinator)) 32 | 33 | if stove_entities: 34 | async_add_entities(stove_entities, True) 35 | 36 | 37 | class RikaFirenetStoveClimate(RikaFirenetEntity, ClimateEntity): 38 | 39 | @property 40 | def current_temperature(self): 41 | temp = self._stove.get_room_temperature() 42 | _LOGGER.info('current_temperature(): ' + str(temp)) 43 | return temp 44 | 45 | @property 46 | def min_temp(self): 47 | return MIN_TEMP 48 | 49 | @property 50 | def max_temp(self): 51 | return MAX_TEMP 52 | 53 | @property 54 | def preset_modes(self): 55 | """Return a list of available preset modes.""" 56 | return SUPPORT_PRESET 57 | 58 | def set_preset_mode(self, preset_mode): 59 | """Set new preset mode.""" 60 | self._stove.set_presence(preset_mode) 61 | self.schedule_update_ha_state() 62 | 63 | @property 64 | def target_temperature(self): 65 | temp = self._stove.get_room_thermostat() 66 | return temp 67 | 68 | @property 69 | def target_temperature_step(self): 70 | return 1 71 | 72 | @property 73 | def hvac_mode(self): 74 | return self._stove.get_hvac_mode() 75 | 76 | @property 77 | def hvac_modes(self): 78 | return HVAC_MODES 79 | 80 | def set_hvac_mode(self, hvac_mode): 81 | _LOGGER.info('set_hvac_mode()): ' + str(hvac_mode)) 82 | self._stove.set_hvac_mode(str(hvac_mode)) 83 | self.schedule_update_ha_state() 84 | 85 | @property 86 | def supported_features(self): 87 | return SUPPORT_FLAGS 88 | 89 | @property 90 | def temperature_unit(self): 91 | return UnitOfTemperature.CELSIUS 92 | 93 | def set_temperature(self, **kwargs): 94 | temperature = int(kwargs.get(ATTR_TEMPERATURE)) 95 | _LOGGER.info('set_temperature(): ' + str(temperature)) 96 | 97 | if kwargs.get(ATTR_TEMPERATURE) is None: 98 | return 99 | 100 | if not self._stove.is_stove_on(): 101 | return 102 | 103 | # do nothing if HVAC is switched off 104 | self._stove.set_stove_temperature(temperature) 105 | self.schedule_update_ha_state() 106 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/sensor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.const import UnitOfMass 4 | from homeassistant.const import UnitOfTemperature 5 | from homeassistant.const import PERCENTAGE 6 | from homeassistant.const import UnitOfTime 7 | 8 | from .entity import RikaFirenetEntity 9 | 10 | from .const import ( 11 | DOMAIN 12 | ) 13 | from .core import RikaFirenetCoordinator 14 | from .core import RikaFirenetStove 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | DEVICE_SENSORS = [ 19 | "stove consumption", 20 | "stove runtime", 21 | "stove temperature", 22 | "stove thermostat", 23 | "stove burning", 24 | "stove status", 25 | "room temperature", 26 | "room thermostat", 27 | "room power request", 28 | "heating power" 29 | ] 30 | 31 | 32 | async def async_setup_entry(hass, entry, async_add_entities): 33 | _LOGGER.info("setting up platform sensor") 34 | coordinator: RikaFirenetCoordinator = hass.data[DOMAIN][entry.entry_id] 35 | 36 | stove_entities = [] 37 | 38 | # Create stove sensors 39 | for stove in coordinator.get_stoves(): 40 | stove_entities.extend( 41 | [ 42 | RikaFirenetStoveSensor(entry, stove, coordinator, sensor) 43 | for sensor in DEVICE_SENSORS 44 | ] 45 | ) 46 | 47 | if stove_entities: 48 | async_add_entities(stove_entities, True) 49 | 50 | 51 | class RikaFirenetStoveSensor(RikaFirenetEntity): 52 | def __init__(self, config_entry, stove: RikaFirenetStove, coordinator: RikaFirenetCoordinator, sensor): 53 | super().__init__(config_entry, stove, coordinator, sensor) 54 | 55 | self._sensor = sensor 56 | 57 | @property 58 | def state(self): 59 | if self._sensor == "stove consumption": 60 | return self._stove.get_stove_consumption() 61 | elif self._sensor == "stove runtime": 62 | return self._stove.get_stove_runtime() 63 | elif self._sensor == "stove temperature": 64 | return self._stove.get_stove_temperature() 65 | elif self._sensor == "stove thermostat": 66 | return self._stove.get_stove_thermostat() 67 | elif self._sensor == "stove burning": 68 | return self._stove.is_stove_burning() 69 | elif self._sensor == "stove status": 70 | return self._stove.get_status_text() 71 | elif self._sensor == "room temperature": 72 | return self._stove.get_room_temperature() 73 | elif self._sensor == "room thermostat": 74 | return self._stove.get_room_thermostat() 75 | elif self._sensor == "room power request": 76 | return self._stove.get_room_power_request() 77 | elif self._sensor == "heating power": 78 | return self._stove.get_heating_power() 79 | 80 | @property 81 | def unit_of_measurement(self): 82 | if "temperature" in self._sensor or "thermostat" in self._sensor: 83 | return UnitOfTemperature.CELSIUS 84 | elif self._sensor == "stove consumption": 85 | return UnitOfMass.KILOGRAMS 86 | elif self._sensor == "stove runtime": 87 | return UnitOfTime.HOURS 88 | elif self._sensor == "heating power": 89 | return PERCENTAGE 90 | 91 | @property 92 | def icon(self): 93 | if "temperature" in self._sensor or "thermostat" in self._sensor: 94 | return "mdi:thermometer" 95 | elif self._sensor == "stove consumption": 96 | return "mdi:weight-kilogram" 97 | elif self._sensor == "stove runtime": 98 | return "mdi:timelapse" 99 | elif self._sensor == "stove burning": 100 | return "mdi:fire" 101 | elif self._sensor == "stove status": 102 | return "mdi:information-outline" 103 | elif self._sensor == "heating power": 104 | return "mdi:speedometer" 105 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/config_flow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import voluptuous as vol 3 | from homeassistant import config_entries 4 | from homeassistant.core import callback 5 | 6 | from .const import (CONF_DEFAULT_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, DOMAIN, PLATFORMS) 7 | from .core import RikaFirenetCoordinator 8 | 9 | _LOGGER = logging.getLogger(__name__) 10 | 11 | class RikaFirenetFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 12 | VERSION = 1 13 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 14 | 15 | def __init__(self): 16 | """Initialize.""" 17 | self._errors = {} 18 | 19 | async def async_step_user(self, user_input=None): 20 | """Handle a flow initialized by the user.""" 21 | self._errors = {} 22 | 23 | # Uncomment the next 2 lines if only a single instance of the integration is allowed: 24 | if self._async_current_entries(): 25 | return self.async_abort(reason="single_instance_allowed") 26 | 27 | if user_input is not None: 28 | valid = await self._test_credentials( 29 | user_input[CONF_USERNAME], user_input[CONF_PASSWORD] 30 | ) 31 | if valid: 32 | return self.async_create_entry( 33 | title=user_input[CONF_USERNAME], data=user_input 34 | ) 35 | else: 36 | self._errors["base"] = "auth" 37 | 38 | return await self._show_config_form(user_input) 39 | 40 | return await self._show_config_form(user_input) 41 | 42 | @staticmethod 43 | @callback 44 | def async_get_options_flow(config_entry): 45 | return RikaFirenetOptionsFlowHandler(config_entry) 46 | 47 | async def _show_config_form(self, user_input): # pylint: disable=unused-argument 48 | """Show the configuration form to edit data.""" 49 | 50 | if user_input is None: 51 | user_input = {} 52 | 53 | schema_properties = { 54 | vol.Required(CONF_USERNAME, default=user_input.get(CONF_USERNAME, None)): str, 55 | vol.Required(CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, None)): str, 56 | } 57 | 58 | return self.async_show_form( 59 | step_id="user", 60 | data_schema=vol.Schema(schema_properties), 61 | errors=self._errors, 62 | ) 63 | 64 | async def _test_credentials(self, username, password): 65 | """Return true if credentials is valid.""" 66 | try: 67 | coordinator = RikaFirenetCoordinator(self.hass, username, password, 21, True) 68 | await self.hass.async_add_executor_job(coordinator.setup) 69 | return True 70 | except Exception: # pylint: disable=broad-except 71 | _LOGGER.exception("test_credentials_exception") 72 | pass 73 | 74 | return False 75 | 76 | 77 | class RikaFirenetOptionsFlowHandler(config_entries.OptionsFlow): 78 | 79 | def __init__(self, config_entry): 80 | """Initialize HACS options flow.""" 81 | self.config_entry = config_entry 82 | self.options = dict(config_entry.options) 83 | 84 | async def async_step_init(self, user_input=None): # pylint: disable=unused-argument 85 | """Manage the options.""" 86 | return await self.async_step_user(user_input) 87 | 88 | async def async_step_user(self, user_input=None): 89 | """Handle a flow initialized by the user.""" 90 | 91 | if user_input is not None: 92 | self.options.update(user_input) 93 | return await self._update_options() 94 | 95 | schema_properties = { 96 | vol.Required(CONF_DEFAULT_TEMPERATURE, default=self.options.get(CONF_DEFAULT_TEMPERATURE)): int 97 | } 98 | 99 | schema_properties.update({ 100 | vol.Required(x, default=self.options.get(x, True)): bool 101 | for x in sorted(PLATFORMS) 102 | }) 103 | 104 | return self.async_show_form( 105 | step_id="user", 106 | data_schema=vol.Schema(schema_properties), 107 | ) 108 | 109 | async def _update_options(self): 110 | """Update config entry options.""" 111 | return self.async_create_entry( 112 | title=self.config_entry.data.get(CONF_USERNAME), data=self.options 113 | ) 114 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/number.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.const import PERCENTAGE 4 | from .entity import RikaFirenetEntity 5 | from homeassistant.components.number import NumberEntity 6 | 7 | from .const import ( 8 | DOMAIN 9 | ) 10 | from .core import RikaFirenetCoordinator 11 | from .core import RikaFirenetStove 12 | 13 | _LOGGER = logging.getLogger(__name__) 14 | 15 | DEVICE_NUMBERS = [ 16 | "room power request", 17 | "heating power", 18 | "convection fan1 level", 19 | "convection fan1 area", 20 | "convection fan2 level", 21 | "convection fan2 area" 22 | ] 23 | 24 | 25 | async def async_setup_entry(hass, entry, async_add_entities): 26 | _LOGGER.info("setting up platform number") 27 | coordinator: RikaFirenetCoordinator = hass.data[DOMAIN][entry.entry_id] 28 | 29 | stove_entities = [] 30 | 31 | # Create stove numbers 32 | for stove in coordinator.get_stoves(): 33 | stove_entities.extend( 34 | [ 35 | RikaFirenetStoveNumber(entry, stove, coordinator, number) 36 | for number in DEVICE_NUMBERS 37 | ] 38 | ) 39 | 40 | if stove_entities: 41 | async_add_entities(stove_entities, True) 42 | 43 | 44 | class RikaFirenetStoveNumber(RikaFirenetEntity, NumberEntity): 45 | def __init__(self, config_entry, stove: RikaFirenetStove, coordinator: RikaFirenetCoordinator, number): 46 | super().__init__(config_entry, stove, coordinator, number) 47 | 48 | self._number = number 49 | 50 | @property 51 | def native_min_value(self) -> float: 52 | if self._number == "room power request": 53 | return 1 54 | elif self._number == "convection fan1 level": 55 | return 0 56 | elif self._number == "convection fan1 area": 57 | return -30 58 | elif self._number == "convection fan2 level": 59 | return 0 60 | elif self._number == "convection fan2 area": 61 | return -30 62 | 63 | return 0 64 | 65 | @property 66 | def native_max_value(self) -> float: 67 | if self._number == "room power request": 68 | return 4 69 | elif self._number == "convection fan1 level": 70 | return 5 71 | elif self._number == "convection fan1 area": 72 | return 30 73 | elif self._number == "convection fan2 level": 74 | return 5 75 | elif self._number == "convection fan2 area": 76 | return 30 77 | 78 | return 100 79 | 80 | @property 81 | def native_step(self) -> float: 82 | if self._number == "room power request": 83 | return 1 84 | elif self._number == "convection fan1 level": 85 | return 1 86 | elif self._number == "convection fan1 area": 87 | return 1 88 | elif self._number == "convection fan2 level": 89 | return 1 90 | elif self._number == "convection fan2 area": 91 | return 1 92 | 93 | return 10 94 | 95 | @property 96 | def native_value(self): 97 | if self._number == "room power request": 98 | return self._stove.get_room_power_request() 99 | elif self._number == "heating power": 100 | return self._stove.get_heating_power() 101 | elif self._number == "convection fan1 level": 102 | return self._stove.get_convection_fan1_level() 103 | elif self._number == "convection fan1 area": 104 | return self._stove.get_convection_fan1_area() 105 | elif self._number == "convection fan2 level": 106 | return self._stove.get_convection_fan2_level() 107 | elif self._number == "convection fan2 area": 108 | return self._stove.get_convection_fan2_area() 109 | 110 | @property 111 | def native_unit_of_measurement(self): 112 | if self._number == "heating power": 113 | return PERCENTAGE 114 | elif self._number == "convection fan1 area": 115 | return PERCENTAGE 116 | elif self._number == "convection fan2 area": 117 | return PERCENTAGE 118 | 119 | @property 120 | def icon(self): 121 | return "mdi:speedometer" 122 | 123 | def set_native_value(self, value: float) -> None: 124 | _LOGGER.info("set_value " + self._number + " " + str(value)) 125 | 126 | if self._number == "room power request": 127 | self._stove.set_room_power_request(int(value)) 128 | elif self._number == "heating power": 129 | self._stove.set_heating_power(int(value)) 130 | elif self._number == "convection fan1 level": 131 | return self._stove.set_convection_fan1_level(int(value)) 132 | elif self._number == "convection fan1 area": 133 | return self._stove.set_convection_fan1_area(int(value)) 134 | elif self._number == "convection fan2 level": 135 | return self._stove.set_convection_fan2_level(int(value)) 136 | elif self._number == "convection fan2 area": 137 | return self._stove.set_convection_fan2_area(int(value)) 138 | 139 | self.schedule_update_ha_state() 140 | -------------------------------------------------------------------------------- /custom_components/rika_firenet/core.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from datetime import datetime, timedelta 4 | 5 | import requests 6 | from bs4 import BeautifulSoup 7 | from homeassistant.components.climate.const import HVACMode, PRESET_AWAY, PRESET_HOME 8 | 9 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed 10 | from .const import DOMAIN 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10) 14 | SCAN_INTERVAL = timedelta(seconds=15) 15 | 16 | 17 | class RikaFirenetCoordinator(DataUpdateCoordinator): 18 | def __init__(self, hass, username, password, default_temperature, config_flow=False): 19 | self.hass = hass 20 | self._username = username 21 | self._password = password 22 | self._default_temperature = default_temperature 23 | self._client = None 24 | self._stoves = None 25 | self.platforms = [] 26 | 27 | if not config_flow: 28 | super().__init__( 29 | hass, 30 | _LOGGER, 31 | name=DOMAIN, 32 | update_method=self.async_update_data, 33 | update_interval=SCAN_INTERVAL 34 | ) 35 | 36 | async def async_update_data(self): 37 | try: 38 | await self.hass.async_add_executor_job(self.update) 39 | except Exception as exception: 40 | raise UpdateFailed(exception) 41 | 42 | def setup(self): 43 | _LOGGER.info("setup()") 44 | self._client = requests.session() 45 | self._stoves = self.setup_stoves() 46 | 47 | def get_stoves(self): 48 | return self._stoves 49 | 50 | def get_default_temperature(self): 51 | return self._default_temperature 52 | 53 | def connect(self): 54 | if self.is_authenticated(): 55 | return 56 | 57 | data = { 58 | 'email': self._username, 59 | 'password': self._password 60 | } 61 | 62 | postResponse = self._client.post('https://www.rika-firenet.com/web/login', data) 63 | 64 | if not ('/logout' in postResponse.text): 65 | raise Exception('Failed to connect with Rika Firenet') 66 | else: 67 | _LOGGER.info('Connected to Rika Firenet') 68 | 69 | def is_authenticated(self): 70 | if 'connect.sid' not in self._client.cookies: 71 | return False 72 | 73 | expiresIn = list(self._client.cookies)[0].expires 74 | epochNow = int(datetime.now().strftime('%s')) 75 | 76 | if expiresIn <= epochNow: 77 | return False 78 | 79 | return True 80 | 81 | def get_stove_state(self, id): 82 | self.connect() 83 | url = 'https://www.rika-firenet.com/api/client/' + id + '/status?nocache=' + str(int(time.time())) 84 | data = self._client.get(url, timeout=10).json() 85 | # _LOGGER.debug('get_stove_state(), url=' + url + ', response=' + str(data)) 86 | 87 | return data 88 | 89 | def setup_stoves(self): 90 | self.connect() 91 | stoves = [] 92 | postResponse = self._client.get('https://www.rika-firenet.com/web/summary') 93 | 94 | soup = BeautifulSoup(postResponse.content, "html.parser") 95 | stoveList = soup.find("ul", {"id": "stoveList"}) 96 | 97 | if stoveList is None: 98 | return stoves 99 | 100 | for stove in stoveList.findAll('li'): 101 | stoveLink = stove.find('a', href=True) 102 | stoveName = stoveLink.attrs['href'].rsplit('/', 1)[-1] 103 | stove = RikaFirenetStove(self, stoveName, stoveLink.text) 104 | _LOGGER.info("Found stove : {}".format(stove)) 105 | stoves.append(stove) 106 | 107 | return stoves 108 | 109 | def update(self): 110 | _LOGGER.info("update()") 111 | for stove in self._stoves: 112 | stove.sync_state() 113 | 114 | def set_stove_controls(self, id, data): 115 | _LOGGER.info("set_stove_control() id: " + id + " data: " + str(data)) 116 | 117 | r = self._client.post('https://www.rika-firenet.com/api/client/' + id + '/controls', data) 118 | 119 | for counter in range(0, 10): 120 | if ('OK' in r.text) == True: 121 | _LOGGER.info('Stove controls updated') 122 | return True 123 | else: 124 | _LOGGER.info('In progress.. ({}/10)'.format(counter)) 125 | time.sleep(2) 126 | 127 | return False 128 | 129 | 130 | class RikaFirenetStove: 131 | def __init__(self, coordinator: RikaFirenetCoordinator, id, name): 132 | self._coordinator = coordinator 133 | self._id = id 134 | self._name = name 135 | self._previous_temperature = None 136 | self._state = None 137 | 138 | def get_id(self): 139 | return self._id 140 | 141 | def get_name(self): 142 | return self._name 143 | 144 | def __repr__(self): 145 | return {'id': self._id, 'name': self._name} 146 | 147 | def __str__(self): 148 | return 'Stove(id=' + self._id + ', name=' + self._name + ')' 149 | 150 | def sync_state(self): 151 | _LOGGER.debug("Updating stove %s", self._id) 152 | self._state = self._coordinator.get_stove_state(self._id) 153 | 154 | def set_stove_temperature(self, temperature): 155 | _LOGGER.info("set_stove_temperature(): " + str(temperature)) 156 | 157 | data = self.get_control_state() 158 | data['targetTemperature'] = str(temperature) 159 | 160 | self._coordinator.set_stove_controls(self._id, data) 161 | self.sync_state() 162 | 163 | def get_control_state(self): 164 | return self._state['controls'] 165 | 166 | def set_presence(self, presence=PRESET_HOME): 167 | room_thermostat = self.get_room_thermostat() 168 | _LOGGER.info("set_presence(): " + str(presence) + 169 | " current room thermostat: " + str(room_thermostat)) 170 | 171 | if presence == PRESET_AWAY: 172 | self._previous_temperature = room_thermostat 173 | self.set_stove_temperature(self.get_stove_set_back_temperature()) 174 | elif presence == PRESET_HOME: 175 | if self._previous_temperature: 176 | self.set_stove_temperature(self._previous_temperature) 177 | else: 178 | self.set_stove_temperature( 179 | self._coordinator.get_default_temperature()) 180 | self._previous_temperature = None 181 | 182 | def get_state(self): 183 | return self._state 184 | 185 | def get_stove_consumption(self): 186 | return self._state['sensors']['parameterFeedRateTotal'] 187 | 188 | def get_stove_runtime(self): 189 | return self._state['sensors']['parameterRuntimePellets'] 190 | 191 | def get_stove_temperature(self): 192 | return float(self._state['sensors']['inputFlameTemperature']) 193 | 194 | def get_stove_thermostat(self): 195 | return float(self._state['controls']['targetTemperature']) 196 | 197 | def get_stove_operation_mode(self): 198 | return float(self._state['controls']['operatingMode']) 199 | 200 | def set_stove_operation_mode(self, mode): 201 | _LOGGER.info("set_stove_operation_mode(): " + str(mode)) 202 | 203 | data = self.get_control_state() 204 | data['operatingMode'] = mode 205 | 206 | self._coordinator.set_stove_controls(self._id, data) 207 | self.sync_state() 208 | 209 | def get_stove_set_back_temperature(self): 210 | return float(self._state['controls']['setBackTemperature']) 211 | 212 | def is_heating_times_active_for_comfort(self): 213 | return self._state['controls']['heatingTimesActiveForComfort'] 214 | 215 | def is_stove_on(self): 216 | return bool(self._state['controls']['onOff']) 217 | 218 | def is_stove_convection_fan1_on(self): 219 | return bool(self._state['controls']['convectionFan1Active']) 220 | 221 | def is_stove_convection_fan2_on(self): 222 | return bool(self._state['controls']['convectionFan2Active']) 223 | 224 | def get_room_thermostat(self): 225 | return float(self._state['controls']['targetTemperature']) 226 | 227 | def get_room_temperature(self): 228 | return float(self._state['sensors']['inputRoomTemperature']) 229 | 230 | def get_room_power_request(self): 231 | return int(self._state['controls']['RoomPowerRequest']) 232 | 233 | def get_convection_fan1_level(self): 234 | return int(self._state['controls']['convectionFan1Level']) 235 | 236 | def get_convection_fan1_area(self): 237 | return int(self._state['controls']['convectionFan1Area']) 238 | 239 | def get_convection_fan2_level(self): 240 | return int(self._state['controls']['convectionFan2Level']) 241 | 242 | def get_convection_fan2_area(self): 243 | return int(self._state['controls']['convectionFan2Area']) 244 | 245 | def set_room_power_request(self, power): 246 | _LOGGER.info("set_room_power_request(): " + str(power)) 247 | 248 | data = self.get_control_state() 249 | data['RoomPowerRequest'] = power 250 | 251 | self._coordinator.set_stove_controls(self._id, data) 252 | self.sync_state() 253 | 254 | def get_heating_power(self): 255 | return int(self._state['controls']['heatingPower']) 256 | 257 | def set_heating_power(self, power): 258 | _LOGGER.info("set_heating_power(): " + str(power)) 259 | 260 | data = self.get_control_state() 261 | data['heatingPower'] = power 262 | 263 | self._coordinator.set_stove_controls(self._id, data) 264 | self.sync_state() 265 | 266 | def set_convection_fan1_level(self, level): 267 | _LOGGER.info("set_convection_fan1_level(): " + str(level)) 268 | 269 | data = self.get_control_state() 270 | data['convectionFan1Level'] = level 271 | 272 | self._coordinator.set_stove_controls(self._id, data) 273 | self.sync_state() 274 | 275 | def set_convection_fan1_area(self, area): 276 | _LOGGER.info("set_convection_fan1_area(): " + str(area)) 277 | 278 | data = self.get_control_state() 279 | data['convectionFan1Area'] = area 280 | 281 | self._coordinator.set_stove_controls(self._id, data) 282 | self.sync_state() 283 | 284 | def set_convection_fan2_level(self, level): 285 | _LOGGER.info("set_convection_fan2_level(): " + str(level)) 286 | 287 | data = self.get_control_state() 288 | data['convectionFan2Level'] = level 289 | 290 | self._coordinator.set_stove_controls(self._id, data) 291 | self.sync_state() 292 | 293 | def set_convection_fan2_area(self, area): 294 | _LOGGER.info("set_convection_fan2_area(): " + str(area)) 295 | 296 | data = self.get_control_state() 297 | data['convectionFan2Area'] = area 298 | 299 | self._coordinator.set_stove_controls(self._id, data) 300 | self.sync_state() 301 | 302 | def is_stove_burning(self): 303 | if self._state['sensors']['statusMainState'] == 4 or self._state['sensors']['statusMainState'] == 5: 304 | return True 305 | else: 306 | return False 307 | 308 | def get_status_text(self): 309 | return self.get_status()[1] 310 | 311 | def get_status_picture(self): 312 | return self.get_status()[0] 313 | 314 | def get_hvac_mode(self): 315 | if not self.is_stove_on(): 316 | return HVACMode.OFF 317 | 318 | if self.get_stove_operation_mode() == 0: 319 | return HVACMode.HEAT 320 | 321 | if not self.is_heating_times_active_for_comfort(): 322 | return HVACMode.HEAT 323 | 324 | return HVACMode.AUTO 325 | 326 | def set_hvac_mode(self, hvac_mode): 327 | if hvac_mode == HVACMode.OFF: 328 | self.turn_off() 329 | elif hvac_mode == HVACMode.AUTO: 330 | self.set_heating_times_active_for_comfort(True) 331 | elif hvac_mode == HVACMode.HEAT: 332 | self.set_heating_times_active_for_comfort(False) 333 | 334 | def set_heating_times_active_for_comfort(self, active): 335 | _LOGGER.info("set_heating_times_active_for_comfort(): " + str(active)) 336 | 337 | data = self.get_control_state() 338 | data['onOff'] = True 339 | data['heatingTimesActiveForComfort'] = active 340 | 341 | self._coordinator.set_stove_controls(self._id, data) 342 | self.sync_state() 343 | 344 | def turn_convection_fan1_on(self): 345 | self.turn_convection_fan1_on_off(True) 346 | 347 | def turn_convection_fan1_off(self): 348 | self.turn_convection_fan1_on_off(False) 349 | 350 | def turn_convection_fan1_on_off(self, on_off=True): 351 | _LOGGER.info("turn_convection_fan1_on_off(): ") 352 | 353 | data = self.get_control_state() 354 | data['convectionFan1Active'] = on_off 355 | 356 | self._coordinator.set_stove_controls(self._id, data) 357 | self.sync_state() 358 | 359 | def turn_convection_fan2_on(self): 360 | self.turn_convection_fan2_on_off(True) 361 | 362 | def turn_convection_fan2_off(self): 363 | self.turn_convection_fan2_on_off(False) 364 | 365 | def turn_convection_fan2_on_off(self, on_off=True): 366 | _LOGGER.info("turn_convection_fan2_on_off(): ") 367 | 368 | data = self.get_control_state() 369 | data['convectionFan2Active'] = on_off 370 | 371 | self._coordinator.set_stove_controls(self._id, data) 372 | self.sync_state() 373 | 374 | def turn_on(self): 375 | self.turn_on_off(True) 376 | 377 | def turn_off(self): 378 | self.turn_on_off(False) 379 | 380 | def turn_on_off(self, on_off=True): 381 | _LOGGER.info("turn_off(): ") 382 | 383 | data = self.get_control_state() 384 | data['onOff'] = on_off 385 | 386 | self._coordinator.set_stove_controls(self._id, data) 387 | self.sync_state() 388 | 389 | def get_status(self): 390 | main_state = self._state['sensors']['statusMainState'] 391 | sub_state = self._state['sensors']['statusSubState'] 392 | frost_started = self._state['sensors']['statusFrostStarted'] 393 | 394 | if frost_started: 395 | return ["/images/status/Visu_Freeze.svg", "frost_protection"] 396 | 397 | if main_state == 1: 398 | if sub_state == 0: 399 | return ["/images/status/Visu_Off.svg", "stove_off"] 400 | elif sub_state == 1: 401 | return ["/images/status/Visu_Standby.svg", "standby"] 402 | elif sub_state == 2: 403 | return ["/images/status/Visu_Standby.svg", "external_request"] 404 | elif sub_state == 3: 405 | return ["/images/status/Visu_Standby.svg", "standby"] 406 | return ["/images/status/Visu_Off.svg", "sub_state_unknown"] 407 | elif main_state == 2: 408 | return ["/images/status/Visu_Ignition.svg", "ignition_on"] 409 | elif main_state == 3: 410 | return ["/images/status/Visu_Ignition.svg", "starting_up"] 411 | elif main_state == 4: 412 | return ["/images/status/Visu_Control.svg", "running"] 413 | elif main_state == 5: 414 | if sub_state == 3 or sub_state == 4: 415 | return ["/images/status/Visu_Clean.svg", "big_clean"] 416 | else: 417 | return ["/images/status/Visu_Clean.svg", "clean"] 418 | elif main_state == 6: 419 | return ["/images/status/Visu_BurnOff.svg", "burn_off"] 420 | elif main_state == 11 or main_state == 13 or main_state == 14 or main_state == 16 or main_state == 17 or main_state == 50: 421 | return ["/images/status/Visu_SpliLog.svg", "split_log_check"] 422 | elif main_state == 20 or main_state == 21: 423 | return ["/images/status/Visu_SpliLog.svg", "split_log_mode"] 424 | 425 | return ["/images/status/Visu_Off.svg", "unknown"] 426 | --------------------------------------------------------------------------------