Home ← STEM HQ ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 43
Scripting Tutorial - Lesson 43: Lua Scripting and the TI Innovator™ Hub:
3. Exploring the OneShotTimer: Learning to Wait!
TI Innovator™ Hub Lesson 0: Using Lua to store and edit TI-BASIC programs for the Innovator™ Hub (Lua Scripting Lesson 40)
TI Innovator™ Hub Lesson 1: SENDING to the Hub: Controlling the Action (Lua Scripting Lesson 41)
TI Innovator™ Hub Lesson 2: READING from the Hub: Real world interaction and data (Lua Scripting Lesson 42)
TI Innovator™ Hub Lesson 3: Exploring the OneShotTimer: Learning to Wait! (Lua Scripting Lesson 43)
TI Innovator™ Hub Lesson 4: Making Music with the Innovator™ Hub (Lua Scripting Lesson 44)
TI Innovator™ Hub Lesson 5: Lua Script for the Norland Research/TI Innovator™ Hub Robot (Lua Scripting Lesson 45)
TI Innovator™ Hub Lesson 6: Build your own Innovator™ Hub Robot (Lua Scripting Lesson 46)
TI Innovator™ Hub Lesson 7: Create a general Innovator™ Hub Document (Lua Scripting Lesson 47)
TI Innovator™ Hub Lesson 8: Create a general Innovator™ Hub and BLE Document (Lua Scripting Lesson 48)
Download supporting file for this tutorial
Texas Instruments TI-Nspire Scripting Support Page
Scripting for the Innovator Hub: Lesson 3: Exploring the OneShotTimer: Learning to Wait!
One of the many useful TI-BASIC commands that you discover very quickly is the simple wait. We take it for granted without often appreciating its value - enter a command, then wait(2) and there is a satisfying 2 second pause before the next command is carried out.
Lua has no such in-built command, although one is readily created in a variety of ways. However, as I quickly discovered, a key difference between TI-BASIC programming and Lua scripting is the element of multi-tasking. This is not supported on the handheld, and hence in TI-BASIC, which means that the simple wait command can work just as you would expect it to: command is issued, wait for a given time, and then proceed to the next.
In Lua however, capable of multi-tasking, if you try this process you quickly discover that the first command is issued, then your wait command is issued - but while this is being carried out, the script happily continues to fulfil subsequent commands. These things can happen simultaneously.
Now overall, of course, such multi-tasking is a great advantage. Suppose you are using a TI-BASIC program to collect some data from a sensor attached to your Innovator Hub. Obviously, it would be nice to see that data as it is being collected - perhaps even see the graph unfold?
Unfortunately, this is not possible. Without multi-tasking capabilities, the handheld (or computer or iPad) running TI-BASIC will not show you any such changes until it finishes what it was working on! So the data will be happily captured and stored, as requested, but you will not see this on Calculator, or spreadsheet or graph screen until the collection process is complete. Frustrating.
This points to a key advantage of controlling your Innovator Hub using Lua - such processes are easily achieved. Properly scripted, your data can be collected and displayed at the same time. Likewise, if you are displaying changes on screen from the effects of some Innovator Hub activity, these can be shown live.
What is not so simple within Lua is to go backwards and recreate that simple sequencing that TI-BASIC delivers freely - and for that, we use a special tool called a oneShotTimer.
The oneShotTimer effectively hijacks the Lua script timer for its own purposes: the timer is linked to a period, and a function. The period will be the length of time that you wish the related function to run for. The function will be specific to the task that you wish to achieve while waiting.
Before we examine these elements in detail, we should first check on those changes to the script which are required here. Once again, the script is essentially the same as those we have studied previously, with just a few additions for the purposes of learning more about this oneShotTimer. These are highlighted in red in the script snippets on the right, and below.
We add a new variable to those previously used (timeStr - to display information from the timer).
Two lists (index and dataList) are created to store accumulated values of numeric results - this is now possible with a timer function, and offers a powerful advantage of using Lua over TI-BASIC. The lists are stored as TI-Nspire variables and so may be viewed (live) in graph or spreadsheet.
You will notice in the menu that we have amalgamated the two sample menus from the previous lessons (SET Samples and READ Samples) and added a third, specific to this lesson - TIMER Samples.
The three functions in the TIMER Samples menu will form the basis for this tutorial.
- In addition to displaying both rxValue and message lines, we have added a timerStr line to display information about the timer as it does its work. We also define initial values for the variables in on.resize.
Here is where the action happens! Three sample functions are provided for you to study.
wait(input)
This is a fairly generic oneShotTimer() function, deliberately styled after the TI-BASIC form - so we call, say, wait(5) and would like the process to pause for 5 seconds and then continue.
Looking ahead into the function, find where the oneShotTimer is called, and look at its arguments: oneShotTimer(1000*time_step,wait) . The first calls a 1 second delay (or whatever your time_step is set to), and then the wait function is called again - but this time WITHOUT any input! This is important - it accepts a delay period only the first time it is called.
This explains the first line of the function: we have set up a delay variable already, and the first time the wait function is called, the input is stored as delay, from which it may be accessed later. So for wait(5), on the first run, delay is set to the value 5. A time_step value is set - use 1 for 1 second intervals, but be careful when using smaller intervals as the read function needs time to gather and send data. Hence you will see an adjustment which forces a read only at 1 second intervals.
While delay is greater than or equal to zero, the timeStr is displayed, the current hubStrOn command is run, and the oneShotTimer function is called. This serves to start the clock, a second passes (or whatever your time_step is set to), delay is decreased, and the wait function is called again. This happens each second until time catches up with the delay value, and then it stops.
The nice feature of this function is that it takes whatever is the current hubStrOn command - SET LIGHT ON, or play sound, or run servo - and adds a 5 second timer. Try reading BRIGHTNESS to set that as the current command. Then run wait(5) and you will see values read at 1 second intervals for 5 seconds, as specified.
blink(rep,waitValue)
Again, styled after a simple TI-BASIC function, this takes a value for number of blinks ('rep') and delay between blinks ('waitValue').
As previously, we link these to existing defined variables (repeats and delay) so that the first time the function is run, these variables are defined with the values desired. So, for example, blink(5,0.5) will blink 5 times at half second intervals.
I defined another variable called "lightState" for this function, since I want to know whether the light is ON or OFF, and switch between these values each time the function is run.
The sequence of this function is similar to the wait function above, although this time it is the variable 'repeats' that serves as the count down, rather than 'delay' (which stays constant and serves the same role as time_step did previously.) This time, though, we step through TWICE the number of steps specified, so that we can turn the light ON and then OFF for each repetition.
alarm(rep,waitValue)
Just to add a little variety, this function is a variation on blink to demonstrate how the same principle can be applied in different ways.
No changes from that previously defined.
- These functions are as previously defined.
end-- TI Innovator Init Values
platform.apilevel = '2.7' local screen = platform.window local date = "021517"
require 'color' pcall(function() require 'asi' end)
local isASIavailable = false local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats local index, dataList = {}, {} var.store("index", index) var.store("dataList", dataList)
function addMsg(input)
msgStr = input print(msgStr) screen:invalidate()end
function on.construction()
end--the next three lines are required for Hub connection TI_Innovator.init(TI_InnovatorStateCallback) TI_Innovator.setReadListener(TI_InnovatorReadCallback) TI_Innovator.connect()
Menu={
{"About",} toolpalette.register(Menu) screen:invalidate(){" ©2017 Compass Learning Technologies", function() end}, {" Version "..date, function() end}, {" Contact: steve@compasstech.com.au ", function() end},}, {"Controls",{"Scan and Connect", function() TI_Innovator.connect() end}, {"Disconnect", function() TI_Innovator.disconnect() on.resize() end}, {"RESET", function() on.resize() end},}, {"SET Samples", {"SET LIGHT ON/OFF (default)", function() hubStrOn = "SET LIGHT ON" hubStrOff = "SET LIGHT OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.RED ON/OFF", function() hubStrOn = "SET COLOR.RED ON" hubStrOff = "SET COLOR.RED OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.GREEN ON/OFF", function() hubStrOn = "SET COLOR.GREEN ON" hubStrOff = "SET COLOR.GREEN OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.BLUE ON/OFF", function() hubStrOn = "SET COLOR.BLUE ON" hubStrOff = "SET COLOR.BLUE OFF" TI_Innovator.Send(hubStrOn) end}, {"SET SOUND 220 5", function() hubStrOn = "SET SOUND 220 5" hubStrOff = "SET SOUND 0 1" TI_Innovator.Send(hubStrOn) end}, {"SET SPEAKER 1 220 5", function() hubStrOn = "SET SPEAKER 1 220 5" TI_Innovator.Send("CONNECT SPEAKER 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET SPEAKER 1 0 1" end}, {"SET LED 1 ON", function() hubStrOn = "SET LED 1 ON" TI_Innovator.Send("CONNECT LED 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET LED 1 0FF" end}, {"SET SERVO 1 CW 50 5", function() TI_Innovator.Send("CONNECT SERVO 1 OUT3") hubStrOn = "SET SERVO 1 CW 50 5" hubStrOff = "SET SERVO 1 CW 50 0" TI_Innovator.Send(hubStrOn) end}, {"SET ANALOG.OUT 1 100", function() hubStrOn = "SET ANALOG.OUT 1 100" TI_Innovator.Send("CONNECT ANALOG.OUT 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET ANALOG.OUT 1 0" end}, {"SET DIGITAL.OUT 1 ON", function() hubStrOn = "SET DIGITAL.OUT 1 ON" TI_Innovator.Send("CONNECT DIGITAL.OUT 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET DIGITAL.OUT 1 0FF" end}, }, {"READ Samples",{"READ BRIGHTNESS (default)", function() hubStrOn = "READ BRIGHTNESS" TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ RANGER 1", function() hubStrOn = "READ RANGER 1" TI_Innovator.Send("CONNECT RANGER 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ DHT 1", function() hubStrOn = "READ DHT 1" TI_Innovator.Send("CONNECT DHT 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ ACCEL 1", function() hubStrOn = "READ ACCEL 1" TI_Innovator.Send("CONNECT ACCEL 1 I2C") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ BAROMETER", function() hubStrOn = "READ BAROMETER" TI_Innovator.Send("CONNECT BAROMETER I2C") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ ANALOG.IN 1", function() hubStrOn = "READ ANALOG.IN 1" TI_Innovator.Send("CONNECT ANALOG.IN 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() hubStrOff = hubStrOn end}, {"READ DIGITAL.IN 1", function() hubStrOn = "READ DIGITAL.IN 1" TI_Innovator.Send("CONNECT DIGITAL.IN 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() hubStrOff = hubStrOn end},}, {"TIMER Samples",{"wait(5)", function() wait(5) end}, {"blink(5,1)", function() blink(5,1) end}, {"alarm(5,0.5)", function() alarm(5,0.5) end},},-- Layout Functions
function on.resize(width, height)
w = screen:width() h = screen:height() hubStrOn, hubStrOff, msgStr, rxValue = "SET LIGHT ON", "SET LIGHT OFF", "SET LIGHT OFF", "" timeStr, delay, repeats = "", 0, 1 index, dataList = {}, {} var.store("index", index) var.store("dataList", dataList) if TI_Innovator.isConnected() then TI_Innovator.Send('BEGIN') addMsg("TI Innovator Hub ready") end screen:invalidate()endfunction on.paint (gc)
end gc:drawString(timeStr, 0.05*w, 0.75*h, "middle") gc:drawString(msgStr, 0.05*w, 0.85*h, "middle")if TI_Innovator.isConnected() then
gc:setColorRGB (0,255,0) gc:fillRect (0, 0, w, 0.025*h)else gc:setColorRGB (255,0,0) gc:fillRect (0, 0, w, 0.025*h) gc:drawString("Wait for connection, then", 0.1*w, 0.1*h, "middle") gc:drawString("press anywhere to activate Hub", 0.1*w, 0.2*h, "middle")gc:drawString(rxValue, 0.05*w, 0.95*h, "middle")
-- TI Innovator User set up
function on.escapeKey()
TI_Innovator.Send(hubStrOff) on.resize() screen:invalidate()endfunction on.mouseDown(x, y)
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub TI_Innovator.Read() -- This is an example of sending a READ command to the Hub screen:invalidate()endfunction on.mouseUp (x,y)
timeStr = '' TI_Innovator.Send(hubStrOff) TI_Innovator.Read() screen:invalidate()endfunction str2list(input, def) local list = {} if input:find(def) then
list = input:split(def) table.foreachi(list,print)elselist = {input}end return list endfunction TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str) if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100 else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end addMsg(hubStrOn) if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage. screen:invalidate()endfunction TI_InnovatorStateCallback(event)
endaddMsg("TI_InnovatorStateCallback") if 'ready' == event then TI_InnovatorConfig() elseif "disconnected" == event then -- user may choose to do some clean up or display a msg when the Hub is disconnected. addMsg("TI_Innovator Hub disconnected") end screen:invalidate()
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here addMsg("TI_Innovator Hub connected")endfunction wait(input)
endif input then delay = input end time_step = 0.1 if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds" delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step TI_Innovator.Send(hubStrOn) if hubStrOn:find("READ") then TI_Innovator.Read() end if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection toolelsedataList[#dataList+1] = tonumber(rxValue) index[#index+1]=#dataList var.store("index", index) var.store("dataList", dataList)else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end oneShotTimer(1000*time_step,wait)TI_Innovator.Send(hubStrOff) delay = 0end screen:invalidate()function blink(rep,waitValue)
endlocal lightState = "OFF" if waitValue then delay = waitValue end if rep then repeats = rep end if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")" if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end TI_Innovator.Send("SET LIGHT "..lightState) oneShotTimer(1000*delay,blink) repeats = repeats - 0.5end screen:invalidate()function alarm(rep,waitValue)
local tone = 220 if waitValue then delay = waitValue end if rep then repeats = rep end if repeats > 0 thenendtimeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")" if math.floor(repeats) == repeats then tone = 440 else tone = 220 end TI_Innovator.Send("SET SOUND "..tone) oneShotTimer(1000*delay,alarm) repeats = repeats - 0.5end screen:invalidate()--End of user code do not modify below this line
-- TI Innovator Code Do not modify any code below this line
-- oneShotTimer BEGINS
local timerstart = timer.start local timerstop = timer.stop timer.start = nil timer.stop = nil local currentTimer = nil
local function timerStart(t)
endcurrentTimer = t timerstart(t.period/1000)
local function timerStop()
endcurrentTimer = nil timerstop()
local function setTimer(t)
endif t.period==nil or type(t.period)~='number' or t.period < 10 then error('period in milliseconds >= 10') end timerStart(t) return t
function oneShotTimer(period, listenerHandler, ...)
endif type(listenerHandler) ~= 'function' then
error('createTimerOneShot: function expected')end setTimer {period = period, oneShot = true, listenerHandler = listenerHandler, params = { ... },}function on.timer()
endlocal ct = currentTimer if currentTimer == nil then
timerStop()end if currentTimer.oneShot thencurrentTimer = nil timerStop()end ct.listenerHandler(unpack(ct.params)) screen:invalidate()-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
endif not pcall(function() require 'asi' end) then
addMsg('Hub NOT available') returnendaddMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n' local HANDSHAKE_ANSWER = 'TISTEM' local portFoundList = { } local state local handshakeState local notifyEvent local asiStateListener local startScanning local stopScanning local portFoundListener local portStateListener local handshake_SendListener local handshake_readListener local handshake_port local TI_Innovator_port local baudRate = asi.BAUD_RATE_DEFAULT local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks local stateCallback = theStateCallback local SendCallback local readCallback
function notifyEvent(event)
addMsg('notifying '.. event) if stateCallback thenendstateCallback(event)elseaddMsg('no callback registered')endfunction startScanning()
if isASIavailable thenendasi.startScanning(portFoundListener) notifyEvent('scanning')endfunction stopScanning()
handshake_port = nil portFoundList = { } asi.stopScanning()endfunction asiStateListener(asiState)
if asi.ON == asiState thenendif state == 'active' thenelseif asi.UNSUPPORTED == asiState thenstartScanning()endnotifyEvent(asi.UNSUPPORTED)endfunction portFoundListener(portFound)
addMsg('portFoundListener '.. portFound:getName()) table.insert(portFoundList, portFound) if not TI_Innovator_port and not handshake_port thenendhandshake_port = portFoundList[1] oneShotTimer(100, handshake_port.connect, handshake_port, portStateListener)endfunction portStateListener(reportedPort, event, error_msg)
addMsg('portStateListener '..reportedPort:getName()..' '..event) if asi.CONNECTED == event thenend-- configure port for handshake reportedPort:setWriteListener(handshake_writeListener) reportedPort:setReadListener(handshake_readListener) reportedPort:setReadTimeout(500) handshakeState = 'handshake' -- write handshake greeting addMsg("HANDSHAKE_GREETING: "..HANDSHAKE_GREETING) oneShotTimer(200, reportedPort.write, reportedPort, HANDSHAKE_GREETING)elseif asi.CONNECTING_FAILED == event thenif reportedPort == handshake_port thenelseif asi.DISCONNECTED == event thentable.remove(portFoundList, 1) if #portFoundList>0 thenelseif reportedPort == TI_Innovator_port thenhandshake_port = portFoundList[1] handshake_port:connect(portStateListener)elsehandshake_port = nilendTI_Innovator_port = nil asi.startScanning(portFoundListener)endif reportedPort == TI_Innovator_port thenendif state == 'active' thenendif reportedPort:getState() == asi.DISCONNECTED thennotifyEvent('disconnected')reportedPort:connect(portStateListener)elseTI_Innovator_port = nil asi.startScanning(portFoundListener)endfunction handshake_writeListener(reportedPort, error_msg)
if error_msg thenendaddMsg('handshake_writeListener '..error_msg) returnend if 'handshake' == handshakeState thenreportedPort:read(#HANDSHAKE_ANSWER+2)endfunction handshake_readListener(reportedPort, error_msg)
if error_msg thenendaddMsg('handshake_writeListener '..error_msg) returnend local answer = reportedPort:getValue() if 'handshake' == handshakeState then-- Validate answer if answer and answer:find(HANDSHAKE_ANSWER) thenelseif 'ready' == handshakeState thenaddMsg("HANDSHAKE_ANSWER: "..HANDSHAKE_ANSWER) stopScanning() handshakeState = 'ready' reportedPort:write('BEGIN\n') reportedPort:read(7)elsereportedPort:disconnect() table.remove(portFoundList, 1) if #portFoundList>0 thenendhandshake_port = portFoundList[1] handshake_port:connect(portStateListener)elsehandshake_port = nilendif answer and answer:find('READY') thenend-- Configure port for normal use TI_Innovator_port = reportedPort TI_Innovator_port:setReadTimeout(readTimeout) TI_Innovator.setWriteListener(SendCallback) TI_Innovator.setReadListener(readCallback) -- Notify launchpad is ready notifyEvent('ready')end-- INTERFACE -- BEGINS --
function TI_Innovator.connect()
state = 'active' startScanning()endfunction TI_Innovator.disconnect()
state = 'inactive' if isASIavailable thenendif TI_Innovator_port thenendTI_Innovator_port:disconnect() TI_Innovator_port = nilend stopScanning()function TI_Innovator.setWriteListener(newWriteCallback)
writeCallback = newWriteCallback if TI_Innovator_port thenendTI_Innovator_port:setWriteListener(writeCallback)endfunction TI_Innovator.setReadListener(newReadCallback, newReadObject)
readCallback = newReadCallback if TI_Innovator_port thenendTI_Innovator_port:setReadListener(readCallback)endfunction TI_Innovator.setBaudRate(newBaudRate)
baudRate = newBaudRate if TI_Innovator_port thenendTI_Innovator_port:setBaudRate(baudRate)endfunction TI_Innovator.setReadTimeout(newReadTimeout)
readTimeout = newReadTimeout if TI_Innovator_port thenendTI_Innovator_port:setReadTimeout(readTimeout)endfunction TI_Innovator.Send(data)
if not TI_Innovator_port thenendaddMsg('No Hub attached') return 'No Hub attached'end local result = TI_Innovator_port:write(data .. '\n') if result then addMsg(tostring(data)..': '.. tostring(result)) else addMsg(tostring(data)) end return resultfunction TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:read(bytes_to_read)function TI_Innovator.request(request)
if not TI_Innovator_port thenendreturn "No TI Hub attached"end local result = TI_Innovator_port:write(request .. '\n') if result then addMsg(tostring(request) .. ': '.. tostring(result)) else addMsg(tostring(request)) end result = TI_Innovator_port:read() addMsg('read() '.. tostring(result)) if result thenaddMsg('Error sending request: ' .. tostring(request))end return resultfunction TI_Innovator.isConnected()
if not TI_Innovator_port thenendreturn falseend return TI_Innovator_port:getState() == asi.CONNECTEDfunction TI_Innovator.getName()
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:getName()function TI_Innovator.getIdentifier()
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:getIdentifier()-- INTERFACE -- ENDS --
-- Wait for the ASI to be up and running
asi.addStateListener(asiStateListener)
end
-- TI Innovator Init Values
platform.apilevel = '2.7' local screen = platform.window local date = "021517"
require 'color' pcall(function() require 'asi' end)
local isASIavailable = false local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats local index, dataList = {}, {} var.store("index", index) var.store("dataList", dataList)
function addMsg(input)
msgStr = input print(msgStr) screen:invalidate()end
function on.construction()
end--the next three lines are required for Hub connection TI_Innovator.init(TI_InnovatorStateCallback) TI_Innovator.setReadListener(TI_InnovatorReadCallback) TI_Innovator.connect()
Menu={
{"About",} toolpalette.register(Menu) screen:invalidate(){" ©2017 Compass Learning Technologies", function() end}, {" Version "..date, function() end}, {" Contact: steve@compasstech.com.au ", function() end},}, {"Controls",{"Scan and Connect", function() TI_Innovator.connect() end}, {"Disconnect", function() TI_Innovator.disconnect() on.resize() end}, {"RESET", function() on.resize() end},}, {"SET Samples", {"SET LIGHT ON/OFF (default)", function() hubStrOn = "SET LIGHT ON" hubStrOff = "SET LIGHT OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.RED ON/OFF", function() hubStrOn = "SET COLOR.RED ON" hubStrOff = "SET COLOR.RED OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.GREEN ON/OFF", function() hubStrOn = "SET COLOR.GREEN ON" hubStrOff = "SET COLOR.GREEN OFF" TI_Innovator.Send(hubStrOn) end}, {"SET COLOR.BLUE ON/OFF", function() hubStrOn = "SET COLOR.BLUE ON" hubStrOff = "SET COLOR.BLUE OFF" TI_Innovator.Send(hubStrOn) end}, {"SET SOUND 220 5", function() hubStrOn = "SET SOUND 220 5" hubStrOff = "SET SOUND 0 1" TI_Innovator.Send(hubStrOn) end}, {"SET SPEAKER 1 220 5", function() hubStrOn = "SET SPEAKER 1 220 5" TI_Innovator.Send("CONNECT SPEAKER 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET SPEAKER 1 0 1" end}, {"SET LED 1 ON", function() hubStrOn = "SET LED 1 ON" TI_Innovator.Send("CONNECT LED 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET LED 1 0FF" end}, {"SET SERVO 1 CW 50 5", function() TI_Innovator.Send("CONNECT SERVO 1 OUT3") hubStrOn = "SET SERVO 1 CW 50 5" hubStrOff = "SET SERVO 1 CW 50 0" TI_Innovator.Send(hubStrOn) end}, {"SET ANALOG.OUT 1 100", function() hubStrOn = "SET ANALOG.OUT 1 100" TI_Innovator.Send("CONNECT ANALOG.OUT 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET ANALOG.OUT 1 0" end}, {"SET DIGITAL.OUT 1 ON", function() hubStrOn = "SET DIGITAL.OUT 1 ON" TI_Innovator.Send("CONNECT DIGITAL.OUT 1 OUT1") TI_Innovator.Send(hubStrOn) hubStrOff = "SET DIGITAL.OUT 1 0FF" end}, }, {"READ Samples",{"READ BRIGHTNESS (default)", function() hubStrOn = "READ BRIGHTNESS" TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ RANGER 1", function() hubStrOn = "READ RANGER 1" TI_Innovator.Send("CONNECT RANGER 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ DHT 1", function() hubStrOn = "READ DHT 1" TI_Innovator.Send("CONNECT DHT 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ ACCEL 1", function() hubStrOn = "READ ACCEL 1" TI_Innovator.Send("CONNECT ACCEL 1 I2C") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ BAROMETER", function() hubStrOn = "READ BAROMETER" TI_Innovator.Send("CONNECT BAROMETER I2C") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() msgStr = rxValue or hubStrOn end}, {"READ ANALOG.IN 1", function() hubStrOn = "READ ANALOG.IN 1" TI_Innovator.Send("CONNECT ANALOG.IN 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() hubStrOff = hubStrOn end}, {"READ DIGITAL.IN 1", function() hubStrOn = "READ DIGITAL.IN 1" TI_Innovator.Send("CONNECT DIGITAL.IN 1 IN1") TI_Innovator.Send(hubStrOn) TI_Innovator.Read() hubStrOff = hubStrOn end},}, {"TIMER Samples",{"wait(5)", function() wait(5) end}, {"blink(5,1)", function() blink(5,1) end}, {"alarm(5,0.5)", function() alarm(5,0.5) end},},-- Layout Functions
function on.resize(width, height)
w = screen:width() h = screen:height() hubStrOn, hubStrOff, msgStr, rxValue = "SET LIGHT ON", "SET LIGHT OFF", "SET LIGHT OFF", "" timeStr, delay, repeats = "", 0, 1 index, dataList = {}, {} var.store("index", index) var.store("dataList", dataList) if TI_Innovator.isConnected() then TI_Innovator.Send('BEGIN') addMsg("TI Innovator Hub ready") end screen:invalidate()endfunction on.paint (gc)
endif TI_Innovator.isConnected() then
gc:setColorRGB (0,255,0) gc:fillRect (0, 0, w, 0.025*h)elsegc:setColorRGB (255,0,0) gc:fillRect (0, 0, w, 0.025*h) gc:drawString("Wait for connection, then", 0.1*w, 0.1*h, "middle") gc:drawString("press anywhere to activate Hub", 0.1*w, 0.2*h, "middle")end gc:drawString(timeStr, 0.05*w, 0.75*h, "middle") gc:drawString(msgStr, 0.05*w, 0.85*h, "middle")gc:drawString(rxValue, 0.05*w, 0.95*h, "middle")
-- TI Innovator User set up
function on.escapeKey()
TI_Innovator.Send(hubStrOff) on.resize() screen:invalidate()endfunction on.mouseDown(x, y)
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub TI_Innovator.Read() -- This is an example of sending a READ command to the Hub screen:invalidate()endfunction on.mouseUp (x,y)
timeStr = '' TI_Innovator.Send(hubStrOff) TI_Innovator.Read() screen:invalidate()endfunction str2list(input, def) local list = {} if input:find(def) then
list = input:split(def) table.foreachi(list,print)elselist = {input}end return list endfunction TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str) if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100 else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end addMsg(hubStrOn) if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage. screen:invalidate()endfunction TI_InnovatorStateCallback(event)
endaddMsg("TI_InnovatorStateCallback") if 'ready' == event then TI_InnovatorConfig() elseif "disconnected" == event then -- user may choose to do some clean up or display a msg when the Hub is disconnected. addMsg("TI_Innovator Hub disconnected") end screen:invalidate()
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here addMsg("TI_Innovator Hub connected")endfunction wait(input)
endif input then delay = input end time_step = 0.1 if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds" delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step TI_Innovator.Send(hubStrOn) if hubStrOn:find("READ") then TI_Innovator.Read() end if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection toolelsedataList[#dataList+1] = tonumber(rxValue) index[#index+1]=#dataList var.store("index", index) var.store("dataList", dataList)else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end oneShotTimer(1000*time_step,wait)TI_Innovator.Send(hubStrOff) delay = 0end screen:invalidate()function blink(rep,waitValue)
endlocal lightState = "OFF" if waitValue then delay = waitValue end if rep then repeats = rep end if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")" if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end TI_Innovator.Send("SET LIGHT "..lightState) oneShotTimer(1000*delay,blink) repeats = repeats - 0.5end screen:invalidate()function alarm(rep,waitValue)
local tone = 220 if waitValue then delay = waitValue end if rep then repeats = rep end if repeats > 0 thenendtimeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")" if math.floor(repeats) == repeats then tone = 440 else tone = 220 end TI_Innovator.Send("SET SOUND "..tone) oneShotTimer(1000*delay,alarm) repeats = repeats - 0.5end screen:invalidate()-- oneShotTimer BEGINS
local timerstart = timer.start local timerstop = timer.stop timer.start = nil timer.stop = nil local currentTimer = nil
local function timerStart(t)
endcurrentTimer = t timerstart(t.period/1000)
local function timerStop()
endcurrentTimer = nil timerstop()
local function setTimer(t)
endif t.period==nil or type(t.period)~='number' or t.period < 10 then error('period in milliseconds >= 10') end timerStart(t) return t
function oneShotTimer(period, listenerHandler, ...)
endif type(listenerHandler) ~= 'function' then
error('createTimerOneShot: function expected')end setTimer {period = period, oneShot = true, listenerHandler = listenerHandler, params = { ... },}function on.timer()
endlocal ct = currentTimer if currentTimer == nil then
timerStop()end if currentTimer.oneShot thencurrentTimer = nil timerStop()end ct.listenerHandler(unpack(ct.params)) screen:invalidate()-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
endif not pcall(function() require 'asi' end) then
addMsg('Hub NOT available') returnendaddMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n' local HANDSHAKE_ANSWER = 'TISTEM' local portFoundList = { } local state local handshakeState local notifyEvent local asiStateListener local startScanning local stopScanning local portFoundListener local portStateListener local handshake_SendListener local handshake_readListener local handshake_port local TI_Innovator_port local baudRate = asi.BAUD_RATE_DEFAULT local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks local stateCallback = theStateCallback local SendCallback local readCallback
function notifyEvent(event)
addMsg('notifying '.. event) if stateCallback thenendstateCallback(event)elseaddMsg('no callback registered')endfunction startScanning()
if isASIavailable thenendasi.startScanning(portFoundListener) notifyEvent('scanning')endfunction stopScanning()
handshake_port = nil portFoundList = { } asi.stopScanning()endfunction asiStateListener(asiState)
if asi.ON == asiState thenendif state == 'active' thenelseif asi.UNSUPPORTED == asiState thenstartScanning()endnotifyEvent(asi.UNSUPPORTED)endfunction portFoundListener(portFound)
addMsg('portFoundListener '.. portFound:getName()) table.insert(portFoundList, portFound) if not TI_Innovator_port and not handshake_port thenendhandshake_port = portFoundList[1] oneShotTimer(100, handshake_port.connect, handshake_port, portStateListener)endfunction portStateListener(reportedPort, event, error_msg)
addMsg('portStateListener '..reportedPort:getName()..' '..event) if asi.CONNECTED == event thenend-- configure port for handshake reportedPort:setWriteListener(handshake_writeListener) reportedPort:setReadListener(handshake_readListener) reportedPort:setReadTimeout(500) handshakeState = 'handshake' -- write handshake greeting addMsg("HANDSHAKE_GREETING: "..HANDSHAKE_GREETING) oneShotTimer(200, reportedPort.write, reportedPort, HANDSHAKE_GREETING)elseif asi.CONNECTING_FAILED == event thenif reportedPort == handshake_port thenelseif asi.DISCONNECTED == event thentable.remove(portFoundList, 1) if #portFoundList>0 thenelseif reportedPort == TI_Innovator_port thenhandshake_port = portFoundList[1] handshake_port:connect(portStateListener)elsehandshake_port = nilendTI_Innovator_port = nil asi.startScanning(portFoundListener)endif reportedPort == TI_Innovator_port thenendif state == 'active' thenendif reportedPort:getState() == asi.DISCONNECTED thennotifyEvent('disconnected')reportedPort:connect(portStateListener)elseTI_Innovator_port = nil asi.startScanning(portFoundListener)endfunction handshake_writeListener(reportedPort, error_msg)
if error_msg thenendaddMsg('handshake_writeListener '..error_msg) returnend if 'handshake' == handshakeState thenreportedPort:read(#HANDSHAKE_ANSWER+2)endfunction handshake_readListener(reportedPort, error_msg)
if error_msg thenendaddMsg('handshake_writeListener '..error_msg) returnend local answer = reportedPort:getValue() if 'handshake' == handshakeState then-- Validate answer if answer and answer:find(HANDSHAKE_ANSWER) thenelseif 'ready' == handshakeState thenaddMsg("HANDSHAKE_ANSWER: "..HANDSHAKE_ANSWER) stopScanning() handshakeState = 'ready' reportedPort:write('BEGIN\n') reportedPort:read(7)elsereportedPort:disconnect() table.remove(portFoundList, 1) if #portFoundList>0 thenendhandshake_port = portFoundList[1] handshake_port:connect(portStateListener)elsehandshake_port = nilendif answer and answer:find('READY') thenend-- Configure port for normal use TI_Innovator_port = reportedPort TI_Innovator_port:setReadTimeout(readTimeout) TI_Innovator.setWriteListener(SendCallback) TI_Innovator.setReadListener(readCallback) -- Notify launchpad is ready notifyEvent('ready')end-- INTERFACE -- BEGINS --
function TI_Innovator.connect()
state = 'active' startScanning()endfunction TI_Innovator.disconnect()
state = 'inactive' if isASIavailable thenendif TI_Innovator_port thenendTI_Innovator_port:disconnect() TI_Innovator_port = nilend stopScanning()function TI_Innovator.setWriteListener(newWriteCallback)
writeCallback = newWriteCallback if TI_Innovator_port thenendTI_Innovator_port:setWriteListener(writeCallback)endfunction TI_Innovator.setReadListener(newReadCallback, newReadObject)
readCallback = newReadCallback if TI_Innovator_port thenendTI_Innovator_port:setReadListener(readCallback)endfunction TI_Innovator.setBaudRate(newBaudRate)
baudRate = newBaudRate if TI_Innovator_port thenendTI_Innovator_port:setBaudRate(baudRate)endfunction TI_Innovator.setReadTimeout(newReadTimeout)
readTimeout = newReadTimeout if TI_Innovator_port thenendTI_Innovator_port:setReadTimeout(readTimeout)endfunction TI_Innovator.Send(data)
if not TI_Innovator_port thenendaddMsg('No Hub attached') return 'No Hub attached'end local result = TI_Innovator_port:write(data .. '\n') if result then addMsg(tostring(data)..': '.. tostring(result)) else addMsg(tostring(data)) end return resultfunction TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:read(bytes_to_read)function TI_Innovator.request(request)
if not TI_Innovator_port thenendreturn "No TI Hub attached"end local result = TI_Innovator_port:write(request .. '\n') if result then addMsg(tostring(request) .. ': '.. tostring(result)) else addMsg(tostring(request)) end result = TI_Innovator_port:read() addMsg('read() '.. tostring(result)) if result thenaddMsg('Error sending request: ' .. tostring(request))end return resultfunction TI_Innovator.isConnected()
if not TI_Innovator_port thenendreturn falseend return TI_Innovator_port:getState() == asi.CONNECTEDfunction TI_Innovator.getName()
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:getName()function TI_Innovator.getIdentifier()
if not TI_Innovator_port thenendreturn "No TI Hub attached"end return TI_Innovator_port:getIdentifier()-- INTERFACE -- ENDS --
-- Wait for the ASI to be up and running
asi.addStateListener(asiStateListener)
end
Our next lesson offers another nice application of the OneShotTimer - playing music from lists of tone values and times!
Home ← STEM HQ ← TI-Nspire Authoring ← TI-Nspire Scripting HQ ← Scripting Tutorial - Lesson 43