Skip to content

Commit

Permalink
Merge pull request #115 from bdraco/always_callback_state_on_pir
Browse files Browse the repository at this point in the history
Always callback state changes when the PIR is activated
  • Loading branch information
sbidy authored Feb 10, 2022
2 parents 950cdb8 + a576f9b commit a30bea0
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 3 deletions.
15 changes: 13 additions & 2 deletions pywizlight/bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

NEVER_TIME = -120.0

_IGNORE_KEYS = {"src", "mqttCd", "ts", "rssi"}

RGB_ORDER = ["r", "g", "b"]
RGB_COLORS = set(RGB_ORDER)
Expand All @@ -45,11 +44,19 @@
RGBWW_ORDER = ["r", "g", "b", "c", "w"]
RGBWW_COLORS = set(RGBWW_ORDER)

_IGNORE_STATE_KEYS = {"mqttCd", "ts", "rssi"}
PIR_SOURCE = "pir"


def states_match(old: Dict[str, Any], new: Dict[str, Any]) -> bool:
"""Check if states match except for keys we do not want to callback on."""
old_src = old.get("src")
new_src = new.get("src")
# Always send the update when there is a PIR change.
if new_src != old_src and PIR_SOURCE in (old_src, new_src):
return False
for key, val in new.items():
if old.get(key) != val and key not in _IGNORE_KEYS:
if key != "src" and old.get(key) != val and key not in _IGNORE_STATE_KEYS:
return False
return True

Expand Down Expand Up @@ -216,6 +223,10 @@ def get_state(self) -> Optional[bool]:
"""Return the state of the bulb."""
return _extract_bool(self.pilotResult, "state")

def get_source(self) -> Optional[str]:
"""Return the source of the state change."""
return _extract_str(self.pilotResult, "src")

def get_mac(self) -> Optional[str]:
"""Return MAC from the bulb."""
return _extract_str(self.pilotResult, "mac")
Expand Down
2 changes: 1 addition & 1 deletion pywizlight/tests/fake_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def get_initial_pilot() -> Dict[str, Any]:
"result": {
"mac": "ABCABCABCABC",
"rssi": -62,
"src": "",
"src": "udp",
"state": False,
"sceneId": 0,
"r": 255,
Expand Down
89 changes: 89 additions & 0 deletions pywizlight/tests/test_bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pywizlight import SCENES, PilotBuilder, wizlight
from pywizlight.bulblibrary import BulbClass, BulbType, Features, KelvinRange
from pywizlight.discovery import discover_lights
from pywizlight.bulb import states_match
from pywizlight.exceptions import WizLightTimeOutError
from pywizlight.tests.fake_bulb import startup_bulb

Expand Down Expand Up @@ -149,6 +150,13 @@ async def test_PilotBuilder_speed(correct_bulb: wizlight) -> None:
assert state and state.get_speed() == 50


@pytest.mark.asyncio
async def test_get_source(correct_bulb: wizlight) -> None:
"""Test getting the source."""
state = await correct_bulb.updateState()
assert state and state.get_source() == "udp"


# ------ Error states -------------------------------------
@pytest.mark.asyncio
async def test_error_PilotBuilder_brightness(correct_bulb: wizlight) -> None:
Expand Down Expand Up @@ -265,3 +273,84 @@ async def test_timeout_PilotBuilder(bad_bulb: wizlight) -> None:
"pywizlight.bulb.SEND_INTERVAL", 0.01
), patch("pywizlight.bulb.TIMEOUT", 0.01):
await bad_bulb.turn_on(PilotBuilder(brightness=255))


@pytest.mark.asyncio
async def test_states_match() -> None:
"""Test states match always sends pir updates but we ignore mqttCd, rssi, and ts."""
state_off_ios = {
"mac": "a8bb50d46a1c",
"rssi": -70,
"src": "ios",
"mqttCd": 0,
"ts": 1644440635,
"state": False,
"sceneId": 0,
}
state_on_ios = {
"mac": "a8bb50d46a1c",
"rssi": -45,
"src": "ios",
"mqttCd": 0,
"ts": 1644440662,
"state": False,
"sceneId": 27,
"speed": 100,
"dimming": 100,
}
state_on_hb = {
"mac": "a8bb50d46a1c",
"rssi": -45,
"src": "hb",
"mqttCd": 0,
"ts": 1644440642,
"state": False,
"sceneId": 27,
"speed": 100,
"dimming": 100,
}
ios_scene27 = {
"mac": "a8bb50d46a1c",
"rssi": -48,
"src": "ios",
"state": True,
"sceneId": 27,
"speed": 100,
"dimming": 100,
}
ios_off = {
"mac": "a8bb50d46a1c",
"rssi": -69,
"src": "ios",
"state": False,
"sceneId": 0,
}
occupancy_detected_scene27 = {
"mac": "a8bb50d46a1c",
"rssi": -48,
"src": "pir",
"state": True,
"sceneId": 27,
"speed": 100,
"dimming": 100,
}
occupancy_not_detected = {
"mac": "a8bb50d46a1c",
"rssi": -69,
"src": "pir",
"state": False,
"sceneId": 0,
}

assert states_match(state_off_ios, state_off_ios)
assert not states_match(state_off_ios, state_on_ios)
assert states_match(
state_on_ios, state_on_hb
) # source change does not matter unless its a PIR
assert not states_match(
ios_scene27, occupancy_detected_scene27
) # source change matters since its a PIR
assert states_match(occupancy_detected_scene27, occupancy_detected_scene27)
assert not states_match(
ios_off, occupancy_not_detected
) # source change matters since its a PIR

0 comments on commit a30bea0

Please sign in to comment.