Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 35
Scripting Tutorial - Lesson 35: BLE - Build Your Own Weather Station 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 a previous lesson of this sequence, we developed a working script to connect to and read temperature data from the TI SensorTag 2.0.
In this lesson, we extend this to more complete capabilities of the Sensor Tag, and collect data from several sensors at the same time! In the process, we will use our SensorTag to build a working weather station!
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.
Communicating with the TI SensorTag
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 (in this case, light intensity) some bit manipulation of the data is required. For this we use an open source library.
Not surprisingly, these changes do make the script longer than previously, but the essential structure and process remain the same. Study the changes carefully.
Note particularly the way in which the BLE specific function
callbackCharacteristics(service) handles the various sensors: reading them remains very much the standard form, but writing to them can vary. For example, while most use string.char(1) as the required signal to trigger the action, and the Barometer first triggers using string.char(1) and then requires string.char(2) to continue.Interesting, too, to observe that the sensor tag actually delivers FOUR different temperature readings - the ambient and target temperatures that we have discussed previously, but both humidity and barometric pressure data also carry temperature calculations - and rarely do any of these actually agree! Something to ponder on the accuracy of BLE sensor readings in general, perhaps?
The script developed here is, intentionally, simple in design and execution. Many variations might be made to the layout and design of the page, and it is assumed that a mechanism for dropping these results out as stored variables would be very desirable for classroom use. 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.
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 into lists to this script... or think of ways to take advantage of those neat simple key buttons?
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 = {'Temperature', 'Humidity', 'Baro Pressure', 'Light Intensity'}
-- 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
-- Temperature local tempRead = 'F000AA01-0451-4000-B000-000000000000' local tempStart = 'F000AA02-0451-4000-B000-000000000000' local dieTemp = nil local irTemp = nil
-- Humidity local humidData = 'F000AA21-0451-4000-B000-000000000000' local humidConf = 'F000AA22-0451-4000-B000-000000000000' local Humidity = nil local theHumidityTemp = nil
-- 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
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/30 + 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) 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 = {dieTemp, Humidity, Pa, Light}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..' Weather Station' 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]..' Weather Station' 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")
local fontSize = math.floor(h/36 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 gc:setColorRGB(color.black) gc:setFont("sansserif", "b", fontSize)
if dieTemp then
local msgT1 = string.format("%s %.1f °C", "Ambient=", dieTemp ) local sw = gc:getStringWidth(msgT1) gc:drawString(msgT1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')
if irTemp then
local msgT2 = string.format("%s %.1f °C", "Target=", irTemp) local sw = gc:getStringWidth(msgT2) gc:drawString(msgT2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')
end
end
if Humidity then
local msgH1 = string.format("%s %.1f %%", "Humidity=", Humidity ) local sw = gc:getStringWidth(msgH1) gc:drawString(msgH1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')
if theHumidityTemp then
local msgH2 = string.format("%s %.1f °C", "Temp=", theHumidityTemp ) local sw = gc:getStringWidth(msgH2) gc:drawString(msgH2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')
end
end
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) + 2*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) + 2*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) + 3*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()
resetall()
end
function resetall()
bleState = ''
--Simple Keys keyPress = nil Lstart = 0 Lstop = 0 Rstart = 0 Rstop = 0
-- Temperature dieTemp = nil irTemp = nil
-- Humidity Humidity = nil theHumidityTemp = nil
-- 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 Light = 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)
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 = 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
-- Simple Keys if characteristic:getUUID() == keysRead then
endcharacteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
-- Temperature if characteristic:getUUID() == tempRead then
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true)
end
if characteristic:getUUID() == tempStart then
characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(1), true)
end
-- Humidity if characteristic:getUUID() == humidData then
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true)
end
if characteristic:getUUID() == humidConf then
characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(1), true)
end
-- 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
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 thenon.escapeKey()
end Lstart = 0 Lstop = 0
end
if Rstop > 0 then
if Rstop > 1 thenon.enterKey()
end Rstart = 0 Rstop = 0
end
var.store("keypress", keyPress)
end
end
end
--Temperature
if characteristic:getUUID() == tempRead then
local value = characteristic:getValue() if value and string.len(tostring(value)) == 4 then
local loWord, hiWord = ble.unpack("s16u16",value) dieTemp = tonumber(hiWord) / 128 irTemp = tonumber(loWord) / 128end
end
-- Humidity if characteristic:getUUID() == humidData then
local value = characteristic:getValue() if value and string.len(tostring(value)) == 4 then
local loWord, hiWord = ble.unpack("s16u16",value) Humidity = -6 + 125*tonumber(hiWord)/65536 theHumidityTemp = -40 + 165*tonumber(loWord)/65536
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
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 = {'Temperature', 'Humidity', 'Baro Pressure', 'Light'}
--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
-- Temperature local tempRead = 'F000AA01-0451-4000-B000-000000000000' local tempStart = 'F000AA02-0451-4000-B000-000000000000' local dieTemp = nil local irTemp = nil
-- Humidity local humidData = 'F000AA21-0451-4000-B000-000000000000' local humidConf = 'F000AA22-0451-4000-B000-000000000000' local Humidity = nil local theHumidityTemp = nil
-- 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
-- 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/30 + 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) 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 = {dieTemp, Humidity, Pa, Light}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..' Weather Station' 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]..' Weather Station' 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")
local fontSize = math.floor(h/36 + 0.5) fontSize = fontSize < 25 and fontSize or 24 fontSize = fontSize > 6 and fontSize or 7 gc:setColorRGB(color.black) gc:setFont("sansserif", "b", fontSize)
if dieTemp then
local msgT1 = string.format("%s %.1f °C", "Ambient=", dieTemp ) local sw = gc:getStringWidth(msgT1) gc:drawString(msgT1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')
if irTemp then
local msgT2 = string.format("%s %.1f °C", "Target=", irTemp) local sw = gc:getStringWidth(msgT2) gc:drawString(msgT2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList), 'middle')
end
end
if Humidity then
local msgH1 = string.format("%s %.1f %%", "Humidity=", Humidity ) local sw = gc:getStringWidth(msgH1) gc:drawString(msgH1, 0.5*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')
if theHumidityTemp then
local msgH2 = string.format("%s %.1f °C", "Temp=", theHumidityTemp ) local sw = gc:getStringWidth(msgH2) gc:drawString(msgH2, 0.8*w - sw/2, 0.05*h + h/(2+#sensorList) + h/(2+#sensorList), 'middle')
end
end
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) + 2*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) + 2*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) + 3*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()
resetall()
end
function resetall()
bleState = ''
--Simple Keys keyPress = nil Lstart = 0 Lstop = 0 Rstart = 0 Rstop = 0
-- Temperature dieTemp = nil irTemp = nil
-- Humidity Humidity = nil theHumidityTemp = nil
-- 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 Light = 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)
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 = 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)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)
-- Temperature if characteristic:getUUID() == tempRead then
end
if characteristic:getUUID() == tempStart then
characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(1), true)
end
-- Humidity if characteristic:getUUID() == humidData then
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true)
end
if characteristic:getUUID() == humidConf then
characteristic:setWriteCompleteListener(callbackCharacteristic) characteristic:write(string.char(1), true)
end
-- 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
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 thenon.escapeKey()
end Lstart = 0 Lstop = 0
end
if Rstop > 0 then
if Rstop > 1 thenon.enterKey()
end Rstart = 0 Rstop = 0
end
var.store("keypress", keyPress)
end
end
end
--Temperature
if characteristic:getUUID() == tempRead then
local value = characteristic:getValue() if value and string.len(tostring(value)) == 4 then
local loWord, hiWord = ble.unpack("s16u16",value) dieTemp = tonumber(hiWord) / 128 -- Conversion to IR Object temperature irTemp = tonumber(loWord) / 128end
end
-- Humidity if characteristic:getUUID() == humidData then
local value = characteristic:getValue() if value and string.len(tostring(value)) == 4 then
local loWord, hiWord = ble.unpack("s16u16",value) Humidity = -6 + 125*tonumber(hiWord)/65536 theHumidityTemp = -40 + 165*tonumber(loWord)/65536
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
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
The next script in our BLE series offers a simpler change of pace - see how easy it is to collect data from a heart monitor!
Home ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 35