Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
139 changes: 69 additions & 70 deletions custom_components/cozylife/light.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,39 @@
"""Platform for sensor integration."""
from __future__ import annotations
from homeassistant.components import zeroconf
import logging
from .tcp_client import tcp_client
from datetime import timedelta
import time

from homeassistant.components.sensor import SensorEntity
from homeassistant.components.switch import SwitchEntity

from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.util import color as colorutil
from homeassistant.components.light import (
PLATFORM_SCHEMA,
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
ATTR_HS_COLOR,
ATTR_KELVIN,
ATTR_RGB_COLOR,
ATTR_TRANSITION,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_ONOFF,
COLOR_MODE_RGB,
COLOR_MODE_UNKNOWN,
FLASH_LONG,
FLASH_SHORT,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
LightEntity,
)
from homeassistant.const import TEMP_CELSIUS, CONF_EFFECT
from homeassistant.const import CONF_EFFECT
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers import entity_platform
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from typing import Any, Final, Literal, TypedDict, final
from typing import Any
from .const import (
DOMAIN,
SWITCH_TYPE_CODE,
LIGHT_TYPE_CODE,
LIGHT_DPID,
SWITCH,
WORK_MODE,
TEMP,
BRIGHT,
HUE,
SAT,
)

from homeassistant.helpers.event import async_track_time_interval
Expand All @@ -74,7 +58,7 @@
})


SCAN_INTERVAL = timedelta(seconds=240)
SCAN_INTERVAL = timedelta(seconds=30)
SWITCH_SCAN_INTERVAL = timedelta(seconds=20)


Expand All @@ -90,7 +74,11 @@

SERVICE_SET_EFFECT = "set_effect"
SERVICE_SET_ALL_EFFECT = "set_all_effect"
scenes = ['manual','natural','sleep','warm','study','chrismas']
#scenes = ['manual','natural','sleep','warm','study','chrismas']
scenes = ['manual','flame','Mármore','Relâmpago','Arco-Íris','metedor','Céu','Balanço','Cavalo','Fogueiras',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In english, please.

'Bump','Cor de fluxo','Ponto estelar','Água corrente','Recursivo','Escalar','chrismas']


SERVICE_SCHEMA_SET_ALL_EFFECT = {
vol.Required(CONF_EFFECT): vol.In([mode.lower() for mode in scenes])
}
Expand Down Expand Up @@ -172,7 +160,7 @@ async def async_set_all_effect(call:ServiceCall):
class CozyLifeSwitchAsLight(LightEntity):
_tcp_client = None
_attr_is_on = True

def __init__(self, tcp_client: tcp_client, hass) -> None:
"""Initialize the sensor."""
_LOGGER.info('__init__')
Expand All @@ -186,7 +174,7 @@ def __init__(self, tcp_client: tcp_client, hass) -> None:
def unique_id(self) -> str | None:
"""Return a unique ID."""
return self._unique_id

async def async_update(self):
await self.hass.async_add_executor_job(self._refresh_state)

Expand All @@ -195,24 +183,24 @@ def _refresh_state(self):
_LOGGER.info(f'_name={self._name},_state={self._state}')
if self._state:
self._attr_is_on = 0 < self._state['1']

@property
def name(self) -> str:
return 'cozylife:' + self._name

@property
def available(self) -> bool:
"""Return if the device is available."""
if self._tcp_client._connect:
return True
else:
return False

@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._attr_is_on

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self._attr_is_on = True
Expand All @@ -224,7 +212,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:
})

return None

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
self._attr_is_on = False
Expand All @@ -234,7 +222,7 @@ async def async_turn_off(self, **kwargs: Any) -> None:
await self.hass.async_add_executor_job(self._tcp_client.control, {
'1': 0
})

return None


Expand All @@ -257,6 +245,7 @@ class CozyLifeLight(CozyLifeSwitchAsLight,RestoreEntity):
_attr_hs_color = (0, 0)
_transitioning = 0


