Skip to content

Commit

Permalink
Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aneisch committed Feb 19, 2024
1 parent 5ec5d5c commit 8957d1a
Show file tree
Hide file tree
Showing 22 changed files with 363 additions and 83 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Also using Grafana/Influx for graphing, both running in Docker containers on NUC
Description | value
-- | --
Lines of ESPHome YAML | 2801
Lines of Home Assistant YAML | 9102
Lines of Home Assistant YAML | 9113
[Integrations](https://www.home-assistant.io/integrations/) in use | 57
Zigbee devices in [`zha`](https://www.home-assistant.io/integrations/zha/) | 26
Z-Wave devices in [`zwave_js`](https://www.home-assistant.io/integrations/zwave_js/) | 37
Expand All @@ -76,7 +76,7 @@ Entities in the [`event`](https://www.home-assistant.io/components/event) domain
Entities in the [`fan`](https://www.home-assistant.io/components/fan) domain | 3
Entities in the [`group`](https://www.home-assistant.io/components/group) domain | 17
Entities in the [`image`](https://www.home-assistant.io/components/image) domain | 8
Entities in the [`input_boolean`](https://www.home-assistant.io/components/input_boolean) domain | 28
Entities in the [`input_boolean`](https://www.home-assistant.io/components/input_boolean) domain | 29
Entities in the [`input_datetime`](https://www.home-assistant.io/components/input_datetime) domain | 34
Entities in the [`input_number`](https://www.home-assistant.io/components/input_number) domain | 6
Entities in the [`input_select`](https://www.home-assistant.io/components/input_select) domain | 19
Expand All @@ -101,7 +101,7 @@ Entities in the [`update`](https://www.home-assistant.io/components/update) doma
Entities in the [`vacuum`](https://www.home-assistant.io/components/vacuum) domain | 1
Entities in the [`weather`](https://www.home-assistant.io/components/weather) domain | 2
Entities in the [`zone`](https://www.home-assistant.io/components/zone) domain | 6
**Total state objects** | **1283**
**Total state objects** | **1284**
## The HACS integrations/plugins that I use:
**Appdaemon**:<br>
[aneisch/follow_me_appdaemon](https://github.com/aneisch/follow_me_appdaemon)<br>
Expand All @@ -125,7 +125,6 @@ Entities in the [`zone`](https://www.home-assistant.io/components/zone) domain |
[blakeblackshear/frigate-hass-integration](https://github.com/blakeblackshear/frigate-hass-integration)<br>
[claytonjn/hass-circadian_lighting](https://github.com/claytonjn/hass-circadian_lighting)<br>
[custom-components/readme](https://github.com/custom-components/readme)<br>
[dbuezas/icsee-ptz](https://github.com/dbuezas/icsee-ptz)<br>
[dlashua/hass-setter](https://github.com/dlashua/hass-setter)<br>
[hacs/integration](https://github.com/hacs/integration)<br>
[moralmunky/Home-Assistant-Mail-And-Packages](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages)<br>
Expand Down
23 changes: 22 additions & 1 deletion custom_components/mail_and_packages/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

DOMAIN = "mail_and_packages"
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "0.3.20"
VERSION = "0.3.21"
ISSUE_URL = "http://github.com/moralmunky/Home-Assistant-Mail-And-Packages"
PLATFORM = "sensor"
PLATFORMS = ["binary_sensor", "camera", "sensor"]
Expand Down Expand Up @@ -556,6 +556,20 @@
"subject": ["delivery is delayed"],
},
"walmart_tracking": {"pattern": ["#[0-9]{7}-[0-9]{7}"]},
# BuildingLink
"buildinglink_delivered": {
"email": ["[email protected]"],
"subject": [
"Your Amazon order has arrived",
"Your USPS delivery has arrived",
"Your UPS delivery has arrived",
"Your FEDEX delivery has arrived",
"You have a package delivery",
"You have a DHL delivery",
"You have an envelope",
],
},
"buildinglink_tracking": {},
# Post NL
"post_nl_delivering": {
"email": ["[email protected]"],
Expand Down Expand Up @@ -992,6 +1006,13 @@
icon="mdi:archive-alert",
key="walmart_exception",
),
# BuildingLink
"buildinglink_delivered": SensorEntityDescription(
name="Mail BuildingLink Delivered",
native_unit_of_measurement="package(s)",
icon="mdi:package-variant-closed",
key="buildinglink_delivered",
),
# Post NL
"post_nl_delivering": SensorEntityDescription(
name="Post NL Delivering",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/mail_and_packages/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"Pillow>=9.0",
"dateparser"
],
"version": "0.3.20"
"version": "0.3.21"
}
7 changes: 5 additions & 2 deletions custom_components/sonoff/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,11 @@ async def async_added_to_hass(self) -> None:

self._attr_is_on = restore.state == STATE_ON

if self.is_on and self.timeout:
ts = restore.attributes[ATTR_LAST_TRIGGERED]
if (
self.is_on
and self.timeout
and (ts := restore.attributes.get(ATTR_LAST_TRIGGERED))
):
left = self.timeout - (dt.utcnow() - dt.parse_datetime(ts)).seconds
if left > 0:
self.task = asyncio.create_task(self.clear_state(left))
Expand Down
41 changes: 37 additions & 4 deletions custom_components/sonoff/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ClimateEntityFeature,
HVACMode,
)
from homeassistant.const import UnitOfTemperature
from homeassistant.const import UnitOfTemperature, MAJOR_VERSION, MINOR_VERSION

from .core.const import DOMAIN
from .core.entity import XEntity
Expand All @@ -29,12 +29,22 @@ class XClimateTH(XEntity, ClimateEntity):
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.DRY]
_attr_max_temp = 99
_attr_min_temp = 1
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
_attr_target_temperature_high = None
_attr_target_temperature_low = None
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = 1

# https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded
if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2):
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
else:
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE_RANGE

heat: bool = None

def set_state(self, params: dict):
Expand Down Expand Up @@ -143,10 +153,20 @@ class XClimateNS(XEntity, ClimateEntity):
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT_COOL, HVACMode.AUTO]
_attr_max_temp = 31
_attr_min_temp = 16
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = 1

# https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded
if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2):
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
else:
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE

def set_state(self, params: dict):
cache = self.device["params"]
if cache != params:
Expand Down Expand Up @@ -230,10 +250,23 @@ class XThermostat(XEntity, ClimateEntity):
_attr_max_temp = 45
_attr_min_temp = 5
_attr_preset_modes = ["manual", "programmed", "economical"]
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = 0.5

# https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded
if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2):
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.PRESET_MODE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
_enable_turn_on_off_backwards_compatibility = False
else:
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
)

def set_state(self, params: dict):
cache = self.device["params"]
if cache != params:
Expand Down
28 changes: 22 additions & 6 deletions custom_components/sonoff/core/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
XLightL1,
XLightL3,
XT5Light,
XZigbeeLight,
)
from ..number import XPulseWidth
from ..remote import XRemote
Expand All @@ -53,6 +54,7 @@
XEnergySensorPOWR3,
XEnergyTotal,
XT5Action,
XButton91,
)
from ..switch import (
XSwitch,
Expand All @@ -68,13 +70,20 @@
DEVICE_CLASS = {
"binary_sensor": (XEntity, BinarySensorEntity),
"fan": (XToggleFan,), # using custom class for overriding is_on function
"dualfan": (XFanDualR3,),
"light": (XEntity, LightEntity),
"sensor": (XEntity, SensorEntity),
"switch": (XEntity, SwitchEntity),
}


def unwrap_cached_properties(attrs: dict):
"""Fix metaclass CachedProperties problem in latest Hass."""
for k, v in list(attrs.items()):
if k.startswith("_attr_") and f"_{k}" in attrs and isinstance(v, property):
attrs[k] = attrs.pop(f"_{k}")
return attrs


def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
"""Make duplicate for cls class with changes in kwargs params.
Expand All @@ -84,9 +93,10 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
if enabled is not None:
kwargs["_attr_entity_registry_enabled_default"] = enabled
if base:
bases = cls.__mro__[-len(XSwitch.__mro__) :: -1]
bases = {k: v for b in bases for k, v in b.__dict__.items()}
return type(cls.__name__, DEVICE_CLASS[base], {**bases, **kwargs})
attrs = cls.__mro__[-len(XSwitch.__mro__) :: -1]
attrs = {k: v for b in attrs for k, v in b.__dict__.items()}
attrs = unwrap_cached_properties({**attrs, **kwargs})
return type(cls.__name__, DEVICE_CLASS[base], attrs)
return type(cls.__name__, (cls,), kwargs)


Expand Down Expand Up @@ -201,6 +211,7 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
82: SPEC_2CH,
83: SPEC_3CH,
84: SPEC_4CH,
91: [XButton91],
102: [XWiFiDoor, XWiFiDoorBattery, RSSI], # Sonoff DW2 Door/Window sensor
103: [XLightB02, RSSI], # Sonoff B02 CCT bulb
104: [XLightB05B, RSSI], # Sonoff B05-B RGB+CCT color bulb
Expand Down Expand Up @@ -349,7 +360,8 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
# https://github.com/AlexxIT/SonoffLAN/issues/1251
212: [Switch1, Switch2, Switch3, Switch4, XT5Light, XT5Action], # T5-4C-86
1000: [XRemoteButton, Battery], # zigbee_ON_OFF_SWITCH_1000
1256: [spec(XSwitch, base="light")], # ZCL_HA_DEVICEID_ON_OFF_LIGHT
# https://github.com/AlexxIT/SonoffLAN/issues/1195
1256: [spec(XSwitch)], # ZCL_HA_DEVICEID_ON_OFF_LIGHT
1257: [XLightD1], # ZigbeeWhiteLight
# https://github.com/AlexxIT/SonoffLAN/issues/972
1514: [XZigbeeCover, spec(XSensor, param="battery", multiply=2)],
Expand All @@ -370,6 +382,8 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
spec(XBinarySensor, param="lock", uid="", default_class="door"),
Battery,
],
# https://github.com/AlexxIT/SonoffLAN/issues/1265
3258: [XZigbeeLight], # ZigbeeColorTunableWhiteLight
4026: [
spec(XBinarySensor, param="water", uid="", default_class="moisture"),
Battery,
Expand All @@ -384,6 +398,8 @@ def spec(cls, base: str = None, enabled: bool = None, **kwargs) -> type:
XRemoteButton,
Battery,
],
# https://github.com/AlexxIT/SonoffLAN/issues/1283
7006: [XZigbeeCover, spec(XSensor, param="battery")],
7014: [
spec(XSensor100, param="temperature"),
spec(XSensor100, param="humidity"),
Expand All @@ -407,7 +423,7 @@ def get_spec(device: dict) -> list:
# DualR3 in cover mode
if uiid in [126, 165] and device["params"].get("workMode") == 2:
classes = [cls for cls in classes if XSwitches not in cls.__bases__]
classes.insert(0, XCoverDualR3)
classes = [XCoverDualR3, XFanDualR3] + classes

# NSPanel Climate disable without switch configuration
if uiid in [133] and not device["params"].get("HMI_ATCDevice"):
Expand Down
3 changes: 2 additions & 1 deletion custom_components/sonoff/core/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@


class XEntity(Entity):
event: bool = False # if True - skip set_state on entity init
params: set = {}
param: str = None
uid: str = None
Expand Down Expand Up @@ -83,7 +84,7 @@ def __init__(self, ewelink: XRegistry, device: XDevice) -> None:
)

try:
self.internal_update(params)
self.internal_update(None if self.event else params)
except Exception as e:
_LOGGER.error(f"Can't init device: {device}", exc_info=e)

Expand Down
16 changes: 10 additions & 6 deletions custom_components/sonoff/core/ewelink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ def setup_devices(self, devices: list[XDevice]) -> list:
_LOGGER.debug(f"{did} UIID {uiid:04} | %s", device["params"])

if parentid := device["params"].get("parentid"):
device["parent"] = next(
d for d in devices if d["deviceid"] == parentid
)
try:
device["parent"] = next(
d for d in devices if d["deviceid"] == parentid
)
except StopIteration:
pass

# at this moment entities can catch signals with device_id and
# update their states, but they can be added to hass later
Expand Down Expand Up @@ -98,13 +101,14 @@ async def send(
ignored if params empty
:param timeout_lan: optional custom LAN timeout
"""
seq = self.sequence()
seq = await self.sequence()

if "parent" in device:
main_device = device["parent"]
if not params_lan:
if params_lan is None and params is not None:
params_lan = params.copy()
params_lan["subDevId"] = device["deviceid"]
if params_lan:
params_lan["subDevId"] = device["deviceid"]
else:
main_device = device

Expand Down
16 changes: 9 additions & 7 deletions custom_components/sonoff/core/ewelink/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,22 @@ class XDevice(TypedDict, total=False):
class XRegistryBase:
dispatcher: dict[str, list[Callable]] = None
_sequence: int = 0
_sequence_lock: asyncio.Lock = asyncio.Lock()

def __init__(self, session: ClientSession):
self.dispatcher = {}
self.session = session

@staticmethod
def sequence() -> str:
async def sequence() -> str:
"""Return sequnce counter in ms. Always unique."""
t = int(time.time()) * 1000
if t > XRegistryBase._sequence:
XRegistryBase._sequence = t
else:
XRegistryBase._sequence += 1
return str(XRegistryBase._sequence)
t = time.time_ns() // 1_000_000
async with XRegistryBase._sequence_lock:
if t > XRegistryBase._sequence:
XRegistryBase._sequence = t
else:
XRegistryBase._sequence += 1
return str(XRegistryBase._sequence)

def dispatcher_connect(self, signal: str, target: Callable) -> Callable:
targets = self.dispatcher.setdefault(signal, [])
Expand Down
Loading

0 comments on commit 8957d1a

Please sign in to comment.