Connect Raspberry Pi to Oregon Scientific BLE Weather Station

Intro: Connect Raspberry Pi to Oregon Scientific BLE Weather Station

Monitor room and outdoor temperature with a BLE thermometer connected to Raspberry Pi

Browsing the web, I found a myriad of temp sensors that you can interface easily with Raspberry Pi. And came across many tutos detailing how to interface these sensors wirelessly to RPi with a 433MHz RF transmitter/receiver kit.

Monitoring the temperature wit RPi was fun but I wanted also the ability to read the temperature at a glance, without having to turn on my computer and open a web page.

Moreover these 433MHz RF kits didn't sound very efficient (low range) and reliable reading users feedback.

Then I came upon Oregon Scientific Wireless Indoor / Outdoor Thermometer with Bluetooth Low Energy Connectivity (EMR211) and thought that it was the perfect match.

I can connect the Weather Station base to my Raspberry Pi3 through Bluetooth Low Energy. I can monitor up to 3 sensors connected to the base + temperature at the base.

Step 1: Oregon Scientific Weather Station BLE Protocol

An easy way to explore the BLE services exposed by the Weather Station is to use a BLE Scanner app on a smartphone or tablet. Here I'll be using Nordic Semiconductor's nRF Connect on Android.

For people that aren't familiar with BLE, you might want to start with this Bluetooth Low Energy introductory guide. This will help you understand terminology like GATT, service, and characteristic.

With the BLE Scanner app, you wilI find all BLE devices surrounding you. And you will see that the Oregon Scientific Weather Station is adverting itself as IDTW211R.

If you connect to the Weather Station, you'll get the list of services exposed by the device.

  • Generic Access UUID: 0x1800
  • Generic Attribute UUID: 0x1801
  • Proprietary Service UUID: 74e7fe00-c6a4-11e2-b7a9-0002a5d5c51b
  • Device Information UUID: 0x180A
  • Battery Service UUID: 0x180F

The service that matters to us is the Proprietary Service (UUID: 74e7fe00-c6a4-11e2-b7a9-0002a5d5c51b). So we click on this service and get the list of characteristics exposed by the service.

The relevant characteristic for our application is UUID: 74e78e10-c6a4-11e2-b7a9-0002a5d5c51b (INDOOR_AND_CH1_TO_3_TH_DATA).

As indicated in the properties, this characteristic isn't readable but offers an indication service. And we will have to enable the indication through the corresponding Client Characteristic Configuration Descriptor (UUID: 0x2902).

It turns out that indication/notification have to be enabled for all the characteristics of the device to start getting any indication.

Step 2: Connect the Raspberry Pi3 to the BLE Weather Station

Now that we have a better idea on how to retrieve temperature data through BLE, let's try to get our Raspberry Pi talking to the Weather Station.

BLE on Raspberry Pi

I'll be using the Raspberry Pi3 (which integrates a WLAN/BT controller) with Raspbian Jessie with Pixel 2016-09-23.

The version of bluez (5.23) in this Raspbian release is quite outdated and we will have to upgrade to later version of bluez to work with Bluetooth Low Energy.

Download bluez 5.43 and install required dependencies:

sudo apt-get update

sudo apt-get install -y libglib2.0-dev libdbus-1-dev libudev-dev libical-dev libreadline-dev

wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.43.tar.xz

tar xvf bluez-5.43.tar.xz

Configure, build and install bluez

./configure
make
sudo make install

And finally reboot the Raspberry Pi.

Now let's verify that the bluetooth stack is up and running with the cmd hciconfig dev

And then confirm that we can scan for BLE devices: sudo hcitool lescan

We should see our Weather Station adverting as IDTW211R.

Next step is to connect to the Weather Station:

sudo gatttool -b <BLE ADDRESS> -t random -I

You should get a new command prompt with the BLE address in between brackets.

connect

You should get a "Connection successful" and the prompt gets colored

Once connected, we can run some commands to get more details on the device. For instance, the primary command will list the services exposed by the device.

Collecting data from the Weather Station

As explained earlier to start getting indications on the INDOOR_AND_CH1_TO_3_TH_DATA (UUID: 74e78e10-c6a4-11e2-b7a9-0002a5d5c51b), we have to enable Indication/Notification on all characteristics.

We enable indication by writting 0x0002 (0x0001 for notification) in Client Characteristic Configuration Descriptor (UUID: 0x2902) of each characteristic. Write shall be placed in little endian format so: char-write-req 0200

In return, you should start getting indications/notifications. The relevant ones for us are INDOOR_AND_CH1_TO_3_TH_DATA indications (so handle 0x0017).

