IOT house with Sonoff and MicroPython
Idea
I bought a few sonoff basics and wanted to set up some sensors and triggers around the house, with some restrictions:
- If a sensor or switch replaces something existing, then there should be always a fallback to that functionality even if there's no connectivity.
- Code should be fully open
- There should be no complex infrastructure to support this
Evaluating the board
I wanted a cheap board that included a mains transformer as both using phone chargers (NodeMCU) or building your own ends up being more expensive, cumbersome and prone to failures. Winner: Sonoff basic.
GPIO | NAME | IN/OUT | NOTES |
---|---|---|---|
0 | button | IN | |
1 | TX | INOUT | outputs garbage on boot |
3 | RX | INOUT | |
12 | relay | OUT | powered from mains |
13 | led | OUT | inverted high/low |
14 | GPIO | INOUT |
Also note that if you fuck up the TX/RX pins you won't be able to flash the Sonoff again.
I ended up choosing to go for independent modules written in MicroPython, where the communication bus is MQTT.
To get MicroPython on the board you should solder the 4 UART pins on the board and flash the firmware.
Flashing
Hardware setup
From easiest to hardest:
- USB UART/TTL breakout pin
- Arduino as a serial 'proxy'
- Raspberry Pi GPIO
Flashing MicroPython
I was running (as recommended here)
esptool.py --port /dev/ttyUSB0 erase_flash
esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash --flash_size=detect 0 esp8266-20170108-v1.8.7.bin
The first issue I had was that the TX/RX pair is reversed on my revision of the board, but that's a simple cable switch.
The second issue is that my firmware flashes kept failing, and as found here, the manufacturer of my board was 5e
so I modified the flash command slightly to
esptool.py --port /dev/ttyUSB0 erase_flash
esptool.py --port /dev/ttyUSB0 write_flash -fs 1MB -fm dout 0x0 esp8266-20180511-v1.9.4.bin
Getting a REPL
While you can use screen
, minicom
, picocom
or other software for serial communication, you also will (most likely) want to push files into the VFS.
What I used for both getting a REPL and pushing files is mpfshell, which sometimes sadly fails, but >90% of the time works great.
My rapid iterations
are done by running
mpfshell ttyUSB0 -c "put main.py; repl; exit"
and pressing Ctrl-D
immediately which soft-reboots the esp8266. I'll see my code run, play around in the REPL and iterate. To exit mpfshell you need to press Ctrl-Alt-]
.
Writing some code
With the way I set up the common functions, you can have something working in ~20 lines of code (omitting imports)
DHT22 -> MQTT
CLIENT_ID = 'TEMPSENSOR'
TEMPTOPIC = b"TEMP/%s" % CLIENT_ID
HUMTOPIC = b"HUM/%s" % CLIENT_ID
led = Pin(13, Pin.OUT)
dht = dht.DHT22(Pin(14))
@common.debounce(60000)
def read_dht():
dht.measure()
common.mqtt.publish(TEMPTOPIC, "%.2f" % dht.temperature())
common.mqtt.publish(HUMTOPIC, "%.2f" % dht.humidity())
def main():
setup_fns = [ lambda: led(1) # Turn off LED, it is inverted
]
common.loop(CLIENT_ID, setup_fn=setup_fns, loop_fn=[read_dht], callback=sub_cb, subtopic=SUBTOPIC)
main()
Pub/sub + button for relay
CLIENT_ID = 'NIGHTLAMP'
SUBTOPIC = b"%s/set" % CLIENT_ID
PUBTOPIC = b"%s/state" % CLIENT_ID
button = Pin(0, Pin.IN)
led = Pin(13, Pin.OUT)
relay = Pin(12, Pin.OUT)
def set_pin(pin, state):
pin(state)
common.mqtt.publish(PUBTOPIC, str(pin()))
def sub_cb(topic, msg):
if msg == b'1':
set_pin(relay, True)
elif msg == b'0':
set_pin(relay, False)
else:
set_pin(relay, not relay())
@common.debounce(250)
def handle_button(pin):
set_pin(relay, not relay())
def main():
setup_fns = [ lambda: button.irq(handler=handle_button, trigger=Pin.IRQ_RISING),
lambda: led(1) # Turn off LED, it is inverted
]
common.loop(CLIENT_ID, setup_fn=setup_fns, loop_fn=[], callback=sub_cb, subtopic=SUBTOPIC)
main()
The mqtt lib is umqtt.simple which is an official lib. You can find the common lib on github.
Getting code on the device
After writing the code, to put it into the device simply run
$ mpfshell ttyUSB0
** Micropython File Shell v0.9.1, sw@kaltpost.de **
-- Running on Python 3.7 using PySerial 3.4 --
mpfs [/]> put HOSTNAME
mpfs [/]> put common.py
mpfs [/]> put main.py
mpfs [/]> put mqtt.py
mpfs [/]> ls
Remote files in '/':
HOSTNAME
common.py
main.py
mqtt.py
Testing the changes:
mpfs [/]> repl
>
MicroPython v1.9.4-8-ga9a3caad0 on 2018-05-11; ESP module with ESP8266
Type "help()"
*** Exit REPL with Ctrl+] ***
for more information.
>>>
PYB: soft reboot
#6 ets_task(40100130, 3, 3fff83ec, 4)
Connected to SSID!
Subscribing to b'PRINTER_POWER/set'
Subscribing to b'PRINTER_POWER/OTA'
#> Manually pressed button
Button was pressed
Setting pin to True
Publish 1 to b'PRINTER_POWER/state'
#> Manually pressed button
Button was pressed
Setting pin to False
Publish 0 to b'PRINTER_POWER/state'
#> Send '1' via MQTT
b'PRINTER_POWER/set'
b'1'
Setting pin to True
Publish 1 to b'PRINTER_POWER/state'
Done.