Use Py-ccm15

This commit is contained in:
Oscar Calvo
2023-08-11 02:11:35 +00:00
parent b3154266ee
commit c18b789cc3
5 changed files with 13 additions and 137 deletions

View File

@@ -1,12 +1,10 @@
"""Climate device for CCM15 coordinator.""" """Climate device for CCM15 coordinator."""
import asyncio
import datetime import datetime
import logging import logging
from typing import Any, Optional from typing import Any, Optional
import aiohttp from ccm15 import CCM15Device, CCM15DeviceState, CCM15SlaveDevice
import httpx import httpx
import xmltodict
from homeassistant.components.climate import ( from homeassistant.components.climate import (
FAN_AUTO, FAN_AUTO,
@@ -19,10 +17,7 @@ from homeassistant.components.climate import (
ClimateEntityFeature, ClimateEntityFeature,
HVACMode, HVACMode,
) )
from homeassistant.const import ( from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
ATTR_TEMPERATURE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@@ -32,9 +27,6 @@ from homeassistant.helpers.update_coordinator import (
) )
from .const import ( from .const import (
BASE_URL,
CONF_URL_CTRL,
CONF_URL_STATUS,
CONST_CMD_FAN_MAP, CONST_CMD_FAN_MAP,
CONST_CMD_STATE_MAP, CONST_CMD_STATE_MAP,
CONST_FAN_CMD_MAP, CONST_FAN_CMD_MAP,
@@ -42,7 +34,6 @@ from .const import (
DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
DOMAIN, DOMAIN,
) )
from .data_model import CCM15DeviceState, CCM15SlaveDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -61,8 +52,8 @@ class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
update_method=self._async_update_data, update_method=self._async_update_data,
update_interval=datetime.timedelta(seconds=interval), update_interval=datetime.timedelta(seconds=interval),
) )
self._ccm15 = CCM15Device(host, port, DEFAULT_TIMEOUT)
self._host = host self._host = host
self._port = port
self._ac_devices: dict[int, CCM15Climate] = {} self._ac_devices: dict[int, CCM15Climate] = {}
def get_devices(self): def get_devices(self):
@@ -76,31 +67,9 @@ class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
except httpx.RequestError as err: # pragma: no cover except httpx.RequestError as err: # pragma: no cover
raise UpdateFailed(f"Error communicating with Device: {err}") from err raise UpdateFailed(f"Error communicating with Device: {err}") from err
async def _fetch_xml_data(self) -> str: # pragma: no cover
url = BASE_URL.format(self._host, self._port, CONF_URL_STATUS)
_LOGGER.debug("Querying url:'%s'", url)
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=DEFAULT_TIMEOUT)
return response.text
async def _fetch_data(self) -> CCM15DeviceState: async def _fetch_data(self) -> CCM15DeviceState:
"""Get the current status of all AC devices.""" """Get the current status of all AC devices."""
str_data = await self._fetch_xml_data() ac_data = await self._ccm15.get_status_async()
doc = xmltodict.parse(str_data)
data = doc["response"]
_LOGGER.debug("Found %s items in host %s", len(data.items()), self._host)
ac_data = CCM15DeviceState(devices={})
ac_index = 0
for ac_name, ac_binary in data.items():
_LOGGER.debug("Found ac_name:'%s', data:'%s'", ac_name, ac_binary)
if ac_binary == "-":
break
bytesarr = bytes.fromhex(ac_binary.strip(","))
ac_slave = CCM15SlaveDevice(bytesarr)
_LOGGER.debug("Index: %s, state:'%s'", ac_index, ac_slave)
ac_data.devices[ac_index] = ac_slave
ac_index += 1
_LOGGER.debug("Found data '%s'", ac_data.devices)
if len(self._ac_devices) == 0: if len(self._ac_devices) == 0:
for ac_index in ac_data.devices: for ac_index in ac_data.devices:
_LOGGER.debug("Creating new ac device at index '%s'", ac_index) _LOGGER.debug("Creating new ac device at index '%s'", ac_index)
@@ -109,45 +78,11 @@ class CCM15Coordinator(DataUpdateCoordinator[CCM15DeviceState]):
async def async_test_connection(self): # pragma: no cover async def async_test_connection(self): # pragma: no cover
"""Test the connection to the CCM15 device.""" """Test the connection to the CCM15 device."""
url = f"http://{self._host}:{self._port}/{CONF_URL_STATUS}" return await self._ccm15.async_test_connection()
try:
async with aiohttp.ClientSession() as session, session.get(
url, timeout=10
) as response:
if response.status == 200:
return True
_LOGGER.debug("Test connection: Cannot connect : %s", response.status)
return False
except (aiohttp.ClientError, asyncio.TimeoutError):
_LOGGER.debug("Test connection: Timeout")
return False
async def async_send_state(self, url: str) -> bool: # pragma: no cover
"""Send the url to set state to the ccm15 slave."""
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=DEFAULT_TIMEOUT)
_LOGGER.debug("API response status code:%d", response.status_code)
return response.status_code in (httpx.codes.OK, httpx.codes.FOUND)
async def async_set_state(self, ac_index: int, state: str, value: int) -> None: async def async_set_state(self, ac_index: int, state: str, value: int) -> None:
"""Set new target states.""" """Set new target states."""
_LOGGER.debug("Calling async_set_states for ac index '%s'", ac_index) if await self._ccm15.async_set_state(ac_index, state, value):
ac_id: int = 2**ac_index
url = BASE_URL.format(
self._host,
self._port,
CONF_URL_CTRL
+ "?ac0="
+ str(ac_id)
+ "&ac1=0"
+ "&"
+ state
+ "="
+ str(value),
)
_LOGGER.debug("Url:'%s'", url)
if await self.async_send_state(url):
await self.async_request_refresh() await self.async_request_refresh()
def get_ac_data(self, ac_index: int) -> Optional[CCM15SlaveDevice]: def get_ac_data(self, ac_index: int) -> Optional[CCM15SlaveDevice]:

View File

@@ -1,63 +0,0 @@
"""Data model to represent state of a CCM15 device."""
from dataclasses import dataclass
import logging
from homeassistant.const import (
UnitOfTemperature,
)
_LOGGER = logging.getLogger(__name__)
@dataclass
class CCM15SlaveDevice:
"""Data retrieved from a CCM15 slave device."""
def __init__(self, bytesarr: bytes) -> None:
"""Initialize the slave device."""
self.unit = UnitOfTemperature.CELSIUS
buf = bytesarr[0]
if (buf >> 0) & 1:
self.unit = UnitOfTemperature.FAHRENHEIT
self.locked_cool_temperature: int = (buf >> 3) & 0x1F
buf = bytesarr[1]
self.locked_heat_temperature: int = (buf >> 0) & 0x1F
self.locked_wind: int = (buf >> 5) & 7
buf = bytesarr[2]
self.locked_ac_mode: int = (buf >> 0) & 3
self.error_code: int = (buf >> 2) & 0x3F
buf = bytesarr[3]
self.ac_mode: int = (buf >> 2) & 7
self.fan_mode: int = (buf >> 5) & 7
buf = (buf >> 1) & 1
self.is_ac_mode_locked: bool = buf != 0
buf = bytesarr[4]
self.temperature_setpoint: int = (buf >> 3) & 0x1F
if self.unit == UnitOfTemperature.FAHRENHEIT:
self.temperature_setpoint += 62
self.locked_cool_temperature += 62
self.locked_heat_temperature += 62
self.is_swing_on: bool = (buf >> 1) & 1 != 0
buf = bytesarr[5]
if ((buf >> 3) & 1) == 0:
self.locked_cool_temperature = 0
if ((buf >> 4) & 1) == 0:
self.locked_heat_temperature = 0
self.fan_locked: bool = buf >> 5 & 1 != 0
self.is_remote_locked: bool = ((buf >> 6) & 1) != 0
buf = bytesarr[6]
self.temperature: int = buf if buf < 128 else buf - 256
@dataclass
class CCM15DeviceState:
"""Data retrieved from a CCM15 device."""
devices: dict[int, CCM15SlaveDevice]

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/ccm15", "documentation": "https://www.home-assistant.io/integrations/ccm15",
"homekit": {}, "homekit": {},
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["xmltodict==0.13.0"], "requirements": ["py-ccm15==0.0.5"],
"ssdp": [], "ssdp": [],
"zeroconf": [] "zeroconf": []
} }

View File

@@ -1483,6 +1483,9 @@ pvo==1.0.0
# homeassistant.components.canary # homeassistant.components.canary
py-canary==0.5.3 py-canary==0.5.3
# homeassistant.components.ccm15
py-ccm15==0.0.5
# homeassistant.components.cpuspeed # homeassistant.components.cpuspeed
py-cpuinfo==8.0.0 py-cpuinfo==8.0.0
@@ -2707,7 +2710,6 @@ xknx==2.11.2
xknxproject==3.2.0 xknxproject==3.2.0
# homeassistant.components.bluesound # homeassistant.components.bluesound
# homeassistant.components.ccm15
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.rest # homeassistant.components.rest
# homeassistant.components.startca # homeassistant.components.startca

View File

@@ -1116,6 +1116,9 @@ pvo==1.0.0
# homeassistant.components.canary # homeassistant.components.canary
py-canary==0.5.3 py-canary==0.5.3
# homeassistant.components.ccm15
py-ccm15==0.0.5
# homeassistant.components.cpuspeed # homeassistant.components.cpuspeed
py-cpuinfo==8.0.0 py-cpuinfo==8.0.0
@@ -1992,7 +1995,6 @@ xknx==2.11.2
xknxproject==3.2.0 xknxproject==3.2.0
# homeassistant.components.bluesound # homeassistant.components.bluesound
# homeassistant.components.ccm15
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.rest # homeassistant.components.rest
# homeassistant.components.startca # homeassistant.components.startca