Indication handle = 0x0017 value: 01 05 01 15 01 ff 7f ff 7f 7f 7f 7f 7f ff ff 7f 7f 7f 7f 7f
Indication handle = 0x0017 value: 82 7f 7f 7f 21 01 f8 00 24 01 ba 00 ff 7f ff 7f ff 7f ff 7f

For each round of indications, we get two data packets of 20 bytes each. The most significant byte indicates the type of data (Type 0 or Type 1). See last picture for more details on the data packets.

Step 3: Python Script to Retrieve Weather Station Data

Now that we succeeded to get the data out of the Weather Station, let's automate the whole process and make some sense of the data.

Here is a python script that connect to the Weather Station, retrieve the data and extract the temperature of the Weather Station base and wireless sensor coming with it.

This script makes use of bluepy library providing an API to allow access to Bluetooth Low Energy devices from Python. So you will have to install this module before executing the script: https://github.com/IanHarvey/bluepy#installation

Usage

The script can be executed with the MAC address passed as argument or without argument.

In this latter case, the script will perform a scan and look for a device adverting as IDTW211R. It has to be executed with root privilege because bluez requires root privilege for scan operations.

python bleWeatherStation.py [mac-address]
sudo python bleWeatherStation.py

bleWeatherStation.py

#!/usr/bin/python
# -*- coding: utf-8 -*- # Connect to Oregon Scientific BLE Weather Station # Copyright (c) 2016 Arnaud Balmelle # # This script will connect to Oregon Scientific BLE Weather Station # and retrieve the temperature of the base and sensors attached to it. # If no mac-address is passed as argument, it will scan for an Oregon Scientific BLE Weather Station. # # Supported Oregon Scientific Weather Station: EMR211 and RAR218HG (and probably BAR218HG) # # Usage: python bleWeatherStation.py [mac-address] # # Dependencies: # - Bluetooth 4.1 and bluez installed # - bluepy library (https://github.com/IanHarvey/bluepy) # # License: Released under an MIT license: http://opensource.org/licenses/MIT

import sys import logging import time import sqlite3 from bluepy.btle import *

# uncomment the following line to get debug information logging.basicConfig(format='%(asctime)s: %(message)s', level=logging.DEBUG)

WEATHERSTATION_NAME = "IDTW211R" # IDTW213R for RAR218HG

