Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 31
Scripting Tutorial - Lesson 31: BLE - Create your own TI-Nspire Remote
Supplement: Working with Scripts on the iPad
Lesson 31: BLE - BLE - Create your own TI-Nspire Remote
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 lesson 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 a specific peripheral and read data (in this case, simple key presses) from this. We will begin with the TI Sensor Tag 2.0 - one of the most affordable and powerful BLE devices around. If you don't yet have one, then perhaps it is time to explore further!
Hearing SensorTag Key Presses
The TI SensorTag comes with what are termed "Simple Keys" - we will refer to these as "left" and "right" keys. The SensorTag 2.0 (or CC2650 SensorTag) has only two buttons, found on the sides. Facing the two strip windows, the left side button also serves to turn the device on and off, but this actually requires a 3-second keypress. The right side button will switch to iBeacon mode after 6 seconds, and reset the device after 10.
All SensorTags use the same broadcast signal (UUID) and transmit the same information when the left and right buttons are pressed. It is a simple thing to take this information and define actions for these events, turning your Tag into a remote control for your script. This has been done with the Lua Objects Gallery scripts.
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 SensorTag keypress script.
- To see the on.resize and on.paint functions, click 'Layout'.
- For menu, keyboard and mouse controls, click 'Menu'.
- For BLE functions common to many scripts, click 'BLE General'.
- For BLE functions specific to this script, click 'BLE Specific'.
- To see the entire script, click 'Full Script'.
Grabbing and Using the BLE Data
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 SensorTag 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
-- SensorTag keyPress Variables
local keysRead = 'FFE1'
local keyPress = nil
local nameList = {'SensorTag', 'LaunchPad', 'HMSoft', 'BT05', 'CC41'}
-- Layout Functions (resize and paint)
function on.resize()
w = screen:width() or 841
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()screen:invalidate()
end
function on.paint(gc)
w = screen:width() or 841
h = screen:height() or 567local 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)if peripheralName then
gc:drawString(peripheralName.." "..bleStatus, 0.1*w, 0.2*h)for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n])
if peripheralName:find(nameList[n]) then gc:setColorRGB(color.green) else gc:setColorRGB(color.gray) end
gc:drawString(nameList[n], 0.95*w - sw, 0.95*h - 0.01125*h*(n-1)*9-0.1*h, 'middle')end
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 bleStatus:find('Connect') then
if keyPress and keyPress ~= 0 then
local str = 'Keypress '..keyPress
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.5*h, "middle")else
local str = 'Press the left or right key...'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.5*h, "middle")
end
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 = ''
keyPress = nil
peripheralOff()
on.resize()end
function on.mouseUp(x, y)
w = screen:width() or 841
h = screen:height() or 567if 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
endscreen: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()
for n =1, #nameList doif peripheral:getName() and (peripheral:getName()):find(nameList[n]) then
peripheral:connect(callbackConnect)
breakend
end
end
screen:invalidate()end
function callbackConnect(peripheral, event)
if event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = 'Connected'
myPeripheral = peripheral
peripheralName = peripheral:getName()
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)
endend
screen:invalidate()
end
-- BLE Specific Functions ---------
function callbackCharacteristics(service)
local characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == keysRead then
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
end
end
function callbackCharacteristic(characteristic)
if characteristic:getUUID() == keysRead then
--Simple Keys
local value = characteristic:getValue()
if value and peripheralName then
if peripheralName:find("Tag") or peripheralName:find("LaunchPad") then
keyPress = ble.unpack('u8', value)else
keyPress = value:gsub("P", "")end
keyPress = keyPress:gsub(" ","")
keyPress = tonumber(keyPress)
end
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-- SensorTag keyPress Variables
local keysRead = 'FFE1'
local keyPress = nil
local nameList = {'Tag', 'LaunchPad', 'HMSoft', 'BT05', 'CC41'}
-- Layout Functions (resize and paint)
function on.resize()
w = screen:width() or 841
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()screen:invalidate()
end
function on.paint(gc)
w = screen:width() or 841
h = screen:height() or 567local 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)if peripheralName then
gc:drawString(peripheralName.." "..bleStatus, 0.1*w, 0.2*h)for n = 1, #nameList do
local sw = gc:getStringWidth(nameList[n])
if peripheralName:find(nameList[n]) then gc:setColorRGB(color.blue) else gc:setColorRGB(color.gray) end
gc:drawString(nameList[n], w/(1+#nameList) + 0.01125*h*(n-1)*w/(1+#nameList) - sw/2, 0.95*h, 'middle')end
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 bleStatus:find('Connect') then
if keyPress and keyPress ~= 0 then
local str = 'Keypress '..keyPress
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.5*h, "middle")else
local str = 'Press the left or right key...'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.5*h, "middle")
end
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'
keyPress = nil
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 567if 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
endscreen: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()
for n =1, #nameList doif peripheral:getName() and (peripheral:getName()):find(nameList[n]) then
peripheral:connect(callbackConnect)
breakend
end
end
screen:invalidate()end
function callbackConnect(peripheral, event)
if event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = 'Connected'
myPeripheral = peripheral
peripheralName = peripheral:getName()
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)
endend
screen:invalidate()
end
-- BLE Specific Functions ---------
function callbackCharacteristics(service)
local characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == keysRead then
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
end
end
function callbackCharacteristic(characteristic)
if characteristic:getUUID() == keysRead then
--Simple Keys
local value = characteristic:getValue()
if value and peripheralName then
if peripheralName:find("Tag") or peripheralName:find("LaunchPad") then
keyPress = ble.unpack('u8', value)else
keyPress = value:gsub("P", "")end
keyPress = keyPress:gsub(" ","")
keyPress = tonumber(keyPress)
end
end
screen:invalidate()
end
BLE Functions Specific to the Simple Keys of the SensorTag
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, key press data. The SensorTag key presses issue 8-bit unsigned data, and so the command ble.unpack('u8', value) to take this apart and read the results - in this case, a '2' value for the left key and '1' for the right key.
The applications of this simple method are broad - these simple results might be used, for example to simulate pressing tab keys, enter keys or escapeKey actions in a script. In this way, the SensorTag behaves like a remote control unit for the script. Why not grab the Lua Objects Gallery document from tutorial 29 and have a play with the various tools provided there. Study the scripts - these are simplified even further than the one developed here, by scanning and connecting automatically. All the user needs to do is to turn on the SensorTag, open the page and wait for connection. Then start controlling the action!
If you have a TI SensorTag 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.
Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 31