Skip to content

Commit

Permalink
Update to Homie v4
Browse files Browse the repository at this point in the history
  • Loading branch information
rroemhild committed Aug 17, 2019
1 parent d3c7ab8 commit 4f05e21
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 205 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
Changelog
=========

2.0.0 (unreleased)
------------------

* Update to Homie v4
* Remove code for arrays
* Add legacy extensions, default disabled

1.0.0
-----

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export PATH := $(PWD)/esp-open-sdk/xtensa-lx106-elf/bin:$(PWD)/micropython/tools:$(PWD)/micropython/ports/unix:$(HOME)/go/bin:$(PATH)

VERSION := 1.0.0
VERSION := 2.0.0-beta.1
MICROPYVERSION := 1.11
PORT ?= /dev/ttyUSB0

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Microhomie

A MicroPython implementation of `Homie <https://github.com/homieiot/convention>`_, a lightweight MQTT convention for the IoT. Main target for Microhomie is the ESP8266 device.

Currently Microhomie implements `Homie v3.0.1 <https://github.com/homieiot/convention/releases/tag/v3.0.1>`_.
Currently Microhomie implements `Homie v4.0.0 <https://github.com/homieiot/convention/releases/tag/v4.0.0>`_.

**Important** Microhomie 1.0.0 (asyncio version) is not compatible with previous 0.3 Microhomie nodes.

Expand Down
9 changes: 6 additions & 3 deletions examples/led/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@


class LED(HomieNode):
def __init__(self, name="Device LED", pin=2):
def __init__(self, name="Onboard LED", pin=2):
super().__init__(id="led", name=name, type="LED")
self.pin = pin
self.led = Pin(pin, Pin.OUT, value=0)

self.led_property = HomieNodeProperty(
id="power",
name="LED",
name="LED Power",
settable=True,
datatype="enum",
format="true,false,toggle",
Expand All @@ -32,6 +32,9 @@ def __init__(self, name="Device LED", pin=2):

def callback(self, topic, payload, retained):
if b"led/power" in topic:
if payload == self.led_property.data:
return

if payload == b"toggle":
self.led(not self.led())
else:
Expand All @@ -48,7 +51,7 @@ def main():
homie.add_node(LED())

# run forever
homie.start()
homie.run_forever()


if __name__ == "__main__":
Expand Down
5 changes: 0 additions & 5 deletions examples/relais/README.rst

This file was deleted.

58 changes: 0 additions & 58 deletions examples/relais/main.py

This file was deleted.

2 changes: 1 addition & 1 deletion homie/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = b"1.0.0"
__version__ = b"2.0.0-beta.1"
7 changes: 7 additions & 0 deletions homie/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
RESTORE_DELAY = const(250)
DEVICE_STATE = b"$state"

# Device states
STATE_INIT = b"init"
STATE_READY = b"ready"

# Property datatypes
P_STRING = b"string"

# Node
PUBLISH_DELAY = const(20)

Expand Down
95 changes: 49 additions & 46 deletions homie/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
RESTORE_DELAY,
SLASH,
UNDERSCORE,
STATE_READY,
STATE_INIT,
)
from mqtt_as import MQTTClient
from uasyncio import get_event_loop, sleep_ms
from utime import time


_EVENT = Event()
def await_ready_state(func):
def new_gen(*args, **kwargs):
Expand All @@ -28,8 +31,8 @@ class HomieDevice:
"""MicroPython implementation of the Homie MQTT convention for IoT."""

def __init__(self, settings):
self._state = "init"
self._stime = time()
self._state = STATE_INIT
self._extensions = settings.EXTENSIONS

self.stats_interval = settings.DEVICE_STATS_INTERVAL

Expand Down Expand Up @@ -61,10 +64,6 @@ def __init__(self, settings):
will=(SLASH.join((self.dtopic, DEVICE_STATE)), b"lost", True, QOS),
)

# Start coros
loop = get_event_loop()
loop.create_task(self.publish_stats())

def add_node(self, node):
"""add a node class of Homie Node to this device"""
node.device = self
Expand Down Expand Up @@ -101,36 +100,23 @@ async def connection_handler(self, client):
for n in nodes:
props = n._properties
for p in props:
is_array = p.range > 1
if p.settable:
self.callback_topics[n.id.encode()] = n.callback
# subscribe topic to restore retained messages
nid_enc = n.id.encode()
if nid_enc not in self.callback_topics:
self.callback_topics[nid_enc] = n.callback
# retained topics to restore messages
if p.restore:
if is_array:
r = range(p.range)
for i in r:
t = b"{}_{}/{}".format(n.id, i, p.id)
await subscribe(t)
await sleep_ms(RESTORE_DELAY)
await unsubscribe(t)
else:
t = b"{}/{}".format(n.id, p.id)
await subscribe(t)
await sleep_ms(RESTORE_DELAY)
await unsubscribe(t)

