Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 36
Scripting Tutorial - Lesson 36: BLE - Exploring Movement and Position with the TI Sensor Tag
Lesson 30: Welcome to Bluetooth (BLE)
Supplement: Working with Scripts on the iPad
Lesson 31: 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 an earlier lesson of this sequence, we developed a working script to use the TI SensorTag 2.0 as a functional weather station.
In this lesson, we extend this to complete the capabilities of the Sensor Tag, and collect data from the movement sensors!
Note: The script on this page is designed for the TI SensorTag 2.0 (Or CC2650 SensorTag). It will not give accurate readings for the original (CC2451) SensorTag. For a general script that works for BOTH tag types, refer to the zipped support files: BLE_Sample_ST.tns.
Movement, Position and the TI SensorTag
As before, adapting our previous script to take fuller advantage of the TI Sensor Tag is reasonably straightforward:
- Add the required UUID values and additional variable names to the initial part of the script (and amend the resetall function).
- Extend the paint function to take account of the new values.
- Add the required features to the BLE Specific Functions.
- In order to access some of the new functionality of the SensorTag 2.0 (movement sensors and light intensity) some bit manipulation of the data is required. For this we use an open source library.
Included among the more obvious "movement" sensors (acclerometer, magnetometer and gyroscope) are the other two "dynamic" sensors (barometer and light intensity), which may also be used to give relatively good information concerning movement and, more interestingly, position!
In fact, with a little systematic measurement and calibration, barometric variations may be used to deliver measure of altitude (or vertical displacement), while light intensity can give us horizontal displacement. Such questions are potentially powerful investigations for STEM-active classrooms, drawing on knowledge of mathematics and science, and taking an engineering approach to solving problems using technology.
The script developed here is, intentionally, simple in design and execution. Many variations might be made to the layout and design of the page. These are left to the reader - or feel free to play with the enhanced BLE_Sample_ST.tns or the more complete TI_SensorTag.tns document which accompanies this tutorials.
Note, however, that this script does include capturing of the live data into lists, which may be dynamically displayed using the Data & Statistics App. The method used here does not actually rely upon the TI-Nspire Lua timer function, but rather on the delivery of data from the SensorTag (by default, this should happen around 10 times a second).
Once again, if you have a SensorTag and appropriate device, you can test this script yourself by copying and pasting from this page. Of course, prepared versions are also available from the download link for this page.
Your homework? Try adding storage of data for magnetometer and gyroscope? And try developing classroom-ready activities for students to capture calibration data and convert their lists into distance, and even velocity data...
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 nameCheckList = {'Tag 2.0', 'CC2650'}
local nameList = {'TI Sensor Tag 2.0', 'CC2650 SensorTag', 'TI SensorTag 20'}
local sensorList = {'Barometric Pressure', 'Light Intensity', 'Accelerometer', 'Magnetometer', 'Gyroscope'}local timeList = {}
var.store("timeList", timeList)
local timeIndex = 0
local pause = true
local baTemp = nil
local baroList = {}
var.store("baroList", baroList)
local lightList = {}
var.store("lightList", lightList)
local acc_x = {}
var.store("acc_x", acc_x)
local acc_y = {}
var.store("acc_y", acc_y)
local acc_z = {}
var.store("acc_z", acc_z)-- TI Sensor Tag Init Variables
--Simple Keys
local keysRead = 'FFE1'
local keyPress = nil
local Lstart = 0
local Lstop = 0
local Rstart = 0
local Rstop = 0-- Barometric Pressure
local baData = 'F000AA41-0451-4000-B000-000000000000'
local baConfig = 'F000AA42-0451-4000-B000-000000000000'
local baCalib = 'F000AA43-0451-4000-B000-000000000000'
local baPeriod = 'F000AA44-0451-4000-B000-000000000000'
local Pa = nil
local baTemp = nil-- Light
local optData = 'F000AA71-0451-4000-B000-000000000000'
local optSwitch = 'F000AA72-0451-4000-B000-000000000000' -- 0: disable, 1: enable
local optPeriod = 'F000AA73-0451-4000-B000-000000000000' -- Period in tens of milliseconds
local Light = nil-- Movement
local moveData = 'F000AA81-0451-4000-B000-000000000000'
local moveConf = 'F000AA82-0451-4000-B000-000000000000'
local movePeriod = 'F000AA83-0451-4000-B000-000000000000'-- 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/16 + 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", "r", fontSize)
local sw1 = gc:getStringWidth(bleState)
gc:drawString(bleState, w/3 - sw1/2, 0.95*h, "middle")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)
local sensorVarList = {Pa, Light, xA, xM, xG}
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')for n = 1, #sensorList do
local sw = gc:getStringWidth(sensorList[n])
if sensorVarList[n] then gc:setColorRGB(color.blue) else gc:setColorRGB(color.gray) end
gc:drawString(sensorList[n], 0.05*w, 0.05*h + h/(2+#sensorList) + (n-1)*h/(2+#sensorList), 'middle')end
if peripheralName ~= '' then
gc:setColorRGB(color.blue)
local str = peripheralName..' Movement Explorer'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.05*h, 'middle')else
gc:setColorRGB(color.gray)
local str = nameList[1]..' Movement Explorer'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.05*h, 'middle')end
local sw2 = gc:getStringWidth(bleStatus)
gc:drawString(bleStatus, 2*w/3 - sw2/2, 0.95*h, "middle")gc:setFont("sansserif", "b", fontSize)
gc:setColorRGB(color.black)
if Pa then
local msgP1 = string.format("%s %.1f hPa", "Pressure=", Pa)
local sw = gc:getStringWidth(msgP1)
gc:drawString(msgP1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')if baTemp then
local msgP2 = string.format("%s %.1f °C", "Temp=", baTemp)
local sw = gc:getStringWidth(msgP2)
gc:drawString(msgP2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')end
end
if Light then
local msgL1 = string.format("%s %.1f Lux", "Light Intensity=", Light)
local sw = gc:getStringWidth(msgL1)
gc:drawString(msgL1, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')end
if xA and yA and zA then
local msgA1 = string.format("%s %.1f hPa", "xA=", xA)
local sw = gc:getStringWidth(msgA1)
gc:drawString(msgA1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')
local msgA2 = string.format("%s %.1f hPa", "yA=", yA)
local sw = gc:getStringWidth(msgA2)
gc:drawString(msgA2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')
local msgA3 = string.format("%s %.1f hPa", "zA=", zA)
local sw = gc:getStringWidth(msgA3)
gc:drawString(msgA3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')end
if xM and yM and zM then
local msgM1 = string.format("%s %.1f hPa", "xM=", xM)
local sw = gc:getStringWidth(msgM1)
gc:drawString(msgM1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')
local msgM2 = string.format("%s %.1f hPa", "yM=", yM)
local sw = gc:getStringWidth(msgM2)
gc:drawString(msgM2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')
local msgM3 = string.format("%s %.1f hPa", "zM=", zM)
local sw = gc:getStringWidth(msgM3)
gc:drawString(msgM3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')end
if xG and yG and zG then
local msgG1 = string.format("%s %.1f hPa", "xG=", xG)
local sw = gc:getStringWidth(msgG1)
gc:drawString(msgG1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')
local msgG2 = string.format("%s %.1f hPa", "yG=", yG)
local sw = gc:getStringWidth(msgG2)
gc:drawString(msgG2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')
local msgG3 = string.format("%s %.1f hPa", "zG=", zG)
local sw = gc:getStringWidth(msgG3)
gc:drawString(msgG3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')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()
timeIndex = 0
timeList = {}
pause = true-- Barometric Pressure
Pa = nil
baTemp = nil-- Acceleration
xA = nil
yA = nil
zA = nil-- Magnetometer
xM = nil
yM = nil
zM = nil-- Gyroscope
xG = nil
yG = nil
zG = nilLight = nil
lightList = {}
baroList = {}
acc_x = {}
acc_y = {}
acc_z = {}
var.store("acc_x", acc_x)
var.store("acc_y", acc_y)
var.store("acc_z", acc_z)
var.store("baroList", baroList)
var.store("timeList", timeList)
var.store("lightList", lightList)screen:invalidate()
end
function resetall()
on.escapeKey()
peripheralOff()
on.resize()
screen:invalidate()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 resetall() 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() or 'Unknown Device'
for n =1, #nameCheckList doif peripheralName and peripheralName:find(nameCheckList[n]) then
peripheral:connect(callbackConnect)
elseif not peripheralName:find('unsupported') then
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 = 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
-- Simple Keys
if characteristic:getUUID() == keysRead thenendcharacteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
-- Barometric Pressure
if characteristic:getUUID() == baData then
readBa = characteristic
readBa:setValueUpdateListener(callbackCharacteristic)
readBa:setNotify(true)end
if characteristic:getUUID() == baCalib then
calibBa = characteristic
calibBa:setValueUpdateListener(callbackCharacteristic)
calibBa:read()end
if characteristic:getUUID() == baConfig then
writeBa = characteristic
local msg = string.char(1)
writeBa:setWriteCompleteListener(callbackCharacteristic)
writeBa:write(msg, true)
local msg = string.char(2)
writeBa:setWriteCompleteListener(callbackCharacteristic)
writeBa:write(msg, true)end
-- Light
if characteristic:getUUID() == optData then --01 M
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
if characteristic:getUUID() == optSwitch then -- 02
local msg = 1 characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(msg), true)
end
--Movement SENSOR
if characteristic:getUUID() == moveData then
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
if characteristic:getUUID() == moveConf then
local msg = ble.pack('u16', 0x007F)
characteristic:setWriteCompleteListener(callbackCharacteristic)
characteristic:write(msg, true)end
if characteristic:getUUID() == movePeriod then
characteristic:setWriteCompleteListener(callbackCharacteristic)
characteristic:write(string.char(1), true)end
end
end
function callbackCharacteristic(characteristic)
-- Simple Keys
if characteristic:getUUID() == keysRead then
local value = characteristic:getValue()
if value and string.len(tostring(value)) == 1 thenkeyPress = ble.unpack("u8",value)
if keyPress then
if keyPress == 2 then
Lstart = timer.getMilliSecCounter()
bleStatus = "Left button"end
if keyPress == 1 then
Rstart = timer.getMilliSecCounter()
bleStatus = "Right button"end
if keyPress == 0 then
bleStatus = 'Connected'
if Lstart ~= 0 then Lstop = math.abs((Lstart - timer.getMilliSecCounter())/1000) end
if Rstart ~= 0 then Rstop = math.abs((Rstart - timer.getMilliSecCounter())/1000) endend
if Lstop > 0 then
if Lstop > 1 then
elseon.enterKey()
pause = not pause
end
Lstart = 0
Lstop = 0end
if Rstop > 0 then
if Rstop > 1 thenelseresetall()
on.escapeKey()
end
Rstart = 0
Rstop = 0end
var.store("keypress", keyPress)
end
end
end
--BAROMETER
if characteristic:getUUID() == baData or characteristic:getUUID() == baCalib then
local value = characteristic:getValue()
if value and string.len(tostring(value)) > 4 thenlocal temp, pressure = ble.unpack('u24', value)
baTemp = temp/100
Pa = ble.unpack("u24", pressure)/100
end
end
-- Light Intensity
if characteristic:getUUID() == optData then
local value = characteristic:getValue()
if value then
local exponent, remnant = ble.unpack("u16", value)
local rawLux = ble.unpack('u16', value)
local exponent = bit.brshift(bit.band(rawLux, 0xF000), 12)
local mantissa = bit.band(rawLux, 0x0FFF)
Light = mantissa * math.pow(2, exponent)/100end
end
-- Movement
if characteristic:getUUID() == moveData then
local value = characteristic:getValue()
if value then
local m1, m2, m3, m4, m5, m6, m7, m8, m9 = ble.unpack("s16s16s16s16s16s16s16s16s16", value)
xG = -1*tonumber(m1)* 500 / 65536
yG = -1*tonumber(m2)* 500 / 65536
zG = -1*tonumber(m3)* 500 / 65536
xA = -3.9*tonumber(m4)* 2 / 32768
yA = -3.9*tonumber(m5)* 2 / 32768
zA = -3.9*tonumber(m6)* 2 / 32768
xM = (tonumber(m7)
yM = (tonumber(m8)
zM = (tonumber(m9)end
end
if pause == false then
if Pa then
timeIndex = timeIndex + 1end
table.insert(timeList, timeIndex)
var.store("timeList", timeList)
table.insert(baroList, Pa)
var.store("baroList", baroList)
if Light then
table.insert(lightList, Light)end
var.store("lightList", lightList)
if xA then
table.insert(acc_x, xA)end
var.store("acc_x", acc_x)
if yA then
table.insert(acc_y, yA)end
var.store("acc_y", acc_y)
if zA then
table.insert(acc_z, zA)end
var.store("acc_z", acc_z)
end
screen:invalidate()
end
-- LuaBit v0.3 (Lua bit library) ---------
--[[---------------
LuaBit v0.3
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
Under the MIT license.
copyright(c) 2006 hanzhao (abrash_han@hotmail.com)
--]]---------------do
------------------------
-- bit lib implementionsendlocal function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) thenend
error("trying to use bitwise operation on non-integer!")end
local function to_bits(n)
check_int(n)end
if(n < 0) then
-- negativeend
return to_bits(bit.bnot(math.abs(n)) + 1)
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = n % 2n = (n-last)/2
if(last == 1) then
tbl[cnt] = 1else
tbl[cnt] = 0end
cnt = cnt + 1
return tbl
local function tbl_to_number(tbl)
local n = table.getn(tbl)end
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*powerpower = power*2
end
return rslt
local function expand(tbl_m, tbl_n)
local big = {}end
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_melse
small = tbl_n
big = tbl_nend
small = tbl_m
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0end
local function bit_or(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_and(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_not(n)
local tbl = to_bits(n)end
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_xor(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) thenend
tbl[i] = 1else
tbl[i] = 0end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
local function bit_rshift(n, bits)
check_int(n)end
local high_bit = 0
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
for i=1, bits do
n = n/2end
n = bit_or(math.floor(n), high_bit)
return math.floor(n)
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)end
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
for i=1, bits do
n = n/2end
return math.floor(n)
local function bit_lshift(n, bits)
check_int(n)end
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
for i=1, bits do
n = n*2end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))end
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
--------------------
-- bit lib interfacebit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
brshift = bit_rshift,
blshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}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 nameCheckList = {'Tag 2.0', 'CC2650'}
local nameList = {'TI Sensor Tag 2.0', 'CC2650 SensorTag', 'TI SensorTag 20'}
local sensorList = {'Barometric Pressure', 'Light Intensity', 'Accelerometer', 'Magnetometer', 'Gyroscope'}local timeList = {}
var.store("timeList", timeList)
local timeIndex = 0
local pause = true
local baTemp = nil
local baroList = {}
var.store("baroList", baroList)
local lightList = {}
var.store("lightList", lightList)
local acc_x = {}
var.store("acc_x", acc_x)
local acc_y = {}
var.store("acc_y", acc_y)
local acc_z = {}
var.store("acc_z", acc_z)--TI Sensor Tag Init Variables
--Simple Keys
local keysRead = 'FFE1'
local keyPress = nil
local Lstart = 0
local Lstop = 0
local Rstart = 0
local Rstop = 0-- Barometric Pressure
local baData = 'F000AA41-0451-4000-B000-000000000000'
local baConfig = 'F000AA42-0451-4000-B000-000000000000'
local baCalib = 'F000AA43-0451-4000-B000-000000000000'
local baPeriod = 'F000AA44-0451-4000-B000-000000000000'
local Pa = nil
local baTemp = nil-- Light
local optData = 'F000AA71-0451-4000-B000-000000000000'
local optSwitch = 'F000AA72-0451-4000-B000-000000000000' -- 0: disable, 1: enable
local optPeriod = 'F000AA73-0451-4000-B000-000000000000' -- Period in tens of milliseconds
local Light = nil-- Movement
local moveData = 'F000AA81-0451-4000-B000-000000000000'
local moveConf = 'F000AA82-0451-4000-B000-000000000000'
local movePeriod = 'F000AA83-0451-4000-B000-000000000000'-- 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/16 + 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", "r", fontSize)
local sw1 = gc:getStringWidth(bleState)
gc:drawString(bleState, w/3 - sw1/2, 0.95*h, "middle")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)
local sensorVarList = {Pa, Light, xA, xM, xG}
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')for n = 1, #sensorList do
local sw = gc:getStringWidth(sensorList[n])
if sensorVarList[n] then gc:setColorRGB(color.blue) else gc:setColorRGB(color.gray) end
gc:drawString(sensorList[n], 0.05*w, 0.05*h + h/(2+#sensorList) + (n-1)*h/(2+#sensorList), 'middle')end
if peripheralName ~= '' then
gc:setColorRGB(color.blue)
local str = peripheralName..' Movement Explorer'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.05*h, 'middle')else
gc:setColorRGB(color.gray)
local str = nameList[1]..' Movement Explorer'
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.05*h, 'middle')end
local sw2 = gc:getStringWidth(bleStatus)
gc:drawString(bleStatus, 2*w/3 - sw2/2, 0.95*h, "middle")gc:setFont("sansserif", "b", fontSize)
gc:setColorRGB(color.black)
if Pa then
local msgP1 = string.format("%s %.1f hPa", "Pressure=", Pa)
local sw = gc:getStringWidth(msgP1)
gc:drawString(msgP1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')if baTemp then
local msgP2 = string.format("%s %.1f °C", "Temp=", baTemp)
local sw = gc:getStringWidth(msgP2)
gc:drawString(msgP2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')end
end
if Light then
local msgL1 = string.format("%s %.1f Lux", "Light Intensity=", Light)
local sw = gc:getStringWidth(msgL1)
gc:drawString(msgL1, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')end
if xA and yA and zA then
local msgA1 = string.format("%s %.1f hPa", "xA=", xA)
local sw = gc:getStringWidth(msgA1)
gc:drawString(msgA1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')
local msgA2 = string.format("%s %.1f hPa", "yA=", yA)
local sw = gc:getStringWidth(msgA2)
gc:drawString(msgA2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')
local msgA3 = string.format("%s %.1f hPa", "zA=", zA)
local sw = gc:getStringWidth(msgA3)
gc:drawString(msgA3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 2*h/(2+#sensorList), 'middle')end
if xM and yM and zM then
local msgM1 = string.format("%s %.1f hPa", "xM=", xM)
local sw = gc:getStringWidth(msgM1)
gc:drawString(msgM1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')
local msgM2 = string.format("%s %.1f hPa", "yM=", yM)
local sw = gc:getStringWidth(msgM2)
gc:drawString(msgM2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')
local msgM3 = string.format("%s %.1f hPa", "zM=", zM)
local sw = gc:getStringWidth(msgM3)
gc:drawString(msgM3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 3*h/(2+#sensorList), 'middle')end
if xG and yG and zG then
local msgG1 = string.format("%s %.1f hPa", "xG=", xG)
local sw = gc:getStringWidth(msgG1)
gc:drawString(msgG1, 0.4*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')
local msgG2 = string.format("%s %.1f hPa", "yG=", yG)
local sw = gc:getStringWidth(msgG2)
gc:drawString(msgG2, 0.65*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')
local msgG3 = string.format("%s %.1f hPa", "zG=", zG)
local sw = gc:getStringWidth(msgG3)
gc:drawString(msgG3, 0.9*w - sw/2, 0.05*h + h/(2+#sensorList) + 4*h/(2+#sensorList), 'middle')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()
timeIndex = 0
timeList = {}
pause = true-- Barometric Pressure
Pa = nil
baTemp = nil-- Acceleration
xA = nil
yA = nil
zA = nil-- Magnetometer
xM = nil
yM = nil
zM = nil-- Gyroscope
xG = nil
yG = nil
zG = nilLight = nil
lightList = {}
baroList = {}
acc_x = {}
acc_y = {}
acc_z = {}
var.store("acc_x", acc_x)
var.store("acc_y", acc_y)
var.store("acc_z", acc_z)
var.store("baroList", baroList)
var.store("timeList", timeList)
var.store("lightList", lightList)screen:invalidate()
end
function resetall()
on.escapeKey()
peripheralOff()
on.resize()
screen:invalidate()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 resetall() 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() or 'Unknown Device'
for n =1, #nameCheckList doif peripheralName and peripheralName:find(nameCheckList[n]) then
peripheral:connect(callbackConnect)
elseif not peripheralName:find('unsupported') then
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 = 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
-- Simple Keys
if characteristic:getUUID() == keysRead thenendcharacteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
-- Barometric Pressure
if characteristic:getUUID() == baData then
readBa = characteristic
readBa:setValueUpdateListener(callbackCharacteristic)
readBa:setNotify(true)
characteristicsFound = characteristicsFound + 1end
if characteristic:getUUID() == baCalib then
calibBa = characteristic
calibBa:setValueUpdateListener(callbackCharacteristic)
calibBa:read()end
if characteristic:getUUID() == baConfig then
writeBa = characteristic
local msg = string.char(1)
writeBa:setWriteCompleteListener(callbackCharacteristic)
writeBa:write(msg, true)
local msg = string.char(2)
writeBa:setWriteCompleteListener(callbackCharacteristic)
writeBa:write(msg, true)end
-- Light
if characteristic:getUUID() == optData then --01 M
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
if characteristic:getUUID() == optSwitch then -- 02
local msg = 1 characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(msg), true)
end
--Movement SENSOR
if characteristic:getUUID() == moveData then
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)end
if characteristic:getUUID() == moveConf then
local msg = ble.pack('u16', 0x007F)
characteristic:setWriteCompleteListener(callbackCharacteristic)
characteristic:write(msg, true)end
if characteristic:getUUID() == movePeriod then
characteristic:setWriteCompleteListener(callbackCharacteristic)
characteristic:write(string.char(1), true)end
end
end
function callbackCharacteristic(characteristic)
-- Simple Keys
if characteristic:getUUID() == keysRead then
local value = characteristic:getValue()
if value and string.len(tostring(value)) == 1 thenkeyPress = ble.unpack("u8",value)
if keyPress then
if keyPress == 2 then
Lstart = timer.getMilliSecCounter()
bleStatus = "Left button"end
if keyPress == 1 then
Rstart = timer.getMilliSecCounter()
bleStatus = "Right button"end
if keyPress == 0 then
bleStatus = 'Connected'
if Lstart ~= 0 then Lstop = math.abs((Lstart - timer.getMilliSecCounter())/1000) end
if Rstart ~= 0 then Rstop = math.abs((Rstart - timer.getMilliSecCounter())/1000) endend
if Lstop > 0 then
if Lstop > 1 then
elseon.enterKey()
pause = not pause
end
Lstart = 0
Lstop = 0end
if Rstop > 0 then
if Rstop > 1 thenelseresetall()
on.escapeKey()
end
Rstart = 0
Rstop = 0end
var.store("keypress", keyPress)
end
end
end
--BAROMETER
if characteristic:getUUID() == baData or characteristic:getUUID() == baCalib then
local value = characteristic:getValue()
if value and string.len(tostring(value)) > 4 thenlocal temp, pressure = ble.unpack('u24', value)
baTemp = temp/100
Pa = ble.unpack("u24", pressure)/100
end
end
-- Light Intensity
if characteristic:getUUID() == optData then
local value = characteristic:getValue()
if value thenlocal exponent, remnant = ble.unpack("u16", value)
local rawLux = ble.unpack('u16', value)
local exponent = bit.brshift(bit.band(rawLux, 0xF000), 12)
local mantissa = bit.band(rawLux, 0x0FFF)
Light = mantissa * math.pow(2, exponent)/100end
end
-- Movement
if characteristic:getUUID() == moveData then
local value = characteristic:getValue()
if value then
local m1, m2, m3, m4, m5, m6, m7, m8, m9 = ble.unpack("s16s16s16s16s16s16s16s16s16", value)
xG = -1*tonumber(m1)* 500 / 65536
yG = -1*tonumber(m2)* 500 / 65536
zG = -1*tonumber(m3)* 500 / 65536
xA = -3.9*tonumber(m4)* 2 / 32768
yA = -3.9*tonumber(m5)* 2 / 32768
zA = -3.9*tonumber(m6)* 2 / 32768
xM = (tonumber(m7)
yM = (tonumber(m8)
zM = (tonumber(m9)end
end
if pause == false then
if Pa then
timeIndex = timeIndex + 1end
table.insert(timeList, timeIndex)
var.store("timeList", timeList)
table.insert(baroList, Pa)
var.store("baroList", baroList)
if Light then
table.insert(lightList, Light)end
var.store("lightList", lightList)
if xA then
table.insert(acc_x, xA)end
var.store("acc_x", acc_x)
if yA then
table.insert(acc_y, yA)end
var.store("acc_y", acc_y)
if zA then
table.insert(acc_z, zA)end
var.store("acc_z", acc_z)
end
screen:invalidate()
end
-- LuaBit v0.3 (Lua bit library) ---------
--[[---------------
LuaBit v0.3
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
Under the MIT license.
copyright(c) 2006 hanzhao (abrash_han@hotmail.com)
--]]---------------do
------------------------
-- bit lib implementionsendlocal function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) thenend
error("trying to use bitwise operation on non-integer!")end
local function to_bits(n)
check_int(n)end
if(n < 0) then
-- negativeend
return to_bits(bit.bnot(math.abs(n)) + 1)
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = n % 2n = (n-last)/2
if(last == 1) then
tbl[cnt] = 1else
tbl[cnt] = 0end
cnt = cnt + 1
return tbl
local function tbl_to_number(tbl)
local n = table.getn(tbl)end
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*powerpower = power*2
end
return rslt
local function expand(tbl_m, tbl_n)
local big = {}end
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_melse
small = tbl_n
big = tbl_nend
small = tbl_m
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0end
local function bit_or(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_and(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_not(n)
local tbl = to_bits(n)end
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) thenend
tbl[i] = 0else
tbl[i] = 1end
return tbl_to_number(tbl)
local function bit_xor(m, n)
local tbl_m = to_bits(m)end
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) thenend
tbl[i] = 1else
tbl[i] = 0end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
local function bit_rshift(n, bits)
check_int(n)end
local high_bit = 0
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
for i=1, bits do
n = n/2end
n = bit_or(math.floor(n), high_bit)
return math.floor(n)
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)end
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
for i=1, bits do
n = n/2end
return math.floor(n)
local function bit_lshift(n, bits)
check_int(n)end
if(n < 0) then
-- negativeend
n = bit_not(math.abs(n)) + 1
for i=1, bits do
n = n*2end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))end
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
--------------------
-- bit lib interfacebit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
brshift = bit_rshift,
blshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}end
Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 34