├── custom_components └── sst_cloud │ ├── const.py │ ├── manifest.json │ ├── translations │ └── en.json │ ├── __init__.py │ ├── number.py │ ├── config_flow.py │ ├── climate.py │ ├── switch.py │ ├── sensor.py │ ├── binary_sensor.py │ └── sst.py ├── hacs.json ├── .github └── workflows │ ├── hassfest.yml │ └── main.yml ├── info.md └── README.md /custom_components/sst_cloud/const.py: -------------------------------------------------------------------------------- 1 | DOMAIN = "sst_cloud" -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SST Cloud integration", 3 | "homeassistant": "2021.5.0", 4 | "country": "RU" 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yml: -------------------------------------------------------------------------------- 1 | name: Validate with hassfest 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - uses: "home-assistant/actions/hassfest@master" 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | hacs: 11 | name: HACS Action 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: HACS Action 15 | uses: "hacs/action@main" 16 | with: 17 | category: "integration" 18 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "sst_cloud", 3 | "name": "SST Cloud integration", 4 | "codeowners": ["@sergeylysov"], 5 | "config_flow": true, 6 | "dependencies": [], 7 | "documentation": "https://github.com/sergeylysov/sst_cloud", 8 | "homekit": {}, 9 | "iot_class": "cloud_polling", 10 | "issue_tracker": "https://github.com/sergeylysov/sst_cloud/issues", 11 | "requirements": ["requests","requests"], 12 | "version": "0.1.33", 13 | "zeroconf": [] 14 | } 15 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "already_configured": "already configured" 5 | }, 6 | "error": { 7 | "cannot_connect": "cannot connect", 8 | "invalid_auth": "invalid_auth", 9 | "unknown": "unknown" 10 | }, 11 | "step": { 12 | "user": { 13 | "data": { 14 | "password": "password", 15 | "username": "username" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from homeassistant.config_entries import ConfigEntry 4 | from homeassistant.core import HomeAssistant 5 | import asyncio 6 | from . import sst 7 | from .const import DOMAIN 8 | import logging 9 | _LOGGER = logging.getLogger(__name__) 10 | PLATFORMS: list[str] = ["climate","sensor", "binary_sensor", "switch","number"] 11 | 12 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 13 | #Создать объект с подключением к сервису 14 | sst1 = sst.SST(hass, entry.data["username"], entry.data["password"]) 15 | hass.data.setdefault(DOMAIN, {})[entry.entry_id] = sst1 16 | await hass.async_add_executor_job( 17 | sst1.pull_data 18 | ) 19 | 20 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 21 | return True 22 | 23 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 24 | unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 25 | if unload_ok: 26 | hass.data[DOMAIN].pop(entry.entry_id) 27 | 28 | return unload_ok 29 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/number.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from homeassistant.helpers.entity import Entity 3 | from .const import DOMAIN 4 | from .import sst 5 | from homeassistant.components.number import PLATFORM_SCHEMA, NumberEntity 6 | from homeassistant.const import * 7 | from homeassistant.exceptions import PlatformNotReady 8 | import logging 9 | _LOGGER = logging.getLogger(__name__) 10 | async def async_setup_entry(hass, config_entry, async_add_entities): 11 | sst1 = hass.data[DOMAIN][config_entry.entry_id] 12 | new_devices = [] 13 | for module in sst1.devices: 14 | if module.get_device_type == 6: 15 | new_devices.append(BrightSet(module,hass)) 16 | if new_devices: 17 | async_add_entities(new_devices) 18 | 19 | class BrightSet(NumberEntity): 20 | def __init__(self, module,hass): 21 | self._hass = hass 22 | self._module = module 23 | self._attr_unique_id = f"{self._module.get_device_id}_bright" 24 | self._attr_name = f"bright" 25 | self._attr_min_value = 0 26 | self._attr_max_value = 9 27 | self._attr_step = 1.0 28 | 29 | async def async_set_native_value(self, value: float): 30 | await self._hass.async_add_executor_job(self._module.set_bright,value) 31 | 32 | @property 33 | def native_step(self): 34 | return self._attr_step 35 | @property 36 | def native_max_value(self): 37 | return self._attr_max_value 38 | 39 | @property 40 | def native_min_value(self): 41 | return self._attr_min_value 42 | @property 43 | def native_value(self): 44 | return self._module.get_bright 45 | 46 | @property 47 | def device_info(self): 48 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | # SST Cloud Integration 2 | 3 | 4 | Unofficial SST Cloud integration for Home Assistant 5 | 6 | 7 | Не официальный плагин для интеграции с облаком SST Cloud 8 | 9 | На текущий момент работает с защитой от протечек. Протестировано на модуле Neptun Smart и ProW+ WiFi. 10 | 11 | А так же с терморегуляторами Equation и EcoSmart 25 12 | 13 | 14 | # Установка 15 | 16 | ## Вариант 1 - черезе HACS 17 | 1) Добавить репозиторий https://github.com/sergeylysov/sst_cloud/ в HACS. Перейти в HACS -> Интеграции -> Спарва сверху 3 точки -> Пользовательские репозитории, вставить ссылку и выбрать категорию "Интеграция" -> Добавить. 18 | 2) Перезагрузить HA. 19 | 3) Настроить интеграцию - Конфигурация -> Устройства и службы -> Добавить интеграцию, в поиске ввести SST, выбрать интеграцию "SST Cloud Integration" 20 | 21 | 22 | ![изображение](https://user-images.githubusercontent.com/18576858/166641784-4cb8b22b-7789-4bc6-942e-467077b82e06.png) 23 | 24 | (Если такая интеграция не находится, Ctrl + F5, чтобы обновить кэш браузера) 25 | 26 | Ввести логин и пароль. 27 | 28 | ![изображение](https://user-images.githubusercontent.com/18576858/187661775-a3f47f99-b9bb-427c-bf5b-c85fd4b93172.png) 29 | 30 | 31 | ## Вариант 2 - Вручную 32 | 1) Cкопировать каталог sst_cloud из репозитория в папку custom_components на сервере Home Assistant 33 | 2)Перезагрузить HA. 34 | 3) Настроить интеграцию - Конфигурация -> Устройства и службы -> Добавить интеграцию, в поиске ввести SST, выбрать интеграцию "SST Cloud Integration" 35 | 36 | 37 | ![изображение](https://user-images.githubusercontent.com/18576858/166641784-4cb8b22b-7789-4bc6-942e-467077b82e06.png) 38 | 39 | (Если такая интеграция не находится, Ctrl + F5, чтобы обновить кэш браузера) 40 | 41 | Ввести логин и пароль. 42 | 43 | ![изображение](https://user-images.githubusercontent.com/18576858/187661824-b0b3ee11-983a-4e59-bf56-4277e97bb2e0.png) 44 | 45 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/config_flow.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | from typing import Any 5 | 6 | import voluptuous as vol 7 | from homeassistant import config_entries, exceptions 8 | from homeassistant.core import HomeAssistant 9 | from .const import DOMAIN # pylint:disable=unused-import 10 | from .sst import SST 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | DATA_SCHEMA = vol.Schema({("username"): str, ("password"): str}) 15 | 16 | 17 | async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: 18 | if len(data["username"]) < 3: 19 | raise InvalidUsername 20 | if len(data["password"]) < 3: 21 | raise InvalidPassword 22 | 23 | return {"title": data["username"]} 24 | 25 | 26 | class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 27 | VERSION = 1 28 | 29 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 30 | 31 | async def async_step_user(self, user_input=None): 32 | 33 | errors = {} 34 | if user_input is not None: 35 | try: 36 | info = await validate_input(self.hass, user_input) 37 | 38 | return self.async_create_entry(title=info["title"], data=user_input) 39 | except InvalidUsername: 40 | errors["username"] = "invalid_username" 41 | except InvalidPassword: 42 | errors["password"] = "invalid_password" 43 | except Exception: # pylint: disable=broad-except 44 | _LOGGER.exception("Unexpected exception") 45 | errors["base"] = "unknown" 46 | 47 | # If there is no user input or there were errors, show the form again, including any errors that were found with the input. 48 | return self.async_show_form( 49 | step_id="user", data_schema=DATA_SCHEMA, errors=errors 50 | ) 51 | 52 | 53 | class InvalidUsername(exceptions.HomeAssistantError): 54 | """Error to indicate we cannot connect.""" 55 | 56 | 57 | class InvalidPassword(exceptions.HomeAssistantError): 58 | """Error to indicate there is an invalid hostname.""" 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SST Cloud Integration 2 | 3 | 4 | Unofficial SST Cloud integration for Home Assistant 5 | 6 | 7 | Не официальный плагин для интеграции с облаком SST Cloud 8 | 9 | # Внимание! 10 | ## В этой версии обновлений нет! В связи с отказом от SST Cloud интеграция дорабатываться не будет. 11 | ## Для пользователей Neptun Smart сделал интеграцию через modbus (спасибо @klim123123 за информацию) работает локально, главное чтобы хоть как то модуль был подключен к сети. 12 | ## пока версия сыровата, но пробовать можно, https://github.com/sergeylysov/neptun_smart_local, в HACS пока не добавил, но можно добавить через пользовательские репозитории, в последствии добавлю в HACS. 13 | 14 | # При переходе на сервис https://atlasiot.ru/ интеграция работать перестанет, обратно на sst-cloud устройства перевести будет нельзя! 15 | 16 | 17 | Теперь доступна утсановка из каталога HACS 18 | 19 | На текущий момент работает с защитой от протечек. Протестировано на модуле Neptun Smart и ProW+ WiFi. 20 | 21 | А так же с терморегуляторами Equation и EcoSmart 25 22 | 23 | 24 | 25 | # Установка 26 | 27 | ## Вариант 1 - через HACS 28 | 1) Перейти в HACS -> Добавить репозиторий -> в поиске ввести sst Cloud, кликнуть на найденый репозиторий и нажать загрузить. 29 | 30 | 31 | ![изображение](https://user-images.githubusercontent.com/18576858/224535384-4716011b-6037-420b-b320-cdc704e0c933.png) 32 | 33 | 34 | 2) Перезагрузить HA. 35 | 3) Настроить интеграцию - Конфигурация -> Устройства и службы -> Добавить интеграцию, в поиске ввести SST, выбрать интеграцию "SST Cloud Integration" 36 | 37 | 38 | ![изображение](https://user-images.githubusercontent.com/18576858/166641784-4cb8b22b-7789-4bc6-942e-467077b82e06.png) 39 | 40 | (Если такая интеграция не находится, Ctrl + F5, чтобы обновить кэш браузера) 41 | 42 | Ввести логин и пароль. 43 | 44 | ![изображение](https://user-images.githubusercontent.com/18576858/187661775-a3f47f99-b9bb-427c-bf5b-c85fd4b93172.png) 45 | 46 | 47 | ## Вариант 2 - Вручную 48 | 1) Cкопировать каталог sst_cloud из репозитория в папку custom_components на сервере Home Assistant 49 | 2)Перезагрузить HA. 50 | 3) Настроить интеграцию - Конфигурация -> Устройства и службы -> Добавить интеграцию, в поиске ввести SST, выбрать интеграцию "SST Cloud Integration" 51 | 52 | 53 | ![изображение](https://user-images.githubusercontent.com/18576858/166641784-4cb8b22b-7789-4bc6-942e-467077b82e06.png) 54 | 55 | (Если такая интеграция не находится, Ctrl + F5, чтобы обновить кэш браузера) 56 | 57 | Ввести логин и пароль. 58 | 59 | ![изображение](https://user-images.githubusercontent.com/18576858/187661824-b0b3ee11-983a-4e59-bf56-4277e97bb2e0.png) 60 | 61 | 62 | ## Добавление счечиков для контроля потребляния воды в HA 63 | к сожалению HA почему то не видит SensorStateClass.TOTAL видимо поэтому не отображает в энергии. 64 | Есть обходной путь, добавить в configuration.yaml для каждого счетчика: 65 | ``` 66 | template: 67 | - sensor: 68 | - name: "water counter" 69 | state: "{{ states('sensor.watercounter') }}" 70 | unit_of_measurement: m³ 71 | device_class: water 72 | state_class: total_increasing 73 | ``` 74 | Тогда будет еще одно устройство, которое уже можно добавить в энергию 75 | 76 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/climate.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.climate import ClimateEntity, ClimateEntityDescription 2 | from homeassistant.helpers.entity import Entity 3 | from homeassistant.config_entries import ConfigEntry 4 | from homeassistant.const import UnitOfTemperature 5 | from homeassistant.core import HomeAssistant 6 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 7 | from homeassistant.components.climate.const import ( 8 | HVACMode, 9 | ClimateEntityFeature 10 | ) 11 | from .const import DOMAIN 12 | from . import sst 13 | import logging 14 | import time 15 | 16 | _LOGGER = logging.getLogger(__name__) 17 | 18 | 19 | async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities): 20 | sst1 = hass.data[DOMAIN][config_entry.entry_id] 21 | new_devices = [] 22 | for module in sst1.devices: 23 | if module.get_device_type == 1: 24 | new_devices.append(Thermostat_equation(module,hass)) 25 | if module.get_device_type == 3: 26 | new_devices.append(Thermostat_equation(module,hass)) 27 | if module.get_device_type == 5: 28 | new_devices.append(Thermostat_equation(module,hass)) 29 | if module.get_device_type == 6: 30 | new_devices.append(Thermostat_equation(module,hass)) 31 | if module.get_device_type == 0: 32 | new_devices.append(Thermostat_equation(module,hass)) 33 | async_add_entities(new_devices) 34 | 35 | 36 | class Thermostat_equation(ClimateEntity): 37 | def __init__(self, module: sst.ThermostatEquation,hass: HomeAssistant): 38 | 39 | self._module = module 40 | self._hass1:HomeAssistant = hass 41 | self._attr_unique_id = f"{self._module.get_device_id}_{self._module.model_name}" 42 | self._attr_name = self._module.get_device_name 43 | # self._attr_hvac_modes = [HVAC_MODE_HEAT,HVAC_MODE_AUTO,HVAC_MODE_OFF] 44 | self._attr_hvac_modes = [HVACMode.AUTO,HVACMode.HEAT,HVACMode.OFF] 45 | self._attr_supported_features = (ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON) 46 | async def async_set_temperature(self, **kwargs) -> None: 47 | temp = kwargs.get("temperature", self.target_temperature) 48 | self.target_temp = temp 49 | await self._hass1.async_add_executor_job( 50 | self.set_temperature,temp) 51 | def set_temperature(self, temp): 52 | self._module.setTemperature(temp) 53 | @property 54 | def target_temperature_step(self) -> float: 55 | """Return the supported step of target temperature.""" 56 | return 1.0 57 | 58 | def turn_on(self): 59 | self._module.switchOn() 60 | def turn_off(self): 61 | self._module.switchOff() 62 | 63 | @property 64 | def hvac_mode(self) -> str: 65 | """Return hvac operation ie. heat, cool mode.""" 66 | 67 | if self._module.get_status == "on": 68 | if self._module.get_mode == "manual": 69 | return HVACMode.HEAT 70 | if self._module.get_mode == "chart": 71 | return HVACMode.AUTO 72 | return HVACMode.OFF 73 | 74 | def set_hvac_mode(self, hvac_mode: str) -> None: 75 | """Set new target hvac mode.""" 76 | # _LOGGER.warning(f"set_hvac_mode {hvac_mode}") 77 | if hvac_mode == HVACMode.OFF: 78 | # _LOGGER.warning("switch off") 79 | self._module.switchOff() 80 | if hvac_mode == HVACMode.HEAT: 81 | if self._module.get_status == "off": 82 | self._module.switchOn() 83 | time.sleep(5) 84 | # _LOGGER.warning(f"switchOn {hvac_mode}") 85 | self._module.switchToManual() 86 | if hvac_mode == HVACMode.AUTO: 87 | if self._module.get_status == "off": 88 | self._module.switchOn() 89 | time.sleep(5) 90 | self._module.switchToChart() 91 | 92 | 93 | 94 | 95 | 96 | 97 | # @property 98 | # def supported_features(self) -> int: 99 | # """Return the list of supported features.""" 100 | # features = ClimateEntityFeature.TARGET_TEMPERATURE|ClimateEntityFeature.TURN_OFF|ClimateEntityFeature.TURN_ON 101 | # return features 102 | 103 | def update(self) -> None: 104 | self._module.update() 105 | 106 | @property 107 | def temperature_unit(self) -> str: 108 | return UnitOfTemperature.CELSIUS 109 | 110 | @property 111 | def current_temperature(self) -> float: 112 | return self._module.get_current_temperature 113 | 114 | @property 115 | def target_temperature(self) -> float: 116 | return self._module.get_target_floor_temperature 117 | 118 | @property 119 | def device_info(self): 120 | return { 121 | "identifiers": {(DOMAIN, self._module.get_device_id)}, 122 | "name": self._module.get_device_name, 123 | "sw_version": "none", 124 | "model": self._module.model_name, 125 | "manufacturer": "SST", 126 | } 127 | 128 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/switch.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from homeassistant.components.switch import SwitchEntity 3 | from homeassistant.core import HomeAssistant 4 | from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType 5 | from . import sst 6 | from .const import DOMAIN 7 | import logging 8 | _LOGGER = logging.getLogger(__name__) 9 | 10 | 11 | async def async_setup_entry(hass, config_entry, async_add_entities): 12 | sst1 = hass.data[DOMAIN][config_entry.entry_id] 13 | new_devices = [] 14 | for module in sst1.devices: 15 | if module.get_device_type == 7 or module.get_device_type == 2 or module.get_device_type == 4: 16 | if module.get_device_type == 7: 17 | new_devices.append(WaterSwitchFirstGroup(module)) 18 | if module.get_grouping == "two_groups": 19 | new_devices.append(WaterSwitchSecondGroup(module)) 20 | if module.get_device_type == 2 or module.get_device_type == 4: 21 | new_devices.append(WaterSwitch(module)) 22 | new_devices.append((Washing_floors_mode(module))) 23 | async_add_entities(new_devices) 24 | 25 | 26 | class WaterSwitchFirstGroup(SwitchEntity): 27 | def __init__(self, module: sst.LeakModule): 28 | self._module = module 29 | self._attr_unique_id = f"{self._module.get_device_id}_WaterSwitchFirstGroup" 30 | if self._module.get_first_group_valves_state == "opened": 31 | self._is_on = True 32 | else: 33 | self._is_on = False 34 | 35 | @property 36 | def name(self): 37 | return "FirstGroup" 38 | 39 | @property 40 | def is_on(self): 41 | if self._module.get_first_group_valves_state == "opened": 42 | self._is_on = True 43 | else: 44 | self._is_on = False 45 | return self._is_on 46 | 47 | def turn_on(self): 48 | self._module.open_valve_first_group() 49 | self._is_on = True 50 | 51 | def turn_off(self): 52 | self._module.close_valve_first_group() 53 | self._is_on = False 54 | 55 | @property 56 | def device_info(self): 57 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 58 | 59 | @property 60 | def icon(self): 61 | return "mdi:pipe-valve" 62 | 63 | 64 | class WaterSwitchSecondGroup(SwitchEntity): 65 | def __init__(self, module: sst.LeakModule): 66 | self._module = module 67 | self._attr_unique_id = f"{self._module.get_device_id}_WaterSwitchSecondGroup" 68 | if self._module.get_second_group_valves_state == "opened": 69 | self._is_on = True 70 | else: 71 | self._is_on = False 72 | 73 | @property 74 | def name(self): 75 | return "SecondGroup" 76 | 77 | @property 78 | def is_on(self): 79 | if self._module.get_second_group_valves_state == "opened": 80 | self._is_on = True 81 | else: 82 | self._is_on = False 83 | return self._is_on 84 | 85 | def turn_on(self, **kwargs): 86 | self._module.open_valve_second_group() 87 | 88 | def turn_off(self, **kwargs): 89 | self._module.close_valve_second_group() 90 | 91 | @property 92 | def device_info(self): 93 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 94 | 95 | @property 96 | def icon(self): 97 | return "mdi:pipe-valve" 98 | 99 | class WaterSwitch(SwitchEntity): 100 | def __init__(self, module: sst.NeptunProwWiFi): 101 | self._module = module 102 | self._attr_unique_id = f"{self._module.get_device_id}_WaterSwitch" 103 | if self._module.get_valves_state == "opened": 104 | self._is_on = True 105 | else: 106 | self._is_on = False 107 | 108 | @property 109 | def name(self): 110 | return "WaterSwitch" 111 | 112 | @property 113 | def is_on(self): 114 | if self._module.get_valves_state == "opened": 115 | self._is_on = True 116 | else: 117 | self._is_on = False 118 | return self._is_on 119 | 120 | def turn_on(self, **kwargs): 121 | self._module.open_valve() 122 | 123 | def turn_off(self, **kwargs): 124 | self._module.close_valve() 125 | 126 | @property 127 | def device_info(self): 128 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 129 | 130 | @property 131 | def icon(self): 132 | return "mdi:pipe-valve" 133 | 134 | 135 | class Washing_floors_mode(SwitchEntity): 136 | def __init__(self, module): 137 | self._module = module 138 | self._attr_unique_id = f"{self._module.get_device_id}_washing_floors_mode" 139 | if self._module.get_washing_floors_mode == "on": 140 | self._is_on = True 141 | else: 142 | self._is_on = False 143 | 144 | @property 145 | def name(self): 146 | return "Washing_floors_mode" 147 | 148 | @property 149 | def is_on(self): 150 | if self._module.get_washing_floors_mode == "on": 151 | self._is_on = True 152 | else: 153 | self._is_on = False 154 | return self._is_on 155 | 156 | def turn_on(self): 157 | self._module.set_on_washing_floors_mode() 158 | self._is_on = True 159 | 160 | def turn_off(self): 161 | self._module.set_off_washing_floors_mode() 162 | self._is_on = False 163 | 164 | @property 165 | def device_info(self): 166 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 167 | 168 | @property 169 | def icon(self): 170 | return "mdi:pail" -------------------------------------------------------------------------------- /custom_components/sst_cloud/sensor.py: -------------------------------------------------------------------------------- 1 | from homeassistant.const import (UnitOfVolume,PERCENTAGE,UnitOfTemperature) 2 | from homeassistant.components.sensor import ( 3 | SensorDeviceClass, 4 | SensorEntity, 5 | SensorStateClass, 6 | ) 7 | from homeassistant.helpers.entity import Entity 8 | 9 | from .const import DOMAIN 10 | from . import sst 11 | import logging 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | async def async_setup_entry(hass, config_entry, async_add_entities): 16 | sst1 = hass.data[DOMAIN][config_entry.entry_id] 17 | new_devices = [] 18 | #Создать все сенсоры, счетчики, датчики протечки 19 | 20 | for module in sst1.devices: 21 | if module.get_device_type == 7 or module.get_device_type == 2 or module.get_device_type == 4: 22 | for counter in module.counters: 23 | new_devices.append(Counter(counter,module)) 24 | 25 | for wSensor in module.wirelessLeakSensors: 26 | new_devices.append(WirelessLeakSensorBattery(wSensor,module)) 27 | if module.get_device_type == 3 or module.get_device_type == 6: 28 | new_devices.append(FloorThemperatureSensor(module)) 29 | new_devices.append(AirThemperatureSensor(module)) 30 | if module.get_device_type == 5: 31 | new_devices.append(FloorThemperatureSensor(module)) 32 | 33 | if new_devices: 34 | async_add_entities(new_devices) 35 | 36 | 37 | class Counter(Entity): 38 | def __init__(self,counter: sst.Counter, module: sst.LeakModule): 39 | self._counter = counter 40 | self._module = module 41 | #Уникальный идентификатор 42 | self._attr_unique_id = f"{self._counter.counter_id}_WaterCounter" 43 | #Отображаемое имя 44 | self._attr_name = f"WaterCounter {self._counter.counter_name}" 45 | #Текущее значение 46 | self._state = self._counter.counter_value/1000 47 | 48 | @property 49 | def device_info(self): 50 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}, 51 | "name": "Water Meter", 52 | "sw_version": "none", 53 | "model": "Neptun Smart", 54 | "manufacturer": "SST", 55 | } 56 | 57 | @property 58 | def icon(self): 59 | return "mdi:counter" 60 | 61 | @property 62 | def state(self): 63 | self._state = self._counter.counter_value/1000 64 | return self._state 65 | 66 | @property 67 | def device_class(self): 68 | """The type of sensor""" 69 | return SensorDeviceClass.WATER 70 | 71 | @property 72 | def unit_of_measurement(self): 73 | """Unit of measurement of the sensor.""" 74 | return UnitOfVolume.CUBIC_METERS 75 | 76 | @property 77 | def state_class(self): 78 | """The state class of sensor""" 79 | return SensorStateClass.TOTAL 80 | 81 | class WirelessLeakSensorBattery(Entity): 82 | _attr_unit_of_measurement = PERCENTAGE 83 | _attr_device_class = SensorDeviceClass.BATTERY 84 | _attr_state_class = SensorStateClass.MEASUREMENT 85 | 86 | def __init__(self, wirelessLeakSensor, module): 87 | self._sensor = wirelessLeakSensor 88 | self._module = module 89 | # Уникальный идентификатор 90 | self._attr_unique_id = f"{self._sensor.get_wireless_leak_serial_number}_WireLessLeakSensorBattery" 91 | # Отображаемое имя 92 | self._attr_name = f"LeakSensor {self._sensor.get_wireless_leak_sensor_name}" 93 | # Текущее значение 94 | self._state = self._sensor.get_wireless_leak_sensor_battery_level 95 | 96 | @property 97 | def device_info(self): 98 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 99 | 100 | @property 101 | def icon(self): 102 | return "mdi:battery" 103 | 104 | @property 105 | def state(self): 106 | self._state = self._sensor.get_wireless_leak_sensor_battery_level 107 | return self._state 108 | 109 | 110 | class AirThemperatureSensor(Entity): 111 | _attr_unit_of_measurement = UnitOfTemperature.CELSIUS 112 | _attr_device_class = SensorDeviceClass.TEMPERATURE 113 | _attr_state_class = SensorStateClass.MEASUREMENT 114 | 115 | def __init__(self, module:sst.ThermostatEquation): 116 | self._module = module 117 | # Уникальный идентификатор 118 | self._attr_unique_id = f"{self._module.get_device_id}_AirThemperatureSensor" 119 | # Отображаемое имя 120 | self._attr_name = f"AirThemperatureSensor" 121 | # Текущее значение 122 | self._state = self._module.get_current_air_temperature 123 | 124 | 125 | @property 126 | def device_info(self): 127 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 128 | 129 | @property 130 | def icon(self): 131 | return "mdi:thermometer" 132 | 133 | @property 134 | def state(self): 135 | self._state = self._module.get_current_air_temperature 136 | return self._state 137 | 138 | 139 | class FloorThemperatureSensor(Entity): 140 | _attr_unit_of_measurement = UnitOfTemperature.CELSIUS 141 | _attr_device_class = SensorDeviceClass.TEMPERATURE 142 | _attr_state_class = SensorStateClass.MEASUREMENT 143 | 144 | def __init__(self, module:sst.ThermostatEquation): 145 | self._module = module 146 | # Уникальный идентификатор 147 | self._attr_unique_id = f"{self._module.get_device_id}_FloorThemperatureSensor" 148 | # Отображаемое имя 149 | self._attr_name = f"FloorThemperatureSensor" 150 | # Текущее значение 151 | self._state = self._module.get_current_temperature 152 | 153 | 154 | @property 155 | def device_info(self): 156 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 157 | 158 | @property 159 | def icon(self): 160 | return "mdi:thermometer" 161 | 162 | @property 163 | def state(self): 164 | self._state = self._module.get_current_temperature 165 | return self._state -------------------------------------------------------------------------------- /custom_components/sst_cloud/binary_sensor.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any, Callable, Tuple 3 | from homeassistant.helpers.entity import Entity 4 | from .const import DOMAIN 5 | from . import sst 6 | from homeassistant.components.binary_sensor import ( 7 | BinarySensorDeviceClass, 8 | ) 9 | import logging 10 | _LOGGER = logging.getLogger(__name__) 11 | async def async_setup_entry(hass, config_entry, async_add_entities): 12 | sst1 = hass.data[DOMAIN][config_entry.entry_id] 13 | 14 | 15 | new_devices = [] 16 | for module in sst1.devices: 17 | if module.get_device_type == 7 or module.get_device_type == 2 or module.get_device_type == 4: 18 | if module.get_device_type == 7: 19 | new_devices.append(MainModule(module)) 20 | if module.get_device_type == 2 or module.get_device_type == 4: 21 | new_devices.append(MainModuleNeptunProw(module)) 22 | for leakSensor in module.leakSensors: 23 | new_devices.append(LeakSensorAlert(leakSensor,module)) 24 | for wSensor in module.wirelessLeakSensors: 25 | new_devices.append(WirelessLeakSensorAlert(wSensor,module)) 26 | new_devices.append(WirelessLeakSensorLost(wSensor, module)) 27 | if module.get_device_type == 7: 28 | new_devices.append(WirelessLeakSensorBatteryDischarge(wSensor,module)) 29 | if module.get_device_type == 7: 30 | new_devices.append(FirstGroupModuleAlert(module)) 31 | if module.get_grouping == "two_groups": 32 | new_devices.append(SecondGroupModuleAlert(module)) 33 | 34 | if new_devices: 35 | async_add_entities(new_devices) 36 | 37 | 38 | 39 | 40 | class WirelessLeakSensorAlert(Entity): 41 | 42 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 43 | 44 | def __init__(self, wirelessLeakSensor, module): 45 | self._sensor = wirelessLeakSensor 46 | self._module = module 47 | # Уникальный идентификатор 48 | self._attr_unique_id = f"{self._sensor.get_wireless_leak_serial_number}_WireLessLeakSensorAlert" 49 | # Отображаемое имя 50 | self._attr_name = f"LeakSensor {self._sensor.get_wireless_leak_sensor_name}" 51 | # Текущее значение 52 | self._is_on = self._sensor.get_wireless_leak_sensor_alert_status 53 | 54 | 55 | @property 56 | def device_info(self): 57 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 58 | 59 | 60 | @property 61 | def icon(self): 62 | return "mdi:water-alert" 63 | 64 | @property 65 | def is_on(self): 66 | self._is_on = self._sensor.get_wireless_leak_sensor_alert_status 67 | return self._is_on 68 | @property 69 | def state(self) -> Literal["on", "off"] | None: 70 | """Return the state of the binary sensor.""" 71 | if (is_on := self.is_on) is None: 72 | return None 73 | return "on" if is_on else "off" 74 | 75 | 76 | class LeakSensorAlert(Entity): 77 | 78 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 79 | 80 | def __init__(self, leakSensor: sst.LeakSensor, module: sst.LeakModule): 81 | self._sensor = leakSensor 82 | self._module = module 83 | # Уникальный идентификатор 84 | self._attr_unique_id = f"{self._sensor.get_leak_sensor_name}_leakSensorAlertOfModule"+str(self._module.get_device_id) 85 | # Отображаемое имя 86 | self._attr_name = f"LeakSensor {self._sensor.get_frendly_name}" 87 | # Текущее значение 88 | self._is_on = self._sensor.get_leak_sensor_alarm_status 89 | if self._sensor.get_leak_sensor_alarm_status == "yes": 90 | self._is_on = True 91 | else: 92 | self._is_on = False 93 | @property 94 | def device_info(self): 95 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 96 | 97 | @property 98 | def icon(self): 99 | return "mdi:water-alert" 100 | 101 | @property 102 | def is_on(self): 103 | if self._sensor.get_leak_sensor_alarm_status == "yes": 104 | self._is_on = True 105 | else: 106 | self._is_on = False 107 | return self._is_on 108 | 109 | @property 110 | def state(self) -> Literal["on", "off"] | None: 111 | """Return the state of the binary sensor.""" 112 | if (is_on := self.is_on) is None: 113 | return None 114 | return "on" if is_on else "off" 115 | 116 | class WirelessLeakSensorLost(Entity): 117 | 118 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 119 | 120 | def __init__(self, wirelessLeakSensor, module: sst.LeakModule): 121 | self._sensor = wirelessLeakSensor 122 | self._module = module 123 | # Уникальный идентификатор 124 | self._attr_unique_id = f"{self._sensor.get_wireless_leak_serial_number}_WirelesleakSensorLost" 125 | # Отображаемое имя 126 | self._attr_name = f"WirelessLeakSensor Lost {self._sensor.get_wireless_leak_sensor_name}" 127 | # Текущее значение 128 | self._is_on = self._sensor.get_wireless_leak_sensor_lost_status 129 | 130 | @property 131 | def device_info(self): 132 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 133 | 134 | @property 135 | def icon(self): 136 | return "mdi:lan-disconnect" 137 | 138 | @property 139 | def is_on(self): 140 | self._is_on = self._sensor.get_wireless_leak_sensor_lost_status 141 | return self._is_on 142 | @property 143 | def state(self) -> Literal["on", "off"] | None: 144 | if (is_on := self.is_on) is None: 145 | return None 146 | return "on" if is_on else "off" 147 | 148 | 149 | class WirelessLeakSensorBatteryDischarge(Entity): 150 | 151 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 152 | 153 | def __init__(self, wirelessLeakSensor: sst.WirelessLeakSensor, module: sst.LeakModule): 154 | self._sensor = wirelessLeakSensor 155 | self._module = module 156 | # Уникальный идентификатор 157 | self._attr_unique_id = f"{self._sensor.get_wireless_leak_serial_number}_WirelesleakSensorBatteryDischarge" 158 | # Отображаемое имя 159 | self._attr_name = f"WirelessLeakSensor battery discharge {self._sensor.get_wireless_leak_sensor_name}" 160 | # Текущее значение 161 | self._is_on = self._sensor.get_wireless_leak_sensor_battery_discharge 162 | 163 | @property 164 | def device_info(self): 165 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 166 | 167 | @property 168 | def icon(self): 169 | return "mdi:battery-remove" 170 | 171 | @property 172 | def is_on(self): 173 | self._is_on = self._sensor.get_wireless_leak_sensor_battery_discharge 174 | return self._is_on 175 | 176 | @property 177 | def state(self) -> Literal["on", "off"] | None: 178 | if (is_on := self.is_on) is None: 179 | return None 180 | return "on" if is_on else "off" 181 | 182 | 183 | class FirstGroupModuleAlert(Entity): 184 | 185 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 186 | 187 | def __init__(self, module: sst.LeakModule): 188 | 189 | self._module = module 190 | # Уникальный идентификатор 191 | self._attr_unique_id = f"{self._module.get_device_id} first_group_alarm_module_alert" 192 | # Отображаемое имя 193 | self._attr_name = f"{self._module.get_device_name} Alert module first_group_alarm" 194 | # Текущее значение 195 | if self._module.first_group_alarm == "yes": 196 | self._is_on = True 197 | else: 198 | self._is_on = False 199 | 200 | @property 201 | def device_info(self): 202 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 203 | 204 | @property 205 | def icon(self): 206 | return "mdi:alert" 207 | 208 | @property 209 | def is_on(self): 210 | if self._module.first_group_alarm == "yes": 211 | self._is_on = True 212 | else: 213 | self._is_on = False 214 | return self._is_on 215 | @property 216 | def state(self) -> Literal["on", "off"] | None: 217 | if (is_on := self.is_on) is None: 218 | return None 219 | return "on" if is_on else "off" 220 | 221 | 222 | class SecondGroupModuleAlert(Entity): 223 | 224 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 225 | 226 | def __init__(self, module: sst.LeakModule): 227 | 228 | self._module = module 229 | # Уникальный идентификатор 230 | self._attr_unique_id = f"{self._module.get_device_id} second_group_alarm_module_alert" 231 | # Отображаемое имя 232 | self._attr_name = f"{self._module.get_device_name} Alert module second_group_alarm" 233 | # Текущее значение 234 | if self._module.second_group_alarm == "yes": 235 | self._is_on = True 236 | else: 237 | self._is_on = False 238 | 239 | @property 240 | def device_info(self): 241 | return {"identifiers": {(DOMAIN, self._module.get_device_id)}} 242 | 243 | @property 244 | def icon(self): 245 | return "mdi:alert" 246 | 247 | @property 248 | def is_on(self): 249 | if self._module.second_group_alarm == "yes": 250 | self._is_on = True 251 | else: 252 | self._is_on = False 253 | return self._is_on 254 | @property 255 | def state(self) -> Literal["on", "off"] | None: 256 | if (is_on := self.is_on) is None: 257 | return None 258 | return "on" if is_on else "off" 259 | 260 | class MainModule(Entity): 261 | 262 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 263 | 264 | def __init__(self, module: sst.LeakModule): 265 | 266 | self._module = module 267 | #self.is_on = False 268 | if (self._module.second_group_alarm == "true") | (self._module.first_group_alarm == "true"): 269 | self._is_on = True 270 | else: 271 | self._is_on = False 272 | # Уникальный идентификатор 273 | self._attr_unique_id = f"{self._module.get_device_id}_main_module" 274 | # Отображаемое имя 275 | self._attr_name = f"Neptun {self._module.get_device_name}" 276 | 277 | 278 | @property 279 | def device_info(self): 280 | return { 281 | "identifiers": {(DOMAIN, self._module.get_device_id)}, 282 | "name": self._module.get_device_name, 283 | "sw_version": "none", 284 | "model": "Neptun Smart", 285 | "manufacturer": "SST", 286 | } 287 | 288 | @property 289 | def icon(self): 290 | return "mdi:water-pump" 291 | 292 | @property 293 | def is_on(self) -> bool: 294 | return self._is_on 295 | 296 | #@final 297 | @property 298 | def state(self) -> Literal["on", "off"] | None: 299 | if (self._module.second_group_alarm == "true") | (self._module.first_group_alarm == "true"): 300 | self._is_on = True 301 | else: 302 | self._is_on = False 303 | if (is_on := self.is_on) is None: 304 | return None 305 | return "on" if is_on else "off" 306 | 307 | def update(self) -> None: 308 | self._module.update() 309 | 310 | 311 | class MainModuleNeptunProw(Entity): 312 | 313 | _attr_device_class = BinarySensorDeviceClass.PROBLEM 314 | 315 | def __init__(self, module: sst.NeptunProwWiFi): 316 | 317 | self._module = module 318 | self._is_on = self._module.alert_status 319 | # Уникальный идентификатор 320 | self._attr_unique_id = f"{self._module.get_device_id}_main_module" 321 | # Отображаемое имя 322 | self._attr_name = f"Neptun {self._module.get_device_name}" 323 | 324 | 325 | @property 326 | def device_info(self): 327 | return { 328 | "identifiers": {(DOMAIN, self._module.get_device_id)}, 329 | "name": self._module.get_device_name, 330 | "sw_version": "none", 331 | "model": "Neptun Prow+ WiFi", 332 | "manufacturer": "SST", 333 | } 334 | 335 | @property 336 | def icon(self): 337 | return "mdi:water-pump" 338 | 339 | @property 340 | def is_on(self) -> bool: 341 | return self._is_on 342 | 343 | #@final 344 | @property 345 | def state(self) -> Literal["on", "off"] | None: 346 | self._is_on = self._module.alert_status 347 | return self._is_on 348 | 349 | def update(self) -> None: 350 | self._module.update() 351 | -------------------------------------------------------------------------------- /custom_components/sst_cloud/sst.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import asyncio 4 | import logging 5 | import time 6 | from homeassistant.core import HomeAssistant 7 | 8 | SST_CLOUD_API_URL = "https://api.sst-cloud.com/" 9 | _LOGGER = logging.getLogger(__name__) 10 | 11 | class SST: 12 | 13 | def __init__(self, hass: HomeAssistant, username: str, password: str) -> None: 14 | self._username = username 15 | self._password = password 16 | self.devices = [] 17 | self.hass:HomeAssistant = hass 18 | 19 | 20 | def pull_data(self): 21 | response = requests.post(SST_CLOUD_API_URL + "auth/login/", 22 | json={"username": self._username, "password": self._password, "email": self._username}, 23 | headers={'Content-Type': 'application/json'}) 24 | self.key = json.loads(response.text)["key"] 25 | response = requests.get(SST_CLOUD_API_URL + "houses", headers={"Authorization": "Token " + self.key}) 26 | houses = json.loads(response.text) 27 | for house in houses: # перебираем все дома 28 | response = requests.get(SST_CLOUD_API_URL + 29 | "houses/" + str(house["id"]) + "/devices", 30 | headers={"Authorization": "Token " + self.key}) 31 | devices = json.loads(response.text) 32 | # Перебираем все устройства в доме 33 | for device in devices: 34 | response = requests.get(SST_CLOUD_API_URL + 35 | "houses/" + str(house["id"]) + "/devices/" + str(device["id"]), 36 | headers={"Authorization": "Token " + self.key}) 37 | json_device = json.loads(response.text) 38 | if json_device["type"] == 7: 39 | self.devices.append(LeakModule(json_device, self)) 40 | if json_device["type"] == 2 or json_device["type"] == 4: 41 | self.devices.append(NeptunProwWiFi(json_device, self)) 42 | if json_device["type"] == 3 or json_device["type"] == 1: 43 | self.devices.append(ThermostatEquation(json_device, self)) 44 | if json_device["type"] == 6: 45 | self.devices.append(ThermostatEcosmart25(json_device, self)) 46 | if json_device["type"] == 5: 47 | self.devices.append(ThermostatOKE20(json_device, self)) 48 | if json_device["type"] == 0: 49 | self.devices.append(ThermostatMCS300(json_device, self)) 50 | #Thermostat MCS300 51 | class ThermostatMCS300: 52 | 53 | def __init__(self,moduleDescription: json, sst: SST): 54 | self._sst = sst 55 | self.model_name = "MCS300" 56 | self._hass:HomeAssistant = sst.hass 57 | self.config = json.loads(moduleDescription["parsed_configuration"]) 58 | self._access_status = self.config["access_status"] 59 | self._id = moduleDescription["id"] 60 | self._device_name = moduleDescription["name"] 61 | self._house_id = moduleDescription["house"] 62 | self._type = moduleDescription["type"] #0 63 | self._current_temperature = self.config["current_temperature"] 64 | self._target_temperature = self.config["settings"]["temperature_manual"] 65 | self._status = self.config["settings"]["status"] 66 | self._mode = self.config["settings"]["mode"] 67 | self._update_flag = True 68 | 69 | def update(self) -> None: 70 | # Обновляем парметры модуля 71 | #Пропускаем обновление после изменения температуры, т.к. после обновления сразу прилетает старое значение 72 | if self._update_flag == True: 73 | response = requests.get(SST_CLOUD_API_URL + 74 | "houses/" + str(self._house_id) + "/devices/" + str(self._id), 75 | headers={"Authorization": "Token " + self._sst.key}) 76 | moduleDescription = json.loads(response.text) 77 | self.config = json.loads(moduleDescription["parsed_configuration"]) 78 | self._access_status = self.config["access_status"] 79 | self._device_name = moduleDescription["name"] 80 | self._house_id = moduleDescription["house"] 81 | self._type = moduleDescription["type"] # 0 82 | self._current_temperature = self.config["current_temperature"] 83 | self._target_temperature = self.config["settings"]["temperature_manual"] 84 | self._status = self.config["settings"]["status"] 85 | self._mode = self.config["settings"]["mode"] 86 | self._update_flag = True 87 | def setTemperature(self,temp) -> None: 88 | self._update_flag = False 89 | self._target_temperature = temp 90 | requests.post(SST_CLOUD_API_URL + 91 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/temperature/", 92 | json={"temperature_manual": temp}, 93 | headers={"Authorization": "Token " + self._sst.key}) 94 | def switchOn(self) -> None: 95 | self._update_flag = False 96 | 97 | requests.post(SST_CLOUD_API_URL + 98 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 99 | json={"status": "on"}, 100 | headers={"Authorization": "Token " + self._sst.key}) 101 | self._status="on" 102 | def switchOff(self) -> None: 103 | self._update_flag = False 104 | requests.post(SST_CLOUD_API_URL + 105 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 106 | json={"status": "off"}, 107 | headers={"Authorization": "Token " + self._sst.key}) 108 | self._status="off" 109 | 110 | def switchToManual(self) -> None: 111 | self._update_flag = False 112 | requests.post(SST_CLOUD_API_URL + 113 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 114 | json={"mode": "manual"}, 115 | headers={"Authorization": "Token " + self._sst.key}) 116 | self._mode="manual" 117 | 118 | def switchToChart(self) -> None: 119 | self._update_flag = False 120 | requests.post(SST_CLOUD_API_URL + 121 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 122 | json={"mode": "chart"}, 123 | headers={"Authorization": "Token " + self._sst.key}) 124 | self._mode="chart" 125 | 126 | 127 | 128 | def set_target_temp(self,temp): 129 | 130 | self._target_temperature = temp 131 | @property 132 | def get_status(self) -> str: 133 | return self._status 134 | @property 135 | def get_avalible_status(self) -> bool: 136 | if self._access_status == "available": 137 | return "true" 138 | else: 139 | return "false" 140 | 141 | @property 142 | def get_device_id(self) -> str: 143 | return self._id 144 | 145 | @property 146 | def get_device_name(self) -> str: 147 | return self._device_name 148 | 149 | @property 150 | def get_device_type(self) -> int: 151 | return self._type 152 | @property 153 | def get_current_temperature(self) -> int: 154 | return self._current_temperature 155 | 156 | @property 157 | def get_target_floor_temperature(self) -> int: 158 | return self._target_temperature 159 | @property 160 | def get_mode(self) -> str: 161 | return self._mode 162 | def get_config(self): 163 | return self.config 164 | 165 | 166 | #Thermostat OKE-20 167 | class ThermostatOKE20: 168 | 169 | def __init__(self,moduleDescription: json, sst: SST): 170 | self._sst = sst 171 | self.model_name = "OKE-20" 172 | self._hass:HomeAssistant = sst.hass 173 | self.config = json.loads(moduleDescription["parsed_configuration"]) 174 | self._access_status = self.config["access_status"] 175 | self._id = moduleDescription["id"] 176 | self._device_name = moduleDescription["name"] 177 | self._house_id = moduleDescription["house"] 178 | self._type = moduleDescription["type"] #5 179 | self._current_temperature_floor = self.config["current_temperature"]["temperature_floor"] 180 | self._target_temperature = self.config["settings"]["temperature_manual"] 181 | self._status = self.config["settings"]["status"] 182 | self._mode = self.config["settings"]["mode"] 183 | self._update_flag = True 184 | 185 | def update(self) -> None: 186 | # Обновляем парметры модуля 187 | #Пропускаем обновление после изменения температуры, т.к. после обновления сразу прилетает старое значение 188 | if self._update_flag == True: 189 | response = requests.get(SST_CLOUD_API_URL + 190 | "houses/" + str(self._house_id) + "/devices/" + str(self._id), 191 | headers={"Authorization": "Token " + self._sst.key}) 192 | moduleDescription = json.loads(response.text) 193 | self.config = json.loads(moduleDescription["parsed_configuration"]) 194 | self._access_status = self.config["access_status"] 195 | self._device_name = moduleDescription["name"] 196 | self._house_id = moduleDescription["house"] 197 | self._type = moduleDescription["type"] # 5 198 | self._current_temperature_floor = self.config["current_temperature"]["temperature_floor"] 199 | self._target_temperature = self.config["settings"]["temperature_manual"] 200 | self._status = self.config["settings"]["status"] 201 | self._mode = self.config["settings"]["mode"] 202 | self._update_flag = True 203 | def setTemperature(self,temp) -> None: 204 | self._update_flag = False 205 | self._target_temperature = temp 206 | requests.post(SST_CLOUD_API_URL + 207 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/temperature/", 208 | json={"temperature_manual": temp}, 209 | headers={"Authorization": "Token " + self._sst.key}) 210 | def switchOn(self) -> None: 211 | self._update_flag = False 212 | 213 | requests.post(SST_CLOUD_API_URL + 214 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 215 | json={"status": "on"}, 216 | headers={"Authorization": "Token " + self._sst.key}) 217 | self._status="on" 218 | def switchOff(self) -> None: 219 | self._update_flag = False 220 | requests.post(SST_CLOUD_API_URL + 221 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 222 | json={"status": "off"}, 223 | headers={"Authorization": "Token " + self._sst.key}) 224 | self._status="off" 225 | 226 | def switchToManual(self) -> None: 227 | self._update_flag = False 228 | requests.post(SST_CLOUD_API_URL + 229 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 230 | json={"mode": "manual"}, 231 | headers={"Authorization": "Token " + self._sst.key}) 232 | self._mode="manual" 233 | 234 | def switchToChart(self) -> None: 235 | self._update_flag = False 236 | requests.post(SST_CLOUD_API_URL + 237 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 238 | json={"mode": "chart"}, 239 | headers={"Authorization": "Token " + self._sst.key}) 240 | self._mode="chart" 241 | 242 | 243 | 244 | def set_target_temp(self,temp): 245 | 246 | self._target_temperature = temp 247 | @property 248 | def get_status(self) -> str: 249 | return self._status 250 | @property 251 | def get_avalible_status(self) -> bool: 252 | if self._access_status == "available": 253 | return "true" 254 | else: 255 | return "false" 256 | 257 | @property 258 | def get_device_id(self) -> str: 259 | return self._id 260 | 261 | @property 262 | def get_device_name(self) -> str: 263 | return self._device_name 264 | 265 | @property 266 | def get_device_type(self) -> int: 267 | return self._type 268 | @property 269 | def get_current_temperature(self) -> int: 270 | return self._current_temperature_floor 271 | 272 | @property 273 | def get_target_floor_temperature(self) -> int: 274 | return self._target_temperature 275 | @property 276 | def get_mode(self) -> str: 277 | return self._mode 278 | def get_config(self): 279 | return self.config 280 | 281 | #Thermostat Equation 282 | class ThermostatEquation: 283 | 284 | def __init__(self,moduleDescription: json, sst: SST): 285 | self._sst = sst 286 | self.model_name = "Equation" 287 | self._hass:HomeAssistant = sst.hass 288 | self.config = json.loads(moduleDescription["parsed_configuration"]) 289 | self._access_status = self.config["access_status"] 290 | self._id = moduleDescription["id"] 291 | self._device_name = moduleDescription["name"] 292 | self._house_id = moduleDescription["house"] 293 | self._type = moduleDescription["type"] #3 294 | self._current_temperature_air = self.config["current_temperature"]["temperature_air"] 295 | self._current_temperature_floor = self.config["current_temperature"]["temperature_floor"] 296 | self._target_temperature = self.config["settings"]["temperature_manual"] 297 | self._status = self.config["settings"]["status"] 298 | self._mode = self.config["settings"]["mode"] 299 | self._update_flag = True 300 | 301 | def update(self) -> None: 302 | # Обновляем парметры модуля 303 | #Пропускаем обновление после изменения температуры, т.к. после обновления сразу прилетает старое значение 304 | if self._update_flag == True: 305 | response = requests.get(SST_CLOUD_API_URL + 306 | "houses/" + str(self._house_id) + "/devices/" + str(self._id), 307 | headers={"Authorization": "Token " + self._sst.key}) 308 | moduleDescription = json.loads(response.text) 309 | self.config = json.loads(moduleDescription["parsed_configuration"]) 310 | self._access_status = self.config["access_status"] 311 | self._device_name = moduleDescription["name"] 312 | self._house_id = moduleDescription["house"] 313 | self._type = moduleDescription["type"] # 3 314 | self._current_temperature_air = self.config["current_temperature"]["temperature_air"] 315 | self._current_temperature_floor = self.config["current_temperature"]["temperature_floor"] 316 | self._target_temperature = self.config["settings"]["temperature_manual"] 317 | self._status = self.config["settings"]["status"] 318 | self._mode = self.config["settings"]["mode"] 319 | self._update_flag = True 320 | def setTemperature(self,temp) -> None: 321 | self._update_flag = False 322 | self._target_temperature = temp 323 | requests.post(SST_CLOUD_API_URL + 324 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/temperature/", 325 | json={"temperature_manual": temp}, 326 | headers={"Authorization": "Token " + self._sst.key}) 327 | def switchOn(self) -> None: 328 | self._update_flag = False 329 | 330 | requests.post(SST_CLOUD_API_URL + 331 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 332 | json={"status": "on"}, 333 | headers={"Authorization": "Token " + self._sst.key}) 334 | self._status="on" 335 | def switchOff(self) -> None: 336 | self._update_flag = False 337 | requests.post(SST_CLOUD_API_URL + 338 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/status/", 339 | json={"status": "off"}, 340 | headers={"Authorization": "Token " + self._sst.key}) 341 | self._status="off" 342 | 343 | def switchToManual(self) -> None: 344 | self._update_flag = False 345 | requests.post(SST_CLOUD_API_URL + 346 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 347 | json={"mode": "manual"}, 348 | headers={"Authorization": "Token " + self._sst.key}) 349 | self._mode="manual" 350 | 351 | def switchToChart(self) -> None: 352 | self._update_flag = False 353 | requests.post(SST_CLOUD_API_URL + 354 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/mode/", 355 | json={"mode": "chart"}, 356 | headers={"Authorization": "Token " + self._sst.key}) 357 | self._mode="chart" 358 | 359 | # 360 | # 361 | # def set_target_temp(self,temp): 362 | # 363 | # self._target_temperature = temp 364 | @property 365 | def get_status(self) -> str: 366 | return self._status 367 | @property 368 | def get_avalible_status(self) -> bool: 369 | if self._access_status == "available": 370 | return "true" 371 | else: 372 | return "false" 373 | 374 | @property 375 | def get_device_id(self) -> str: 376 | return self._id 377 | 378 | @property 379 | def get_device_name(self) -> str: 380 | return self._device_name 381 | 382 | @property 383 | def get_device_type(self) -> int: 384 | return self._type 385 | @property 386 | def get_current_temperature(self) -> int: 387 | return self._current_temperature_floor 388 | 389 | @property 390 | def get_current_air_temperature(self) -> int: 391 | return self._current_temperature_air 392 | @property 393 | def get_target_floor_temperature(self) -> int: 394 | return self._target_temperature 395 | @property 396 | def get_mode(self) -> str: 397 | return self._mode 398 | def get_config(self): 399 | return self.config 400 | 401 | #Thermostat Ecosmart25 402 | class ThermostatEcosmart25(ThermostatEquation): 403 | 404 | def __init__(self,moduleDescription: json, sst: SST): 405 | super().__init__(moduleDescription,sst) 406 | self._bright = super().get_config()["settings"]["bright"] 407 | self._sensor_air = super().get_config()["settings"]["sensor_set"]["air"] #selected/unselected 408 | self._sensor_floor = super().get_config()["settings"]["sensor_set"]["floor"] # selected/unselected 409 | self.model_name = "EcoSmart 25" 410 | 411 | def update(self) -> None: 412 | super().update() 413 | self._bright = super().get_config()["settings"]["bright"] 414 | self._sensor_air = super().get_config()["settings"]["sensor_set"]["air"] # selected/unselected 415 | self._sensor_floor = super().get_config()["settings"]["sensor_set"]["floor"] # selected/unselected 416 | 417 | @property 418 | def get_bright(self) -> int: 419 | return self._bright 420 | 421 | @property 422 | def get_current_temperature(self) -> int: 423 | if self._sensor_air == "selected": 424 | return self._current_temperature_air 425 | if self._sensor_floor == "selected": 426 | return self._current_temperature_floor 427 | 428 | def set_bright(self,bright:int) -> None: 429 | _LOGGER.warning(f"rq set bright {bright}") 430 | self._update_flag = False 431 | requests.post(SST_CLOUD_API_URL + 432 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/bright/", 433 | json={"bright": bright}, 434 | headers={"Authorization": "Token " + self._sst.key}) 435 | self._bright = bright 436 | #Neptun ProW+ WiFi 437 | class NeptunProwWiFi: 438 | def __init__(self, moduleDescription: json, sst: SST): 439 | self._sst = sst 440 | config = json.loads(moduleDescription["parsed_configuration"]) 441 | self._access_status = config["access_status"] # Main device "available" is true 442 | self._device_name = moduleDescription["name"] 443 | self._house_id = moduleDescription["house"] 444 | self._type = moduleDescription["type"] #2 or 4 445 | self._id = moduleDescription["id"] 446 | self._valves_state = config["settings"]["valve_settings"] 447 | self.alert_status = config["settings"]["status"]["alert"] 448 | self._dry_flag = config["settings"]["dry_flag"] 449 | self.counters = [] 450 | response = requests.get(SST_CLOUD_API_URL + 451 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/counters", 452 | headers={"Authorization": "Token " + self._sst.key}) 453 | countersJson = json.loads(response.text) 454 | for counterDesc in countersJson: 455 | self.counters.append(Counter(counterDesc["id"], counterDesc["name"], counterDesc["value"])) 456 | self.leakSensors = [] 457 | # Перебрать статус всех проводных датчиков протечки 458 | i = 0 459 | for leakSensorDesc in config["lines_status"]: 460 | self.leakSensors.append( 461 | LeakSensor(leakSensorDesc, config["lines_status"][leakSensorDesc],moduleDescription["line_names"][i])) 462 | i = i + 1 463 | self.wirelessLeakSensors = [] 464 | response = requests.get(SST_CLOUD_API_URL + 465 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/wireless_sensors", 466 | headers={"Authorization": "Token " + self._sst.key}) 467 | wirelessSensors = json.loads(response.text) 468 | # Перебираем все беспроводные датчики 469 | for wirelessSensorDesc in wirelessSensors: 470 | self.wirelessLeakSensors.append(WirelessLeakSensor443(wirelessSensorDesc)) 471 | 472 | def close_valve(self): 473 | requests.post(SST_CLOUD_API_URL + 474 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/valve_settings/", 475 | json={"valve_settings":"closed"}, 476 | headers={"Authorization": "Token " + self._sst.key}) 477 | self._valves_state = "closed" 478 | 479 | def open_valve(self): 480 | requests.post(SST_CLOUD_API_URL + 481 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/valve_settings/", 482 | json={"valve_settings":"opened"}, 483 | headers={"Authorization": "Token " + self._sst.key}) 484 | self._valves_state = "opened" 485 | def set_on_washing_floors_mode(self): 486 | requests.post(SST_CLOUD_API_URL + 487 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/dry_flag/", 488 | json={"dry_flag":"on"}, 489 | headers={"Authorization": "Token " + self._sst.key}) 490 | self._dry_flag = "on" 491 | def set_off_washing_floors_mode(self): 492 | requests.post(SST_CLOUD_API_URL + 493 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/dry_flag/", 494 | json={"dry_flag": "off"}, 495 | headers={"Authorization": "Token " + self._sst.key}) 496 | self._dry_flag = "off" 497 | 498 | 499 | @property 500 | def get_avalible_status(self) -> bool: 501 | if self._access_status == "available": 502 | return "true" 503 | else: 504 | return "false" 505 | 506 | @property 507 | def get_device_id(self) -> str: 508 | return self._id 509 | 510 | @property 511 | def get_device_name(self) -> str: 512 | return self._device_name 513 | 514 | @property 515 | def get_device_type(self) -> int: 516 | return self._type 517 | 518 | @property 519 | def get_valves_state(self) -> str: 520 | # opened or closed 521 | return self._valves_state 522 | @property 523 | def get_washing_floors_mode(self)-> str: 524 | return self._dry_flag 525 | 526 | def update(self) -> None: 527 | # Обновляем парметры модуля 528 | response = requests.get(SST_CLOUD_API_URL + 529 | "houses/" + str(self._house_id) + "/devices/" + str(self._id), 530 | headers={"Authorization": "Token " + self._sst.key}) 531 | json_device = json.loads(response.text) 532 | config = json.loads(json_device["parsed_configuration"]) 533 | self._access_status = config["access_status"] # Main device "available" is true 534 | self._device_name = json_device["name"] 535 | self._house_id = json_device["house"] 536 | self._type = json_device["type"] 537 | self._id = json_device["id"] 538 | self._valves_state = config["settings"]["valve_settings"] 539 | self.alert_status = config["settings"]["status"]["alert"] 540 | self._dry_flag = config["settings"]["dry_flag"] 541 | # Обновляем статус счетчиков 542 | response = requests.get(SST_CLOUD_API_URL + 543 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/counters", 544 | headers={"Authorization": "Token " + self._sst.key}) 545 | countersJson = json.loads(response.text) 546 | for counter in self.counters: 547 | counter.update(countersJson) 548 | 549 | # Обновляем статус датчиков 550 | for leakSensor in self.leakSensors: 551 | leakSensor.update(config["lines_status"]) 552 | # Обновляем статус беспроводных датчиков 553 | response = requests.get(SST_CLOUD_API_URL + 554 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/wireless_sensors", 555 | headers={"Authorization": "Token " + self._sst.key}) 556 | # print(response.text) 557 | wirelessSensorsJson = json.loads(response.text) 558 | for wirelessSensor in self.wirelessLeakSensors: 559 | wirelessSensor.update(wirelessSensorsJson) 560 | 561 | #Neptun Smart 562 | class LeakModule: 563 | def __init__(self, moduleDescription: json, sst: SST): 564 | self._sst = sst 565 | config = json.loads(moduleDescription["parsed_configuration"]) 566 | self._access_status = config["access_status"] # Main device "available" is true 567 | self._device_id = config["device_id"] 568 | self._device_name = moduleDescription["name"] 569 | self._house_id = moduleDescription["house"] 570 | self._type = moduleDescription["type"] #7 571 | self._id = moduleDescription["id"] 572 | self._first_group_valves_state = config["module_settings"]["module_config"]["first_group_valves_state"] 573 | self._second_group_valves_state = config["module_settings"]["module_config"]["second_group_valves_state"] 574 | self.first_group_alarm = config["module_settings"]["module_status"]["first_group_alarm"] 575 | self.second_group_alarm = config["module_settings"]["module_status"]["second_group_alarm"] 576 | self._washing_floors_mode = config["module_settings"]["module_status"]["washing_floors_mode"] 577 | self._grouping = config["module_settings"]["module_config"]["grouping"] 578 | self.counters = [] 579 | response = requests.get(SST_CLOUD_API_URL + 580 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/counters", 581 | headers={"Authorization": "Token " + self._sst.key}) 582 | countersJson = json.loads(response.text) 583 | for counterDesc in countersJson: 584 | self.counters.append(Counter(counterDesc["id"], counterDesc["name"], counterDesc["value"])) 585 | 586 | self.leakSensors = [] 587 | # Перебрать статус всех проводных датчиков протечки 588 | i=0 589 | for leakSensorDesc in config["module_settings"]["wire_lines_status"]: 590 | self.leakSensors.append( 591 | LeakSensor(leakSensorDesc, config["module_settings"]["wire_lines_status"][leakSensorDesc],moduleDescription["line_names"][i])) 592 | i=i+1 593 | self.wirelessLeakSensors = [] 594 | response = requests.get(SST_CLOUD_API_URL + 595 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/wireless_sensors", 596 | headers={"Authorization": "Token " + self._sst.key}) 597 | wirelessSensors = json.loads(response.text) 598 | # Перебираем все беспроводные датчики 599 | for wirelessSensorDesc in wirelessSensors: 600 | self.wirelessLeakSensors.append(WirelessLeakSensor(wirelessSensorDesc)) 601 | 602 | def close_valve_first_group(self): 603 | if self._grouping == "two_groups": 604 | requests.patch(SST_CLOUD_API_URL + 605 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 606 | json={"module_config": {"first_group_valves_state": "closed"}}, 607 | headers={"Authorization": "Token " + self._sst.key}) 608 | else: 609 | requests.patch(SST_CLOUD_API_URL + 610 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 611 | json={"module_config":{"first_group_valves_state":"closed","second_group_valves_state":"closed"}}, 612 | headers={"Authorization": "Token " + self._sst.key}) 613 | self._first_group_valves_state = "closed" 614 | 615 | def open_valve_first_group(self): 616 | if self._grouping == "two_groups": 617 | requests.patch(SST_CLOUD_API_URL + 618 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 619 | json={"module_config": {"first_group_valves_state": "opened"}}, 620 | headers={"Authorization": "Token " + self._sst.key}) 621 | else: 622 | requests.patch(SST_CLOUD_API_URL + 623 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 624 | json={"module_config":{"first_group_valves_state":"opened","second_group_valves_state":"opened"}}, 625 | headers={"Authorization": "Token " + self._sst.key}) 626 | self._first_group_valves_state = "opened" 627 | 628 | def close_valve_second_group(self): 629 | requests.patch(SST_CLOUD_API_URL + 630 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 631 | json={"module_config": {"second_group_valves_state": "closed"}}, 632 | headers={"Authorization": "Token " + self._sst.key}) 633 | self._second_group_valves_state = "closed" 634 | 635 | def open_valve_second_group(self): 636 | requests.patch(SST_CLOUD_API_URL + 637 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 638 | json={"module_config": {"second_group_valves_state": "opened"}}, 639 | headers={"Authorization": "Token " + self._sst.key}) 640 | self._second_group_valves_state = "opened" 641 | 642 | def set_on_washing_floors_mode(self): 643 | requests.patch(SST_CLOUD_API_URL + 644 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 645 | json={"washing_floors_mode":"on"}, 646 | headers={"Authorization": "Token " + self._sst.key}) 647 | self._washing_floors_mode = "on" 648 | def set_off_washing_floors_mode(self): 649 | requests.patch(SST_CLOUD_API_URL + 650 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/module_settings/", 651 | json={"washing_floors_mode": "off"}, 652 | headers={"Authorization": "Token " + self._sst.key}) 653 | self._washing_floors_mode = "off" 654 | 655 | 656 | @property 657 | def get_avalible_status(self) -> bool: 658 | if self._access_status == "available": 659 | return "true" 660 | else: 661 | return "false" 662 | 663 | @property 664 | def get_device_id(self) -> str: 665 | return self._id 666 | 667 | @property 668 | def get_device_name(self) -> str: 669 | return self._device_name 670 | 671 | @property 672 | def get_device_type(self) -> int: 673 | return self._type 674 | 675 | @property 676 | def get_first_group_valves_state(self) -> str: 677 | # opened or closed 678 | return self._first_group_valves_state 679 | 680 | @property 681 | def get_second_group_valves_state(self) -> str: 682 | # opened or closed 683 | return self._second_group_valves_state 684 | 685 | @property 686 | def get_washing_floors_mode(self)-> str: 687 | return self._washing_floors_mode 688 | @property 689 | def get_grouping(self)-> str: 690 | return self._grouping 691 | 692 | def update(self) -> None: 693 | # Обновляем парметры модуля 694 | response = requests.get(SST_CLOUD_API_URL + 695 | "houses/" + str(self._house_id) + "/devices/" + str(self._id), 696 | headers={"Authorization": "Token " + self._sst.key}) 697 | json_device = json.loads(response.text) 698 | config = json.loads(json_device["parsed_configuration"]) 699 | self._access_status = config["access_status"] # Main device "available" is true 700 | self._device_id = config["device_id"] 701 | self._device_name = json_device["name"] 702 | self._house_id = json_device["house"] 703 | self._type = json_device["type"] 704 | self._id = json_device["id"] 705 | self._first_group_valves_state = config["module_settings"]["module_config"]["first_group_valves_state"] 706 | self._second_group_valves_state = config["module_settings"]["module_config"]["second_group_valves_state"] 707 | self.first_group_alarm = config["module_settings"]["module_status"]["first_group_alarm"] 708 | self.second_group_alarm = config["module_settings"]["module_status"]["second_group_alarm"] 709 | self._washing_floors_mode = config["module_settings"]["module_status"]["washing_floors_mode"] 710 | # Обновляем статус счетчиков 711 | response = requests.get(SST_CLOUD_API_URL + 712 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/counters", 713 | headers={"Authorization": "Token " + self._sst.key}) 714 | countersJson = json.loads(response.text) 715 | for counter in self.counters: 716 | counter.update(countersJson) 717 | 718 | # Обновляем статус датчиков 719 | for leakSensor in self.leakSensors: 720 | leakSensor.update(config["module_settings"]["wire_lines_status"]) 721 | # Обновляем статус беспроводных датчиков 722 | response = requests.get(SST_CLOUD_API_URL + 723 | "houses/" + str(self._house_id) + "/devices/" + str(self._id) + "/wireless_sensors", 724 | headers={"Authorization": "Token " + self._sst.key}) 725 | # print(response.text) 726 | wirelessSensorsJson = json.loads(response.text) 727 | for wirelessSensor in self.wirelessLeakSensors: 728 | wirelessSensor.update(wirelessSensorsJson) 729 | 730 | 731 | class Counter: 732 | def __init__(self, id: int, name: str, value: int): 733 | self._id = id 734 | self.name = name 735 | self._value = value 736 | 737 | @property 738 | def counter_id(self) -> int: 739 | return self._id 740 | 741 | @property 742 | def counter_name(self) -> str: 743 | return self.name 744 | 745 | @property 746 | def counter_value(self) -> int: 747 | return self._value 748 | 749 | def update(self, countersJson: json) -> None: 750 | for counterJson in countersJson: 751 | if self._id == counterJson["id"]: 752 | if (counterJson["value"] > (self._value - 5000)) and (counterJson["value"] < (self._value + 5000)): 753 | self._value = counterJson["value"] 754 | 755 | 756 | class LeakSensor: 757 | def __init__(self, name: str, status: str): 758 | self._name = name 759 | self._alarm = status 760 | def __init__(self, name: str, status: str, frendly_name: str): 761 | self._name = name 762 | self._alarm = status 763 | self._frendly_name = frendly_name 764 | 765 | @property 766 | def get_frendly_name(self) -> str: 767 | return self._frendly_name 768 | 769 | @property 770 | def get_leak_sensor_name(self) -> str: 771 | return self._name 772 | 773 | @property 774 | def get_leak_sensor_alarm_status(self) -> bool: 775 | return self._alarm 776 | 777 | def update(self, LeakSensorsDesc: json): 778 | self._alarm = LeakSensorsDesc[self._name] 779 | # print("sensor "+ self._name +" status updated") 780 | 781 | 782 | class WirelessLeakSensor: 783 | def __init__(self, wirelessLeakSensorDescription): 784 | self._type = 868 785 | self._name = wirelessLeakSensorDescription["name"] 786 | self._battery_level = wirelessLeakSensorDescription["battery"] 787 | self._alert = wirelessLeakSensorDescription["attention"] 788 | self._lost = wirelessLeakSensorDescription["sensor_lost"] #! 789 | self._battery_discharge = wirelessLeakSensorDescription["battery_discharge"] #! 790 | self._serial = wirelessLeakSensorDescription["serial_number"] #! 791 | 792 | 793 | @property 794 | def get_wireless_leak_serial_number(self) -> str: 795 | return self._serial 796 | 797 | @property 798 | def get_wireless_leak_sensor_name(self) -> str: 799 | return self._name 800 | 801 | @property 802 | def get_wireless_leak_sensor_battery_level(self) -> int: 803 | return self._battery_level 804 | 805 | @property 806 | def get_wireless_leak_sensor_alert_status(self) -> bool: 807 | return self._alert 808 | 809 | @property 810 | def get_wireless_leak_sensor_lost_status(self) -> bool: 811 | return self._lost 812 | 813 | @property 814 | def get_wireless_leak_sensor_battery_discharge(self) -> bool: 815 | return self._battery_discharge 816 | 817 | @property 818 | def get_type(self) -> int: 819 | return self._type 820 | 821 | def update(self, wireless_sensor_description: str): 822 | for sensor_desc in wireless_sensor_description: 823 | if sensor_desc["serial_number"] == self._serial: 824 | self._battery_level = sensor_desc["battery"] 825 | self._alert = sensor_desc["attention"] 826 | self._lost = sensor_desc["sensor_lost"] 827 | self._battery_discharge = sensor_desc["battery_discharge"] 828 | 829 | 830 | class WirelessLeakSensor443: 831 | def __init__(self, wirelessLeakSensorDescription): 832 | self._name = wirelessLeakSensorDescription["name"] 833 | self._battery_level = wirelessLeakSensorDescription["battery"] 834 | self._alert = wirelessLeakSensorDescription["attention"] 835 | self._line = wirelessLeakSensorDescription["line"] 836 | self._signal_level = wirelessLeakSensorDescription["signal_level"] 837 | self._type = 443 838 | 839 | 840 | @property 841 | def get_type(self) -> int: 842 | return self._type 843 | 844 | @property 845 | def get_line(self) -> int: 846 | return self._line 847 | 848 | @property 849 | def get_wireless_leak_sensor_name(self) -> str: 850 | return self._name 851 | 852 | @property 853 | def get_wireless_leak_sensor_battery_level(self) -> int: 854 | return self._battery_level 855 | 856 | @property 857 | def get_wireless_leak_sensor_alert_status(self) -> bool: 858 | return self._alert 859 | 860 | @property 861 | def get_wireless_leak_serial_number(self) -> str: 862 | return self._name + "line" + str(self._line) 863 | 864 | @property 865 | def get_wireless_leak_sensor_lost_status(self) -> bool: 866 | if self._signal_level == 0: 867 | return True 868 | else: 869 | return False 870 | 871 | def update(self, wireless_sensor_description: str): 872 | for sensor_desc in wireless_sensor_description: 873 | if sensor_desc["name"] == self._name: 874 | self._battery_level = sensor_desc["battery"] 875 | self._alert = sensor_desc["attention"] 876 | self._signal_level = sensor_desc["signal_level"] 877 | --------------------------------------------------------------------------------