class WeatherStation: def __init__(self, mac): self._data = {} try: self.p = Peripheral(mac, ADDR_TYPE_RANDOM) self.p.setDelegate(NotificationDelegate()) logging.debug('WeatherStation connected !') except BTLEException: self.p = 0 logging.debug('Connection to WeatherStation failed !') raise def _enableNotification(self): try: # Enable all notification or indication self.p.writeCharacteristic(0x000c, "\x02\x00") self.p.writeCharacteristic(0x000f, "\x02\x00") self.p.writeCharacteristic(0x0012, "\x02\x00") self.p.writeCharacteristic(0x0015, "\x01\x00") self.p.writeCharacteristic(0x0018, "\x02\x00") self.p.writeCharacteristic(0x001b, "\x02\x00") self.p.writeCharacteristic(0x001e, "\x02\x00") self.p.writeCharacteristic(0x0021, "\x02\x00") self.p.writeCharacteristic(0x0032, "\x01\x00") logging.debug('Notifications enabled') except BTLEException as err: print(err) self.p.disconnect() def monitorWeatherStation(self): try: # Enable notification self._enableNotification() # Wait for notifications while self.p.waitForNotifications(1.0): # handleNotification() was called continue logging.debug('Notification timeout') except: return None regs = self.p.delegate.getData() if regs is not None: # expand INDOOR_AND_CH1_TO_3_TH_DATA_TYPE0 self._data['index0_temperature'] = ''.join(regs['data_type0'][4:6] + regs['data_type0'][2:4]) self._data['index1_temperature'] = ''.join(regs['data_type0'][8:10] + regs['data_type0'][6:8]) self._data['index2_temperature'] = ''.join(regs['data_type0'][12:14] + regs['data_type0'][10:12]) self._data['index3_temperature'] = ''.join(regs['data_type0'][16:18] + regs['data_type0'][14:16]) self._data['index0_humidity'] = regs['data_type0'][18:20] self._data['index1_humidity'] = regs['data_type0'][20:22] self._data['index2_humidity'] = regs['data_type0'][22:24] self._data['index3_humidity'] = regs['data_type0'][24:26] self._data['temperature_trend'] = regs['data_type0'][26:28] self._data['humidity_trend'] = regs['data_type0'][28:30] self._data['index0_humidity_max'] = regs['data_type0'][30:32] self._data['index0_humidity_min'] = regs['data_type0'][32:34] self._data['index1_humidity_max'] = regs['data_type0'][34:36] self._data['index1_humidity_min'] = regs['data_type0'][36:38] self._data['index2_humidity_max'] = regs['data_type0'][38:40] # expand INDOOR_AND_CH1_TO_3_TH_DATA_TYPE1 self._data['index2_humidity_min'] = regs['data_type1'][2:4] self._data['index3_humidity_max'] = regs['data_type1'][4:6] self._data['index3_humidity_min'] = regs['data_type1'][6:8] self._data['index0_temperature_max'] = ''.join(regs['data_type1'][10:12] + regs['data_type1'][8:10]) self._data['index0_temperature_min'] = ''.join(regs['data_type1'][14:16] + regs['data_type1'][12:14]) self._data['index1_temperature_max'] = ''.join(regs['data_type1'][18:20] + regs['data_type1'][16:18]) self._data['index1_temperature_min'] = ''.join(regs['data_type1'][22:24] + regs['data_type1'][20:22]) self._data['index2_temperature_max'] = ''.join(regs['data_type1'][26:28] + regs['data_type1'][24:26]) self._data['index2_temperature_min'] = ''.join(regs['data_type1'][30:32] + regs['data_type1'][28:30]) self._data['index3_temperature_max'] = ''.join(regs['data_type1'][34:36] + regs['data_type1'][32:34]) self._data['index3_temperature_min'] = ''.join(regs['data_type1'][38:40] + regs['data_type1'][36:38]) return True else: return None def getValue(self, indexstr): val = int(self._data[indexstr], 16) if val >= 0x8000: val = ((val + 0x8000) & 0xFFFF) - 0x8000 return val def getIndoorTemp(self): if 'index0_temperature' in self._data: temp = self.getValue('index0_temperature') / 10.0 max = self.getValue('index0_temperature_max') / 10.0 min = self.getValue('index0_temperature_min') / 10.0 logging.debug('Indoor temp : %.1f°C, max : %.1f°C, min : %.1f°C', temp, max, min) return temp else: return None def getOutdoorTemp(self): if 'index1_temperature' in self._data: temp = self.getValue('index1_temperature') / 10.0 max = self.getValue('index1_temperature_max') / 10.0 min = self.getValue('index1_temperature_min') / 10.0 logging.debug('Outdoor temp : %.1f°C, max : %.1f°C, min : %.1f°C', temp, max, min) return temp else: return None def disconnect(self): self.p.disconnect() class NotificationDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) self._indoorAndOutdoorTemp_type0 = None self._indoorAndOutdoorTemp_type1 = None def handleNotification(self, cHandle, data): formatedData = binascii.b2a_hex(data) if cHandle == 0x0017: # indoorAndOutdoorTemp indication received if formatedData[0] == '8': # Type1 data packet received self._indoorAndOutdoorTemp_type1 = formatedData logging.debug('indoorAndOutdoorTemp_type1 = %s', formatedData) else: # Type0 data packet received self._indoorAndOutdoorTemp_type0 = formatedData logging.debug('indoorAndOutdoorTemp_type0 = %s', formatedData) else: # skip other indications/notifications logging.debug('handle %x = %s', cHandle, formatedData) def getData(self): if self._indoorAndOutdoorTemp_type0 is not None: # return sensors data return {'data_type0':self._indoorAndOutdoorTemp_type0, 'data_type1':self._indoorAndOutdoorTemp_type1} else: return None

class ScanDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) def handleDiscovery(self, dev, isNewDev, isNewData): global weatherStationMacAddr if dev.getValueText(9) == WEATHERSTATION_NAME: # Weather Station in range, saving Mac address for future connection logging.debug('WeatherStation found') weatherStationMacAddr = dev.addr

if __name__=="__main__":

weatherStationMacAddr = None if len(sys.argv) < 2: # No MAC address passed as argument try: # Scanning to see if Weather Station in range scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(2.0) except BTLEException as err: print(err) print('Scanning required root privilege, so do not forget to run the script with sudo.') else: # Weather Station MAC address passed as argument, will attempt to connect with this address weatherStationMacAddr = sys.argv[1] if weatherStationMacAddr is None: logging.debug('No WeatherStation in range !') else: try: # Attempting to connect to device with MAC address "weatherStationMacAddr" weatherStation = WeatherStation(weatherStationMacAddr) if weatherStation.monitorWeatherStation() is not None: # WeatherStation data received indoor = weatherStation.getIndoorTemp() outdoor = weatherStation.getOutdoorTemp() else: logging.debug('No data received from WeatherStation') weatherStation.disconnect() except KeyboardInterrupt: logging.debug('Program stopped by user')