def __init__(self, tcp_client: tcp_client, hass, scenes) -> None:
"""Initialize the sensor."""
_LOGGER.info('__init__')
Expand Down Expand Up @@ -306,7 +295,7 @@ async def async_set_effect(self, effect: str):
self._effect = effect
if self._attr_is_on:
await self.async_turn_on(effect=effect)


@property
def effect(self):
Expand Down Expand Up @@ -339,9 +328,24 @@ def _refresh_state(self):
self._attr_color_temp = round(
self._max_mireds-self._state['3'] * self._miredsratio)


if '16' in self._state:
__attr_hs_color_temp = self._state['16']
# converting to integer
int_value = int(__attr_hs_color_temp, 16)
self._attr_color_mode = COLOR_MODE_HS
HHHH = int_value >> 32 & 0xffff
SSSS = int_value >> 16 & 0xffff
r, g, b = colorutil.color_hs_to_RGB(round(HHHH), round(SSSS / 10))
## May need to adjust
hs_color = colorutil.color_RGB_to_hs(r, g, b)
self._attr_hs_color = hs_color


if '4' in self._state:
self._attr_brightness = int(self._state['4'] / 1000 * 255)


if '5' in self._state:
color = self._state['5']
if color < 60000:
Expand All @@ -362,6 +366,7 @@ def calc_color_temp(self):
autocolortemp = colorutil.color_temperature_kelvin_to_mired(
colortemp_in_kelvin)
return autocolortemp

def calc_brightness(self):
if self._cl == None:
self._cl = self.hass.data.get(DATA_CIRCADIAN_LIGHTING)
Expand All @@ -378,6 +383,10 @@ def color_temp(self) -> int | None:
"""Return the CT color value in mireds."""
return self._attr_color_temp

def hex2complement(self, number):
hexadecimal_result = format(number, "03X")
return hexadecimal_result.zfill(4) # .zfill(n) adds leading 0's if the integer has less digits than n

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self._attr_is_on = True
Expand All @@ -393,7 +402,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:

transition = kwargs.get(ATTR_TRANSITION)



# rgb = kwargs.get(ATTR_RGB_COLOR)
#flash = kwargs.get(ATTR_FLASH)
Expand All @@ -407,14 +416,15 @@ async def async_turn_on(self, **kwargs: Any) -> None:
if self._attr_is_on:
originalbrightness = self._attr_brightness

payload = {'1': 255, '2': 0}
payload = {'1': 1, '2': 0}
count = 0
if brightness is not None:
# Color: mininum light brightness 12, max 1000
# White mininum light brightness 4, max 1000
self._effect = 'manual'
payload['4'] = round(brightness / 255 * 1000)
self._attr_brightness = brightness
payload.pop('2', None)
count += 1

if colortemp is not None:
Expand All @@ -428,23 +438,33 @@ async def async_turn_on(self, **kwargs: Any) -> None:
count += 1

if hs_color is not None:
# 0-360
# 0-1000
self._effect = 'manual'
self._attr_color_mode = COLOR_MODE_HS
self._attr_hs_color = hs_color
r, g, b = colorutil.color_hs_to_RGB(*hs_color)
# color is not balanced right. needs additional tuning
hs_color = colorutil.color_RGB_to_hs(r, g, b)
payload['5'] = round(hs_color[0])
payload['6'] = round(hs_color[1] * 10)
# HHHH Matiz: 0-360
# SSSS Saturação 0-1000
# TTTT HS vlues
HHHH = self.hex2complement(round(hs_color[0]))
SSSS = self.hex2complement(round(hs_color[1]*10))
payload['16'] = str(HHHH) + str(SSSS) + "ffff"
count += 1

if count == 0:
#autocolortemp when brightness color temp and hs_color is not set
if effect is not None:
if effect is not None:
self._effect = effect
if self._effect == 'natural':

# Check if the element is in the list
if self._effect in scenes:
# Get the position of the element in the list
position = scenes.index(self._effect)
payload['2'] = 1
payload['19'] = 39 + position

