Accessing the Bluetooth low energy Humidity Sensor (Hygrometer) on the TI SensorTag
Accessing the Bluetooth low energy Humidity Sensor (Hygrometer) on the TI SensorTag
Accessing the Bluetooth low energy Humidity Sensor (Hygrometer) on the TI SensorTag
This blog explores how to read relative humidity using new TI SensorTag, a Bluetooth low energy peripheral designed to be a hardware and software reference program for the CC2541 chip. It shows a simple, text-based program that's great for experimenting with the sensor. The complete listing appears at the end of the blog. A companion blog, Controlling the TI SensorTag with techBASIC, gives background information about Bluetooth low energy devices, the TI SensorTag, and accessing Bluetooth devices using an iPhone or iPad. It also gives a complete program that accesses all six sensors on the TI SensorTag, showing the values using a GUI interface.
What is a Hygrometer?
A hygrometer measures the moisture in an environment, in our case the humidity in air. We all know what humidity is, especially those of us who have lived in the Ohio River Valley. It's all that water in the air that doesn't seem to be falling as rain—yet! But what is it really, and what does a humidity meter measure?
First, humidity is not water vapor. The clouds you see are well beyond humidity; they are tiny drops of water or ice crystals suspended in air, like hot chocolate suspended in water. Humidity is the water that's actually part of the air, existing as a gas of individual molecules mixed with the nitrogen, oxygen, CO2 and other gases in the air. It's more like dissolving sugar in water than suspending chocolate. As you know from experience, though, there is only so much sugar a glass of water can hold. If you put too much sugar in a glass of water, no amount of stirring will dissolve it. The same thing happens with air—it can only hold a certain amount of water. The amount varies with temperature and pressure, but there is always a limit. That limit is the saturation amount, known as the saturated vapor pressure. The relative humidity is a measure of the percentage of water that is in the air compared to the amount of water the air can hold. The Ohio River Valley in the summer often has very high humidity, so sweat doesn't evaporate well, causing us to feel very hot. Here in Albuquerque, we have a dry heat. As I write this article, the humidity is 33%. It really does make a difference—sweat evaporates quickly, cooling the body, so a 90° day doesn't really feel as bad as 80° in a humid environment.
The SHT21 Digital Humidity Sensor used on the SensorTag reports relative humidity. It needs the temperature to calculate the humidity, so it reports that, too.
Accessing the Hygrometer
Here's the Generic Attribute Profile (GATT) for the humidity service.
Attribute 0xAA22 turns the sensor on or off. Our program turns it on by writing a 1 to this attribute. You can turn it off by writing a zero, but this simple program doesn't turn the sensor off.
CASE "F000AA22" & servicesHeader
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
0xAA21 is used to read the humidity and temperature. There are two ways to read sensors on the SensorTag, either reading it when the program wants the humidity or asking the hygrometer to notify the program whenever a new sensor reading is available. The first step is to tell the hygrometer which way we want to read values. Write a 1 and 0 to the characteristic to receive notifications when a value is available, then ask it to start sending notifications.
CASE "F000AA21" & servicesHeader
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
Use readCharacteristic to read a single value.
peripheral.readCharacteristic(characteristics(i), 1)
Whether you read the data once using readCharacteristic or ask for notifications with setNotify, the values are always returned using a call to BLECharacteristicInfo. The temperature and humidity are returned in two unsigned integers, with the temperature in the first two bytes and the humidity in the second. Both are unsigned values stores least-significant byte first. Here's the conversion from the value returned to a relative humidity.
where h3,4 is the value returned from the SensorTag, and h is the relative humidity, which should have a value from 0 to 100. While it is possible to have a humidity over 100%, a situation known as super-saturation, the SHT21 sensor doesn't claim to work in that region, so you should not see values over 100.
Use this formula to convert the raw temperature value to degrees Celsius.
Here's what we get after converting the equations to BASIC.
SUB BLECharacteristicInfo (time AS DOUBLE, peripheral AS BLEPeripheral, characteristic AS BLECharacteristic, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 2 THEN
DIM value(1) AS INTEGER
value = characteristic.value
SELECT CASE characteristic.uuid
CASE "F000AA21" & servicesHeader
! Update the humidity indicator.
v& = value(3) BITOR (value(4) << 8)
v& = v& BITAND $00FFFC
v = v&*125.0/65536 - 6.0
t& = (value(1) BITOR (value(2) << 8)) BITAND $00FFFF
t = -46.85 + 175.72*t&/65536.0
PRINT "Relative humidity: "; v; ", temperature = "; t
CASE ELSE
PRINT "Read from "; characteristic.uuid
END SELECT
ELSE IF kind = 3 AND err <> 0 THEN
PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message
END IF
END SUB
The Source
Here's the complete source for the hygrometer program. See the source code from Controlling the TI SensorTag with techBASIC for a program that displays the relative humidity using a nice GUI.
! Simple program to access the humidity sensor on the TI SensorTag.
! Set up variables to hold the peripheral and the characteristics
! for the battery and buzzer.
DIM sensorTag AS BLEPeripheral
! We will look for these services.
DIM servicesHeader AS STRING, services(1) AS STRING
servicesHeader = "-0451-4000-B000-000000000000"
services(1) = "F000AA20" & servicesHeader : ! Humidity
hum% = 1
! Start the BLE service and begin scanning for devices.
debug = 0
BLE.startBLE
DIM uuid(0) AS STRING
BLE.startScan(uuid)
! Called when a peripheral is found. If it is a Sensor Tag, we
! initiate a connection to it and stop scanning for peripherals.
!
! Parameters:
! time - The time when the peripheral was discovered.
! peripheral - The peripheral that was discovered.
! services - List of services offered by the device.
! advertisements - Advertisements (information provided by the
! device without the need to read a service/characteristic)
! rssi - Received Signal Strength Indicator
!
SUB BLEDiscoveredPeripheral (time AS DOUBLE, peripheral AS BLEPeripheral, services() AS STRING, advertisements(,) AS STRING, rssi)
IF peripheral.bleName = "TI BLE Sensor Tag" THEN
sensorTag = peripheral
BLE.connect(sensorTag)
BLE.stopScan
END IF
END SUB
! Called to report information about the connection status of the
! peripheral or to report that services have been discovered.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! kind - The kind of call. One of
! 1 - Connection completed
! 2 - Connection failed
! 3 - Connection lost
! 4 - Services discovered
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLEPeripheralInfo (time AS DOUBLE, peripheral AS BLEPeripheral, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 1 THEN
! The connection was established. Look for available services.
IF debug THEN PRINT "Connection made."
peripheral.discoverServices(uuid)
ELSE IF kind = 2 OR kind = 3 THEN
IF debug THEN PRINT "Connection lost: "; kind
BLE.connect(sensorTag)
ELSE IF kind = 4 THEN
! Services were found. If it is one of the ones we are interested
! in, begin discovery of its characteristics.
DIM availableServices(1) AS BLEService
availableServices = peripheral.services
FOR s = 1 to UBOUND(services, 1)
FOR a = 1 TO UBOUND(availableServices, 1)
IF services(s) = availableServices(a).uuid THEN
IF debug THEN PRINT "Discovering characteristics for "; services(s)
peripheral.discoverCharacteristics(uuid, availableServices(a))
END IF
NEXT
NEXT
END IF
END SUB
! Called to report information about a characteristic or included
! services for a service. If it is one we are interested in, start
! handling it.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! service - The service whose characteristic or included
! service was found.
! kind - The kind of call. One of
! 1 - Characteristics found
! 2 - Included services found
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLEServiceInfo (time AS DOUBLE, peripheral AS BLEPeripheral, service AS BLEService, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 1 THEN
! Get the characteristics.
DIM characteristics(1) AS BLECharacteristic
characteristics = service.characteristics
FOR i = 1 TO UBOUND(characteristics, 1)
IF service.uuid = services(hum%) THEN
! Found the humidity sensor.
SELECT CASE characteristics(i).uuid
CASE "F000AA21" & servicesHeader
! Tell the humidity sensor to begin sending data.
IF debug THEN PRINT "Start humidity sensor."
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
CASE "F000AA22" & servicesHeader
! Turn the humidity sensor on.
IF debug THEN PRINT "Humidity on."
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
END SELECT
END IF
NEXT
END IF
END SUB
! Called to return information from a characteristic.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! characteristic - The characteristic whose information
! changed.
! kind - The kind of call. One of
! 1 - Called after a discoverDescriptors call.
! 2 - Called after a readCharacteristics call.
! 3 - Called to report status after a writeCharacteristics
! call.
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLECharacteristicInfo (time AS DOUBLE, peripheral AS BLEPeripheral, characteristic AS BLECharacteristic, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 2 THEN
DIM value(1) AS INTEGER
value = characteristic.value
SELECT CASE characteristic.uuid
CASE "F000AA21" & servicesHeader
! Update the humidity indicator.
v& = value(3) BITOR (value(4) << 8)
v& = v& BITAND $00FFFC
v = v&*125.0/65536 - 6.0
t& = (value(1) BITOR (value(2) << 8)) BITAND $00FFFF
t = -46.85 + 175.72*t&/65536.0
PRINT "Relative humidity: "; v; ", temperature = "; t
CASE ELSE
PRINT "Read from "; characteristic.uuid
END SELECT
ELSE IF kind = 3 AND err <> 0 THEN
PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message
END IF
END SUB
Wednesday, October 31, 2012
Programs in this blog use some features from techBASIC 2.4. We had expected Apple to release it by now. Once they do, it will be a free upgrade to the current version, techBASIC 2.3. techBASIC 2.3 is available now in the App Store.