Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 32
Scripting Tutorial - Lesson 32: BLE - Measuring Temperature with Vernier Go Wireless Temp
(UPDATED to include Vernier Go-Link support)
Lesson 30: Welcome to Bluetooth (BLE)
Supplement: Working with Scripts on the iPad
Lesson 31: BLE - Create your own TI-Nspire Remote
Lesson 32: BLE - Measuring Temperature with the Vernier Go Wireless Temp (UPDATED to include Vernier Go-Link support)
Lesson 33: BLE - Measuring Heart Rate
Lesson 34: BLE - Measuring Temperature with the TI Sensor Tag
Lesson 35: BLE - Build Your Own Weather Station with the TI Sensor Tag
Lesson 36: BLE - Exploring Movement and Position with the TI Sensor Tag
Lesson 37: Lua, LaunchPads and BLE: Making Music via BLE
Lesson 39: Lua, LaunchPads and BLE: Build your own BLE Robot for under $USD40
Download supporting files for this tutorial
Download this page in PDF format
Texas Instruments TI-Nspire Scripting Support Page
In the first lessons of this sequence, you were introduced to the fundamentals of BLE scripting using Ti-Nspire/Lua. You learned how to scan for BLE devices, and to connect to certain of these.
In this lesson, you will connect to specific peripherals and read data (in this case, temperature data) from these. We will begin with the Vernier Go Wireless Temp probe, since this is one of the easiest BLE connections, and then extend to the TI Sensor Tag - a little more involved, but offering the flexibility to then go on to a host of other sensors. Even if you do not have the Vernier probe, it is worth following this first section, as the principles underly all that follows.
Communicating with BLE Devices
With the growing array of BLE-capable devices, there will be a great variety of different ways in which they may be configured to operate. The principles we describe here will not work for all BLE devices. They will work for the specific devices described, particularly for those which operate as sensors of some sort.
Such devices collect data and make it available in quite specific forms. Part of your scripting will be taking this raw data and converting into the expected form.
Most of the common BLE probes, when activated, perform several functions:
broadcast identifying information, called UUID (universally unique ID) numbers. They may broadcast a range of these, designed to identify different functionality (as in the different sensors available with the TI Sensor Tag), but may also serve other purposes (such as giving useful information about how best to collect the data that is being offered).
They
collect real world data of some sort - temperature, humidity, heart rate, location and many more. They will generally
listen for the correct signals which will activate different aspects of their functionality (For example, the device may be configured to read data when the program wants the temperature, or may ask the thermometer to notify the program whenever a new sensor reading is available). They will probably
Two important BLE terms should be introduced before proceeding: services and characteristics. Think of services as the general organizing categories for a BLE device. For the Vernier temperature probe, think of temperature as a service. For the TI Sensor Tag, services include temperature, humidity, barometric pressure, acceleration, magnetometer and gyroscope categories.
Then the characteristics may be thought of as the data associated with these categories.
For example, the Vernier Go Wireless Temp probe may use the following UUID values (Note: the names used here are arbitrary but informative):
VST_FIREFLY_TEMPERATURE_DATA_UUID = '1A97C2F8-DA04-11E2-B53A-00264AA53EFC' - This UUID is used to carry with it the temperature data being collected.
VST_FIREFLY_TEMPERATURE_SAMPLING_PERIOD_UUID = '1A97C2FA-DA04-11E2-B53A-00264AA53EFC' - This UUID carries instructions regarding the sampling period.
RENAME_SERVICE_UUID = '180A' - This UUID offers an option of rename services, while
RENAME_CHARACTERISTIC_UUID = '2A00' - This UUID supports renaming of characteristics.
For our purposes here, we will require and use only the first of these UUIDs. Ignoring the others will simply assume the default settings for the device.
Interactive Script Exploration
This page has been enhanced using JavaScript so that we can focus easily upon the various elements of our script.
- Click the 'init' button to see the initial variable definitions in the script window below for our Vernier Go Wireless Temp script.
- To see the on.resize and on.paint functions, click 'Layout'.
- For menu, keyboard and mouse controls, click 'Menu'.
Reading and Displaying BLE Data: Vernier Temperature Probe
We now focus upon the critical BLE functions for our script, first looking at General BLE Functions. Here we trace the path of the BLE functionality throughout the script.
These first steps review the previous tutorial.
require 'bleCentral': This occurs immediately that the script is run and tests the validity of the platform and device.
ble.addStateListener(listenerCallback): This is placed in on.resize, setting up the BLE listener. It calls... listenerCallback(state, scriptError): Checks the state (BLE.ON, BLE.OFF, etc) and may act upon errors. peripheralOn(): If all conditions are met, then the user may initiate this command. As described previously, this calls... bleCentral.startScanning(callbackScan): Scanning begins, calling... callbackScan(peripheral): Carrying the name of a found peripheral, if this name is found then... peripheral:connect(callbackConnect): This brings us up to the stage that we reached in the previous tutorial. The next steps are new.
callbackConnect(peripheral, event): Carrying the found peripheral and the event associated with this, we are ready to connect, and...
peripheral:discoverServices(callbackServices): All conditions being met, it is time to discover which services are available. callbackServices(peripheral): Services are composed of characteristics. This function runs through found services looking for these. service:discoverCharacteristics(callbackCharacteristics): Multiple characteristics will often be discovered, and each of these will lead to a callback test. Note again that these are general BLE functions - they are not specific to the Vernier probe and so almost everything that has gone before (apart from a few initial variables) may be reused for other BLE devices.
platform.apilevel = '2.5'
screen = platform.window w = screen:width() h = screen:height()
pcall(function ()
require 'bleCentral' end)require "color" local bleState = '' local bleStatus = 'Stand by' local peripheralName = '' local myPeripheral = nil local characteristicsFound = 0
-- Vernier GoTemp Init Variables
-- Layout Functions (resize and paint)local VST_FIREFLY_TEMPERATURE_DATA_UUID = '1A97C2F8-DA04-11E2-B53A-00264AA53EFC'
local vTemp = nil
local vTempF = nil
local units = '°C'
local unitsF = '°F'
local nameCheckList = {'Go'}
local nameList = {'Vernier GoTemp'}
ble.addStateListener(listenerCallback) end) refreshMenu()function on.resize()
w = screen:width() or 841 h = screen:height() or 567
pcall(function()
screen:invalidate()
end
function on.paint(gc)
w = screen:width() or 841 h = screen:height() or 567
local fontSize = math.floor(h/28 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end gc:setFont("sansserif", "b", fontSize) gc:drawString(bleState, 0.1*w, 0.1*h) gc:drawString(bleStatus, 0.1*w, 0.2*h)
for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n]) if peripheralName:find(nameCheckList[n]) then gc:setColorRGB(color.blue) else gc:setColorRGB(color.gray) end gc:drawString(nameList[n], w/(1+#nameList) + (n-1)*w/(1+#nameList) - sw/2, 0.95*h, 'middle')
end
gc:setColorRGB(color.green) gc:fillRect(0.9*w, 0.9*h, 0.1*w, 0.1*h) gc:setColorRGB(color.black) gc:drawRect(0.9*w, 0.9*h, 0.1*w, 0.1*h) local sw = gc:getStringWidth('Scan') gc:drawString('Scan', 0.95*w - sw/2, 0.95*h, 'middle')
gc:setColorRGB(color.red) gc:fillRect(0, 0.9*h, 0.1*w, 0.1*h) gc:setColorRGB(color.black) gc:drawRect(0, 0.9*h, 0.1*w, 0.1*h) local sw = gc:getStringWidth('Stop') gc:drawString('Stop', 0.05*w - sw/2, 0.95*h, 'middle')
local fontSize = math.floor(h/20 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 gc:setFont("sansserif", "b", fontSize)
if vTemp then
local msgT1 = string.format("%s %.1f "..units, "Ambient Temp = ", vTemp ) local sw = gc:getStringWidth(msgT1) gc:drawString(msgT1, 0.5*w - sw/2, 0.5*h)
end
if vTempF then
local msgT2 = string.format("%s %.1f "..unitsF, "Temp (Fahrenheit) = ", vTempF) local sw = gc:getStringWidth(msgT2) gc:drawString(msgT2, 0.5*w - sw/2, 0.6*h)
end
end
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
}{"Controls",
},{"Scan and Connect", function() peripheralOn() end}, {"Disconnect", function() peripheralOff() end}, {"Reset", function() bleState = '' bleStatus = 'Stand by' on.resize() end},
toolpalette.register(Menu)
end
function on.enterKey()
peripheralOn()
end
function on.escapeKey()
resetall()
end
function resetall()
bleState = '' vTemp = nil vTempF = nil peripheralOff() on.resize()
end
function on.mouseUp(x, y)
w = screen:width() or 841 h = screen:height() or 567
if x > 0.9*w and y > 0.9*h then on.enterKey() end if x < 0.1*w and y > 0.9*h then on.escapeKey() end screen:invalidate()
end
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON then bleState = 'BLE ON' elseif state == ble.OFF then bleState = 'BLE OFF' elseif state == ble.RESETTING then bleState = 'BLE RESET' elseif state == ble.UNSUPPORTED then bleState = 'UNSUPPORTED' if scriptError then print('Error message: BLE not supported') end end
screen:invalidate()
end
function peripheralOn()
bleCentral.startScanning(callbackScan) bleStatus = 'Scanning' screen:invalidate()
end
function peripheralOff()
bleCentral.stopScanning()
if myPeripheral then myPeripheral:disconnect() endbleStatus = 'Stand by' peripheralName = '' screen:invalidate()
end
function callbackScan(peripheral)
if peripheral ~= nil then peripheralName = peripheral:getName() or 'Unknown Device' for n =1, #nameCheckList do
if peripheralName and peripheralName:find(nameCheckList[n]) then
peripheral:connect(callbackConnect)
else
peripheralName = peripheralName..' (unsupported)'
end
end
end
screen:invalidate()end
function callbackConnect(peripheral, event)
if event == bleCentral.CONNECTED then
bleCentral.stopScanning() bleStatus = 'Connected' myPeripheral = peripheral peripheralName = peripheralName:gsub('(unsupported)', '') peripheral:discoverServices(callbackServices)
elseif event == bleCentral.DISCONNECTED then bleStatus = 'Disconnected' peripheralName = ''
end screen:invalidate()
end
function callbackServices(peripheral)
if peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
local services = peripheral:getServices() for _,service in ipairs(services) do service:discoverCharacteristics(callbackCharacteristics) end
end
screen:invalidate()
end
-- BLE Specific Functions ---------
function callbackCharacteristics(service)
local characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == VST_FIREFLY_TEMPERATURE_DATA_UUID then
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true) characteristicsFound = characteristicsFound + 1
end
end
end
function callbackCharacteristic(characteristic)
if characteristic:getUUID() == VST_FIREFLY_TEMPERATURE_DATA_UUID then
--Vernier Go Temp
local value = characteristic:getValue() local data = ble.unpack('u16', value) vTemp = data/128 vTempF = (vTemp*9/5)+32
end
screen:invalidate()
end
platform.apilevel = '2.5'
screen = platform.window w = screen:width() h = screen:height()
pcall(function ()
require 'bleCentral' end)require "color" local bleState = '' local bleStatus = 'Stand by' local peripheralName = '' local myPeripheral = nil local characteristicsFound = 0
-- Vernier GoTemp Init Variables
local VST_FIREFLY_TEMPERATURE_DATA_UUID = '1A97C2F8-DA04-11E2-B53A-00264AA53EFC'
local vTemp = nil
local vTempF = nil
local units = '°C'
local unitsF = '°F'
local nameCheckList = {'Go'}
local nameList = {'Vernier GoTemp'}
-- Layout Functions (resize and paint)ble.addStateListener(listenerCallback) end) refreshMenu()function on.resize()
w = screen:width() or 841 h = screen:height() or 567
pcall(function()
screen:invalidate()
end
function on.paint(gc)
w = screen:width() or 841 h = screen:height() or 567
local fontSize = math.floor(h/28 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end gc:setFont("sansserif", "b", fontSize) gc:drawString(bleState, 0.1*w, 0.1*h) gc:drawString(bleStatus, 0.1*w, 0.2*h)
for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n]) if peripheralName:find(nameCheckList[n]) then gc:setColorRGB(color.blue) else gc:setColorRGB(color.gray) end gc:drawString(nameList[n], w/(1+#nameList) + (n-1)*w/(1+#nameList) - sw/2, 0.95*h, 'middle')
end
gc:setColorRGB(color.green) gc:fillRect(0.9*w, 0.9*h, 0.1*w, 0.1*h) gc:setColorRGB(color.black) gc:drawRect(0.9*w, 0.9*h, 0.1*w, 0.1*h) local sw = gc:getStringWidth('Scan') gc:drawString('Scan', 0.95*w - sw/2, 0.95*h, 'middle')
gc:setColorRGB(color.red) gc:fillRect(0, 0.9*h, 0.1*w, 0.1*h) gc:setColorRGB(color.black) gc:drawRect(0, 0.9*h, 0.1*w, 0.1*h) local sw = gc:getStringWidth('Stop') gc:drawString('Stop', 0.05*w - sw/2, 0.95*h, 'middle')
local fontSize = math.floor(h/20 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 gc:setFont("sansserif", "b", fontSize)
if vTemp then
local msgT1 = string.format("%s %.1f "..units, "Ambient Temp = ", vTemp ) local sw = gc:getStringWidth(msgT1) gc:drawString(msgT1, 0.5*w - sw/2, 0.5*h)
end
if vTempF then
local msgT2 = string.format("%s %.1f "..unitsF, "Temp (Fahrenheit) = ", vTempF) local sw = gc:getStringWidth(msgT2) gc:drawString(msgT2, 0.5*w - sw/2, 0.6*h)
end
end
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
}{"Controls",
},{"Scan and Connect", function() peripheralOn() end}, {"Disconnect", function() peripheralOff() end}, {"Reset", function() bleState = '' bleStatus = 'Stand by' on.resize() end},
toolpalette.register(Menu)
end
function on.enterKey()
peripheralOn()
end
function on.escapeKey()
resetall()
end
function resetall()
bleState = '' vTemp = nil vTempF = nil peripheralOff() on.resize()
end
function on.mouseUp(x, y)
w = screen:width() or 841 h = screen:height() or 567
if x > 0.9*w and y > 0.9*h then on.enterKey() end if x < 0.1*w and y > 0.9*h then on.escapeKey() end screen:invalidate()
end
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON then bleState = 'BLE ON' elseif state == ble.OFF then bleState = 'BLE OFF' elseif state == ble.RESETTING then bleState = 'BLE RESET' elseif state == ble.UNSUPPORTED then bleState = 'UNSUPPORTED' if scriptError then print('Error message: BLE not supported') end end
screen:invalidate()
end
function peripheralOn()
bleCentral.startScanning(callbackScan) bleStatus = 'Scanning' screen:invalidate()
end
function peripheralOff()
bleCentral.stopScanning()
if myPeripheral then myPeripheral:disconnect() endbleStatus = 'Stand by' peripheralName = '' screen:invalidate()
end
function callbackScan(peripheral)
if peripheral ~= nil then peripheralName = peripheral:getName() or 'Unknown Device' for n =1, #nameCheckList do
if peripheralName and peripheralName:find(nameCheckList[n]) then
peripheral:connect(callbackConnect)
else
peripheralName = peripheralName..' (unsupported)'
end
end
end
screen:invalidate()end
function callbackConnect(peripheral, event)
if event == bleCentral.CONNECTED then
bleCentral.stopScanning() bleStatus = 'Connected' myPeripheral = peripheral peripheralName = peripheralName:gsub('(unsupported)', '') peripheral:discoverServices(callbackServices)
elseif event == bleCentral.DISCONNECTED then bleStatus = 'Disconnected' peripheralName = ''
end screen:invalidate()
end
function callbackServices(peripheral)
if peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
local services = peripheral:getServices() for _,service in ipairs(services) do service:discoverCharacteristics(callbackCharacteristics) end
end
screen:invalidate()
end
-- BLE Specific Functions ---------
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true) characteristicsFound = characteristicsFound + 1function callbackCharacteristics(service)
local characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == VST_FIREFLY_TEMPERATURE_DATA_UUID then
end
end
end
function callbackCharacteristic(characteristic)
if characteristic:getUUID() == VST_FIREFLY_TEMPERATURE_DATA_UUID then
--Vernier Go Temp
local value = characteristic:getValue() local data = ble.unpack('u16', value) vTemp = data/128 vTempF = (vTemp*9/5)+32
end
screen:invalidate()
end
BLE Functions Specific to the Vernier Temperature Probe
Time to see where the real action happens!
callbackCharacteristics(service): You will have seen from the final command above that each found service generates a callback. These are now checked against known UUID values, and also use characteristic:setNotify(true) to send a message to the peripheral to confirm connection. characteristic:setValueUpdateListener(callbackCharacteristic) then initiates...
callbackCharacteristic(characteristic) : Checking again that you are talking to the right device, the data from that device may now be configured to draw from it the required information - in this case, ambient temperature data. The Vernier probe issues 16-bit unsigned data, and so the command ble.unpack('u16', value) to take this apart and perform necessary calculations.
If you have a Vernier probe and Nspire iPad App, you may test this entire script by copying and pasting from this web page into the TI-Nspire Lua Script Editor.
We are now also able to offer support for the extensive range of sensors supported by the Vernier Go-Link, thanks to the great work of Fred Fotsch.
This amazing device takes dozens of Vernier probes and broadcasts, not just a single value like temperature (or, more correctly, voltage) but an array of information, identifying the type of probe connected, along with the necessary data to convert the raw data into meaningful sensor information.
This script would be a good extension to come back and study, perhaps after you have completed the next few BLE tutorials. However, if you happen to have a GoLink and a few Vernier probes, go right ahead and play with the TNS file attached.
Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 32