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

Core v Beta progress update #361

Merged
merged 21 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
12 changes: 6 additions & 6 deletions custom_components/plugwise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant

from .const import CONF_USB_PATH
from .const import CONF_USB_PATH # pw-beta usb
from .gateway import async_setup_entry_gw, async_unload_entry_gw
from .usb import async_setup_entry_usb, async_unload_entry_usb
from .usb import async_setup_entry_usb, async_unload_entry_usb # pw-beta usb


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Plugwise components from a config entry."""
if entry.data.get(CONF_HOST):
return await async_setup_entry_gw(hass, entry)
if entry.data.get(CONF_USB_PATH):
return await async_setup_entry_usb(hass, entry)
if entry.data.get(CONF_USB_PATH): # pw-beta usb
return await async_setup_entry_usb(hass, entry) # pw-beta usb
return False # pragma: no cover


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload the Plugwise components."""
if entry.data.get(CONF_HOST):
return await async_unload_entry_gw(hass, entry)
if entry.data.get(CONF_USB_PATH):
return await async_unload_entry_usb(hass, entry)
if entry.data.get(CONF_USB_PATH): # pw-beta usb
return await async_unload_entry_usb(hass, entry) # pw-beta usb
return False # pragma: no cover
56 changes: 25 additions & 31 deletions custom_components/plugwise/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,27 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from plugwise.nodes import PlugwiseNode

from .const import (
ATTR_SCAN_DAYLIGHT_MODE,
ATTR_SCAN_RESET_TIMER,
ATTR_SCAN_SENSITIVITY_MODE,
ATTR_SED_CLOCK_INTERVAL,
ATTR_SED_CLOCK_SYNC,
ATTR_SED_MAINTENANCE_INTERVAL,
ATTR_SED_SLEEP_FOR,
ATTR_SED_STAY_ACTIVE,
CB_NEW_NODE,
COORDINATOR,
DOMAIN,
LOGGER,
PW_TYPE,
SERVICE_USB_SCAN_CONFIG,
SERVICE_USB_SCAN_CONFIG_SCHEMA,
SERVICE_USB_SED_BATTERY_CONFIG,
SERVICE_USB_SED_BATTERY_CONFIG_SCHEMA,
SEVERITIES,
STICK,
USB,
USB_MOTION_ID,
)
from .const import ATTR_SCAN_DAYLIGHT_MODE # pw-beta
from .const import ATTR_SCAN_RESET_TIMER # pw-beta
from .const import ATTR_SCAN_SENSITIVITY_MODE # pw-beta
from .const import ATTR_SED_CLOCK_INTERVAL # pw-beta
from .const import ATTR_SED_CLOCK_SYNC # pw-beta
from .const import ATTR_SED_MAINTENANCE_INTERVAL # pw-beta
from .const import ATTR_SED_SLEEP_FOR # pw-beta
from .const import ATTR_SED_STAY_ACTIVE # pw-beta
from .const import CB_NEW_NODE # pw-beta
from .const import COORDINATOR, DOMAIN, LOGGER, SEVERITIES, STICK
from .const import PW_TYPE # pw-beta
from .const import SERVICE_USB_SCAN_CONFIG # pw-beta
from .const import SERVICE_USB_SCAN_CONFIG_SCHEMA # pw-beta
from .const import SERVICE_USB_SED_BATTERY_CONFIG # pw-beta
from .const import SERVICE_USB_SED_BATTERY_CONFIG_SCHEMA # pw-beta
from .const import USB # pw-beta
from .const import USB_MOTION_ID # pw-beta
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .models import PW_BINARY_SENSOR_TYPES, PlugwiseBinarySensorEntityDescription
from .usb import PlugwiseUSBEntity
from .usb import PlugwiseUSBEntity # pw-beta

PARALLEL_UPDATES = 0

