Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f729acc
Added api calls and fixed startup problems
andrew-dddd Dec 18, 2025
13be33a
Added api calls and fixed startup problems
andrew-dddd Dec 18, 2025
17a45b4
Merge branch 'mode-change-support' of github.com:andrew-dddd/tech-con…
andrew-dddd Dec 18, 2025
1c1e2b5
Fixed issue with second call as none
andrew-dddd Dec 18, 2025
369322f
Remeved unnecesary logs
andrew-dddd Dec 18, 2025
261045a
Added preset mode support
andrew-dddd Dec 18, 2025
222988a
Updated mapping
andrew-dddd Dec 18, 2025
1e57092
Added setting preset mode and changing state
andrew-dddd Dec 18, 2025
4de966c
Fix
andrew-dddd Dec 18, 2025
429a2c6
Used f-strings instead of concatenation
andrew-dddd Dec 18, 2025
cdd1b6b
Fixed typo in url
andrew-dddd Dec 18, 2025
9af6b6a
Cosmetics
andrew-dddd Dec 18, 2025
c85a00c
Fix
andrew-dddd Dec 18, 2025
218d015
Added coordinator to avoid excessive api calls
andrew-dddd Dec 19, 2025
419f9a5
Coordinator fixes
andrew-dddd Dec 19, 2025
3512e1b
updated coordinator
andrew-dddd Dec 19, 2025
75694aa
Another fix
andrew-dddd Dec 19, 2025
7f0127f
Another fix
andrew-dddd Dec 19, 2025
67ce854
Another fix
andrew-dddd Dec 19, 2025
caf91b2
Another fix
andrew-dddd Dec 19, 2025
78f6abe
Added caching with decorator
andrew-dddd Dec 19, 2025
2604e62
Added debug log
andrew-dddd Dec 19, 2025
f3145b7
Log message update
andrew-dddd Dec 19, 2025
3d7bfb9
Cleaned the code a bit
andrew-dddd Dec 19, 2025
1e10d24
Fix
andrew-dddd Dec 19, 2025
56859a6
Fix
andrew-dddd Dec 19, 2025
4806bb7
Fix
andrew-dddd Dec 19, 2025
1a8e605
Added setting state after changing preset
andrew-dddd Dec 19, 2025
59d7f34
Bumped version
andrew-dddd Dec 19, 2025
4d88e2a
Added hub as separate entity
andrew-dddd Dec 23, 2025
ff093c7
Merge branch 'mode-change-support' of github.com:andrew-dddd/tech-con…
andrew-dddd Dec 23, 2025
9e5fe2e
Added id
andrew-dddd Dec 28, 2025
c14a2c7
Replaced climate with select entity
andrew-dddd Dec 28, 2025
c3083b1
Replaced climate with select entity
andrew-dddd Dec 28, 2025
d28f2bf
Replaced climate with select entity
andrew-dddd Dec 28, 2025
e748f7e
Added logs
andrew-dddd Dec 28, 2025
8f07d82
added reauth
andrew-dddd Dec 28, 2025
7441df1
extracted select entity
andrew-dddd Dec 28, 2025
371ef98
added file
andrew-dddd Dec 28, 2025
fd32d7b
centralized coordinator creation
andrew-dddd Dec 28, 2025
d30364d
fix
andrew-dddd Dec 28, 2025
c9d7fd6
fix
andrew-dddd Dec 28, 2025
fbe9734
fix
andrew-dddd Dec 28, 2025
3cac5ac
Merge branch 'master' into mode-change-support
andrew-dddd Dec 28, 2025
b80747e
Fix
andrew-dddd Dec 28, 2025
bddd0c3
Added typed responses
andrew-dddd Jan 8, 2026
cb4f04a
removed reauth
andrew-dddd Jan 8, 2026
4b6f927
removed whitespaces
andrew-dddd Jan 8, 2026
40e4d96
added minor version
andrew-dddd Jan 8, 2026
1bae648
udpated imports
andrew-dddd Jan 8, 2026
dc315e2
fix
andrew-dddd Jan 8, 2026
73c17a2
fix
andrew-dddd Jan 8, 2026
235b3c3
fix
andrew-dddd Jan 8, 2026
85b6422
Merge branch 'master' into mode-change-support
andrew-dddd Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion custom_components/tech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.typing import ConfigType
from custom_components.tech.tech_update_coordinator import TechUpdateCoordinator
from .tech_update_coordinator import TechUpdateCoordinator

