Grouch mumbling about computers.

2018-08-06

IOT house with Sonoff and MicroPython

TAGS: [ iot ]

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

sonoff pi pinout All wired up

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.