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.
From easiest to hardest:
- USB UART/TTL breakout pin
- Arduino as a serial 'proxy'
- Raspberry Pi GPIO
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
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.
rapid iterations are done by running
mpfshell ttyUSB0 -c "put main.py; repl; exit"
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
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()
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, email@example.com ** -- 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'