Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ When Home Assistant publishes its birth message (`online` on `homeassistant/stat
- Event
- Device Trigger
- Tag Scanner
- Notify



Expand Down Expand Up @@ -427,6 +428,15 @@ scanner = uhome.TagScanner(device, 'RFID Reader')
scanner.scan('E9F35959')
```

### Notify

Use `Notify` when Home Assistant should send notification messages to the device over MQTT. The device subscribes to the generated command topic, and uhome restores that subscription after reconnects.

```
display = uhome.Notify(device, 'Display')
display.set_action(lambda msg: print(msg))
```

## Testing

Desktop tests can be run from the repository root with:
Expand Down
86 changes: 86 additions & 0 deletions tests/test_notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import os
import sys
import unittest

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, "uhome"))

from test_uhome import FakeClock, FakeMQTTClient

import uhome


class NotifyTests(unittest.TestCase):
def setUp(self):
self.clock = FakeClock()
self._old_ticks_ms = uhome.ticks_ms
self._old_ticks_diff = uhome.ticks_diff
uhome.ticks_ms = self.clock.ticks_ms
uhome.ticks_diff = self.clock.ticks_diff

def tearDown(self):
uhome.ticks_ms = self._old_ticks_ms
uhome.ticks_diff = self._old_ticks_diff

def payloads_for(self, mqtt, topic):
return [payload for pub_topic, payload, retain, qos in mqtt.published if pub_topic == topic]

def test_discovery_config_contains_notify_schema(self):
device = uhome.Device("Notify Device")
notify = uhome.Notify(device, "Display", command_template="{{ message }}", retain=True)

self.assertEqual("Notify Device", notify.conf["dev"]["name"])
self.assertEqual("notify_device_display", notify.conf["uniq_id"])
self.assertEqual(device.will_topic, notify.conf["avty_t"])
self.assertEqual(notify.topic_for("command"), notify.conf["cmd_t"])
self.assertEqual("{{ message }}", notify.conf["command_template"])
self.assertTrue(notify.conf["retain"])
self.assertNotIn("stat_t", notify.conf)

def test_set_action_subscribes_and_handles_notifications(self):
device = uhome.Device("Notify Device")
notify = uhome.Notify(device, "Display")
received = []
self.assertTrue(notify.set_action(lambda msg: received.append(msg)))
mqtt = FakeMQTTClient()
self.assertTrue(device.connect(mqtt))

self.assertIn((notify.get_topic(), 0), mqtt.subscribed)
mqtt.deliver(notify.get_topic(), "hello")
self.assertTrue(device.loop())
self.assertEqual(["hello"], received)

def test_command_subscription_survives_reconnect(self):
device = uhome.Device("Notify Device")
notify = uhome.Notify(device, "Display")
received = []
notify.set_action(lambda msg: received.append(msg))
mqtt = FakeMQTTClient()
self.assertTrue(device.connect(mqtt))
mqtt.clear_history()

mqtt.fail_publish = True
self.assertFalse(device.publish("any/topic", "x"))
mqtt.fail_publish = False
self.clock.advance(1000)
self.assertTrue(device.loop())

self.assertIn((notify.get_topic(), 0), mqtt.subscribed)
self.assertTrue(self.payloads_for(mqtt, notify.discovery_topic))
mqtt.deliver(notify.get_topic(), "after reconnect")
self.assertTrue(device.loop())
self.assertEqual(["after reconnect"], received)

def test_notify_has_no_cached_state_to_republish(self):
device = uhome.Device("Notify Device")
notify = uhome.Notify(device, "Display")
mqtt = FakeMQTTClient()
self.assertTrue(device.connect(mqtt))
mqtt.clear_history()

self.assertTrue(notify.republish())
self.assertEqual([], mqtt.published)


if __name__ == "__main__":
unittest.main()
39 changes: 39 additions & 0 deletions uhome/uhome.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,45 @@ def republish(self):
return True


class Notify(Entity):
"""
MQTT notify entity that receives Home Assistant notification messages.

Example::

display = uhome.Notify(device, "Display")
display.set_action(lambda msg: print(msg))
"""

entity_type = 'notify'
_action = None

def make_conf(self, **kwargs):
conf = super().make_conf(**kwargs)
conf["cmd_t"] = self.topic_for("command")
return conf

def get_topic(self):
return self.conf['cmd_t']

def set_action(self, action):
"""
Subscribe to notifications sent by Home Assistant's notify service.
"""
self._action = action
return self._subscribe(self.conf['cmd_t'], self._handle_action)

def republish(self):
"""
Notify entities receive commands from Home Assistant and have no cached state.
"""
return True

def _handle_action(self, msg):
if self._action:
self._action(msg)


class Sensor(Entity):
"""
More information about MQTT Sensors: https://www.home-assistant.io/integrations/sensor.mqtt/
Expand Down
Loading