Share

Recommendations

  • Tiny Home Contest

    Tiny Home Contest
  • Fix It! Contest

    Fix It! Contest
  • Metalworking Contest

    Metalworking Contest

34 Discussions

0
None
ДмитрийЛ36

7 months ago

Hi i have a little problem.

I run script to get temperature and humidity every 10 minutes from my RAR213HG, it's ok.

But after time, maby 1-2 days, Station not respond to requests, and not detected by adroid application. resolution is to power off station, and when power on. Mabe need to pair ?

1 reply
0
None
ДмитрийЛ36

7 months ago

Thanks ! I successeful run script on Ubuntu server with Xiaomi bluetooth to read my RAR213HG.
But need to read channels 4 and 5. anybody try to read this data ?

1 reply
0
None
ble_userДмитрийЛ36

Reply 7 months ago

Believe the data is on handle 0x0020. I've only got 3 channels so am unable to confirm if the data structure is the same as handle 0x0017

0x0e: 001b034000000000
0x17: 01e0004600de00fd7f313d357d00c0382e463539
0x17: 82347d7df000b60090003900ea00b600fd7ffd7f
0x20: 01fd7ffd7ffd7ffd7f7d7d7d7df0ff7d7d7d7d7d
0x20: 827d7d7dfd7ffd7ffd7ffd7ffd7ffd7ffd7ffd7f
0x1a: ab270000000001
0x1d: 1112021515050b00000000000000000000000000

0
None
rdebug

1 year ago

Thanks a lot,

I've been able to get the data from a BAR218HG station on RPI Zero with IoT pHat using your instructable.

I'd like to retrieve the past data (24h or 1week) like Weather@Home android app.

Do you have any hint on how to query the main station to get that data ?

Thanks again

2 replies
0
None
ble_userrdebug

Reply 7 months ago

On a BAR218HG the history is obtainable through handle 0x11.
Writing (0x80,0x00,0xff) to 0x11 will get 7 day history, (0x80,0x00,0xfe) last 24 hours.
The middle character is the channel: 0x00 internal temp/humidity, 0x01 internal pressure, 0x10 ch1 temp/humidity, 0x11 ch2 temp/humidity.
I have found that after a reset day 8 of the history is lost so for following days you get days 9-7-6-5-4-3 -2.
Description of a factory reset is a bit misleading as it does not reset/clear the history.

Below is example of data from the last 24 hour history
0x11: 003db10035a60036a60036a90036ab00363d
0x11: 0038af0036b30036b80035c00034c800333d
0x11: 0033ca0033cb0033cb0034ca0033c500343d
0x11: 002ec40034c40035c60035c90034cc00343d
0x11: 0029c50035be0037be0037be0038b500393d
0x11: 013d5227542756275e27612768276f2774273d
0x11: 01357b277f2785278d2792279027902792273d
0x11: 012d922797279a27a327a827ae27ac27ab273d
0x11: 0125a627a627a727a627a927ab27ab27a7273d
0x11: 103db4003ba6003ca5003ca8003caa003b3d
0x11: 1038ae003bb2003bb6003bbd003bc3003a3d
0x11: 1033c5003ac90039cb0039c7003ac4003b3d
0x11: 102ec3003bc3003bc5003bc8003aca003b3d
0x11: 1029c2003bbe003cbe003cc0003db5003d3d
0x11: 113db10038a60038a60039a90038ac00383d
0x11: 1138af0038b40038b90038c10037c900373d
0x11: 1133cb0037c90037c90037c70037c300383d
0x11: 112ec20039c20039c40039c60038c700383d
0x11: 1129c0003abc003bbb003bbb003bb2003b3d

Hope the above helps

0
None
Arnørdebug

Reply 1 year ago

You're welcome. Glad to hear that you were able to get the code to work on Rpi Zero.

No, I wasn't able to figure out how to retrieve historical data.
So I am running the python script every hour thanks to cron utility and storing the results in a SQLite database on my RPi.

0
None
ThomasR213

1 year ago

