TI-Nspire & SensorTag / 気象関連のデータをリアルタイムにグラフ化する



The following Lua script will obtain weather data from TI SensorTag.
Those data will be stored into the native applications of TI-Nspire by the var.storeAt() function.
The Graphs application can plot them as scatter plots in real-time.

.lua

require "bleCentral"

local timeUnit

local bleState        = ""
local peripheralState = ""
local myPeripheral    = nil
local peripheralName  = ""

-- Temperature
TMP007 = {
   name       = "TMP007" ,
   dataUUID   = "F000AA01-0451-4000-B000-000000000000" ,
   confUUID   = "F000AA02-0451-4000-B000-000000000000" ,
   loData     = nil ,
   loDataUnit = "°C (ir)" ,
   hiData     = nil ,
   hiDataUnit = "°C (die)" ,
   convert    = function(value)
                   local loRaw, hiRaw = string.unpack("s16s16", value) -- IR[0:7], IR[8:15], DIE[0:7], DIE[8:15]
                   local irTemp  = tonumber(loRaw)/128
                   local dieTemp = tonumber(hiRaw)/128
                   return irTemp, dieTemp
                end ,
}

-- Humidity
HDC1000 = {
   name       = "HDC1000" ,
   dataUUID   = "F000AA21-0451-4000-B000-000000000000" ,
   confUUID   = "F000AA22-0451-4000-B000-000000000000" ,
   loData     = nil ,
   loDataUnit = "°C" ,
   hiData     = nil ,
   hiDataUnit = "%RH" ,
   convert    = function(value)
                   local loRaw, hiRaw = string.unpack("u16u16", value) -- Temp[0:7], Temp[8:15], Hum[0:7], Hum[8:15]
                   local temp     = (tonumber(loRaw) / 65536) * 165 - 40
                   local humidity = (tonumber(hiRaw) / 65536) * 100
                   return temp, humidity 
                end ,
}

-- Pressure
BMP280 = {
   name       = "BMP280" ,
   dataUUID   = "F000AA41-0451-4000-B000-000000000000" ,
   confUUID   = "F000AA42-0451-4000-B000-000000000000" ,
   loData     = nil ,
   loDataUnit = "°C" ,
   hiData     = nil ,
   hiDataUnit = "hPa" ,
   convert    = function(value)
                   local loRaw, hiRaw = string.unpack("s24u24", value) -- Temp[0:7], Temp[8:15], Temp[16:23], Press[0:7], Press[8:15], Press[16:23]
                   local temp
                   if loRaw > 0 then
                      temp = tonumber(loRaw) / 100
                   else
                      temp = -(tonumber(loRaw) + 16777216) / 100 -- 16777216 = 2^24
                   end
                   local pressure = tonumber(hiRaw) / 100
                   return temp, pressure
                end ,
}

-- Illumination
OPT3001 = {
   name       = "OPT3001" ,
   dataUUID   = "F000AA71-0451-4000-B000-000000000000" ,
   confUUID   = "F000AA72-0451-4000-B000-000000000000" ,
   loData     = nil ,
   loDataUnit = "none" ,
   hiData     = nil ,
   hiDataUnit = "lux" ,
   convert    = function(value)
                   local raw = string.unpack("u16", value) -- LightLSB:LightMSB
                   local exponent = math.evalStr("shift(("..raw.." and 0hF000), −12)")
                   local mantissa = math.evalStr("("..raw.." and 0h0FFF)")
                   return 0, mantissa * (0.01 * 2^exponent)
                end ,
}
sensorsList = {TMP007, HDC1000, BMP280, OPT3001}

function on.resize()
   W, H = platform.window:width(), platform.window:height()
   leftMargin = W/100
   fontSize   = W/23
   lineSpace  = H/8
   platform.window:invalidate()
end
function on.construction()
   refreshMenu()
   ble.addStateListener(stateListener)
end
function on.paint(gc)
   gc:setFont("sansserif", "r", fontSize)
   gc:drawString(bleState        , leftMargin, lineSpace * 0)
   gc:drawString(peripheralName  , leftMargin, lineSpace * 1)
   gc:drawString(peripheralState , leftMargin, lineSpace * 2)

   for i, sensor in ipairs(sensorsList) do
      if sensor.loData or sensor.hiData then
         gc:drawString(
            string.format("%s :  %.1f %s ,  %.1f %s", 
               sensor.name ,
               sensor.hiData ,
               sensor.hiDataUnit ,
               sensor.loData ,
               sensor.loDataUnit
            ),
            leftMargin, lineSpace * (i + 2)
         )
      end
   end

end
function on.timer()
   dataStore()
end

------------------
-- Elapsed Time --
------------------
elapsedTime_co = coroutine.wrap(
   function(timeUnit)
      local initTime = timer.getMilliSecCounter()
      for i = 1, math.huge do
         coroutine.yield((timer.getMilliSecCounter() - initTime) / timeUnit, i)
      end
   end
)

