Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to code to fix wifi bulbs #93

Merged
merged 1 commit into from
Jan 6, 2025
Merged
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
102 changes: 39 additions & 63 deletions custom_components/sengledapi/light.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/python3

"""Platform for light Sengled hintegration."""
"""Platform for light Sengled integration."""

import logging
from datetime import timedelta
Expand All @@ -11,11 +11,8 @@
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP,
SUPPORT_COLOR,
LightEntity,
ColorMode,
LightEntity,
)
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.util import color as colorutil
Expand All @@ -33,7 +30,7 @@

async def async_setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Sengled Light platform."""
_LOGGER.debug("""Creating new Sengled light component""")
_LOGGER.debug("Creating new Sengled light component")
# Add devices
add_entities(
[
Expand All @@ -55,7 +52,7 @@ def __init__(self, light):
self._name = light._friendly_name
self._state = light._state
self._brightness = light._brightness
self._avaliable = light._avaliable
self._available = light._available
self._device_mac = light._device_mac
self._device_model = light._device_model
self._color_temperature = light._color_temperature
Expand Down Expand Up @@ -83,42 +80,28 @@ def unique_id(self):

@property
def available(self):
"""Return the connection status of this light"""
_LOGGER.debug("Light.py _avaliable %s", self._avaliable)
return self._avaliable
"""Return the connection status of this light."""
_LOGGER.debug("Light.py _available %s", self._available)
return self._available

@property
def extra_state_attributes(self):
"""Return device attributes of the entity."""
if self._device_model == "E13-N11":
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"state": self._state,
"available": self._avaliable,
"device model": self._device_model,
"rssi": self._device_rssi,
"mac": self._device_mac,
"alarm status ": self._alarm_status,
"color": self._color,
"color Temp": self._color_temperature,
"color r": self._rgb_color_r,
"color g": self._rgb_color_g,
"color b": self._rgb_color_b,
}
else:
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"state": self._state,
"available": self._avaliable,
"device model": self._device_model,
"rssi": self._device_rssi,
"mac": self._device_mac,
"color": self._color,
"color Temp": self._color_temperature,
"color r": self._rgb_color_r,
"color g": self._rgb_color_g,
"color b": self._rgb_color_b,
}
attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION,
"state": self._state,
"available": self._available,
"device model": self._device_model,
"rssi": self._device_rssi,
"mac": self._device_mac,
"alarm status ": self._alarm_status,
"color": self._color,
"color Temp": self._color_temperature,
"color r": self._rgb_color_r,
"color g": self._rgb_color_g,
"color b": self._rgb_color_b,
}
return attributes

@property
def color_temp(self):
Expand Down Expand Up @@ -154,38 +137,31 @@ def is_on(self):
return self._state

@property
def supported_features(self):
"""Flags Supported Features"""
features = 0
def supported_color_modes(self):
"""Return the supported color modes for the light."""
color_modes = set()
if self._support_brightness:
features = SUPPORT_BRIGHTNESS
color_modes.add(ColorMode.BRIGHTNESS)
if self._support_color_temp:
features = features | SUPPORT_COLOR_TEMP
color_modes.add(ColorMode.COLOR_TEMP)
if self._support_color:
features = features | SUPPORT_COLOR
_LOGGER.debug("supported_features: %s", features)
return features
color_modes.add(ColorMode.HS)
return color_modes

@property
def supported_color_modes(self):
"""Flags Supported Features"""
features = set()
if self._support_brightness:
features.add(ColorMode.BRIGHTNESS)
if self._support_color_temp:
features.add(ColorMode.COLOR_TEMP)
def color_mode(self):
"""Return the current color mode of the light."""
if self._support_color:
features.add(ColorMode.HS)
_LOGGER.debug("supported_color_modes: %s", features)
return features
return ColorMode.HS
elif self._support_color_temp:
return ColorMode.COLOR_TEMP
else:
return ColorMode.BRIGHTNESS

async def async_turn_on(self, **kwargs):
_LOGGER.debug("turn_on kwargs: %s", kwargs)
"""Turn on or control the light."""
if (
ATTR_BRIGHTNESS not in kwargs
and ATTR_HS_COLOR not in kwargs
and ATTR_COLOR_TEMP not in kwargs
if not any(
key in kwargs for key in (ATTR_BRIGHTNESS, ATTR_HS_COLOR, ATTR_COLOR_TEMP)
):
await self._light.async_toggle(ON)
if ATTR_BRIGHTNESS in kwargs:
Expand All @@ -210,7 +186,7 @@ async def async_update(self):
"""
await self._light.async_update()
self._state = self._light.is_on()
self._avaliable = self._light._avaliable
self._available = self._light._available
self._state = self._light._state
self._brightness = self._light._brightness
self._color_temperature = self._light._color_temperature
Expand Down
6 changes: 2 additions & 4 deletions custom_components/sengledapi/sengledapi/devices/bulbs/bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(
self._device_mac = device_mac
self._friendly_name = friendly_name
self._state = state
self._avaliable = isonline
self._available = isonline
self._just_changed_state = True
self._device_model = device_model
self._device_rssi = -30
Expand All @@ -67,8 +67,6 @@ async def async_toggle(self, onoff):
if onoff == "1":
self._state = True
else:
# We don't know what is coming in this parameter and the API is sensitive to other values
onoff = "0"
self._state = False
if self._wifi_device:
_LOGGER.info(
Expand All @@ -89,7 +87,7 @@ async def async_toggle(self, onoff):
)
else:
_LOGGER.info(
"SengledApi: Bulb %s %s toggling.",
"SengledApi: Bulb %s %s turning on.",
self._friendly_name,
self._device_mac,
)
Expand Down
8 changes: 6 additions & 2 deletions custom_components/sengledapi/sengledapi/devices/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ class AccessTokenError(SengledApiError):
pass


class SengledApiAccessToken:
pass
class SengledApiAccessToken(SengledApiError):
"""Raised when the api encounters issues with the AccessToken."""

def __init__(self, message="Invalid or missing AccessToken"):
self.message = message
super().__init__(self.message)
72 changes: 42 additions & 30 deletions custom_components/sengledapi/sengledapi/devices/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@

_LOGGER.info("SengledApi: Initializing Request")

import asyncio
import functools
from concurrent.futures import ThreadPoolExecutor

async def async_create_ssl_context():
loop = asyncio.get_running_loop()
with ThreadPoolExecutor() as executor:
return await loop.run_in_executor(
executor, functools.partial(ssl.create_default_context, cafile=certifi.where())
)


class Request:
def __init__(self, url, payload, no_return=False):
Expand All @@ -30,33 +41,26 @@ def __init__(self, url, payload, no_return=False):
"Connection": "keep-alive",
}