Expand All @@ -55,7 +49,7 @@ async def async_setup_entry(
return await async_setup_entry_gateway(hass, config_entry, async_add_entities)


async def async_setup_entry_usb(hass, config_entry, async_add_entities):
async def async_setup_entry_usb(hass, config_entry, async_add_entities): # pw-beta
"""Set up Plugwise binary sensor based on config_entry."""
api_stick = hass.data[DOMAIN][config_entry.entry_id][STICK]
platform = entity_platform.current_platform.get()
Expand Down Expand Up @@ -150,8 +144,9 @@ def __init__(
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
# pw-beta: show Plugwise notifications as HA persistent notifications
if self._notification:
if (
self._notification
): # pw-beta: show Plugwise notifications as HA persistent notifications
for notify_id, message in self._notification.items():
self.hass.components.persistent_notification.async_create(
message, "Plugwise Notification:", f"{DOMAIN}.{notify_id}"
Expand All @@ -172,17 +167,15 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
if self.entity_description.key != "plugwise_notification":
return None

attrs: dict[str, list[str]] = {}
attrs: dict[str, list[str]] = {f"{severity}_msg": [] for severity in SEVERITIES}
CoMPaTech marked this conversation as resolved.
Show resolved Hide resolved
self._notification = {} # pw-beta
if notify := self.coordinator.data.gateway["notifications"]:
for notify_id, details in notify.items():
for notify_id, details in notify.items(): # pw-beta uses notify_id
for msg_type, msg in details.items():
msg_type = msg_type.lower()
if msg_type not in SEVERITIES:
msg_type = "other" # pragma: no cover

if f"{msg_type}_msg" not in attrs:
attrs[f"{msg_type}_msg"] = []
attrs[f"{msg_type}_msg"].append(msg)

self._notification[
Expand All @@ -192,6 +185,7 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
return attrs


# pw-beta
# Github issue #265
class USBBinarySensor(PlugwiseUSBEntity, BinarySensorEntity): # type: ignore[misc]
"""Representation of a Plugwise USB Binary Sensor."""
Expand Down
39 changes: 24 additions & 15 deletions custom_components/plugwise/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

from typing import Any

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ClimateEntity,
ClimateEntityFeature,
HVACAction,
HVACMode,
Expand All @@ -25,7 +25,8 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import CONF_HOMEKIT_EMULATION # pw-beta homekit emulation
from .const import COORDINATOR, DOMAIN, MASTER_THERMOSTATS
from .const import COORDINATOR # pw-beta
from .const import DOMAIN, MASTER_THERMOSTATS
from .coordinator import PlugwiseDataUpdateCoordinator
from .entity import PlugwiseEntity
from .util import plugwise_command
Expand All @@ -41,11 +42,14 @@ async def async_setup_entry(
config_entry.entry_id
][COORDINATOR]

# pw-beta homekit emulation
homekit_enabled: bool = config_entry.options.get(CONF_HOMEKIT_EMULATION, False)
homekit_enabled: bool = config_entry.options.get(
CONF_HOMEKIT_EMULATION, False
) # pw-beta homekit emulation

async_add_entities(
PlugwiseClimateEntity(coordinator, device_id, homekit_enabled)
PlugwiseClimateEntity(
coordinator, device_id, homekit_enabled
) # pw-beta homekit emulation
for device_id, device in coordinator.data.devices.items()
if device["dev_class"] in MASTER_THERMOSTATS
)
Expand Down Expand Up @@ -91,9 +95,10 @@ def __init__(

self._attr_min_temp = self.device["thermostat"]["lower_bound"]
self._attr_max_temp = self.device["thermostat"]["upper_bound"]
if resolution := self.device["thermostat"]["resolution"]:
# Ensure we don't drop below 0.1
self._attr_target_temperature_step = max(resolution, 0.1)
# Ensure we don't drop below 0.1
self._attr_target_temperature_step = max(
self.device["thermostat"]["resolution"], 0.1
)

@property
def current_temperature(self) -> float:
Expand Down Expand Up @@ -128,7 +133,9 @@ def target_temperature_low(self) -> float:
@property
def hvac_mode(self) -> HVACMode:
"""Return HVAC operation ie. auto, heat, heat_cool, or off mode."""
if (mode := self.device["mode"]) is None or mode not in self.hvac_modes:
if (
mode := self.device["mode"]
) is None or mode not in self.hvac_modes: # pw-beta add to Core
return HVACMode.HEAT # pragma: no cover
# pw-beta homekit emulation
if self._homekit_enabled and self._homekit_mode == HVACMode.OFF:
Expand All @@ -137,7 +144,7 @@ def hvac_mode(self) -> HVACMode:
return HVACMode(mode)

@property
def hvac_action(self) -> HVACAction:
def hvac_action(self) -> HVACAction: # pw-beta add to Core
"""Return the current running hvac operation if supported."""
# When control_state is present, prefer this data
if (control_state := self.device.get("control_state")) == "cooling":
Expand All @@ -154,7 +161,7 @@ def hvac_action(self) -> HVACAction:
]
if hc_data["binary_sensors"]["heating_state"]:
return HVACAction.HEATING
if hc_data["binary_sensors"].get("cooling_state", False):
if hc_data["binary_sensors"].get("cooling_state", False): # pw-beta adds False
return HVACAction.COOLING

return HVACAction.IDLE
Expand All @@ -167,8 +174,10 @@ def preset_mode(self) -> str | None:
@plugwise_command
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if ATTR_HVAC_MODE in kwargs:
await self.async_set_hvac_mode(kwargs[ATTR_HVAC_MODE])
if ATTR_HVAC_MODE in kwargs: # pw-beta add to Core
await self.async_set_hvac_mode(
kwargs[ATTR_HVAC_MODE]
) # pw-beta add to Core

data: dict[str, Any] = {}
if ATTR_TEMPERATURE in kwargs:
Expand All @@ -187,7 +196,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None:
await self.coordinator.api.set_temperature(self.device["location"], data)

@plugwise_command
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the hvac mode."""
if hvac_mode not in self.hvac_modes:
raise HomeAssistantError("Unsupported hvac_mode")
Expand Down
51 changes: 29 additions & 22 deletions custom_components/plugwise/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Config flow for Plugwise integration."""
from __future__ import annotations

import datetime as dt # pw-beta
import datetime as dt # pw-beta options
from typing import Any

import serial.tools.list_ports
import serial.tools.list_ports # pw-beta usb
import voluptuous as vol

from homeassistant import config_entries
Expand All @@ -29,47 +29,51 @@
InvalidAuthentication,
InvalidSetupError,
InvalidXMLError,
NetworkDown,
PortError,
ResponseError,
StickInitError,
TimeoutException,
UnsupportedDeviceError,
)

# pw-beta Note; the below are explicit through isort
from plugwise.exceptions import NetworkDown # pw-beta usb
from plugwise.exceptions import PortError # pw-beta usb
from plugwise.exceptions import StickInitError # pw-beta usb
from plugwise.exceptions import TimeoutException # pw-beta usb
from plugwise.smile import Smile
from plugwise.stick import Stick
from plugwise.stick import Stick # pw-beta usb

from .const import (
API,
CONF_MANUAL_PATH,
CONF_USB_PATH,
COORDINATOR,
DEFAULT_PORT,
DEFAULT_USERNAME,
DOMAIN,
FLOW_NET,
FLOW_SMILE,
FLOW_STRETCH,
FLOW_TYPE,
FLOW_USB,
PW_TYPE,
SMILE,
STICK,
STRETCH,
STRETCH_USERNAME,
ZEROCONF_MAP,
)

# pw-beta Note; the below are explicit through isort
from .const import CONF_HOMEKIT_EMULATION # pw-beta option
from .const import CONF_MANUAL_PATH # pw-beta usb
from .const import CONF_REFRESH_INTERVAL # pw-beta option
from .const import CONF_USB_PATH # pw-beta usb
from .const import DEFAULT_SCAN_INTERVAL # pw-beta option
from .const import FLOW_NET # pw-beta usb
from .const import FLOW_TYPE # pw-beta usb
from .const import FLOW_USB # pw-beta usb
from .const import STICK # pw-beta usb

CONNECTION_SCHEMA = vol.Schema(
{vol.Required(FLOW_TYPE, default=FLOW_NET): vol.In([FLOW_NET, FLOW_USB])}
)
) # pw-beta usb


@callback
def plugwise_stick_entries(hass):
def plugwise_stick_entries(hass): # pw-beta usb
"""Return existing connections for Plugwise USB-stick domain."""
sticks = []
for entry in hass.config_entries.async_entries(DOMAIN):
Expand All @@ -81,7 +85,9 @@ def plugwise_stick_entries(hass):
# Github issue: #265
# Might be a `tuple[dict[str, str], Stick | None]` for typing, but that throws
# Item None of Optional[Any] not having attribute mac [union-attr]
async def validate_usb_connection(self, device_path=None) -> tuple[dict[str, str], Any]:
async def validate_usb_connection(
self, device_path=None
) -> tuple[dict[str, str], Any]: # pw-beta usb
"""Test if device_path is a real Plugwise USB-Stick."""
errors = {}

Expand Down Expand Up @@ -240,7 +246,7 @@ async def async_step_zeroconf(

async def async_step_user_usb(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> FlowResult: # pw-beta usb
"""Step when user initializes a integration."""
errors: dict[str, str] = {}
ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
Expand Down Expand Up @@ -278,7 +284,7 @@ async def async_step_user_usb(

async def async_step_manual_path(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> FlowResult: # pw-beta usb
"""Step when manual path to device."""
errors: dict[str, str] = {}
if user_input is not None:
Expand Down Expand Up @@ -369,18 +375,19 @@ async def async_step_user(
errors=errors,
)

# pw-beta
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> config_entries.OptionsFlow:
def async_get_options_flow(
config_entry: ConfigEntry,
) -> config_entries.OptionsFlow: # pw-beta options
"""Get the options flow for this handler."""
return PlugwiseOptionsFlowHandler(config_entry)


# pw-beta - change the scan-interval via CONFIGURE
# pw-beta - add homekit emulation via CONFIGURE
# pw-beta - change the frontend refresh interval via CONFIGURE
class PlugwiseOptionsFlowHandler(config_entries.OptionsFlow):
class PlugwiseOptionsFlowHandler(config_entries.OptionsFlow): # pw-beta options
"""Plugwise option flow."""

def __init__(self, config_entry: ConfigEntry) -> None: # pragma: no cover
Expand Down Expand Up @@ -410,7 +417,7 @@ async def async_step_init(
coordinator = self.hass.data[DOMAIN][self.config_entry.entry_id][COORDINATOR]
interval: dt.timedelta = DEFAULT_SCAN_INTERVAL[
coordinator.api.smile_type
] # pw-beta
] # pw-beta options

data = {
vol.Optional(
Expand Down
Loading