Wemos & Amazon Echo


#MicroPython: WeMos and Amazon Echo (Alexa) - Switching LED Colors!

I don’t have any Belkin WeMo system or Philips Hue light bulbs. However, I have two ESP32 running MicroPython (see my last article), and a W2812b LED strip, and I thought I should be able to say, "Echo/Alexa, turn on the kitchen light" or "Echo/Alexa, turn on the blue light" and It should work with this setup.

And... it works as you can see in this video!

If you are interesting in only using the code, then click here. Otherwise, you can read the complete story... ;)

Python Code: https://github.com/lemariva/uPyEcho

If you’re not up to speed with MicroPython, Wipy see earlier articles in here.

State of the Art ;)

I started to search the web, and I found the "Alexa Skills" and the first repository was flask-ask. I tried to modified it, so that it runs using MicroPython, but there were too many dependencies that are still not implemented on MicroPython (and because of the size, there are not going to be implemented or should not be implemented), and that was not possible. Another problem was that the skill must run behind a public HTTPS server (HTTP should be enough, if it is only for you) or AWS Lambda. I thought some options would be to open a port on my router, or to use ngrok as proposed in the development amazon blog. I didn't like both ideas, the first one was too risky: an open port with a webserver on MicroPython listening to it, and the second one: there isn't any possibility to run ngrok on MicroPython, and it is again a bridge over the router, it was also too risky.

I looked forward the web again and I found the n8henrie/fauxmo repository. This is a great repository: it is a Python 3 (MicroPython is a lean and efficient implementation of the Python 3! -> Nice!) module that emulates Belkin WeMo devices for use with the Amazon Echo. That was, what I wanted. I looked to it closer, and I found that it uses libraries that need some dependencies that are not working on MicroPython (on ESP32) yet (update > may be using import upip and upip.install('<libraries>') works - I am going to publish an article soon).

The n8henrie/fauxmo repository was forked from makermusings/fauxmo. I looked forward on the web and I found that there are a port of the makermusings/fauxmo to C++ for the ESP32 (fauxmoESP). But, I wanted to use MicroPython! Then, I cloned the makermusings/fauxmo library, and modified it. Now, it is working on the ESP32 (WeMos board - ESP-WROOM-32) running MicroPython.

How does it Work? Backgrounds on the Communication

I took Fig. 1 from Chris(derossi). This figure describes the complete communication. The Belkin WeMo devices advertise themselves on the network responding to searches, and define the details of their control interfaces. The Amazon Echo searches for the WeMo devices (UDP broadcast formatted as an HTTP request) and know about the WeMo API. To understand the communication, Chris(derossi) used Wireshark to sniff the traffic between the Amazon Echo and the Belkin devices. You can find more info about the tests here.

The Echo starts to search the network for new devices (UDP broadcast formatted as an HTTP request). In this case, the WeMos board emulates a WeMo device and responds to the requests of the Amazon Echo (UDP messages to the IP address and port that made the search request). Then, the Amazon Echo send HTTP GET request to the specific URLs that the WeMos responded, and the WeMos sends back the device description. The device description can be found in the original post of Chris(derossi) as well as details of the UDP broadcast and the HTTP GET requests. When you tell then the Echo to turn a device on or off, it sends an HTTP request (details again here) to the WeMos that includes a <BinaryState> element that contains a 1 for turn on, or a 0 for turn off. Then, the WeMos responds back with a HTTP/1.1 200 OK to confirm that the operation has been executed.

Amazon Echo - MicroPython
Fig. 1: Communication between Amazon Echo & WeMos (MicroPython)

Micropython

I ported the original code of makermusings/fauxmo to work on MicroPython. MicroPython is a lean and efficient implementation of the Python 3 that includes a small subset of the Python standard library. I've introduced the following modifications

  • MicroPython is an implementation of Python 3, the class was written for Python 2.7. Then, minor changes were made to run on Python 3.
  • MicroPython supports uselect.poll(). That means, the class poller that waits for incoming data to be read works on MicroPython using uselect.poll(). On Python the poll.register(fd[, eventmask]) registers a fd descriptor with the epoll object. On MicroPython the poll.register(obj[, eventmask]) register an obj with the epoll object. This is a main difference, while on Python, you need to register the socket’s file descriptor socket.fileno(), you have to register the socket object on MicroPython. Taking this in consideration
    • In the class poller, the methods add(...), remove(...) and poll(...) were modified;
    • In the class upnp_device, the sockets(...) method was added to return the socket object;
  • socket.inet_aton(ip_string) is not supported on MicroPython. Then, the following function was written
    def inet_aton(addr):
      ip_as_bytes = bytes(map(int, addr.split('.')))
      return ip_as_bytes
    
  • dgb function for debugging messages was added;
  • I've introduced one of the pull request to the code: The device description using json as
      devices = [
          {"description": "white lights",
           "port": 12345,
           "handler": rest_api_handler((255,255,255), 50)},
          {"description": "red lights",
           "port": 12346,
           "handler": rest_api_handler((255,0,0), 50)},
          {"description": "blue lights",
           "port": 12340,
           "handler": rest_api_handler((0,0,255), 50)},
      ]
    
  • The class rest_api_handler(object) was modified to introduce the WS2812b support;
  • Threading was also implemented. The main loop runs in a separated thread and reads the received data using polling.
  • The garbage collector was needed to avoid full memory error. I think, MicroPython has still some memory leak problems yet;
  • Some try and except clauses were added to the code (read the next topic!).
  • The MicroPython version for ESP32 doesn't have a time.gmtime() or a machine.RTC(). Using a ntp server and sockets, I've implemented some classes and functions in order to get the actual time (it is alpha release, it should be optimized!).