from .const import DOMAIN
from .tech import Tech
Expand Down
59 changes: 17 additions & 42 deletions custom_components/tech/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import logging
from typing import Any, Final

from custom_components.tech.tech_update_coordinator import TechUpdateCoordinator
from .models.module import ZoneElement
from .tech_update_coordinator import TechUpdateCoordinator
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
Expand Down Expand Up @@ -63,18 +64,18 @@ class TechThermostat(CoordinatorEntity, ClimateEntity):
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
_attr_preset_modes = DEFAULT_PRESETS

def __init__(self, device: dict[str, Any], coordinator, api: Tech) -> None:
def __init__(self, device: ZoneElement, coordinator, api: Tech) -> None:
"""Initialize the Tech device."""
self._api = api
self._id: int = device["zone"]["id"]
self._zone_mode_id = device["mode"]["id"]
self._id: int = device.zone.id
self._zone_mode_id = device.mode.id
self._udid = coordinator.udid

# Set unique_id first as it's required for entity registry
self._attr_unique_id = f"{self._udid}_{self._id}"
self._attr_device_info = {
"identifiers": {(DOMAIN, self._attr_unique_id)},
"name": device["description"]["name"],
"name": device.description.name,
"manufacturer": "Tech",
}

Expand All @@ -91,29 +92,29 @@ def __init__(self, device: dict[str, Any], coordinator, api: Tech) -> None:

self.update_properties(coordinator.get_zones()[self._id])

def update_properties(self, device: dict[str, Any]) -> None:
def update_properties(self, device: ZoneElement) -> None:
"""Update the properties from device data."""
self._attr_name = device["description"]["name"]
self._attr_name = device.description.name

zone = device["zone"]
if zone["setTemperature"] is not None:
self._attr_target_temperature = zone["setTemperature"] / 10
zone = device.zone
if zone.setTemperature is not None:
self._attr_target_temperature = zone.setTemperature / 10

if zone["currentTemperature"] is not None:
self._attr_current_temperature = zone["currentTemperature"] / 10
if zone.currentTemperature is not None:
self._attr_current_temperature = zone.currentTemperature / 10

if zone["humidity"] is not None:
self._attr_current_humidity = zone["humidity"]
if zone.humidity is not None:
self._attr_current_humidity = zone.humidity

state = zone["flags"]["relayState"]
state = zone.flags.relayState
if state == "on":
self._attr_hvac_action = HVACAction.HEATING
elif state == "off":
self._attr_hvac_action = HVACAction.IDLE
else:
self._attr_hvac_action = HVACAction.OFF

mode = zone["zoneState"]
mode = zone.zoneState
self._attr_hvac_mode = HVACMode.HEAT if mode in ["zoneOn", "noAlarm"] else HVACMode.OFF

@callback
Expand All @@ -137,32 +138,6 @@ async def async_set_temperature(self, **kwargs: Any) -> None:
temperature,
ex
)

async def async_set_preset_mode(self, preset_mode: str) -> None:
try:
if self._attr_preset_mode == CHANGE_PRESET:
_LOGGER.debug("Preset mode change already in progress for %s", self._attr_name)
return

preset_mode_id = DEFAULT_PRESETS.index(preset_mode)
await self._api.set_module_menu(
self._udid,
"mu",
1000,
preset_mode_id
)

self._attr_preset_modes = [CHANGE_PRESET]
self._attr_preset_mode = CHANGE_PRESET

