Skip to content
This repository has been archived by the owner on Jul 23, 2021. It is now read-only.

Commit

Permalink
Merge 25da73f into 1b37a68
Browse files Browse the repository at this point in the history
  • Loading branch information
robmarkcole committed Mar 14, 2020
2 parents 1b37a68 + 25da73f commit 58a5b70
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 668 deletions.
31 changes: 11 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
[FOR COMMUNITY SUPPORT PLEASE USE THIS THREAD](https://community.home-assistant.io/t/hue-motion-sensors-remotes-custom-component)

## Overview
This custom component provides support for Hue motion sensors, remotes, and some friends of Hue devices.
This custom integration provides support for the official [Hue motion sensors](https://www2.meethue.com/en-us/p/hue-motion-sensor/046677473389) and the Hue device tracker (allows tracking the mobile with the Hue app installed). Note that these sensors [are officially integrated with Home Assistant](https://www.home-assistant.io/integrations/hue/), but a different approach is taken in this custom integration.

The _Hue motion_ devices are handled as separate entities for motion (`binary_sensor`), light level, and temperature (`sensor` entities in HA).

* The approach here is to add them as unique `binary_sensor` entities, with the light level and temperature values attached to its state attributes.
* Also, this integration offers a 1Hz refresh update for this sensors, in opposition to current 5-sec scan interval in the official integration.

**Be advised that the increased update rate induced to the Hue bridges by this custom integration may raise connectivity problems**, which would appear as errors in the official hue integration.

If you experience this kind of issues, please disable this integration and check again before opening a new issue in HA Core. The problem may be originated here.

## Installation

Expand All @@ -20,14 +29,10 @@ Once installed add to your configuration:
```
binary_sensor:
- platform: huesensor
remote:
- platform: huesensor
device_tracker:
- platform: huesensor
```

Hue dimmer remotes can be used for a click and long press (hold button for 2 sec and see LED blink twice).

As per [this issue](https://github.com/robmarkcole/Hue-sensors-HASS/issues/48) it is recommended to use the default naming options in the Hue app in order to ensure sensible sensor names in HA.

## Front end display
Expand Down Expand Up @@ -68,20 +73,6 @@ Temperature, light level and other data in the sensor attributes can be broken o
<img src="https://github.com/robmarkcole/Hue-sensors-HASS/blob/master/hue.png" width="500">
</p>

## Track Updates

This custom component can be tracked with the help of [HACS](https://hacs.xyz/).

## Debugging

If you get an error when using this component, the procedure for debugging is as follows.

1. Open an issue here on Github. Include the error message, release number of the custom component.
2. Download the Hue API response following the instructions [here](https://www.hackster.io/robin-cole/hijack-a-hue-remote-to-control-anything-with-home-assistant-5239a4#toc-hue-api-1). Save into a .json file.
3. Parse the json file using the [hue_sensors package](https://pypi.python.org/pypi/hue-sensors/1.2) and report the device ID (e.g. RWL_06-02) that is causing your issue.

There are a couple of examples of this process in the debugging_issues folder.

## Developers

* Create venv -> `$ python3 -m venv venv`
Expand All @@ -94,7 +85,7 @@ There are a couple of examples of this process in the debugging_issues folder.

Please format code usign [Black](https://github.com/psf/black) before opening a pull request.

A big thanks to [@yottatsa](https://github.com/yottatsa) for his many contributions to this work, check out his profile!
A big thanks to [Atsuko Ito](https://github.com/yottatsa) and [Eugenio Panadero](https://github.com/azogue) for their many contributions to this work!

## ✨ Support this work

Expand Down
17 changes: 2 additions & 15 deletions custom_components/huesensor/data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
BINARY_SENSOR_MODELS,
ENTITY_ATTRS,
parse_hue_api_response,
REMOTE_MODELS,
)

_LOGGER = logging.getLogger(__name__)

_KNOWN_MODEL_IDS = tuple(BINARY_SENSOR_MODELS + REMOTE_MODELS)

# Scan interval for remotes and binary sensors is set to < 1s
# Scan interval for binary sensors is set to < 1s
# just to ~ensure that an update is called for each HA tick,
# as using an exact 1s misses some of the ticks
DEFAULT_SCAN_INTERVAL = timedelta(seconds=0.5)
Expand Down Expand Up @@ -51,7 +48,7 @@ def __init__(self, hass):
self._registered_platforms = {}

async def _iter_data(
self, models_filter: Tuple[str] = _KNOWN_MODEL_IDS
self, models_filter: Tuple[str] = BINARY_SENSOR_MODELS
) -> AsyncIterable[Tuple[bool, str, str, dict]]:
async for bridge in async_get_bridges(self.hass):
await bridge.sensor_manager.coordinator.async_request_refresh()
Expand Down Expand Up @@ -137,16 +134,6 @@ async def _add_new_entities(self, new_entities, scan_interval=None):
scan_interval.total_seconds(),
entity_cls.__name__,
)
elif scan_interval < self._scan_interval:
_LOGGER.info(
"Re-Configure a scan_interval of %.2f s for %s devices"
" (before it was %.2f s)",
scan_interval.total_seconds(),
entity_cls.__name__,
self._scan_interval.total_seconds(),
)
await self.async_stop_scheduler()
self._scan_interval = scan_interval

async def async_add_platform_entities(
self, entity_cls, platform_models, func_add_entities, scan_interval,
Expand Down
185 changes: 6 additions & 179 deletions custom_components/huesensor/hue_api_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,8 @@
)
from homeassistant.const import STATE_OFF, STATE_ON

REMOTE_MODELS = ("RWL", "ROM", "FOH", "ZGP", "Z3-")
BINARY_SENSOR_MODELS = ("SML",)
ENTITY_ATTRS = {
"RWL": ["last_updated", "last_button_event", "battery", "on", "reachable"],
"ROM": ["last_updated", "last_button_event", "battery", "on", "reachable"],
"ZGP": ["last_updated", "last_button_event"],
"FOH": ["last_updated", "last_button_event"],
"Z3-": [
"last_updated",
"last_button_event",
"battery",
"on",
"reachable",
"dial_state",
"dial_position",
"software_update",
],
"SML": [
"light_level",
"battery",
Expand All @@ -57,51 +42,6 @@
"threshold_offset",
],
}
FOH_BUTTONS = {
16: "left_upper_press",
20: "left_upper_release",
17: "left_lower_press",
21: "left_lower_release",
18: "right_lower_press",
22: "right_lower_release",
19: "right_upper_press",
23: "right_upper_release",
100: "double_upper_press",
101: "double_upper_release",
98: "double_lower_press",
99: "double_lower_release",
}
RWL_BUTTONS = {
ZLL_SWITCH_BUTTON_1_INITIAL_PRESS: "1_click",
ZLL_SWITCH_BUTTON_2_INITIAL_PRESS: "1_click",
ZLL_SWITCH_BUTTON_3_INITIAL_PRESS: "1_click",
ZLL_SWITCH_BUTTON_4_INITIAL_PRESS: "1_click",
ZLL_SWITCH_BUTTON_1_HOLD: "1_hold",
ZLL_SWITCH_BUTTON_2_HOLD: "2_hold",
ZLL_SWITCH_BUTTON_3_HOLD: "3_hold",
ZLL_SWITCH_BUTTON_4_HOLD: "4_hold",
ZLL_SWITCH_BUTTON_1_SHORT_RELEASED: "1_click_up",
ZLL_SWITCH_BUTTON_2_SHORT_RELEASED: "2_click_up",
ZLL_SWITCH_BUTTON_3_SHORT_RELEASED: "3_click_up",
ZLL_SWITCH_BUTTON_4_SHORT_RELEASED: "4_click_up",
ZLL_SWITCH_BUTTON_1_LONG_RELEASED: "1_hold_up",
ZLL_SWITCH_BUTTON_2_LONG_RELEASED: "2_hold_up",
ZLL_SWITCH_BUTTON_3_LONG_RELEASED: "3_hold_up",
ZLL_SWITCH_BUTTON_4_LONG_RELEASED: "4_hold_up",
}
TAP_BUTTONS = {
ZGP_SWITCH_BUTTON_1: "1_click",
ZGP_SWITCH_BUTTON_2: "2_click",
ZGP_SWITCH_BUTTON_3: "3_click",
ZGP_SWITCH_BUTTON_4: "4_click",
}
Z3_BUTTON = {
1000: "initial_press",
1001: "repeat",
1002: "short_release",
1003: "long_release",
}
Z3_DIAL = {1: "begin", 2: "end"}


def parse_sml(response: Dict[str, Any]) -> Dict[str, Any]:
Expand Down Expand Up @@ -159,129 +99,16 @@ def parse_sml(response: Dict[str, Any]) -> Dict[str, Any]:
return data


def parse_zgp(response: Dict[str, Any]) -> Dict[str, Any]:
"""Parse the json response for a ZGPSWITCH Hue Tap."""
button = TAP_BUTTONS.get(response["state"]["buttonevent"], "No data")

return {
"model": "ZGP",
"name": response["name"],
"state": button,
"last_button_event": button,
"last_updated": response["state"]["lastupdated"].split("T"),
}


def parse_rwl(response: Dict[str, Any]) -> Dict[str, Any]:
"""Parse the json response for a RWL Hue remote."""
button = RWL_BUTTONS.get(response["state"]["buttonevent"])

return {
"model": "RWL",
"name": response["name"],
"state": button,
"battery": response["config"]["battery"],
"on": response["config"]["on"],
"reachable": response["config"]["reachable"],
"last_button_event": button,
"last_updated": response["state"]["lastupdated"].split("T"),
}


def parse_foh(response: Dict[str, Any]) -> Dict[str, Any]:
"""Parse the JSON response for a FOHSWITCH (type still = ZGPSwitch)."""
press = response["state"]["buttonevent"]
button = FOH_BUTTONS.get(press, "No data")

return {
"model": "FOH",
"name": response["name"],
"state": button,
"last_button_event": button,
"last_updated": response["state"]["lastupdated"].split("T"),
}


def parse_z3_rotary(response: Dict[str, Any]) -> Dict[str, Any]:
"""Parse the json response for a Lutron Aurora Rotary Event."""
turn = response["state"]["rotaryevent"]
dial = Z3_DIAL.get(turn, "No data")
dial_position = response["state"]["expectedrotation"]

return {
"model": "Z3-",
"name": response["name"],
"dial_state": dial,
"dial_position": dial_position,
"software_update": response["swupdate"]["state"],
"battery": response["config"]["battery"],
"on": response["config"]["on"],
"reachable": response["config"]["reachable"],
"last_updated": response["state"]["lastupdated"].split("T"),
}


def parse_z3_switch(response: Dict[str, Any]) -> Dict[str, Any]:
"""Parse the json response for a Lutron Aurora."""
press = response["state"]["buttonevent"]
button = Z3_BUTTON.get(press, "No data")

return {
"last_button_event": button,
"state": button,
"last_updated": response["state"]["lastupdated"].split("T"),
}


def _ident_raw_sensor(
raw_sensor_data: Dict[str, Any]
) -> Tuple[Optional[str], Callable]:
"""Identify sensor types and return unique identifier and parser."""
model_id = raw_sensor_data["modelid"][0:3]
unique_sensor_id = raw_sensor_data["uniqueid"]

if model_id == "SML":
sensor_key = model_id + "_" + unique_sensor_id[:-5]
return sensor_key, parse_sml

elif model_id in ("RWL", "ROM"):
sensor_key = model_id + "_" + unique_sensor_id[:-5]
return sensor_key, parse_rwl

elif model_id in ("FOH", "ZGP"):
# **** New Model ID ****
# needed for uniqueness
sensor_key = model_id + "_" + unique_sensor_id[-14:-3]
if model_id == "FOH":
return sensor_key, parse_foh

return sensor_key, parse_zgp

elif model_id == "Z3-":
# Newest Model ID / Lutron Aurora / Hue Bridge
# treats it as two sensors, I wanted them combined
if raw_sensor_data["type"] == "ZLLRelativeRotary": # Rotary Dial
# Rotary key is substring of button
sensor_key = model_id + "_" + unique_sensor_id[:-5]
return sensor_key, parse_z3_rotary
else:
sensor_key = model_id + "_" + unique_sensor_id
return sensor_key, parse_z3_switch

return None, None


def parse_hue_api_response(sensors: Iterable[Dict[str, Any]]):
"""Take in the Hue API json response."""
data_dict = {} # The list of sensors, referenced by their hue_id.

# Loop over all keys (1,2 etc) to identify sensors and get data.
for sensor in sensors:
_key, _raw_parser = _ident_raw_sensor(sensor)
if _key is None:
continue

parsed_sensor = _raw_parser(sensor)
# Filter sensors by model.
for sensor in filter(lambda x: x["modelid"].startswith("SML"), sensors):
model_id = sensor["modelid"][0:3]
unique_sensor_id = sensor["uniqueid"]
_key = model_id + "_" + unique_sensor_id[:-5]
parsed_sensor = parse_sml(sensor)
if _key not in data_dict:
data_dict[_key] = parsed_sensor
else:
Expand Down
71 changes: 0 additions & 71 deletions custom_components/huesensor/remote.py

This file was deleted.

Loading

0 comments on commit 58a5b70

Please sign in to comment.