# final subscribe to /set topic
if is_array:
r = range(p.range)
for i in r:
t = b"{}_{}/{}/set".format(n.id, i, p.id)
await subscribe(t)
else:
t = b"{}/{}/set".format(n.id, p.id)
t = b"{}/{}".format(n.id, p.id)
await subscribe(t)
await sleep_ms(RESTORE_DELAY)
await unsubscribe(t)

# final subscribe to /set topics
t = b"{}/{}/set".format(n.id, p.id)
await subscribe(t)

await self.publish_properties()
await self.set_state("ready")
await self.set_state(STATE_READY)

def sub_cb(self, topic, msg, retained):
# print("MQTT MESSAGE: {} --> {}, {}".format(topic, msg, retained))
Expand All @@ -144,8 +130,6 @@ def sub_cb(self, topic, msg, retained):
# node property callbacks
nt = topic.split(SLASH)
node = nt[len(self.dtopic.split(SLASH))]
if UNDERSCORE in node:
node = node.split(UNDERSCORE)[0]
if node in self.callback_topics:
self.callback_topics[node](topic, msg, retained)

Expand Down Expand Up @@ -174,15 +158,10 @@ async def publish_properties(self):
publish = self.publish

# device properties
await publish(b"$homie", b"3.0.1")
await publish(b"$homie", b"4.0.0")
await publish(b"$name", self.device_name)
await publish(DEVICE_STATE, b"init")
await publish(b"$fw/name", b"Microhomie")
await publish(b"$fw/version", __version__)
await publish(DEVICE_STATE, STATE_INIT)
await publish(b"$implementation", bytes(platform, "utf-8"))
await publish(b"$localip", utils.get_local_ip())
await publish(b"$mac", utils.get_local_mac())
await publish(b"$stats", b"interval,uptime,freeheap")
await publish(
b"$nodes", b",".join([n.id.encode() for n in self.nodes])
)
Expand All @@ -192,23 +171,43 @@ async def publish_properties(self):
for n in nodes:
await n.publish_properties()

if self._extensions:
await publish(b"$extensions", b",".join(self._extensions))
if b"org.homie.legacy-firmware:0.1.1:[4.x]" in self._extensions:
await self.legacy_firmware()
if b"org.homie.legacy-stats:0.1.1:[4.x]" in self._extensions:
await self.legacy_stats()

async def legacy_firmware(self):
publish = self.publish
await publish(b"$localip", utils.get_local_ip())
await publish(b"$mac", utils.get_local_mac())
await publish(b"$fw/name", b"Microhomie")
await publish(b"$fw/version", __version__)

async def legacy_stats(self):
await self.publish(b"$stats/interval", self.stats_interval)
# Start stats coro
loop = get_event_loop()
loop.create_task(self.publish_stats())

@await_ready_state
async def publish_stats(self):
from utime import time
start_time = time()
delay = self.stats_interval * MAIN_DELAY
publish = self.publish
while True:
uptime = time() - self._stime
# re-publish interval for some controller (i.e. openhab)
await publish(b"$stats/interval", self.stats_interval)
uptime = time() - start_time
await publish(b"$stats/uptime", uptime)
await publish(b"$stats/freeheap", mem_free())
await sleep_ms(delay)

async def set_state(self, val):
if val in ["ready", "disconnected", "sleeping", "alert"]:
if val in [STATE_READY, b"disconnected", b"sleeping", b"alert"]:
self._state = val
await self.publish(DEVICE_STATE, val)
if val == "ready":
if val == STATE_READY:
_EVENT.set()
await sleep_ms(MAIN_DELAY)
_EVENT.clear()
Expand All @@ -226,6 +225,10 @@ async def run(self):
while True:
await sleep_ms(MAIN_DELAY)

def start(self):
def run_forever(self):
loop = get_event_loop()
loop.run_until_complete(self.run())

def start(self):
# DeprecationWarning
self.run_forever()

0 comments on commit 4f05e21

Please sign in to comment.