Smart Home with enOcean sensors, Philips Hue and Weather underground, running on influxDb and Grafana in a Raspberry Pi (Part 2 of 2)
This is part of my Challenge to make 26 things before 2018 ends.
This is the second part of my blog post, if you want to read how this started click here)
The architecture looks as following:
If you want to skip the next sections, here’s the link to the project repository:
https://github.com/alignan/home-improvements
enOcean gateway controller setup (USB 300)
In my setup I used the enOcean USB 300 controller.
The first step was to check if the USB stick was detected by the OS:
$ dmesg
[134190.301364] usb 1-1.3: new full-speed USB device number 4 using dwc_otg
[134190.458809] usb 1-1.3: New USB device found, idVendor=0403, idProduct=6001
[134190.458819] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[134190.458823] usb 1-1.3: Product: FT232R USB UART
[134190.458827] usb 1-1.3: Manufacturer: FTDI
[134190.458831] usb 1-1.3: SerialNumber: A90551EY
[134190.690127] usbcore: registered new interface driver usbserial
[134190.690168] usbcore: registered new interface driver usbserial_generic
[134190.690201] usbserial: USB Serial support registered for generic
[134190.698717] usbcore: registered new interface driver ftdi_sio
[134190.698759] usbserial: USB Serial support registered for FTDI USB Serial Device
[134190.698918] ftdi_sio 1-1.3:1.0: FTDI USB Serial Device converter detected
[134190.699010] usb 1-1.3: Detected FT232RL
[134190.699817] usb 1-1.3: FTDI USB Serial Device converter now attached to ttyUSB0
Also checked:
$ lsusb
Bus 001 Device 004: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
I powered on an enOcean sensor to check if the Gateway would receive any reading. It was required to change the default baudrate to 57600
first, and then dump data from the /dev/ttyUSB0
to console:
$ stty -F /dev/ttyUSB0 57600
$ hexdump -C < /dev/ttyUSB0
00000000 55 00 0a 07 01 eb a5 00 00 54 08 01 80 f5 bc 00 |U........T......|
00000010 01 ff ff ff ff 2e 00 41 55 00 0a 07 01 eb a5 00 |.......AU.......|
00000020 00 50 08 01 9c 44 00 01 ff ff ff ff 32 00 33 55 |.P...D......2.3U|
The radio telegrams are described in the enOcean ESP3 protocol
Initial testing
enOcean python client
I stumbled upon this python-based client to communicate over serial to the enOcean controller.
$ sudo pip install enocean
I had to modify the enocean example to initialize the SerialCommunicator
to use the /dev/ttyUSB0
port.
communicator = SerialCommunicator(port='/dev/ttyUSB0')
I pressed the learn button in my AFRISO Temperature Sensor FTM T, this is what I received:
2018-10-13 12:35:04,506 - enocean.protocol.packet - DEBUG - learn received, EEP detected, RORG: 0xA5, FUNC: 0x02, TYPE: 0x05, Manufacturer: 0x2D
2018-10-13 12:35:04,507 - enocean.communicators.SerialCommunicator - DEBUG - 01:80:F5:BC->FF:FF:FF:FF (-60 dBm): 0x01 ['0xa5', '0x8', '0x28', '0x2d', '0x80', '0x1', '0x80', '0xf5', '0xbc', '0x0'] ['0x1', '0xff', '0xff', '0xff', '0xff', '0x3c', '0x0'] OrderedDict()
TMP: {u'value': 32.94117647058823, u'description': u'Temperature (linear)', u'unit': u'\xb0C', u'raw_value': 45}
Understanding enOcean Telegrams
From the frame above there are three important fields:
- RORG: the ERP radio telegram type (8 bits)
- FUNC: basic functionality of the data content (6 bits)
- TYPE: type of device in its individual characteristics (7 bits)
The frame we received corresponds to 4BS or 4-byte communication (RORG=0xA5
):
A 4BS telegram carries a payload of 4 bytes. The sequence of the 4 data bytes is historically reversed, so that DB_3 appears first and DB_0 last on the radio interface. The bits are addressed in the sequence of the data flow, however (offset). Hence, DB_3.BIT_7 has the offset position 0 and DB_0.BIT_3 (LRN bit) has the offset position 28. The actual content-bits in a byte are not affected by this, i.e., they are described from right (2H0) to left (2H7) in the ascending order.
Temperature sensor
The bytes following the 0xa5
byte corresponds to the data payload (starting from DB_3) as follows: '0x8', '0x28', '0x2d', '0x80'
, being 0x2d
the raw_value
of 45, as shown in the received packet. The calculations to obtain the actual temperature value are given by the offset, size and bit range information of the profile, per the EEP (enOcean Equipment Profile) description.
The FUNC=0x02
and TYPE=0x05
corresponds to the Temperature Sensors (given by the FUNC
value) with a range from 0-40°C (as per its TYPE
value). The units are Celsius, and the calculation is made by taking the bits DB1.0-DB1.7
(bitrange) with a 16-bit offset. In this profile, DB_3
and DB_2
are not used, and DB0.3
shows the learn bit
status. A shortcut
identifier is used to resume the description before, in this case as TMP
(linear temperature).
Note also the 0x01
at the start of the frame: this correspond to the RADIO_ERP1
packet type (Radio Telegram). This is further detailed in the Radio Addressing Destination Telegram format
(ADT). The Sender ID
field follows the 4 bytes of the payload (as the 4BS
lenght), thus '0x1', '0x80', '0xf5', '0xbc', '0x0'
are equivalent to the address 01:80:F5:BC
.
The next octets are part of the Optional Data
fields, being 0x01
the number of sub-telegram received, '0xff', '0xff', '0xff', '0xff'
the destination address (broadcast), and finally the signal strength
byte (in dBm) and the security level
byte (being zero related to telegram not processed).
CO2 Pure sensor with power failure detection
When I pressed the learn
button of the Afriso CO2 sensor I got the following frames:
2018-10-18 20:49:12,733 - enocean.protocol.packet - DEBUG - learn received, EEP detected, RORG: 0xA5, FUNC: 0x09, TYPE: 0x09, Manufacturer: 0x2D
2018-10-18 20:49:12,734 - enocean.communicators.SerialCommunicator - DEBUG - 01:9C:44:11->FF:FF:FF:FF (-54 dBm): 0x01 ['0xa5', '0x24', '0x48', '0x2d', '0x80', '0x1', '0x9c', '0x44', '0x11', '0x0'] ['0x1', '0xff', '0xff', '0xff', '0xff', '0x36', '0x0'] OrderedDict()
The FUNC=0x09
and TYPE=0x09
corresponds to the Pure CO2 sensors with power failure detection, with 8-bit resolution and up to 2000ppm. The device is quite cool as it reflects the recommended CO2 thresholds over a LED (green if OK, yellow likely to ventilate the room, and red to definitely ventilate), and it has a message whenever a digital input changes (when power is lost), so it might be used as well to detect power failures in the room.
The shortcut
identifier is CO2
(data from DB1.7-DB1.0
), and the failure detection identifier is LRNB
(from DB0.2
).
Putting everything together
The CO2
profile was not implemented in the enocean application, I had to fork it and implement it on my own (just copy and pasting a XML block and filling the details), the link to the updated file is here.
<profiles func="0x09" description="Gas Sensor">
<profile description="Pure CO2 Sensor with Power Failure Detection" type="0x09">
<data>
<value shortcut="CO2" description="Pure CO2 sensor with 8 bit resolution and 0–2000ppm" offset="16" size="8" unit="ppm">
<range>
<min>0</min>
<max>255</max>
</range>
<scale>
<min>0</min>
<max>2000</max>
</scale>
</value>
<enum shortcut="PFD" description="Power Failure Detection" offset="28" size="1">
<item description="Power failure not detected" value="0" />
<item description="Power failure detected" value="1" />
</enum>
</data>
</profile>
</profiles>
The implementation itself was quite simple once I was able to verify the data frames from the radio, however rather than inspecting packets for its TYPE
and FUNC
fields (my preferred approach), I had to create a device mapping based on location as these fields were only populated by the library whenever the learn
bit was set - perhaps something related to the protocol itself, but I wanted to implement this in a single lazy Sunday evening.
ENOCEAN_DEVICES = {
'01:80:F5:BC': 'main_bedroom_temperature',
'01:9C:44:11': 'living_room_CO2'
}
Here’s the looper:
# endless loop receiving radio packets
while communicator.is_alive():
try:
# Loop to empty the queue...
packet = communicator.receive.get(block=True, timeout=1)
meas = {}
if packet.packet_type == PACKET.RADIO:
if packet.rorg == RORG.BS4 and packet.sender_hex in ENOCEAN_DEVICES:
# a hack as the rorg_type and rorg_func fields are only populated when the learn bit is set,
# perhaps something I don't understand about the protocol... I'm taking the lazy approach...
if ENOCEAN_DEVICES[packet.sender_hex] == 'main_bedroom_temperature':
# if packet.rorg_type == 0x05 and packet.rorg_func == 0x02:
packet.select_eep(0x02, 0x05)
packet.parse_eep()
meas[ENOCEAN_DEVICES[packet.sender_hex]] = round(packet.parsed['TMP']['value'], 2)
if ENOCEAN_DEVICES[packet.sender_hex] == 'living_room_CO2':
# if packet.rorg_type == 0x09 and packet.rorg_func == 0x09:
packet.select_eep(0x09, 0x09)
packet.parse_eep()
meas[ENOCEAN_DEVICES[packet.sender_hex]] = round(packet.parsed['CO2']['value'], 2)
if meas:
publish_to_database(meas)
except queue.Empty:
continue
except KeyboardInterrupt:
break
except Exception:
traceback.print_exc(file=sys.stdout)
break
time.sleep(0.1)
As a next step I will replace the /dev/ttyUSB0
with a proper udev
rule.