Internet of Blinkenlights version 0.2

It’s been a while since I’ve been playing with my Internet of Things aspiration, a box that makes it possible for me to control my curtains from the comfort of my bed.  In addition, I want to collect sensor data (temperature, humidity, light) and send it all to a command center.  Preferably, I want to integrate the whole shebang with HomeKit.

Since my last post, I have received the remaining hardware components, and decided to take a stab at the software and low-power electronics.  Last time, I was doing some high-voltage soldering and mucking about with a Raspberry Pi 3 (as I didn’t have the real board yet).  This time, I’ve put all the real low-power electronics together:

IMG_7229

At the heart (number 2) we have a small board that can be ordered with a Pine64, called a wifi remote module.  It’s a simple board with an ESP8266 SoC and a couple connectors.  The chip is very simple and low-power, and supports standard 802.11 b/g/n networking.  The board is connected via I2C to a light sensor (4), a temperature/humidity sensor (5), and a generic I/O board (6).

The idea of the board is to collect data from these external sensors and send it all via wifi to a command center.  To improve the wifi-reception, I added an antenna (3) I had lying around, but I don’t think that’s necessary as my wifi coverage is quite good.  Configuration of the board is done using a serial port; to make that work in the real world, I used an USB to serial adapter (1), which also allows powering the board.

IMG_6529My idea with the extra I/O board (6) was to control a pair of relays to control my blinds.  It turns out that the board actually comes with a relay built-in, and also has a number of general-purpose I/O pins, so it might not even be necessary with this extra I/O ability.  As I had already ordered the boards, I decided to hook it up and see if I could make it work anyway.  I can always use the extra I/O for more blinking diodes.

I have hooked all the I/O pins up to diodes (except one because I was short a resistor and too lazy to fetch one) on my bread-board (7).  Instead of powering it directly, I used another Kickstarter project, the Toaster (8), which is a power supply for bread-boards, which supplies 5V + 3.3V at the top and 0V + variable voltage at the bottom.

Screenshot 2016-08-21 02.28.25The ESP8266 board doesn’t really have a lot of documentation, so I had to try and reverse engineer the operation from the source code and trial-and-error.  I grabbed the source code for the server and tried compiling it.  The code is written for the Pine64, but I’m frankly too lazy to set that up now, and decided to just spin up a virtual machine and try running it there.

To do that, I’d have to recompile the source myself, so I migrated the very obscure project to a simple CMake project.  If you’re interested, feel free to grab it at https://gitlab.westergaard.eu/klafbang/pinewifi.  Just figuring out which port this server was running on, turned out to be non-trivial as it is hard-coded deep down in some library code.  I changed that and made it configurable (I was contemplating putting up a proxy to capture the traffic).  It turned out not to be necessary to set up a proxy; while tinkering with the code, I discovered a commented-out debug mode, which I switched back on.

With the server running, I need to configure the ESP8266 board to connect to it.  This is where the serial interface (1) comes into play.  Installing the drivers, which conveniently exist for OS X, it is just a simple matter of connecting using a serial terminal.  Waitaminute, it’s decades since I last needed a serial terminal, do I have to install some shitty 3270-like emulator-shit?  Luckily, it turns out that screen can do this as well

screen /dev/tty.SLAB_USBtoUART 57600

Configure wifi name and password, and set up a server to connect to.  Simple enough.

Screenshot 2016-08-21 02.42.45

(screen doesn’t show local echo so my command don’t show up, but I just use the command server to set up the server name, and then wifi to set up the wifi configuration.)

The board supports modern wifi encryption (even if only 2.4 GHz networks), and fetches information using DHCP.  I set it up with a fixed IP address so it’s easier to refer to, but that’s not necessary.  After a restart (the gibberish) and knocking a hole in the firewall on my virtual machine, I have a connection.

Screenshot 2016-08-21 02.50.11

As we can see, the server listens on port 35000 (that’s a modification I made) and is exchanging packets with the ESP8266.  So far, so good.  There’s also a small demo application, which polls the device and sensors.  This provides a decent starting point for figuring out what is happening:

Screenshot 2016-08-21 02.55.01

The client communicates with the server using a console running on port 10000.  Telnetting to this, allows a bit more control:

Screenshot 2016-08-21 02.59.07

