Skip to content
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/.idea/
/__pycache__/
__pycache__/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ You have two options for installation:
- Restart HA server.

### WORKING ON:
- Brink Flair 200
- Brink Renovent 180
- Brink Renovent 300
- Brink Renovent 400 Plus
Expand Down
8 changes: 7 additions & 1 deletion custom_components/brink_ventilation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [Platform.SELECT, Platform.BINARY_SENSOR, Platform.FAN]
PLATFORMS = [Platform.SELECT, Platform.BINARY_SENSOR, Platform.FAN, Platform.SENSOR]

CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)

Expand Down Expand Up @@ -100,9 +100,15 @@ async def async_get_devices(hass: HomeAssistant, entry: ConfigEntry, brink_clien
# Retrieve additional description
for system in systems:
description = await brink_client.get_description_values(system["system_id"], system["gateway_id"])

# Add core ventilation control values
system["ventilation"] = description["ventilation"]
system["mode"] = description["mode"]
system["filters_need_change"] = description["filters_need_change"]

# Add any additional sensors (CO2, temperature, humidity, etc.)
for key, value in description.items():
system[key] = value

hass.data[DOMAIN][entry.entry_id][DATA_DEVICES] = systems

Expand Down
2 changes: 1 addition & 1 deletion custom_components/brink_ventilation/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from custom_components.brink_ventilation import BrinkHomeDeviceEntity
from . import BrinkHomeDeviceEntity

from .const import (
DATA_CLIENT,
Expand Down
13 changes: 13 additions & 0 deletions custom_components/brink_ventilation/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Constant values for the Brink Home component."""
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.const import CONCENTRATION_PARTS_PER_MILLION

DOMAIN = "brink_ventilation"
DEFAULT_NAME = "Brink"
Expand All @@ -11,3 +13,14 @@
DEFAULT_SCAN_INTERVAL = 30

API_URL = "https://www.brink-home.com/portal/api/portal/"

# Define sensor types with their properties
SENSOR_TYPES = {
"co2": {
"device_class": SensorDeviceClass.CO2,
"state_class": SensorStateClass.MEASUREMENT,
"unit": CONCENTRATION_PARTS_PER_MILLION,
"icon": "mdi:molecule-co2",
"pattern": r"(?=.*\bPPM\b)(?=.*\bCO2\b)",
},
}
94 changes: 65 additions & 29 deletions custom_components/brink_ventilation/core/brink_home_cloud.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Implementation for Brink-Home Cloud"""
import asyncio
import async_timeout
import logging
import re

import aiohttp
import async_timeout

from ..const import API_URL
from ..const import API_URL, SENSOR_TYPES
from ..translations import TRANSLATIONS

_LOGGER = logging.getLogger(__name__)
Expand All @@ -31,15 +33,15 @@ async def _api_call(self, url, method, data=None):
"%s request: %s, data %s",
method,
url,
data
data,
)
try:
async with async_timeout.timeout(self.timeout):
req = await self._http_session.request(
method,
url,
json=data,
headers=self.headers
headers=self.headers,
)

if req.status == 401:
Expand Down Expand Up @@ -72,7 +74,7 @@ async def login(self):

_LOGGER.debug(
"login result: %s",
result
result,
)

return result
Expand All @@ -87,50 +89,79 @@ async def get_systems(self):
mapped_result = []

for system in result:
mapped_result.append({
'system_id': system["id"],
'gateway_id': system["gatewayId"],
'name': system['name']
})
mapped_result.append(
{
'system_id': system["id"],
'gateway_id': system["gatewayId"],
'name': system['name']
},
)

_LOGGER.debug(
"get_systems result: %s",
mapped_result
mapped_result,
)

return mapped_result

async def get_description_values(self, system_id, gateway_id):
"""Gets values info."""
url = f"{API_URL}GetAppGuiDescriptionForGateway?GatewayId={gateway_id}&SystemId={system_id}"
url = (f"{API_URL}GetAppGuiDescriptionForGateway?GatewayId="
f"{gateway_id}&SystemId={system_id}")

response = await self._api_call(url, "GET")
result = await response.json()

_LOGGER.debug(
"Response result: %s",
result
result,
)

menu_items = result.get("menuItems", [])
if not menu_items:
_LOGGER.debug("No menu items found in API response")
return {}

menu_item = menu_items[0]
pages = menu_item.get("pages", [])
home_page = pages[0]
parameters = home_page.get("parameterDescriptors", [])
ventilation = self.__find(parameters, "uiId", "Lüftungsstufe")
mode = self.__find(parameters, "uiId", "Betriebsart")
filters_need_change = self.__find(parameters, "uiId", "Status Filtermeldung")

if not pages:
_LOGGER.debug("No pages found in menu item")
return {}

# Extract all parameters from all pages
all_parameters = []
for page in pages:
parameters = page.get("parameterDescriptors", [])
all_parameters.extend(parameters)

_LOGGER.debug(f"Found {len(all_parameters)} parameters across all pages")

# Find the basic parameters
ventilation = self.__find(all_parameters, "uiId", "Lüftungsstufe")
mode = self.__find(all_parameters, "uiId", "Betriebsart")
filters_need_change = self.__find(
all_parameters, "uiId", "Status Filtermeldung",
)

# Initialize the result dictionary with the basic parameters
description_result = {
"ventilation": self.__get_type(ventilation),
"mode": self.__get_type(mode),
"filters_need_change": self.__get_type(filters_need_change)
}

# Look for CO2 sensors and other sensors and add them to the result
for param in all_parameters:
param_name = param.get("name", "")

# Add CO2 sensors
if re.search(SENSOR_TYPES.get("co2")["pattern"], param_name):
_LOGGER.debug(f"Found CO2 sensor: {param_name}")
description_result[param_name] = self.__get_type(param)

_LOGGER.debug(
"get_description_values result: %s",
description_result
description_result,
)

return description_result
Expand All @@ -149,15 +180,21 @@ def __get_values(type):
extracted = []
for value in values:
if value["isSelectable"]:
extracted.append({
"value": value["value"],
"text": TRANSLATIONS.get(value["displayText"], value["displayText"])
})
extracted.append(
{
"value": value["value"],
"text": TRANSLATIONS.get(
value["displayText"], value["displayText"],
)
},
)

return extracted

# 1 as mode value changes mode to manual every time you change ventilation value
async def set_ventilation_value(self, system_id, gateway_id, mode, ventilation, value):
async def set_ventilation_value(
self, system_id, gateway_id, mode, ventilation, value,
):
ventilation_value = ventilation["values"][value]["value"]
if ventilation_value is None:
return
Expand Down Expand Up @@ -203,14 +240,13 @@ async def set_mode_value(self, system_id, gateway_id, mode, value):

await self._api_call(url, "POST", data)

def __find(self, arr , attr, value):
def __find(self, arr, attr, value):
for obj in arr:
try:
if obj[attr] == value:
return obj
except:
_LOGGER.debug(
"find error: %s",
value
value,
)

8 changes: 2 additions & 6 deletions custom_components/brink_ventilation/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@
import logging
import math

from homeassistant.components.fan import (
DOMAIN,
FanEntity,
FanEntityFeature
)
from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.util.percentage import int_states_in_range, ranged_value_to_percentage, percentage_to_ranged_value

from custom_components.brink_ventilation import BrinkHomeDeviceEntity
from . import BrinkHomeDeviceEntity

from .const import (
DATA_CLIENT,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/brink_ventilation/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from custom_components.brink_ventilation import BrinkHomeDeviceEntity
from . import BrinkHomeDeviceEntity

from .const import (
DATA_CLIENT,
Expand Down
Loading