def get_response(self, jsession_id):
self._header = {
"Content-Type": "application/json",
"Cookie": "JSESSIONID={}".format(jsession_id),
"Connection": "keep-alive",
}

r = requests.post(self._url, headers=self._header, data=self._payload)
data = r.json()
return data

async def async_get_response(self, jsession_id):
self._header = {
"Content-Type": "application/json",
"Cookie": "JSESSIONID={}".format(jsession_id),
"Host": "element.cloud.sengled.com:443",
"Cookie": f"JSESSIONID={jsession_id}",
"Connection": "keep-alive",
}

# Asynchronously create the SSL context in a non-blocking way.
sslcontext = await async_create_ssl_context()

# Use aiohttp's ClientSession for asynchronous HTTP requests.
async with aiohttp.ClientSession() as session:
sslcontext = ssl.create_default_context(cafile=certifi.where())
async with session.post(
self._url, headers=self._header, data=self._payload, ssl=sslcontext
) as resp:
data = await resp.json()
# _LOGGER.debug("SengledApi: data from Response %s ", str(data))
return data
async with session.post(self._url, headers=self._header, data=self._payload, ssl=sslcontext) as response:
# Make sure to handle potential exceptions and non-JSON responses appropriately.
if response.status == 200:
data = await response.json()
return data
else:
_LOGGER.error("Failed to get response, status: %s", response.status)
return None

########################Login#####################################
def get_login_response(self):
Expand All @@ -68,14 +72,18 @@ def get_login_response(self):

async def async_get_login_response(self):
_LOGGER.info("SengledApi: Get Login Response async.")
sslcontext = await async_create_ssl_context()
async with aiohttp.ClientSession() as session:
sslcontext = ssl.create_default_context(cafile=certifi.where())
async with session.post(
self._url, headers=self._header, data=self._payload, ssl=sslcontext
) as resp:
data = await resp.json()
_LOGGER.debug("SengledApi: Get Login Response %s ", str(data))
return data
if resp.status == 200:
data = await resp.json()
_LOGGER.debug("SengledApi: Get Login Response %s ", str(data))
return data
else:
_LOGGER.error("Failed to get login response, status: %s", resp.status)
return None

######################Session Timeout#################################
def is_session_timeout_response(self, jsession_id):
Expand All @@ -100,13 +108,17 @@ async def async_is_session_timeout_response(self, jsession_id):
"sid": jsession_id,
"X-Requested-With": "com.sengled.life2",
}
sslcontext = await async_create_ssl_context()
async with aiohttp.ClientSession() as session:
sslcontext = ssl.create_default_context(cafile=certifi.where())
async with session.post(
self._url, headers=self._header, data=self._payload, ssl=sslcontext
) as resp:
data = await resp.json()
_LOGGER.info(
"SengledApi: Get Session Timeout Response Async %s", str(data)
)
return data
if resp.status == 200:
data = await resp.json()
_LOGGER.info(
"SengledApi: Get Session Timeout Response Async %s", str(data)
)
return data
else:
_LOGGER.error("Failed to get session timeout response, status: %s", resp.status)
return None
Loading