The Project board is for sharing projects based on Silicon Labs' component with other community members. View Projects Guidelines ›

The goal of this project was to make a very simple python script that runs on a Raspberry Pi and collects data from one or more Thunderboard Sense devices, using the same Google Firebase backend and web application that the official app uses.


To get started, go to, and clone the repository. Follow the instructions and set up a firebase account and project, replacing the database name in src/main.js as instructed. For this example, I have not yet gotten authentication working, so change the database.rules.json for now to allow anyone to write (basically replacing "auth.uid === 'YsGcsiI8hkwjImSrr25uZdqlNw3Qkgi8vUWx9MU6'": with "true")


  "rules": {
    ".read": false,
    ".write": false,
      ".read": true,
      ".write": true
      ".read": true,
      ".write": true
      ".write": true,
      ".indexOn": ["startTime", "contactInfo/deviceName"],
      "$session":{//2592000000 is 30 days
        ".read": "data.child('startTime').val() > (now - 2992000000)",
        ".write":  true


Now, deploy the app to Firebase using the firebase tools



sudo npm install -g firebase
firebase login
firebase deploy --project <your firebase project id>


In the Firebase console you should now see your deployed rules, as well as the application url. Following the URL should bring you to the default page:

Screenshot 2017-03-09 01.35.43.png

Now the next step is to log onto the Raspberry Pi, and install the necessary tools. The script uses bluepy to communicate with the sensor, and python-firebase to push data to the cloud. I did have some trouble installing python-firebase because of the specific version of the requests library, but eventually got it installed.


Remember to put in your own database URL in the script:

from firebase import firebase
import uuid
import time

class Thundercloud:

   def __init__(self):

      self.addr     = 'https://-- Your database URL'
      self.firebase = firebase.FirebaseApplication(self.addr, None)


The script continuously looks for advertising thunderboards, and automatically connects to the board if a new one is discovered.


$ sudo python3 
No Thunderboard Sense devices found!
No Thunderboard Sense devices found!
Starting thread <Thread(Thread-1, initial)> for 37020

Thunder Sense #37020
Ambient Light:	0.25 Lux
Sound Level:	37.83
tVOC:		0
Humidity:	28.25 %RH
Temperature:	29.86 C
Pressure:	951.586
UV Index:	0
eCO2:		0

When a Thunderboard Sense has been discovered and connected to, the script will print out the read data periodically. Take note of the number in "Thunder Sense #37020". We will need to give the number to the web application.


The script generates a session ID for each board connected, and then continuously generates json strings for the data read from the Thunderboard Sense. The json string is then inserted into the appropriate location in the database. Firebase has a useful live database view that shows us that our data is indeed being pushed into the cloud:

Screenshot 2017-03-09 01.20.16.png

Finally if you go to the app url and append your Thunderboard Sense id, you should see the data being displayed https://<project id>

Screenshot 2017-03-09 01.19.34.png

 Screenshot 2017-03-09 01.19.47.png simply discovers and sets up handles to the different characteristics. It also contains functions to read these characteristics:



from bluepy.btle import *
import struct
from time import sleep

class Thunderboard:

   def __init__(self, dev):  = dev
      self.char = dict() = ''
      self.session = ''
      self.coinCell = False

      # Get device name and characteristics

      scanData = dev.getScanData()

      for (adtype, desc, value) in scanData:
         if (desc == 'Complete Local Name'):
   = value

      ble_service = Peripheral()
      ble_service.connect(dev.addr, dev.addrType)
      characteristics = ble_service.getCharacteristics()

      for k in characteristics:
         if k.uuid == '2a6e':
            self.char['temperature'] = k
         elif k.uuid == '2a6f':
            self.char['humidity'] = k
         elif k.uuid == '2a76':
            self.char['uvIndex'] = k
         elif k.uuid == '2a6d':
            self.char['pressure'] = k
         elif k.uuid == 'c8546913-bfd9-45eb-8dde-9f8754f4a32e':
            self.char['ambientLight'] = k

         elif k.uuid == 'c8546913-bf02-45eb-8dde-9f8754f4a32e':
            self.char['sound'] = k

         elif k.uuid == 'efd658ae-c401-ef33-76e7-91b00019103b':
            self.char['co2'] = k

         elif k.uuid == 'efd658ae-c402-ef33-76e7-91b00019103b':
            self.char['voc'] = k

         elif k.uuid == 'ec61a454-ed01-a5e8-b8f9-de9ec026ec51':
            self.char['power_source_type'] = k

   def readTemperature(self):
      value = self.char['temperature'].read()
      value = struct.unpack('<H', value)
      value = value[0] / 100
      return value

   def readHumidity(self):
      value = self.char['humidity'].read()
      value = struct.unpack('<H', value)
      value = value[0] / 100
      return value

   def readAmbientLight(self):
      value = self.char['ambientLight'].read()
      value = struct.unpack('<L', value)
      value = value[0] / 100
      return value

   def readUvIndex(self):
      value = self.char['uvIndex'].read()
      value = ord(value)
      return value

   def readCo2(self):
      value = self.char['co2'].read()
      value = struct.unpack('<h', value)
      value = value[0]
      return value

   def readVoc(self):
      value = self.char['voc'].read()
      value = struct.unpack('<h', value)
      value = value[0]
      return value

   def readSound(self):
      value = self.char['sound'].read()
      value = struct.unpack('<h', value)
      value = value[0] / 100
      return value

   def readPressure(self):
      value = self.char['pressure'].read()
      value = struct.unpack('<L', value)
      value = value[0] / 1000
      return value handles the connection to the Firebase database. getSession() generates a new session ID and is called once for every new Thunderboard Sense connection. putEnvironmentData() inserts the data and updates the timestamps:



from firebase import firebase
import uuid
import time

class Thundercloud:

   def __init__(self):

      self.addr     = 'https://'-- Firebase Database Name --''
      self.firebase = firebase.FirebaseApplication(self.addr, None)

   def getSession(self, deviceId):

      timestamp = int(time.time() * 1000)
      guid = str(uuid.uuid1())

      url = 'thunderboard/{}/sessions'.format(deviceId)
      self.firebase.put(url, timestamp, guid)

      d = {
            "startTime" : timestamp,
            "endTime" : timestamp,
            "shortURL": '',
            "contactInfo" : {
                 "fullName":"First and last name",
                 "deviceName": 'Thunderboard #{}'.format(deviceId)
             "temperatureUnits" : 0,
             "measurementUnits" : 0,

      url = 'sessions'
      self.firebase.put(url, guid, d)

      return guid

   def putEnvironmentData(self, guid, data):

      timestamp = int(time.time() * 1000)
      url = 'sessions/{}/environment/data'.format(guid)
      self.firebase.put(url, timestamp, data)

      url = 'sessions/{}'.format(guid)
      self.firebase.put(url, 'endTime', timestamp)


Finally, continuously searches for new Thunderboard Sense devices, and spawns a new thread for each one successfully connected to:


from bluepy.btle import *
import struct
from time import sleep
from tbsense import Thunderboard
from thundercloud import Thundercloud
import threading

def getThunderboards():
    scanner = Scanner(0)
    devices = scanner.scan(3)
    tbsense = dict()
    for dev in devices:
        scanData = dev.getScanData()
        for (adtype, desc, value) in scanData:
            if desc == 'Complete Local Name':
                if 'Thunder Sense #' in value:
                    deviceId = int(value.split('#')[-1])
                    tbsense[deviceId] = Thunderboard(dev)

    return tbsense

def sensorLoop(fb, tb, devId):

    session = fb.getSession(devId)
    tb.session = session
    value = tb.char['power_source_type'].read()
    if ord(value) == 0x04:
        tb.coinCell = True

    while True:

        text = ''

        text += '\n' + + '\n'
        data = dict()


            for key in tb.char.keys():
                if key == 'temperature':
                        data['temperature'] = tb.readTemperature()
                        text += 'Temperature:\t{} C\n'.format(data['temperature'])

                elif key == 'humidity':
                    data['humidity'] = tb.readHumidity()
                    text += 'Humidity:\t{} %RH\n'.format(data['humidity'])

                elif key == 'ambientLight':
                    data['ambientLight'] = tb.readAmbientLight()
                    text += 'Ambient Light:\t{} Lux\n'.format(data['ambientLight'])

                elif key == 'uvIndex':
                    data['uvIndex'] = tb.readUvIndex()
                    text += 'UV Index:\t{}\n'.format(data['uvIndex'])

                elif key == 'co2' and tb.coinCell == False:
                    data['co2'] = tb.readCo2()
                    text += 'eCO2:\t\t{}\n'.format(data['co2'])

                elif key == 'voc' and tb.coinCell == False:
                    data['voc'] = tb.readVoc()
                    text += 'tVOC:\t\t{}\n'.format(data['voc'])

                elif key == 'sound':
                    data['sound'] = tb.readSound()
                    text += 'Sound Level:\t{}\n'.format(data['sound'])

                elif key == 'pressure':
                    data['pressure'] = tb.readPressure()
                    text += 'Pressure:\t{}\n'.format(data['pressure'])


        fb.putEnvironmentData(session, data)

def dataLoop(fb, thunderboards):
    threads = []
    for devId, tb in thunderboards.items():
        t = threading.Thread(target=sensorLoop, args=(fb, tb, devId))
        print('Starting thread {} for {}'.format(t, devId))

if __name__ == '__main__':
    fb = Thundercloud()

    while True:
        thunderboards = getThunderboards()
        if len(thunderboards) == 0:
            print("No Thunderboard Sense devices found!")
            dataLoop(fb, thunderboards)


  • Bluetooth Low Energy
  • Projects
  • Wireless
  • Sensors
  • Hi Mr. DDB,

    you did a gat job in publishing this code.


    I'm trying to interface the Thunderboard Sense board using Node.js with the final target to deliver a nodered node for it, based on NOBLE for Bluetooth 4.0 interface.

    I've been partially sucessfull at the moment in reading some environmnet data.


    I'd like to be able to drive GPIO signals, so to be able to drive relays (or somthing else) directrly soldered  to the board.

    I cannot find any hint about which UUIS Services and Characteristics that should be used in order to achieve this.

    I've done no changes to the Sense factory firmware.

    Sure you have far btter experience then me ... so I'd appreciate a suggestion.


    Thanks very much.


  • Hi DDB,

    Nice job.Is there any update on this project?


  • Hi @DDB,

    Anybody from Silicon Labs please upgrade thundercloud github.


    The files from github are different from site.


    Best regards,





  • I was able to get through the tutorial and make a little help doc where I ran into troubles. It might help you. I am still very new with all of this stuff so I am by no means an expert at this stuff


    msam wrote:

    Hi @DDB,

    Anybody from Silicon Labs please upgrade thundercloud github.


    The files from github are different from site.


    Best regards,






  • @DDB - great work on this and I'm sure many people have used your project as a start for theirs. Thanks. 


    One quick question which I hope you can help me with; I've got a similar set up to you with the TB Sense connecting to the Pi and transmitting data to the cloud. All works great for short periods of time however I wonder if there is anything that can be done in the Python code to ensure long term connectivity.... 


    - e.g.: check if the BLE connection is still open. If not then reset program or reboot Pi...?


    I'm not expert here, although have made some success from this community, so am not entirely sure how to handle this? Any advice?





  • Very nice job.

    I wanted to report a problem.

    After many readings (I do not know how much) the application stops responding.
    By stopping it with CTRL + C you have the following output:


    No Thunderboard Sense devices found!
    Thunder Sense #19172
    Sound Level:    4
    eCO2:           0
    Temperature:    26 C
    tVOC:           0
    Humidity:       20 %RH
    Ambient Light:  17 Lux
    Pressure:       1010
    UV Index:       0
    No Thunderboard Sense devices found!
    Traceback (most recent call last):
      File "", line 95, in <module>
        thunderboards = getThunderboards()
      File "", line 10, in getThunderboards
        devices = scanner.scan(3)
      File "/usr/lib/python2.7/site-packages/bluepy/", line 680, in scan
      File "/usr/lib/python2.7/site-packages/bluepy/", line 648, in process
        resp = self._waitResp(['scan', 'stat'], remain)
      File "/usr/lib/python2.7/site-packages/bluepy/", line 307, in _waitResp
        fds = self._poller.poll(timeout*1000)
    Exception IOError: (32, 'Broken pipe') in <bound method Peripheral.__del__ of <bluepy.btle.Peripheral instance at 0xa2eed0>> ignored
    root@OpenWrt:~/thundercloud# python

    Anyone understand and solve the problem?

  • I'd like to make this project as well, but I am very new to this stuff. Already at
    firebase deploy --project <your firebase project id>

    I get an error, which reads: "Error: Specified public directory does not exist, can't deploy hosting". Which directory is meant here, and where should it be set?

    Also, I was wondering about step 3 of the Firebase Setup: exactly how should the firebase project name be set in the .json file? My line 2 reads: 

    "database": {

    Should the firebase project name come after the colon?

    I'll be thankful for any help, 


  • Does it work on Raspberry Pi 3A+ board?
  • Hi @DDB thanks for the awesome explanation. I have tried your code but I am always getting error like below:

    raise BTLEDisconnectError("Failed to connect to peripheral %s, addr type: %s" % (addr, addrType),rsp)
    bluepy.btle.BTLEDisconnectError:Failed to connect to peripheral 5e:8e:81:72:f6:fa, addr type: public

    Also, I noticed that only when I press reset button in Tbsense2 manually and run my code instinctively, Tbsense 2(Thunderboard sense #15671) is getting appeared in the scanner output after that I am not able to see tbsense2 in the scanner output further.

    Appreciate any help