elif self._effect == 'natural':
if CIRCADIAN_BRIGHTNESS:
brightness = self.calc_brightness()
payload['4'] = round(brightness / 255 * 1000)
Expand All @@ -454,37 +474,12 @@ async def async_turn_on(self, **kwargs: Any) -> None:
payload['3'] = 1000 - \
round((colortemp - self._min_mireds) / self._miredsratio)
_LOGGER.info(f'color={colortemp},payload3={payload["3"]}')
elif self._effect == 'sleep':
#payload['4'] = 4
#payload['3'] = 0
#payload['4'] = 12
brightness = 5
self._attr_brightness = brightness
payload['4'] = round(brightness / 255 * 1000)
self._attr_color_mode = COLOR_MODE_HS
self._attr_hs_color = (16,100)
payload['5'] = round(16)
payload['6'] = round(1000)
elif self._effect == 'study':
payload['4'] = 1000
payload['3'] = 1000
elif self._effect == 'warm':
payload['4'] = 1000
payload['3'] = 0
elif self._effect == 'chrismas':
payload['2'] = 1
payload['4'] = 1000
payload['8'] = 500
payload['7'] = '03000003E8FFFF007803E8FFFF00F003E8FFFF003C03E8FFFF00B403E8FFFF010E03E8FFFF002603E8FFFF'

self._transitioning = 0

if transition:
if transition:
self._transitioning = time.time()
now = self._transitioning
if self._effect =='chrismas':
await self.hass.async_add_executor_job(self._tcp_client.control, payload)
return None
if brightness:
payloadtemp = {'1': 255, '2': 0}
p4i = round(originalbrightness / 255 * 1000)
Expand All @@ -499,7 +494,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:
p3f = payload['3']
p3steps = abs(round((p3i-p3f)/4))
steps = p3steps if p3steps > p4steps else p4steps
if steps <= 0:
if steps <= 0:
return None
stepseconds = transition / steps
if stepseconds < 0.3:
Expand All @@ -526,7 +521,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:
p5steps = abs(round((p5i - p5f) / 3))
p6steps = abs(round((p6i - p6f) / 10))
steps = max([p4steps, p5steps, p6steps])
if steps <= 0:
if steps <= 0:
return None
stepseconds = transition / steps
if stepseconds < 4:
Expand All @@ -535,7 +530,7 @@ async def async_turn_on(self, **kwargs: Any) -> None:
_LOGGER.info(f'steps={steps}')
for s in range(steps):
payloadtemp['4']= round(p4i + (p4f - p4i) * s / steps)
if p5steps != 0:
if p5steps != 0:
payloadtemp['5']= round(p5i + (p5f - p5i) * s / steps)
payloadtemp['6']= round(p6i + (p6f - p6i) * s / steps)
if now == self._transitioning:
Expand All @@ -554,6 +549,10 @@ async def async_turn_off(self, **kwargs: Any) -> None:
await super().async_turn_off(*kwargs)
return None

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def hs_color(self) -> tuple[float, float] | None:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/cozylife/model.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion custom_components/cozylife/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def async_setup_platform(


switches = []
for item in config.get('switches') or []:
for item in config.get('switches'):
client = tcp_client(item.get('ip'))
client._device_id = item.get('did')
client._pid = item.get('pid')
Expand Down
11 changes: 6 additions & 5 deletions custom_components/cozylife/tcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ class tcp_client(object):
_port = 5555
_connect = None # socket

_device_id = str # str
_device_id = 'temp_id' # str
# _device_key = str
_pid = str
_device_type_code = str
_device_type_code = '01'
_icon = str
_device_model_name = str
_dpid = []
_device_model_name = 'light'
_dpid = [3, 5]
# last sn
_sn = str

Expand All @@ -52,7 +52,7 @@ def __init__(self, ip, timeout=3):

def disconnect(self):
if self._connect:
try:
try:
#self._connect.shutdown(socket.SHUT_RDWR)
self._connect.close()
except:
Expand All @@ -68,6 +68,7 @@ def _initSocket(self):
s.settimeout(self.timeout)
s.connect((self._ip, self._port))
self._connect = s

except:
_LOGGER.info(f'_initSocketerror,ip={self._ip}')
self.disconnect()
Expand Down
Loading