├── .github └── workflows │ └── hacs.yml ├── .gitignore ├── README.md ├── custom_components └── gyverlamp │ ├── __init__.py │ ├── config_flow.py │ ├── light.py │ ├── manifest.json │ └── translations │ ├── en.json │ └── ru.json ├── hacs.json └── screen.png /.github/workflows/hacs.yml: -------------------------------------------------------------------------------- 1 | name: HACS validation 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | hacs: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - uses: "actions/checkout@v2" 12 | - uses: "hacs/action@main" 13 | with: { category: "integration" } 14 | hassfest: 15 | runs-on: "ubuntu-latest" 16 | steps: 17 | - uses: "actions/checkout@v3" 18 | - uses: "home-assistant/actions/hassfest@master" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea/ 3 | .homeassistant/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GyverLamp для Home Assistant 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) 4 | 5 | Компонент интеграции [Home Assistant][1] с [Огненной Wi-Fi Лампой][2] замечательного российского изобретателя [Александра Майорова][3]. Обязательно **не забывам поддержать** автора этого изобретения. 6 | 7 | [1]: https://www.home-assistant.io/ 8 | [2]: https://alexgyver.ru/gyverlamp/ 9 | [3]: https://alexgyver.ru/about_gyver/ 10 | 11 | Компонент работает со стандартной прошивкой лампы. Текущее состояние лампы **опрашивается раз в 30 секунд**, поэтому, после старта Home Assistant, она будет пол минуты выключена. 12 | 13 | Поддерживается: 14 | 15 | - включение/выключение лампы 16 | - установка яркости 17 | - установка эффекта из списка 18 | - установка скорости и масштаба через тон и насыщенность (круг с набором цветов в интерфейсе HA) 19 | - **насыщенность** это **скорость**, ближе к центру - быстрее 20 | - **цвет** это **масштаб**, но в некоторых эффектах цвет это цвет :) 21 | 22 | ![](screen.png) 23 | 24 | ## Установка 25 | 26 | **Способ 1.** [HACS](https://hacs.xyz/) 27 | 28 | > HACS > Интеграции > 3 точки (правый верхний угол) > Пользовательские репозитории > URL: `AlexxIT/GyverLamp`, Категория: Интеграция > Добавить > подождать > GyverLamp > Установить 29 | 30 | **Способ 2.** Вручную скопируйте папку `gyverlamp` из [latest release](https://github.com/AlexxIT/GyverLamp/releases/latest) в директорию `/config/custom_components`. 31 | 32 | ## Настройка 33 | 34 | [![Home Assistant компонент для Лампы Гайвера на стандартной прошивке](https://img.youtube.com/vi/riYsv5k_EdY/mqdefault.jpg)](https://www.youtube.com/watch?v=riYsv5k_EdY) 35 | 36 | **Способ 1.** GUI 37 | 38 | > Настройки > Интеграции > Добавить интеграцию > **GyverLamp** 39 | 40 | Если интеграции нет в списке - очистите кэш браузера. 41 | 42 | **Способ 2.** YAML 43 | 44 | ```yaml 45 | light gyverlamp: 46 | - platform: gyverlamp 47 | host: 192.168.1.123 48 | name: Лампа Гайвера 49 | effects: 50 | - Конфетти 51 | - Огонь 52 | - ... 53 | ``` 54 | -------------------------------------------------------------------------------- /custom_components/gyverlamp/__init__.py: -------------------------------------------------------------------------------- 1 | from homeassistant.config_entries import ConfigEntry 2 | from homeassistant.core import HomeAssistant 3 | 4 | DOMAIN = "gyverlamp" 5 | PLATFORMS = ["light"] 6 | 7 | 8 | async def async_setup(hass, hass_config): 9 | # used only with GUI setup 10 | hass.data[DOMAIN] = {} 11 | return True 12 | 13 | 14 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 15 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 16 | 17 | if not entry.update_listeners: 18 | entry.add_update_listener(async_update_options) 19 | 20 | return True 21 | 22 | 23 | async def async_update_options(hass: HomeAssistant, entry: ConfigEntry): 24 | await hass.config_entries.async_reload(entry.entry_id) 25 | 26 | 27 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): 28 | return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 29 | -------------------------------------------------------------------------------- /custom_components/gyverlamp/config_flow.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import homeassistant.helpers.config_validation as cv 4 | import voluptuous as vol 5 | from homeassistant.config_entries import ConfigFlow, OptionsFlow, ConfigEntry 6 | from homeassistant.const import CONF_HOST 7 | from homeassistant.core import callback 8 | 9 | from . import DOMAIN 10 | from .light import CONF_EFFECTS, EFFECTS 11 | 12 | 13 | def parse_effects(data: str) -> list: 14 | return re.split(r"\s*,\s*", data.strip()) 15 | 16 | 17 | class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): 18 | VERSION = 1 19 | 20 | async def async_step_user(self, user_input=None): 21 | if user_input is not None: 22 | host = user_input[CONF_HOST] 23 | user_input[CONF_EFFECTS] = parse_effects(user_input[CONF_EFFECTS]) 24 | return self.async_create_entry(title=host, data={}, options=user_input) 25 | 26 | effects = ",".join(EFFECTS) 27 | return self.async_show_form( 28 | step_id="user", 29 | data_schema=vol.Schema( 30 | { 31 | vol.Required(CONF_HOST): cv.string, 32 | vol.Optional(CONF_EFFECTS, default=effects): cv.string, 33 | } 34 | ), 35 | ) 36 | 37 | @staticmethod 38 | @callback 39 | def async_get_options_flow(config_entry): 40 | return OptionsFlowHandler(config_entry) 41 | 42 | 43 | class OptionsFlowHandler(OptionsFlow): 44 | def __init__(self, config_entry: ConfigEntry): 45 | self.config_entry = config_entry 46 | 47 | async def async_step_init(self, user_input=None): 48 | host = self.config_entry.options[CONF_HOST] 49 | effects = ",".join(self.config_entry.options[CONF_EFFECTS]) 50 | return self.async_show_form( 51 | step_id="user", 52 | data_schema=vol.Schema( 53 | { 54 | vol.Required(CONF_HOST, default=host): cv.string, 55 | vol.Optional(CONF_EFFECTS, default=effects): cv.string, 56 | } 57 | ), 58 | ) 59 | 60 | async def async_step_user(self, user_input: dict = None): 61 | user_input[CONF_EFFECTS] = parse_effects(user_input[CONF_EFFECTS]) 62 | return self.async_create_entry(title="", data=user_input) 63 | -------------------------------------------------------------------------------- /custom_components/gyverlamp/light.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | 4 | import homeassistant.helpers.config_validation as cv 5 | import voluptuous as vol 6 | from homeassistant.components.light import ( 7 | ColorMode, 8 | LightEntity, 9 | LightEntityFeature, 10 | PLATFORM_SCHEMA, 11 | ) 12 | from homeassistant.config_entries import ConfigEntry 13 | from homeassistant.const import CONF_HOST, CONF_NAME 14 | from homeassistant.core import HomeAssistant 15 | from homeassistant.helpers.device_registry import DeviceInfo 16 | 17 | from . import DOMAIN 18 | 19 | _LOGGER = logging.getLogger(__name__) 20 | 21 | CONF_EFFECTS = "effects" 22 | 23 | EFFECTS = [ 24 | "Конфетти", 25 | "Огонь", 26 | "Радуга вертикальная", 27 | "Радуга горизонтальная", 28 | "Смена цвета", 29 | "Безумие", 30 | "Облака", 31 | "Лава", 32 | "Плазма", 33 | "Радуга", 34 | "Павлин", 35 | "Зебра", 36 | "Лес", 37 | "Океан", 38 | "Цвет", 39 | "Снег", 40 | "Матрица", 41 | "Светлячки", 42 | ] 43 | 44 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 45 | { 46 | vol.Required(CONF_HOST): cv.string, 47 | vol.Optional(CONF_NAME): cv.string, 48 | vol.Optional(CONF_EFFECTS): cv.ensure_list, 49 | } 50 | ) 51 | 52 | 53 | def setup_platform(hass, config, add_entities, discovery_info=None): 54 | add_entities([GyverLamp(config)], True) 55 | 56 | 57 | async def async_setup_entry( 58 | hass: HomeAssistant, entry: ConfigEntry, async_add_entities 59 | ): 60 | entity = GyverLamp(entry.options, entry.entry_id) 61 | async_add_entities([entity], True) 62 | 63 | hass.data[DOMAIN][entry.entry_id] = entity 64 | 65 | 66 | class GyverLamp(LightEntity): 67 | _attr_color_mode = ColorMode.HS 68 | _attr_should_poll = True 69 | _attr_supported_color_modes = {ColorMode.HS} 70 | _attr_supported_features = LightEntityFeature.EFFECT 71 | 72 | def __init__(self, config: dict, unique_id=None): 73 | self._attr_effect_list = config.get(CONF_EFFECTS, EFFECTS) 74 | self._attr_name = config.get(CONF_NAME, "Gyver Lamp") 75 | self._attr_unique_id = unique_id 76 | 77 | self._attr_device_info = DeviceInfo( 78 | identifiers={(DOMAIN, unique_id)}, 79 | manufacturer="@AlexGyver", 80 | model="GyverLamp", 81 | ) 82 | 83 | self.host = config[CONF_HOST] 84 | 85 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 86 | self.sock.settimeout(5) 87 | 88 | @property 89 | def address(self) -> tuple: 90 | return self.host, 8888 91 | 92 | def debug(self, message): 93 | _LOGGER.debug(f"{self.host} | {message}") 94 | 95 | def turn_on( 96 | self, 97 | brightness: int = None, 98 | effect: str = None, 99 | hs_color: tuple = None, 100 | **kwargs, 101 | ): 102 | payload = [] 103 | if brightness: 104 | payload.append("BRI%d" % brightness) 105 | 106 | if effect: 107 | try: 108 | payload.append("EFF%d" % self._attr_effect_list.index(effect)) 109 | except ValueError: 110 | payload.append(effect) 111 | 112 | if hs_color: 113 | scale = round(hs_color[0] / 360.0 * 100.0) 114 | payload.append("SCA%d" % scale) 115 | speed = hs_color[1] / 100.0 * 255.0 116 | payload.append("SPD%d" % speed) 117 | 118 | if not self._attr_is_on: 119 | payload.append("P_ON") 120 | 121 | self.debug(f"SEND {payload}") 122 | 123 | for data in payload: 124 | self.sock.sendto(data.encode(), self.address) 125 | resp = self.sock.recv(1024) 126 | self.debug(f"RESP {resp}") 127 | 128 | def turn_off(self, **kwargs): 129 | self.sock.sendto(b"P_OFF", self.address) 130 | resp = self.sock.recv(1024) 131 | self.debug(f"RESP {resp}") 132 | 133 | def update(self): 134 | try: 135 | self.sock.sendto(b"GET", self.address) 136 | data = self.sock.recv(1024).decode().split(" ") 137 | self.debug(f"UPDATE {data}") 138 | # bri eff spd sca pow 139 | i = int(data[1]) 140 | self._attr_effect = ( 141 | self._attr_effect_list[i] if i < len(self._attr_effect_list) else None 142 | ) 143 | self._attr_brightness = int(data[2]) 144 | self._attr_hs_color = ( 145 | float(data[4]) / 100.0 * 360.0, 146 | float(data[3]) / 255.0 * 100.0, 147 | ) 148 | self._attr_is_on = data[5] == "1" 149 | self._attr_available = True 150 | 151 | except Exception as e: 152 | self.debug(f"Can't update: {e}") 153 | self._attr_available = False 154 | -------------------------------------------------------------------------------- /custom_components/gyverlamp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "gyverlamp", 3 | "name": "GyverLamp", 4 | "codeowners": [ 5 | "@AlexxIT" 6 | ], 7 | "config_flow": true, 8 | "dependencies": [], 9 | "documentation": "https://github.com/AlexxIT/GyverLamp", 10 | "iot_class": "local_polling", 11 | "issue_tracker": "https://github.com/AlexxIT/GyverLamp/issues", 12 | "requirements": [], 13 | "version": "1.1.2" 14 | } 15 | -------------------------------------------------------------------------------- /custom_components/gyverlamp/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "data": { 6 | "host": "Host", 7 | "effects": "Effects" 8 | } 9 | } 10 | } 11 | }, 12 | "options": { 13 | "step": { 14 | "user": { 15 | "data": { 16 | "host": "Host", 17 | "effects": "Effects" 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /custom_components/gyverlamp/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "data": { 6 | "host": "Хост", 7 | "effects": "Эффекты" 8 | } 9 | } 10 | } 11 | }, 12 | "options": { 13 | "step": { 14 | "user": { 15 | "data": { 16 | "host": "Хост", 17 | "effects": "Эффекты" 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GyverLamp", 3 | "render_readme": true, 4 | "country": "RU" 5 | } 6 | -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexxIT/GyverLamp/3a64f7294dc25ed451baed5ec2819dce1eaafbf0/screen.png --------------------------------------------------------------------------------