As you can see in the video and in the cover picture, I've combined the WeMos with the WS2812b LEDs. I've extended the WS2812b Python driver to support the WeMos board. The new version of the driver can be found here.

The device description in main.py includes three fields

{"description": "blue lights",
 "port": 12340,
 "handler": rest_api_handler((0,0,255), 50)}

the first one blue lights corresponds to the device name; you are going to use this name every time you want to switch the device on or off. I mean, "echo, turn on/off the blue lights". The second one, 12340 is the port of each device. Amazon Echo is going to get the device configuration from, and send the commands to this http://wemos-ip:12340 address. The third argument is the handler, in this case I use the class rest_api_handler() in which the constructur has two arguments: (0,0,255) corresponds to the LED color in RGB format, and 50 to the brightness. You can rewrite this class.

A big limitation :(!!!

MicroPython has nowaday (Oct. 2017) problems with the number of parallel opened sockets. The maximum number is 8. Each socket has a socket’s file descriptor (a small integer) that identifies the socket object. The method add(...) of the class poller register the sockets to the epoll object. The fileno number increases up to 7, and if there is no free socket between 0 and 7, then overflows and replaces a still opened socket. I put some try and except clauses to avoid fatal errors. But, this results in a limitation.

As a result, the maximal number of opened sockets limits the devices that you can register to a maximum of 5 (+ 1 for broadcasting, + 1 for responding with the devices description "setup.xml" = 7) . With a top of 3 devices, you are going to get some errors which are "excepted" but all of your devices are going to be recognized simultaneously. If you have more than 3, some of your devices are not going to be recognized (usually you get less than 3 recognized), you need to start the search of devices again, and if you have some luck you can find the rest of them. If you have less than 3 devices, you are not going to see any failure and all devices are going to be recognized simultaneously. It should be possible to optimize the code and solve this problem, or maybe it is possible to solve the maximal number of socket's file descriptors (bug report coming...).

Do it Yourself

You need the following Hardware & Software

WeMos Board WeMos Board x 1   
WeMos D1 Mini NodeMcu Lua WIFI ESP8266 (next release) x 1   
INR18650 INR18650 3.7v Battery x1
WS2812b WS2812B LED Strip x1
Python uPyEcho
Python WS2812b

And here are the steps that you should do to get the code working!

  • Read the blog article: #Micropython: Getting Started! and install MicroPython on the WeMos board;
  • Download the repository: uPyEcho;
  • Modify the following lines in the boot.py
    • ssid_ = <your ssid>
    • wp2_pass = <your wpa2 password>
  • Modify if you want (or need) to, the following lines in the main.py file
    • The code line
      ws2812_chain =  WS2812(ledNumber=ledNumber, brightness=100)
      
      defines the WS2812 LED strip. The argument ledNumber defines the size of the LED strip. In my case, I used 144 LEDs.
    • The code lines
      devices = [
      {"description": "white lights",
       "port": 12340,
       "handler": rest_api_handler((255,255,255), 50)}, 
       ... ]
      
      define the devices that are going to be found by Amazon Echo. As I said before, you can add up to 5 devices, but read again the section "A big limitation :(!!!". You need to define a name (e.g. white lights), a port (e.g. 12340), and a class (e.g. rest_api_handler) that is used as a handler. If you want to use further the rest_api_handler class, you can select the LED color in RGB (e.g. white > (255,255,255)), and the brightness of the LED stripin % (e.g. 50). The WS2812b class uses the SPI unit, and the pin 23 as data pin. Do not forget to connect the also the GND.
  • Upload the code, connect the LED strip and restart the board;
  • Start a device search from Amazon Echo. You can use the Alexa application, or just say, "echo/alexa, search for new devices" and wait;
  • Say, "echo/alexa, turn on the <your device name>", it should work, if not, leave a comment or write me.

Further Developments

  • Port this code to the Wipy 2.0. MicroPython on WiPy 2.0 doesn't not support multicast yet. I am going to write to the Wipy developers to see if it is possible to implement this. The line
    self.ssock.setsockopt(usocket.IPPROTO_IP, usocket.IP_ADD_MEMBERSHIP, self.mreq)
    
    doesn't not work on the Wipy 2.0 running the last version of MicroPython (Oct. 2017).
  • I've just bought the Wemos D1 Mini NodeMcu Lua WIFI ESP8266 Development Board. I'm going to test if the code works on the ESP8266 (it's another firmware file), and may be that could be the cheapest multi-output Alexa-enabled device on the planet!
{{ message }}

{{ 'Comments are closed.' | trans }}