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
-- Layout Functions (resize and paint)--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'
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/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) 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 sensorVarList = {Pa, Light, xA, xM, xG}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 = nil
Light = 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 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 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 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)
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) end
end
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 then
endcharacteristic: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 then
keyPress = 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 thenelseon.enterKey()
pause = not pause
end Lstart = 0 Lstop = 0
end
if Rstop > 0 then
if Rstop > 1 thenelseresetall()
on.escapeKey()
end Rstart = 0 Rstop = 0
end
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 then
local temp, pressure = ble.unpack('u24', value) baTemp = temp/100 Pa = ble.unpack("u24", pressure)/100end
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)/100
end
end
-- Movement
if characteristic:getUUID() == moveData then
local value = characteristic:getValue() if value thenlocal 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 + 1 table.insert(timeList, timeIndex) var.store("timeList", timeList) table.insert(baroList, Pa) var.store("baroList", baroList)endif Light then
table.insert(lightList, Light) var.store("lightList", lightList)endif xA then
table.insert(acc_x, xA) var.store("acc_x", acc_x)endif yA then
table.insert(acc_y, yA) var.store("acc_y", acc_y)endif zA then
table.insert(acc_z, zA) var.store("acc_z", acc_z)endend
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 implementions
endlocal function check_int(n) -- checking not float
if(n - math.floor(n) > 0) thenenderror("trying to use bitwise operation on non-integer!")endlocal function to_bits(n)
check_int(n) if(n < 0) thenend return tbl-- negative return to_bits(bit.bnot(math.abs(n)) + 1)end -- to bits table local tbl = {} local cnt = 1 while (n > 0) dolocal last = n % 2 if(last == 1) thenn = (n-last)/2 cnt = cnt + 1tbl[cnt] = 1elsetbl[cnt] = 0endlocal function tbl_to_number(tbl)
local n = table.getn(tbl) local rslt = 0 local power = 1 for i = 1, n doendrslt = rslt + tbl[i]*powerpower = power*2 end return rsltlocal function expand(tbl_m, tbl_n)
local big = {} local small = {} if(table.getn(tbl_m) > table.getn(tbl_n)) thenendbig = tbl_m small = tbl_nelsebig = tbl_n small = tbl_mend -- expand small for i = table.getn(small) + 1, table.getn(big) dosmall[i] = 0endlocal function bit_or(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i]== 0 and tbl_n[i] == 0) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_and(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i]== 0 or tbl_n[i] == 0) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_not(n)
local tbl = to_bits(n) local size = math.max(table.getn(tbl), 32) for i = 1, size doendif(tbl[i] == 1) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_xor(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i] ~= tbl_n[i]) thenend --table.foreach(tbl, print) return tbl_to_number(tbl)tbl[i] = 1elsetbl[i] = 0endlocal function bit_rshift(n, bits)
check_int(n) local high_bit = 0 if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1 high_bit = 2147483648 -- 0x80000000end for i=1, bits don = n/2 n = bit_or(math.floor(n), high_bit)end return math.floor(n)-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n) if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1end for i=1, bits don = n/2end return math.floor(n)local function bit_lshift(n, bits)
check_int(n) if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1end for i=1, bits don = n*2end return bit_and(n, 4294967295) -- 0xFFFFFFFFlocal function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n)) local lhs = bit_or(m, n) local rslt = bit_and(lhs, rhs) return rsltend-------------------- -- bit lib interface
bit = {
-- 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)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/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) 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 sensorVarList = {Pa, Light, xA, xM, xG}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 = nil
Light = 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 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 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 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)
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) end
end
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 then
endcharacteristic: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 then
keyPress = 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 thenelseon.enterKey()
pause = not pause
end Lstart = 0 Lstop = 0
end
if Rstop > 0 then
if Rstop > 1 thenelseresetall()
on.escapeKey()
end Rstart = 0 Rstop = 0
end
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 then
local temp, pressure = ble.unpack('u24', value) baTemp = temp/100 Pa = ble.unpack("u24", pressure)/100end
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)/100
end
end
-- Movement
if characteristic:getUUID() == moveData then
local value = characteristic:getValue() if value thenlocal 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 + 1 table.insert(timeList, timeIndex) var.store("timeList", timeList) table.insert(baroList, Pa) var.store("baroList", baroList)endif Light then
table.insert(lightList, Light) var.store("lightList", lightList)endif xA then
table.insert(acc_x, xA) var.store("acc_x", acc_x)endif yA then
table.insert(acc_y, yA) var.store("acc_y", acc_y)endif zA then
table.insert(acc_z, zA) var.store("acc_z", acc_z)endend
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 implementions
endlocal function check_int(n) -- checking not float
if(n - math.floor(n) > 0) thenenderror("trying to use bitwise operation on non-integer!")endlocal function to_bits(n)
check_int(n) if(n < 0) thenend return tbl-- negative return to_bits(bit.bnot(math.abs(n)) + 1)end -- to bits table local tbl = {} local cnt = 1 while (n > 0) dolocal last = n % 2 if(last == 1) thenn = (n-last)/2 cnt = cnt + 1tbl[cnt] = 1elsetbl[cnt] = 0endlocal function tbl_to_number(tbl)
local n = table.getn(tbl) local rslt = 0 local power = 1 for i = 1, n doendrslt = rslt + tbl[i]*powerpower = power*2 end return rsltlocal function expand(tbl_m, tbl_n)
local big = {} local small = {} if(table.getn(tbl_m) > table.getn(tbl_n)) thenendbig = tbl_m small = tbl_nelsebig = tbl_n small = tbl_mend -- expand small for i = table.getn(small) + 1, table.getn(big) dosmall[i] = 0endlocal function bit_or(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i]== 0 and tbl_n[i] == 0) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_and(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i]== 0 or tbl_n[i] == 0) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_not(n)
local tbl = to_bits(n) local size = math.max(table.getn(tbl), 32) for i = 1, size doendif(tbl[i] == 1) thenend return tbl_to_number(tbl)tbl[i] = 0elsetbl[i] = 1endlocal function bit_xor(m, n)
local tbl_m = to_bits(m) 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 doendif(tbl_m[i] ~= tbl_n[i]) thenend --table.foreach(tbl, print) return tbl_to_number(tbl)tbl[i] = 1elsetbl[i] = 0endlocal function bit_rshift(n, bits)
check_int(n) local high_bit = 0 if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1 high_bit = 2147483648 -- 0x80000000end for i=1, bits don = n/2 n = bit_or(math.floor(n), high_bit)end return math.floor(n)-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n) if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1end for i=1, bits don = n/2end return math.floor(n)local function bit_lshift(n, bits)
check_int(n) if(n < 0) thenend-- negative n = bit_not(math.abs(n)) + 1end for i=1, bits don = n*2end return bit_and(n, 4294967295) -- 0xFFFFFFFFlocal function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n)) local lhs = bit_or(m, n) local rslt = bit_and(lhs, rhs) return rsltend-------------------- -- bit lib interface
bit = {
-- 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