Skip to content

Commit

Permalink
Minor Update
Browse files Browse the repository at this point in the history
  • Loading branch information
lunDreame authored Jan 18, 2025
1 parent f2f85cc commit 3b820bc
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 52 deletions.
39 changes: 28 additions & 11 deletions custom_components/kocom_wallpad/connection.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from __future__ import annotations

from typing import Optional

import asyncio
import re
import serial_asyncio

from .const import LOGGER


class RS485Connection:
"""Connection class for RS485 communication with read lock protection."""
"""Connection class for RS485 communication with IP or serial support."""

def __init__(self, host: str, port: int):
def __init__(self, host: str, port: Optional[int] = None):
"""Initialize the connection."""
self.host = host
self.port = port
Expand All @@ -19,17 +22,31 @@ def __init__(self, host: str, port: int):
self.reconnect_interval = 5
self._running = True

def is_ip_address(self) -> bool:
"""Check if the host is an IP address."""
ip_pattern = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
return bool(ip_pattern.match(self.host))

async def connect(self) -> bool:
"""Connect to the device."""
"""Connect to the device using IP or serial."""
try:
self.reader, self.writer = await asyncio.open_connection(
self.host, self.port
)
if self.is_ip_address():
if not self.port:
raise ValueError("Port must be provided for IP connections.")
self.reader, self.writer = await asyncio.open_connection(
self.host, self.port
)
LOGGER.info(f"Connected to {self.host}:{self.port}")
else:
self.reader, self.writer = await serial_asyncio.open_serial_connection(
url=self.host, baudrate=9600
)
LOGGER.info(f"Connected to serial port {self.host}")

self.is_connected = True
LOGGER.info(f"Connected to {self.host}:{self.port}")
return True
except Exception as e:
LOGGER.error(f"Socket connection failed: {e}")
LOGGER.error(f"Connection failed: {e}")
self.is_connected = False
return False

Expand Down Expand Up @@ -71,10 +88,10 @@ async def send(self, packet: bytearray) -> bool:
return False

async def receive(self) -> Optional[bytes]:
"""Receive data from the device with read lock protection."""
"""Receive data from the device."""
if not self.is_connected or not self.reader:
return None

try:
data = await self.reader.read(1024)
if not data:
Expand All @@ -92,7 +109,7 @@ async def receive(self) -> Optional[bytes]:
return None


async def test_connection(host: str, port: int, timeout: int = 5) -> bool:
async def test_connection(host: str, port: Optional[int] = None, timeout: int = 5) -> bool:
"""Test the connection with a timeout."""
connection = RS485Connection(host, port)
try:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/kocom_wallpad/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
BRAND_NAME = "Kocom"
MANUFACTURER = "KOCOM Co., Ltd"
MODEL = "Smart Wallpad"
SW_VERSION = "1.1.2"
SW_VERSION = "1.1.3"