await self.coordinator.async_request_refresh()
except Exception as ex:
_LOGGER.error(
"Failed to set preset mode for %s to %s: %s",
self._attr_name,
preset_mode,
ex
)

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
Expand Down
35 changes: 19 additions & 16 deletions custom_components/tech/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for Tech Sterowniki integration."""
from typing import Any
import logging, uuid
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
Expand All @@ -7,6 +8,7 @@
from .const import DOMAIN # pylint:disable=unused-import
from .tech import Tech
from types import MappingProxyType
from .models.module import Module, UserModule

_LOGGER = logging.getLogger(__name__)

Expand All @@ -28,7 +30,7 @@ async def validate_input(hass: core.HomeAssistant, data):
if not await api.authenticate(data["username"], data["password"]):
raise InvalidAuth
modules = await api.list_modules()

# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
Expand All @@ -42,6 +44,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Tech Sterowniki."""

VERSION = 1
MINOR_VERSION = 1
# Pick one of the available connection classes in homeassistant/config_entries.py
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

Expand All @@ -50,19 +53,19 @@ async def async_step_user(self, user_input=None):
errors = {}
if user_input is not None:
try:
_LOGGER.debug("Context: " + str(self.context))
_LOGGER.debug("Context: " + str(self.context))
validated_input = await validate_input(self.hass, user_input)

modules = self._create_modules_array(validated_input=validated_input)
modules: list[UserModule] = self._create_modules_array(validated_input=validated_input)

if len(modules) == 0:
return self.async_abort("no_modules")

if len(modules) > 1:
for module in modules[1:len(modules)]:
await self.hass.config_entries.async_add(self._create_config_entry(module=module))
return self.async_create_entry(title=modules[0]["version"], data=modules[0])

return self.async_create_entry(title=modules[0].module_title, data=modules[0])
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
Expand All @@ -85,10 +88,10 @@ async def async_step_reauth(self, user_input=None):

return await self.async_step_user()

def _create_config_entry(self, module: dict) -> ConfigEntry:
def _create_config_entry(self, module: UserModule) -> ConfigEntry:
return ConfigEntry(
data=module,
title=module["version"],
title=module.module_title,
entry_id=uuid.uuid4().hex,
discovery_keys=MappingProxyType({}),
domain=DOMAIN,
Expand All @@ -99,24 +102,24 @@ def _create_config_entry(self, module: dict) -> ConfigEntry:
unique_id=None,
subentries_data=[])

def _create_modules_array(self, validated_input: dict) -> [dict]:
def _create_modules_array(self, validated_input: dict) -> list[UserModule]:
return [
self._create_module_dict(validated_input, module_dict)
for module_dict in validated_input["modules"]
]

def _create_module_dict(self, validated_input: dict, module_dict: dict) -> dict:
return {
"user_id": validated_input["user_id"],
"token": validated_input["token"],
"module": module_dict,
"version": module_dict["version"] + ": " + module_dict["name"]
}
def _create_module_dict(self, validated_input: dict, module: Module) -> UserModule:
return UserModule(
user_id=validated_input["user_id"],
token=validated_input["token"],
module=module,
module_title=module.version + ": " + module.name
)


class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate there is invalid auth."""
"""Error to indicate there is invalid auth."""
50 changes: 50 additions & 0 deletions custom_components/tech/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Models package for Tech API responses."""
from .module_menu import (
MenuElementOption,
MenuElementParams,
MenuElement,
ModuleMenuData,
ModuleMenuResponse,
)
from .module import (
Module,
ModuleData,
Zones,
ZoneElement,
Zone,
ZoneDescription,
ZoneMode,
ZoneSchedule,
ZoneFlags,
ScheduleInterval,
GlobalSchedules,
GlobalSchedule,
ControllerParameters,
ControllerMode,
Tile,
TileParams,
)

__all__ = [
"MenuElementOption",
"MenuElementParams",
"MenuElement",
"ModuleMenuData",
"ModuleMenuResponse",
"Module",
"ModuleData",
"Zones",
"ZoneElement",
"Zone",
"ZoneDescription",
"ZoneMode",
"ZoneSchedule",
"ZoneFlags",
"ScheduleInterval",
"GlobalSchedules",
"GlobalSchedule",
"ControllerParameters",
"ControllerMode",
"Tile",
"TileParams",
]
Loading