I received my Raspberry Pi B+, EnOcean Pi and sensor kit on Wednesday. The Raspi and EnOcean Pi were easy to setup, as I expected, just do a normal install and disable the default serial console on Raspi and you’re ready to go. Other people have covered this topic already, so there’s no need for me to go over it.
Unlike most of the contestants, I didn’t install any specific software to the Pi at first, just opened a serial console with ‘screen’ and pressed the learn button on the temperature sensor, to see that I get something from the serial. As I had opened the package at work, the sensors were charged enough to send couple of commands already. Of course I couldn’t understand anything that was printed, but getting something was enough at this point.
The protocol is described very well in two documents, “EnOceanSerialProtocol3.pdf“ and “EnOcean_Equipment_Profiles_EEP_V2.61_public.pdf”. The first one contains the serial interface description, while the second concentrates on device profiles (in other words: how to interpret the data inside the serial messages).
After the initial test was done, it was time to write a small Python-script to interpret the data. I knew (from the documentation I had read) that the messages start with a header, consisting of start byte (0x55), number of data bytes, message type and checksum. The basic reading of the serial port is shown at the end of this post.
At the moment of writing, I do have a fully working parser for the data messages, with checksum checking. Also I can read the temperature from the sensor. Below is a small sample of reading my fridge temperature. Should really adjust the thermostat, it’s not keeping my beers cool enough
2014-08-08 22:13:09,241 - enocean.listener - DEBUG - 0x01 ['0xd5', '0x8', '0x1', '0x82', '0x5d', '0xab', '0x0'] ['0x1', '0xff', '0xff', '0xff', '0xff', '0x3c', '0x0'] {u'status': 0, u'destination': 4294967295L, u'dBm': -60, u'sender': 25320875, u'learn': False}
2014-08-08 22:30:05,935 - enocean.listener - DEBUG - 0x01 ['0xa5', '0x0', '0x0', '0xd4', '0x8', '0x1', '0x81', '0xb7', '0x44', '0x0'] ['0x1', '0xff', '0xff', '0xff', '0xff', '0x4a', '0x0'] {u'status': 0, u'dBm': -74, u'sender': 25278276, u'destination': 4294967295L, u'learn': False, u'data': {u'TMP': {u'value': 6.745098039215686, u'description': u'Temperature (linear)', u'unit': u'C', u'raw_value': 212}}}
2014-08-08 22:57:29,834 - enocean.listener - DEBUG - 0x01 ['0xa5', '0x0', '0x0', '0xb0', '0x8', '0x1', '0x81', '0xb7', '0x44', '0x0'] ['0x1', '0xff', '0xff', '0xff', '0xff', '0x49', '0x0'] {u'status': 0, u'dBm': -73, u'sender': 25278276, u'destination': 4294967295L, u'learn': False, u'data': {u'TMP': {u'value': 12.392156862745098, u'description': u'Temperature (linear)', u'unit': u'C', u'raw_value': 176}}}
The log contains first the packet type (0x01 for RADIO), after this comes data bytes in the message, then optional data bytes and then the parsed data as dictionary.
Unfortunately I’ve ran into some problems with the data parsing, as the EnOcean Equipment Profiles aren’t exactly easy to use, mainly because of licensing. I have been in contact with EnOcean about this issue, and I’m hoping to resolve it somehow. At the moment, I haven’t figured out a reasonable method. I’ll write a separate blog post about these issues, when I have the time.
What next?
My first task is to try and find a solution for the issues I’m having. After this is done, I’ll be making the library public and hoping other people test the library too.
In the meantime, I’ll try and put up a simple example of the library usage by implementing an web server to show the library in action by learning new devices automatically, saving them to the database and showing the data.
Example code for reading the data from serial:
import serial
# Initialize serial port and buffer
s = serial.Serial('/dev/ttyAMA0', 57600, timeout=0.1)
buf = []
while True:
try:
# Read characters from serial as integers
# reads up to 30 chars, with a set timeout
buf += [ord(c) for c in s.read(30)]
if 0x55 in buf:
# Messages always start with 0x55,
# get buffer from that point onwards
buf = buf[buf.index(0x55):]
try:
# First two chars represent the data length,
# with high bit first
data_len = (buf[1] << 8) | buf[2]
# Optional data length is in the next char
opt_len = buf[3]
except IndexError:
# If there's an index error, the data isn't complete
# -> continue with next iteration (to wait for additional data)
continue
# Message length should be 7 chars + data + optional data
msg_len = 7 + data_len + opt_len
if len(buf) < msg_len:
# If buffer is too short, the data isn't complete
continue
# Message should be in buffer, from 0 to msg_len bytes
msg = buf[0:msg_len]
# Remove message from buffer by reindexing it
buf = buf[msg_len:]
# Print the message as list of hex codes
print(['0x%02X' % x for x in msg])
except KeyboardInterrupt:
break
s.close()