DEVICE_TYPE = "device_type"
ROOM_ID = "room_id"
Expand Down
4 changes: 2 additions & 2 deletions custom_components/kocom_wallpad/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(
self.packet = packet
self.packet_update_signal = f"{DOMAIN}_{self.gateway.host}_{self.device_id}"

self._attr_unique_id = f"{BRAND_NAME}_{self.device_id}-{self.gateway.host}".lower()
self._attr_unique_id = f"{BRAND_NAME}_{self.device_id}:{self.gateway.host}".lower()
self._attr_name = f"{BRAND_NAME} {self.device_name}"
self._attr_extra_state_attributes = {
DEVICE_TYPE: self.packet._device.device_type,
Expand Down Expand Up @@ -86,14 +86,14 @@ def async_handle_packet_update(self, packet: KocomPacket) -> None:

async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
async_dispatcher_connect(
self.hass,
self.packet_update_signal,
self.async_handle_packet_update
)
)
await super().async_added_to_hass()

@property
def extra_restore_state_data(self) -> RestoredExtraData:
Expand Down
6 changes: 3 additions & 3 deletions custom_components/kocom_wallpad/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ async def _handle_device_update(self, packet: KocomPacket) -> None:
device = packet._device
dev_id = create_dev_id(device.device_type, device.room_id, device.sub_id)

packet_update_signal = f"{DOMAIN}_{self.host}_{dev_id}"
async_dispatcher_send(self.hass, packet_update_signal, packet)

if dev_id not in self.entities[platform]:
self.entities[platform][dev_id] = packet

add_signal = f"{DOMAIN}_{platform.value}_add"
async_dispatcher_send(self.hass, add_signal, packet)

packet_update_signal = f"{DOMAIN}_{self.host}_{dev_id}"
async_dispatcher_send(self.hass, packet_update_signal, packet)

def parse_platform(self, packet: KocomPacket) -> Platform | None:
"""Parse the platform from the packet."""
platform = PLATFORM_MAPPING.get(type(packet))
Expand Down
32 changes: 23 additions & 9 deletions custom_components/kocom_wallpad/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,33 +55,47 @@ def __init__(
) -> None:
"""Initialize the light."""
super().__init__(gateway, packet)
self.has_brightness = False
self.max_brightness = 0

if self.is_brightness:
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._attr_color_mode = ColorMode.BRIGHTNESS

@property
def is_brightness(self) -> bool:
"""Return whether brightness is supported."""
return bool(self.packet._last_data[self.packet.device_id]["bri_lv"])

@property
def max_brightness(self) -> int:
"""Return the maximum supported brightness."""
return len(self.packet._last_data[self.packet.device_id]["bri_lv"]) + 1

@property
def is_on(self) -> bool:
"""Return true if light is on."""
if self.packet._device.state.get(BRIGHTNESS):
self.has_brightness = True
if self.is_brightness:
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._attr_color_mode = ColorMode.BRIGHTNESS
self.max_brightness = len(self.packet._device.state[LEVEL]) + 1

return self.packet._device.state[POWER]

@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
if self.packet._device.state[BRIGHTNESS] not in self.packet._device.state[LEVEL]:
brightness = self.packet._device.state.get(BRIGHTNESS, 0)
level = self.packet._device.state.get(LEVEL, [])

if brightness not in level:
return 255
return ((225 // self.max_brightness) * self.packet._device.state[BRIGHTNESS]) + 1
return ((225 // self.max_brightness) * brightness) + 1

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on light."""
if self.has_brightness:
if self.is_brightness:
brightness = int(kwargs.get(ATTR_BRIGHTNESS, 255))
brightness = ((brightness * 3) // 225) + 1
if brightness not in self.packet._device.state[LEVEL]:
brightness_level = self.packet._device.state.get(LEVEL, [])
if brightness not in brightness_level:
brightness = 255
make_packet = self.packet.make_brightness_status(brightness)
else:
Expand Down
4 changes: 2 additions & 2 deletions custom_components/kocom_wallpad/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"documentation": "https://github.com/lunDreame/kocom-wallpad",
"iot_class": "local_push",
"issue_tracker": "https://github.com/lunDreame/kocom-wallpad/issues",
"requirements": [],
"version": "1.1.2"
"requirements": ["pyserial-asyncio"],
"version": "1.1.3"
}
32 changes: 16 additions & 16 deletions custom_components/kocom_wallpad/pywallpad/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(self, connection: RS485Connection) -> None:
self.device_callbacks: list[Callable[[KocomPacket], Awaitable[None]]] = []
self.packet_queue: asyncio.Queue[PacketQueue] = asyncio.Queue()
self.last_packet: KocomPacket | None = None
self.packet_lock: asyncio.Lock = asyncio.Lock()
#self.packet_lock: asyncio.Lock = asyncio.Lock()

async def start(self) -> None:
"""Start the client."""
Expand Down Expand Up @@ -108,9 +108,9 @@ async def _process_packet(self, packet: bytes) -> None:
f"{log_message}: {parsed_packet}, {parsed_packet._device}, {parsed_packet._last_data}"
)
if isinstance(parsed_packet, KocomPacket):
async with self.packet_lock:
#async with self.packet_lock:
self.last_packet = parsed_packet
#_LOGGER.debug(f"Updated last packet: {parsed_packet}")
# _LOGGER.debug(f"Updated last packet: {parsed_packet}")

if parsed_packet._device is None:
continue
Expand Down Expand Up @@ -139,20 +139,20 @@ async def _process_queue(self) -> None:
start_time = time.time()

while (time.time() - start_time) < 1.0:
async with self.packet_lock:
if self.last_packet is None:
await asyncio.sleep(0.1)
continue

if (self.last_packet.device_id == packet.device_id and
self.last_packet.sequence == packet.sequence and
self.last_packet.dest == packet.src and
self.last_packet.src == packet.dest):
found_match = True
self.last_packet = None
break

#async with self.packet_lock:
if self.last_packet is None:
await asyncio.sleep(0.1)
continue

if (self.last_packet.device_id == packet.device_id and
self.last_packet.sequence == packet.sequence and
self.last_packet.dest == packet.src and
self.last_packet.src == packet.dest):
found_match = True
self.last_packet = None
break

await asyncio.sleep(0.1)

if not found_match:
_LOGGER.debug("not received ack retrying..")
Expand Down
17 changes: 9 additions & 8 deletions custom_components/kocom_wallpad/pywallpad/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ def __init__(self, packet: bytes) -> None:
super().__init__(packet)
if self.device_id not in self._class_last_data:
self._class_last_data[self.device_id] = {
"is_hot_water": False,
"last_target_temp": 22,
}
self._class_last_data["is_hotwater"] = False
self._last_data.update(self._class_last_data)

def parse_data(self) -> list[Device]:
Expand Down Expand Up @@ -316,9 +316,9 @@ def parse_data(self) -> list[Device]:
)
)

#if is_hotwater or self._last_data["is_hotwater"]:
#if is_hotwater or self._last_data[self.device_id]["is_hot_water"]:
# _LOGGER.debug(f"Hot water: {is_hotwater}")
# self._last_data["is_hotwater"] = True
# self._last_data[self.device_id]["is_hot_water"] = True
# devices.append(
# Device(
# device_type=self.device_name(),
Expand Down Expand Up @@ -676,6 +676,8 @@ def parse_data(self) -> list[Device]:
state={POWER: self._ev_invoke},
)
)
self._ev_invoke = False

devices.append(
Device(
device_type=self.device_name(capital=True),
Expand All @@ -686,6 +688,9 @@ def parse_data(self) -> list[Device]:
)
)

if self._ev_direction == self.Direction.ARRIVAL:
self._ev_direction = self.Direction.IDLE

if int(self._ev_floor) >> 4 == 0x08: # 지하 층
self._ev_floor = f"B{str(int(self._ev_floor) & 0x0F)}"

Expand All @@ -703,11 +708,7 @@ def parse_data(self) -> list[Device]:
sub_id=FLOOR,
)
)

if self._ev_direction == self.Direction.ARRIVAL:
self._ev_invoke = False
self._ev_direction = self.Direction.IDLE


return devices

def make_power_status(self, power: bool) -> bytearray:
Expand Down
3 changes: 3 additions & 0 deletions custom_components/kocom_wallpad/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"data": {
"host": "Host",
"port": "Port"
},
"data_description": {
"host": "If it is serial communication, enter Host and ignore Port."
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions custom_components/kocom_wallpad/translations/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"data": {
"host": "호스트",
"port": "포트"
},
"data_description": {
"host": "시리얼 통신인 경우 호스트에 입력하고 포트는 무시하세요."
}
}
},
Expand Down

0 comments on commit 3b820bc

Please sign in to comment.