IOT house with Sonoff and MicroPython, part 1
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.
|1||TX||INOUT||outputs garbage on boot|
|12||relay||OUT||powered from mains|
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.
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
, the manufacturer of my board was
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
or other software for serial communication, you also will (most likely) want to push files into the
What I used for both getting a REPL and pushing files is mpfshell , which sometimes sadly fails, but >90% of the time works great.
are done by running
mpfshell ttyUSB0 -c "put main.py; repl; exit"
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
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()