Hi, Appreciate you sharing this. I have set this up on my RP3 at home and things were going great. However after a couple of days the RP3 stopped receiving temps from the weather station. see terminal output below. I had already edited the cron file to automate this every 5 minutes, so thinking this might be to frequent for the weather station I changed this to once every hour. Unfortunately the same thing happens. I also tried to change the timeout in self.p.waitForNotifications (as in below comment) but no change also. The only way I have found is to remove the batteries from the weather station and reboot it, then it all works fine again. Any help would be appreciated. Thanks.

Untitled.png
6 replies
0
None
ArnøThomasR213

Reply 10 months ago

Hello Thomas,

Sorry for the late reply. I missed your message.

Yes, I did face hangs of the Weather Station base where Rpi can connect over BLE and enable indication/notification but the base wouldn't throw any indication. And it's exactly what you're facing.

Increasing notification timeout doesn't help. You have to reset the base to get it back to work.

These hangs disappeared once I upgraded to Raspbian Jessie with Pixel (2016-09-23) and bluez 5.43.

Please note also that only one single device can be connect to the Weather Station base at a time. If you're already connected to the base with the Weather@Home mobile app, then you won't be able to connect your Rpi.

Hope you will be able to solve your problem.

Cheers,

Arnø

0
None
ThomasR223Arnø

Reply 10 months ago

No worries Arn0, I appreciate you getting back to me. It is good to know I am not the only one who had these issue! Presently I have just set the cron job up to run every hour, at this interval the weather stations seems to be okay. I will experiment with the Raspbian upgrade and see how I go.

0
None
PibenThomasR223

Reply 9 months ago

Hi Thomas - just to share I have that script running with a cron every 5 minutes for many months and it is fairly stable. I've faced hangs a couple of times but suspect they appeared when station was taken far away from RPI (too long range for BLE). It somehow never recover from that even when station is brought back close enough

0
None
ThomasR213Piben

Reply 9 months ago

Thanks for the info Piben, I might try it one last time with a clean install of Raspbian and the weather station sitting 30cm from the pi before I try and intercept the 433Mhz signal.

0
None
ThomasR213Arnø

Reply 10 months ago

Hi Arnø, I tried with the version of raspbian you suggested (Jessie 2016-09-23) and Bluez 5.43, but still no good. I am curious, how often do you get temp data from your setup? I think I will have to go down the path of using a 433Mhz receiver to read the temperatures. BLE was a neat idea, but doesn't seem reliable enough at the moment. Thanks.

0
None
ArnøThomasR213

Reply 10 months ago

I have a cron job capturing temp data every hour.

It's been running for more than 6 months without any hang.

Is your weather station close to your Rpi? Another user reported me that he was facing some hangs every time he would move the weather station away from his Rpi.

Good luck with 433Mhz receiver :)

0
None
Piben

1 year ago

Excellent tutorial Arnø! For me it worked on first trial with a RAR218HG Oregon scientific weather station. I only had to implement slight changes to report humidity - though all framework was there and ready for that. Awsome.

Thanks a lot for sharing!

1 reply
0
None
JurgenS30Piben

Reply 9 months ago

Thanks a lot ARNO for this contribution !

Hi Piben,

I'm trying to do the same on a RAR213HG,

What changes have you made to get Humidity datas ?

Does someone has found a workaround for the connectivity issue ? (to avoid remove battery/reset Oregon Base sensor)

0
None
MattS426

10 months ago

Thanks for sharing this, it is excellent. One question I have is how to best set this up to ping the device every 10 minutes to receive data and then write this data to a textfile for later processing. I have managed to configure the script to return data from my BAR218HG (see attached photo) but would like to log data for comparison to other sensors I have running. Presumably I should use CRON (I am not sure how though)

2017-11-15-090352_1824x984_scrot.png
3 replies
0
None
ArnøMattS426

Reply 10 months ago

Hi Matt,

Yes, you should schedule the script execution with cron.

## Add the following lines to crontab

@hourly python /home/pi/bleWeatherStation.py [mac-address] >> /home/pi/cron.log

Have fun with your Weather Station.

Cheers,

Arnø

0
None
ThomasR213

Reply 1 year ago

I am running bluez version 5.43. I only connect using the pi, so will have to investigate further.

0
None
ArnøThomasR213

Reply 1 year ago

Hello Thomas,

Yes, I did experience hangs of the Weather Station base where Rpi can connect over BLE and enable indication/notification but the base wouldn't throw any indication. The only way out is to reset the base.

It looks to be much more stable though since I upgraded to bluez 5.43.

Please note also that only one single device can be connect to the Weather Station base at a time. If you're already connected to the base with the Weather@Home mobile app, then you won't be able to connect your Rpi.

Hope you will be able to solve your problem.

Cheers,

Arnø