mirror of
https://github.com/home-assistant/core.git
synced 2025-08-15 10:31:39 +02:00
Use Py-ccm15
This commit is contained in:
@@ -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]:
|
||||||
|
@@ -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]
|
|
@@ -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": []
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user