This is all good and fine for plying around, but there are some serious issues for using this in practice: 1) the server is written in C; not C++ or something like that.  Plain C.  2) the protocol between the server and ESP8266 is some weird binary homebrew protocol.  3) communication between the server and client is some weird homebrew ASCII protocol.  4) everything is unencrypted.

I’ve decided that I need to make my own server which exposes the devices securely using a simple REST protocol.  To make that possible, I need to understand the homebrew binary protocol.  I throw some commands at the device to get a handle of what is happening.  I cross-reference with with what I can find from the source code and the help page inside the console.

Based on the source and the binary tracing information, it seems the format of packets is:

Offset Name Length (bytes)
0

STX (0x02) 1
1

Length 1
2

Counter 1
3

MAC 6
9

RSSI 1
10

Result 1
11

Data 1+
Length-2

CRC 2

Counter, MAC, and CRC are completely redundant since this is happening over TCP/IP. Perhaps somebody experimented with UDP? Or just don’t know much about protocol design and used a protocol from a lower-level domain where those fields might make sense. The Data field starts with an opcode and contains optional parameters. I dislike having the result as a separate parameter (why isn’t it just a parameter of the command?). The STX seems to be a protocol identifier? Or extra opcode (it seems there’s some kind of overflow packet as well). But largely: simple enough, just ignore the first 9 or 10 bytes and the last 2 bytes of each packet.

Luckily, I found a list of opcodes as well:

Name Value Extra data
Cmd01_Nop 0x01
Cmd02_HWINFO 0x02 fwid(2B),fwVer(4B)
Cmd03_HWSTATUS 0x03
Cmd11_I2CPOLL 0x11 Addr
Cmd12_I2CREAD 0x12 Addr, Location, Len
Cmd13_I2CWRITE 0x13 Addr, Location, Len, Data
Cmd14_I2CSTART 0x14
Cmd15_I2cDisable 0x15
Cmd16_I2cIntr 0x16
Cmd20_PWMENABLE 0x20
Cmd21_PWMCFG 0x21
Cmd30_SET_IO 0x30 iomask, iostatus
Cmd31_RELAY 0x31 status
Cmd32_SET_PORT 0x32 PortSetting
Cmd33_PUSH_IP_STATUS 0x33
Cmd40_SPIENABLE 0x40
Cmd41_SPICFG 0x41
Cmd42_SPIRXTX 0x42

The command largely match with what I see on the help page, so that’s easy to figure out. I also find a list indicating that the result valus can be one of:

Name

Value
ACK 0x80
NACK 0x81
ACKRT2 0x90

The ACKRT2 is used to indicate messages initiated by the ESP8266 board. Messy protocol with a bunch on unnecessary redundancy, but it mostly works.

Next up, I wanted to try and talk to all devices. I first tried just scanning for all I2C address using the “i2cpoll” command. That worked eminently, and I found the addresses:

Sensor Address(es)
Light 29/39/49
Temperature 00 + 40
I/O 38/39/3a/3b/3c/3d/3e/3f

I then found manuals for each, which helped further. Communicating with each sensor requires a couple reads/writes, and of course each has different protocols for that. I have sample code for the light and temperature sensors, so decided to focus on the I/O board, and found that I just have to read/write the state of the pins at a particular address, so to switch all diodes off, I just issue

i2cwrite 0 38 41 1 0

The first 0 means board number 0, the 38 means the I2C address of the board (the default setting), 41 is the address to write to to set the pin state using the default configuration from the I/O manual (link in table). Finally, 1 is the length of data to write (1 byte) and 0 indicates all off. ff would be all on, and aa or 55 would be every second on. In the picture above, the setting aa is used. Each of the 8 bits is used to control/read one of the 8 GPIO pins.

Next, I want to control the GPIO pins built-in to the board. The pins can be in input or in output mode. To use them as outputs, we switch them all to output using the command:

cfgport 0 ff 0

The first 0 is again the board number. The ff indicates set the status of all pins. It’s also possible to set the status of pins individually. The last 0 means set all to output ports; 0 is output, 1 is input.

Now, we can control the pins individually or together using the same idea. To set the configuration in the picture above, we use:

setio 0 ff 10

0 is board number; ff means set all pins and 10 is the id of the middle pin. There’s also pin 08 and pin 20.

And that pretty much concludes my reverse engineering of the protocol. Next steps are implementing an alternative server with a more modern interface. Additionally, I should try making a full prototype including PSU and relays and try controlling it over this simple server.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.