Skip to content

Commit

Permalink
WIP: Tune performance and memory usage (#23)
Browse files Browse the repository at this point in the history
* Less call to self on mqtt setup
* Use generators for properties and data
* Use generators for payload
* Remove most global calls
* Remove namedtuple
* Move HomieDevice to homie.device
* Get version from module
* Add start method to homie class
* Use one method to publish device and node properties
  • Loading branch information
rroemhild committed Nov 7, 2018
1 parent 3cec821 commit 738eb0d
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 300 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ __pycache__
dist/
app/
docs/_build
*.bin
2 changes: 1 addition & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ In most cases you write your own node classes. But if you just want to test publ
import settings
from homie.node.simple import SimpleHomieNode
from homie import HomieDevice
from homie.device import HomieDevice
homie_device = HomieDevice(settings)
Expand Down
34 changes: 19 additions & 15 deletions examples/example-error.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,32 @@
import settings

from homie.node.error import Error
from homie import HomieDevice
from homie.device import HomieDevice
from machine import WDT


wdt = WDT(timeout=3000)
def main():
wdt = WDT(timeout=3000)

homie = HomieDevice(settings)
homie = HomieDevice(settings)

homie.add_node(Error())
homie.add_node(Error())

# publish device and node properties
homie.publish_properties()
# publish device and node properties
homie.publish_properties()

while True:
# reset the errors
homie.errors = 0
while True:
# reset the errors
homie.errors = 0

# publish device data
homie.publish_data()
# publish device data
homie.publish_data()

# feed wdt if we have no errors
if not homie.errors:
wdt.feed()
# feed wdt if we have no errors
if not homie.errors:
wdt.feed()

utime.sleep(1)
utime.sleep(1)


main()
29 changes: 17 additions & 12 deletions examples/example-led.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,27 @@
import settings

from homie.node.led import LED
from homie import HomieDevice
from homie.device import HomieDevice

homie = HomieDevice(settings)

# Add LED node to device
homie.add_node(LED(pin=2))
def main():
homie = HomieDevice(settings)

# publish device and node properties
homie.publish_properties()
# Add LED node to device
homie.add_node(LED(pin=2))

while True:
# publish device and node properties
homie.publish_properties()

# publish device data
homie.publish_data()
while True:

# check for new mqtt messages
homie.mqtt.check_msg()
# publish device data
homie.publish_data()

utime.sleep(1)
# check for new mqtt messages
homie.mqtt.check_msg()

utime.sleep(1)


main()
27 changes: 16 additions & 11 deletions examples/example-simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
import settings

from homie.node.simple import SimpleHomieNode
from homie import HomieDevice
from homie.device import HomieDevice

homie = HomieDevice(settings)

node = SimpleHomieNode("nodetype", "node_property")
def main():
homie = HomieDevice(settings)

homie.add_node(node)
node = SimpleHomieNode("nodetype", "node_property")

# publish device and node properties
homie.publish_properties()
homie.add_node(node)

while True:
# publish device and node properties
homie.publish_properties()

# publish device data
homie.publish_data()
while True:

node.value = utime.time()
# publish device data
homie.publish_data()

utime.sleep(1)
node.value = utime.time()

utime.sleep(1)


main()
199 changes: 1 addition & 198 deletions homie/__init__.py
Original file line number Diff line number Diff line change
@@ -1,198 +1 @@
import sys
import utime
from ucollections import namedtuple
from umqtt.simple import MQTTClient

from homie import utils

__version__ = b'0.1.2'


RETRY_DELAY = 10


Property = namedtuple('Property', (
'topic',
'payload',
'retain',
))


class HomieDevice:

"""MicroPython implementation of the Homie MQTT convention for IoT."""

def __init__(self, settings):
self.errors = 0
self.settings = settings

self.nodes = []
self.node_ids = []
self.topic_callbacks = {}

self.start_time = utime.time()
self.next_update = utime.time()
self.stats_interval = self.settings.DEVICE_STATS_INTERVAL

# base topic
self.topic = b'/'.join((self.settings.MQTT_BASE_TOPIC,
self.settings.DEVICE_ID))

# setup wifi
utils.setup_network()
utils.wifi_connect()

try:
self._umqtt_connect()
except Exception:
print('ERROR: can not connect to MQTT')
# self.mqtt.publish = lambda topic, payload, retain, qos: None

def _umqtt_connect(self):
# mqtt client
mqtt = MQTTClient(
self.settings.DEVICE_ID,
self.settings.MQTT_BROKER,
port=self.settings.MQTT_PORT,
user=self.settings.MQTT_USERNAME,
password=self.settings.MQTT_PASSWORD,
keepalive=self.settings.MQTT_KEEPALIVE,
ssl=self.settings.MQTT_SSL,
ssl_params=self.settings.MQTT_SSL_PARAMS)

mqtt.DEBUG = True

# set callback
mqtt.set_callback(self.sub_cb)

# set last will testament
mqtt.set_last_will(self.topic + b'/$online', b'false',
retain=True, qos=1)

mqtt.connect()

# subscribe to device topics
mqtt.subscribe(self.topic + b'/$stats/interval/set')
mqtt.subscribe(self.topic + b'/$broadcast/#')

self.mqtt = mqtt

def add_node(self, node):
"""add a node class of HomieNode to this device"""
self.nodes.append(node)

# add node_ids
try:
self.node_ids.extend(node.get_node_id())
except NotImplementedError:
raise
except Exception:
print('ERROR: getting Node')

# subscribe node topics
for topic in node.subscribe:
topic = b'/'.join((self.topic, topic))
self.mqtt.subscribe(topic)
self.topic_callbacks[topic] = node.callback

def sub_cb(self, topic, message):
# device callbacks
print('MQTT SUBSCRIBE: {} --> {}'.format(topic, message))

if b'$stats/interval/set' in topic:
self.stats_interval = int(message.decode())
self.publish(b'$stats/interval', self.stats_interval, True)
self.next_update = utime.time() + self.stats_interval
elif b'$broadcast/#' in topic:
for node in self.nodes:
node.broadcast(topic, message)
else:
# node property callbacks
if topic in self.topic_callbacks:
self.topic_callbacks[topic](topic, message)

def publish(self, topic, payload, retain=True, qos=1):
# try wifi reconnect in case it lost connection
utils.wifi_connect()

if not isinstance(payload, bytes):
payload = bytes(str(payload), 'utf-8')
t = b'/'.join((self.topic, topic))
done = False
while not done:
try:
print('MQTT PUBLISH: {} --> {}'.format(t, payload))
self.mqtt.publish(t, payload, retain=retain, qos=qos)
done = True
except Exception as e:
# some error during publishing
done = False
done_reconnect = False
utime.sleep(RETRY_DELAY)
# tries to reconnect
while not done_reconnect:
try:
self._umqtt_connect()
self.publish_properties() # re-publish
done_reconnect = True
except Exception as e:
done_reconnect = False
print('ERROR: cannot connect, {}'.format(str(e)))
utime.sleep(RETRY_DELAY)

def publish_properties(self):
"""publish device and node properties"""
# node properties
properties = (
Property(b'$homie', b'2.0.1', True),
Property(b'$online', b'true', True),
Property(b'$name', self.settings.DEVICE_NAME, True),
Property(b'$fw/name', self.settings.DEVICE_FW_NAME, True),
Property(b'$fw/version', __version__, True),
Property(b'$implementation', bytes(sys.platform, 'utf-8'), True),
Property(b'$localip', utils.get_local_ip(), True),
Property(b'$mac', utils.get_local_mac(), True),
Property(b'$stats/interval', self.stats_interval, True),
Property(b'$nodes', b','.join(self.node_ids), True)
)

# publish all properties
for prop in properties:
self.publish(*prop)

# device properties
for node in self.nodes:
try:
for prop in node.get_properties():
self.publish(*prop)
except NotImplementedError:
raise
except Exception as error:
self.node_error(node, error)

def publish_data(self):
"""publish node data if node has updates"""
self.publish_device_stats()
# node data
for node in self.nodes:
try:
if node.has_update():
for prop in node.get_data():
self.publish(*prop)
except NotImplementedError:
raise
except Exception as error:
self.node_error(node, error)

def publish_device_stats(self):
if utime.time() > self.next_update:
# uptime
uptime = utime.time() - self.start_time
self.publish(b'$stats/uptime', uptime, True)
# set next update
self.next_update = utime.time() + self.stats_interval

def node_error(self, node, error):
self.errors += 1
print('ERROR: during publish_data for node: {}'.format(node))
print(error)
__version__ = b"0.2.1"

0 comments on commit 738eb0d

Please sign in to comment.