--------------------------
-- Store or Delete data --
--------------------------
function dataStore()
   local elapsedTime, At = elapsedTime_co(timeUnit)
   var.storeAt("time"  , round(elapsedTime    , 1) , At)
   var.storeAt("temp"  , round(TMP007.hiData  , 1) , At)
   var.storeAt("humid" , round(HDC1000.hiData , 1) , At)
   var.storeAt("press" , round(BMP280.hiData  , 1) , At)
   var.storeAt("illumi", round(OPT3001.hiData , 1) , At)
end
function dataDelete()
   var.store("time"  , {})
   var.store("temp"  , {})
   var.store("humid" , {})
   var.store("press" , {})
   var.store("illumi", {})
end

------------------------------
-- Menu, Keyboard and Mouse --
------------------------------
function refreshMenu()
   Menu = {
      {"Controls" ,
         {"Scan and Connect", function() peripheralOn()  end} ,
         {"Disconnect"      , function() peripheralOff() end} ,
      } ,
      {"Start plotting",
         {"every 2 sec"  , function() timeUnit = 1000    ; dataStore() ; timer.start(2)    ; end} ,
         {"every 0.5 min", function() timeUnit = 60000   ; dataStore() ; timer.start(30)   ; end} ,
         {"every 1 min"  , function() timeUnit = 60000   ; dataStore() ; timer.start(60)   ; end} ,
         {"every 10 min" , function() timeUnit = 60000   ; dataStore() ; timer.start(600)  ; end} ,
         {"every 0.5 hr" , function() timeUnit = 3600000 ; dataStore() ; timer.start(1800) ; end} ,
         {"every 1 hr"   , function() timeUnit = 3600000 ; dataStore() ; timer.start(3600) ; end} ,
      } ,
      {"Delete old data" ,
         {"Delete old data", function() dataDelete() end} ,
      } ,
   }
   toolpalette.register(Menu)
end

--------------------------
-- Define state listner --
--------------------------
function stateListener(state)
   if state == "on" then
      peripheralOn()
   end 
   bleState = "BLE: "..state
   platform.window:invalidate()
end

----------------------------
-- Start or stop scanning --
----------------------------
function peripheralOn()
   if peripheralState ~= "connected" then
      bleCentral.startScanning(scanner)
   end 
   platform.window:invalidate()
end
function peripheralOff()
   bleCentral.stopScanning()
   if myPeripheral then
      myPeripheral:disconnect()
   end
   peripheralName = "" 
   platform.window:invalidate()
end

--------------------
-- Define scanner --
--------------------
function scanner(peripheral)
   local name = peripheral:getName() or ""
   if peripheral and name:find("SensorTag") then
      myPeripheral   = peripheral
      peripheralName = name
      peripheral:connect(connector) 
   end
   platform.window:invalidate()
end

----------------------
-- Define connector --
----------------------
function connector(peripheral, event)
   local name = peripheral:getName() or ""
   if event == bleCentral.CONNECTED and name:find("SensorTag") then
      bleCentral.stopScanning()
      myPeripheral    = peripheral
      peripheralName  = name
      peripheral:discoverServices(servicesDiscoverer)
   elseif event == bleCentral.DISCONNECTED then 
      myPeripheral    = nil
      peripheralName  = ""
   end
   peripheralState = peripheral:getState()
   platform.window:invalidate()
end

--------------------------------
-- Define services discoverer --
--------------------------------
function servicesDiscoverer(peripheral)
   if peripheral:getState() == bleCentral.CONNECTED then
      local servicesList = peripheral:getServices()
      for _, v in ipairs(servicesList) do
         v:discoverCharacteristics(characteristicsDiscoverer)
      end
   end
   platform.window:invalidate()
end

---------------------------------------
-- Define characteristics discoverer --
---------------------------------------
function characteristicsDiscoverer(service)
   local characteristicsList = service:getCharacteristics()
   for _, characteristic in ipairs(characteristicsList) do
      local UUID = characteristic:getUUID() 


      for _, sensor in ipairs(sensorsList) do
         if UUID == sensor.dataUUID then
            characteristic:setValueUpdateListener(valueUpdateListener)
            characteristic:setNotify(true)
         elseif UUID == sensor.confUUID then
            -- characteristic:setWriteCompleteListener(valueUpdateListener)
            characteristic:write(string.uchar(0x01), true)
         end
      end


   end
end

----------------------------------
-- Define value-update listener --
----------------------------------
function valueUpdateListener(characteristic)
   local UUID = characteristic:getUUID()


   for _, sensor in ipairs(sensorsList) do
      if UUID == sensor.dataUUID then
         local value = characteristic:getValue()
         if value then
            sensor.loData, sensor.hiData = sensor.convert(value)
         end
      end
   end


   platform.window:invalidate()
end

-----------------------
-- General functions --
-----------------------
function round(number, digits) return math.eval("round("..(number or 0)..", "..digits..")") end


.tns
PlotWeatherData_5.tns - Google ドライブ