knife.timer on Lua syntax - not behaving as expected - timer

If anyone is familiar with knife.timer on Lua, could you please look at my code and tell me what I have out of order?
I'm hoping to do two things:
have a countdown timer that ticks down every second and
have a timer that after six seconds begins blinking my characters for 3 more seconds before changing state.
With the following code, my countdown timer starts at 9 but gets well into the negative tens. My characters begin to blink after what feels like 4 seconds, and continue blinking for a few seconds after changing state.
I have Timer:update(dt) in main, so I'm not sure why the timing is off. And I thought the finish would not call the change state function until the characters' 16 iterations of blinking were done.
function PlayerPilotState:update(dt)
self.player.currentAnimation:update(dt)
Timer.every(1, function()
self.timer = self.timer - 1
end)
Timer.after(6, function()
Timer.every(0.2, function()
self.player.blinking = not self.player.blinking
self.player.otherPlayer.blinking = not self.player.otherPlayer.blinking
end):finish(function()
self.player:changeState('falling')
self.player.otherPlayer:changeState('falling')
end):limit(16)
end)
end
Thanks!

With the basic countdown timer, you never specify when it should stop. Try using :limit(9) or self.timer = math.max(0,self.timer - 1)
2.Have you timed it properly (it's hard to feel how much time has passed), since you use Timer.after. The :finish() function happens inside the :after(), and after the :every(), which could cause things to be weird. I suggest adding the :limit before the :finish().
Timer.after(6, function()
Timer.every(0.2, function()
self.player.blinking = not self.player.blinking
self.player.otherPlayer.blinking = not self.player.otherPlayer.blinking
end):limit(16):finish(function()
self.player:changeState('falling')
self.player.otherPlayer:changeState('falling')
end)
end)

Both of the above comments were extremely useful. It turns out that putting timers under update is a bad idea because they either are calling new timers or refreshing strangely. What I ended up with, and works perfectly is:
function PlayerPilotState:enter(params)
Timer.every(1, function()
self.timer = self.timer - 1
end)
Timer.after(6, function()
Timer.every(0.2, function()
self.player.blinking = not self.player.blinking
self.player.otherPlayer.blinking = not self.player.otherPlayer.blinking
end)
:limit(15)
:finish(function()
self.player.blinking = false
self.player.otherPlayer.blinking = false
self.player:changeState('falling')
self.player.otherPlayer:changeState('falling')
end)
end)
end
A follow up question I would have, is how to do this without using an enter function? I guess any function call that called only once (with maybe a boolean flag to call it) would work. Thanks!

Related

NodeMCU timer stops unexpectedly

I have a NodeMCU Lua application that uses two timers. Each timer invokes a function that causes an HTTP request to be made to a local server.
After a few iterations, one of the timers stops, and the other timer continues. The number of iterations before the timer stops seems to be random. I have run the test script many times and the point at which the timer stops is never the same. Note: it is not always the same timer that halts.
Here is some test code that reliably demonstrates this problem:
ctr1=0
ctr2=0
local function doCmdChk()
ctr1 = ctr1 + 1
http.get( "http://192.168.2.38/ICmd.py?i=" .. ctr1 , nil,
function(rspCode, payload)
tmr.start(1)
end)
end
local function sendData()
ctr2 = ctr2 + 1
local msgBdy = '{"s":"' .. ctr2 .. '","i":"test23", "d":"heap='..node.heap()..'"}'
http.post("http://192.168.2.38/DeviceScan.py", "Content-Type: text/json\r\n", msgBdy,
function(rspCode, payload)
tmr.start(2)
end)
end
--mainline start:
tmr.alarm(1, 3000, tmr.ALARM_SEMI, doCmdChk)
tmr.alarm(2, 5000, tmr.ALARM_SEMI, sendData)
My application doesn't fire off the HTTP requests as quickly as the test code, but when the application runs for several hours the same result eventually occurs (i.e. one of the timers stops running). Reducing the time between HTTP requests makes the error occur sooner.
Has anyone encountered this issue? Does anyone have any ideas as to how to troubleshoot this issue? (not being able to reliably send continuous HTTP requests is a show stopper for this application).
The solution is to set up flags so that only one http request is outstanding at any given time. Here is the previous test script that includes the flags:
ctr1=0
ctr2=0
sendFlag=true
local function doCmdChk()
if sendFlag then
sendFlag=false
ctr1 = ctr1 + 1
http.get( "http://192.168.2.38/ICmd.py?i=" .. ctr1 , nil,
function(rspCode, payload)
sendFlag=true
tmr.start(1)
end)
else
tmr.alarm(3, 1000, tmr.ALARM_SINGLE, doCmdChk)
end
end
local function sendData()
if sendFlag then
sendFlag=false
ctr2 = ctr2 + 1
local msgBdy = '{"s":"' .. ctr2 .. '","i":"test23", "d":"heap='..node.heap()..'"}'
http.post("http://192.168.2.38/DeviceScan.py", "Content-Type: text/json\r\n", msgBdy,
function(rspCode, payload)
sendFlag=true
tmr.start(2)
end)
else
tmr.alarm(3, 1000, tmr.ALARM_SINGLE, sendData)
end
end
--mainline start:
tmr.alarm(1, 3000, tmr.ALARM_SEMI, doCmdChk)
tmr.alarm(2, 5000, tmr.ALARM_SEMI, sendData)
I ran this script for several hours and both http send functions continued to work as expected.
I tried the node.task.post() option, test script as follows:
ctr1=0
ctr2=0
local function doCmdChk()
ctr1 = ctr1 + 1
http.get( "http://192.168.2.38/ICmd.py?i=" .. ctr1 , nil,
function(rspCode, payload)
sendFlag=true
tmr.start(1)
end)
end
local function sendData()
ctr2 = ctr2 + 1
local msgBdy = '{"s":"' .. ctr2 .. '","i":"test23", "d":"heap='..node.heap()..'"}'
http.post("http://192.168.2.38/DeviceScan.py", "Content-Type: text/json\r\n", msgBdy,
function(rspCode, payload)
sendFlag=true
tmr.start(2)
end)
end
--mainline start:
tmr.alarm(1, 3000, tmr.ALARM_SEMI, function() node.task.post(node.task.MEDIUM_PRIORITY, doCmdChk) end)
tmr.alarm(2, 5000, tmr.ALARM_SEMI, function() node.task.post(node.task.HIGH_PRIORITY, sendData) end)
But after a couple of hours of running one of the http callbacks was not invoked, so there must have been a collision.
I don't know much about NodeMCU, but according to the reference manual the http.post and http.get callback functions are invoked when a response is received. Hence timers are only restarted when a response is received. Any chance there is a delay or maybe you never get a response?
Restarting a timer after some third party stuff has responded would add a variable delay and therefor should not be very precise. I would not expect it to be as accurate as some test code.
For debugging I suggest you print the actual delays between invoked callbacks or the delay between post/get and response.

lua timing sound files

I'm a musician attempting to write a music reading programme for guitarists.
I want to time two consecutive sounds so that the first stops when the second begins. Each should last a predetermined duration (defined in this example as 72 in 60000/72). As a beginner coder I'm struggling and would really appreciate any help.
-- AUDIO 1 --
local aa = audio.loadStream(sounds/chord1.mp3)
audio.play(aa)
-- TIMER 1 --
local timeLimit = 1
local function timerDown()
timeLimit = timeLimit-1
if(timeLimit==0)then
end
end
timer.performWithDelay( 60000/72, timerDown, timeLimit )
-- TIMER 2 --
local timeLimit = 1
local function timerDown()
timeLimit = timeLimit-1
if(timeLimit==0)then
-- AUDIO 2 --
local aa = audio.loadStream(sounds/chord2.mp3])
audio.play(aa)
end
end
timer.performWithDelay( 60000/72, timerDown, timeLimit )
There are a few things to note here. Sorry for the wall of text!
Strings (text)
Must be enclosed in quotes.
local aa = audio.loadStream(sounds/chord1.mp3)
becomes:
local aa = audio.loadStream('sounds/chord1.mp3')
Magic numbers
Values which aren't explained anywhere should be avoided. They make code harder to understand and harder to maintain or modify.
timer.performWithDelay(60000/72, timerDown, timeLimit)
becomes:
-- Might be slight overkill but hopefully you get the idea!
local beatsToPlay = 10
local beatsPerMinute = 72
local millisPerMinute = 60 * 1000
local playTimeMinutes = beatsToPlay / beatsPerMinute
local playTimeMillis = playTimeMinutes * millisPerMinute
timer.performWithDelay(playTimeMillis, timerDown, timeLimit)
Corona API
It is an invaluable skill when programming to be able to read and understand documentation. Corona's API is documented here.
audio.loadStream()'s docs tell you that it returns an audio handle which you can use to play sounds which is what you've got already. It also reminds you that you should dispose of the handle when you are done so you'll need to add that in.
timer.performWithDelay()'s docs tell you that it needs the delay time in milliseconds and a listener which is what will be activated at that time, so you will need to write a listener of some description. If you follow the link to listener or if you look at the examples further down the page then you'll see that a simple function will suffice.
audio.play() is fine as it is but if you read the docs then it informs you of some more functionality which you could use to your advantage. Namely the options parameter, which includes duration and onComplete. duration is how long - in millis - to play the sound. onComplete is a listener which will be triggered when the sound has finished playing.
The result
Using timers only:
local function playAndQueue(handle, playTime, queuedHandle, queuedPlayTime)
audio.play(handle, { duration = playTime })
timer.performWithDelay(playTime, function(event)
audio.dispose(handle)
audio.play(queuedHandle, { duration = queuedPlayTime })
end)
timer.performWithDelay(playTime + queuedPlayTime, function(event)
audio.dispose(queuedHandle)
end)
end
local audioHandle1 = audio.loadStream('sounds/chord1.mp3')
local audioHandle2 = audio.loadStream('sounds/chord2.mp3')
local beatsToPlay = 10
local beatsPerMinute = 72
local millisPerMinute = 60 * 1000
local playTimeMinutes = beatsToPlay / beatsPerMinute
local playTimeMillis = playTimeMinutes * millisPerMinute
playAndQueue(audioHandle1, playTimeMillis, audioHandle2, playTimeMillis)
Using onComplete:
local function playAndQueue(handle, playTime, queuedHandle, queuedPlayTime)
-- Before we can set the 1st audio playing we have to define what happens
-- when it is done (disposes self and starts the 2nd audio).
-- Before we can start the 2nd audio we have to define what happens when
-- it is done (disposes of the 2nd audio handle)
local queuedCallback = function(event)
audio.dispose(queuedHandle)
end
local callback = function(event)
audio.dispose(handle)
local queuedOpts = {
duration = queuedPlayTime,
onComplete = queuedCallback
}
audio.play(queuedHandle, queuedOpts)
end
local opts = {
duration = playTime,
onComplete = callback
}
audio.play(handle, opts)
end
local audioHandle1 = audio.loadStream('sounds/chord1.mp3')
local audioHandle2 = audio.loadStream('sounds/chord2.mp3')
local beatsToPlay = 10
local beatsPerMinute = 72
local millisPerMinute = 60 * 1000
local playTimeMinutes = beatsToPlay / beatsPerMinute
local playTimeMillis = playTimeMinutes * millisPerMinute
playAndQueue(audioHandle1, playTimeMillis, audioHandle2, playTimeMillis)
You might find that using onComplete works out better than pure timers since you might end up disposing the audio handle just before is is done being used for playback (and causing errors). I haven't had any experience with Corona so I'm not sure how robust its timer or audio libraries are.

How to run a function during a limited time?

I've a function and would like to call here each 2 seconds during 3 seconds.
I tried timer.performwithDelay() but it doesn't answer to my question.
Here is the function I want to call each 2 secondes during 3 seconds :
function FuelManage(event)
if lives > 0 and pressed==true then
lifeBar[lives].isVisible=false
lives = lives - 1
-- print( lifeBar[lives].x )
livesValue.text = string.format("%d", lives)
end
end
How can I use timer.performwithDelay(2000, callback, 1) to call my function FuelManage(event) ?
So it looks like what you are actually after is to start a few check 2 seconds from "now", for a duration of 3 seconds. You can schedule registering and unregistering for the enterFrame events. Using this will call your FuelManage function every time step during the period of interest:
function cancelCheckFuel(event)
Runtime:removeListener('enterFrame', FuelManager)
end
function FuelManage(event)
if lives > 0 and pressed==true then
lifeBar[lives].isVisible=false
lives = lives - 1
-- print( lifeBar[lives].x )
livesValue.text = string.format("%d", lives)
end
end
-- fuel management:
local startFuelCheckMS = 2000 -- start checking for fuel in 2 seconds
local fuelCheckDurationMS = 3000 -- check for 3 seconds
local stopFuelCheckMS = startFuelCheckMS + fuelCheckDurationMS
timer.performWithDelay(
startFuelCheckMS,
function() Runtime:addEventListener('enterFrame', FuelManager) end,
1)
timer.performWithDelay(
stopFuelCheckMS,
function() Runtime:removeEventListener('enterFrame', FuelManager) end,
1)
If this is too high frequency, then you'll want to use a timer, and keep track of time:
local fuelCheckDurationMS = 3000 -- check for 3 seconds
local timeBetweenChecksMS = 200 -- check every 200 ms
local totalCheckTimeMS = 0
local startedChecking = false
function FuelManage(event)
if lives > 0 and pressed==true then
lifeBar[lives].isVisible=false
lives = lives - 1
-- print( lifeBar[lives].x )
livesValue.text = string.format("%d", lives)
end
if totalCheckTimeMS < 3000 then
timer.performWithDelay(timeBetweenChecksMS, FuelManage, 1)
if startedChecking then
totalCheckTimeMS = totalCheckTimeMS + timeBetweenChecksMS
end
startedChecking = true
end
end
-- fuel management:
local startFuelCheckMS = 2000 -- start checking for fuel in 2 seconds
timer.performWithDelay(startFuelCheckMS, FuelManage, 1)
Set a timer inside a timer like this:
function FuelManage(event)
if lives > 0 and pressed==true then
lifeBar[lives].isVisible=false
lives = lives - 1
-- print( lifeBar[lives].x )
livesValue.text = string.format("%d", lives)
end
end
-- Main timer, called every 2 seconds
timer.performwithDelay(2000, function()
-- Sub-timer, called every second for 3 seconds
timer.performwithDelay(1000, FuelManage, 3)
end, 1)
Be careful though because the way it's setup know you will have an infinite number of timer running very soon... Since the first timer has a lower lifetime than the second one. So you might think if you would like to secure the second timer by making sure it's cancelled first before calling it again, this kind of thing.

Construct dynamic timer with lua

I have created a timer called "timer", but I'm trying to create a function that will arm or disarm timer which is specified in it parameters
timer = sys.timer.create()
function MainTimer(timerName, action, time)
if action == "arm" then
timerName:arm(time)
else
timerName:disarm()
end
end
MainTimer("timer", "arm", 30)
but I'm getting an error from lua saying lua:272: attempt to call method 'arm' (a nil value)
where you think I did a mistake.
Thank you
Extra quotes :-)
MainTimer(timer, "arm", 30)

Creating a timer using Lua

I would like to create a timer using Lua, in a way that I could specify a callback function to be triggered after X seconds have passed.
What would be the best way to achieve this? ( I need to download some data from a webserver that will be parsed once or twice an hour )
Cheers.
If milisecond accuracy is not needed, you could just go for a coroutine solution, which you resume periodically, like at the end of your main loop, Like this:
require 'socket' -- for having a sleep function ( could also use os.execute(sleep 10))
timer = function (time)
local init = os.time()
local diff=os.difftime(os.time(),init)
while diff<time do
coroutine.yield(diff)
diff=os.difftime(os.time(),init)
end
print( 'Timer timed out at '..time..' seconds!')
end
co=coroutine.create(timer)
coroutine.resume(co,30) -- timer starts here!
while coroutine.status(co)~="dead" do
print("time passed",select(2,coroutine.resume(co)))
print('',coroutine.status(co))
socket.sleep(5)
end
This uses the sleep function in LuaSocket, you could use any other of the alternatives suggested on the Lua-users Wiki
Try lalarm, here:
http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/
Example (based on src/test.lua):
-- alarm([secs,[func]])
alarm(1, function() print(2) end); print(1)
Output:
1
2
If it's acceptable for you, you can try LuaNode. The following code sets a timer:
setInterval(function()
console.log("I run once a minute")
end, 60000)
process:loop()
use Script.SetTimer(interval, callbackFunction)
After reading this thread and others I decided to go with Luv lib. Here is my solution:
uv = require('luv') --luarocks install luv
function set_timeout(timeout, callback)
local timer = uv.new_timer()
local function ontimeout()
uv.timer_stop(timer)
uv.close(timer)
callback()
end
uv.timer_start(timer, timeout, 0, ontimeout)
return timer
end
set_timeout(1000, function() print('ok') end) -- time in ms
uv.run() --it will hold at this point until every timer have finished
On my Debian I've install lua-lgi packet to get access to the GObject based libraries.
The following code show you an usage demonstrating that you can use few asynchronuous callbacks:
local lgi = require 'lgi'
local GLib = lgi.GLib
-- Get the main loop object that handles all the events
local main_loop = GLib.MainLoop()
cnt = 0
function tictac()
cnt = cnt + 1
print("tic")
-- This callback will be called until the condition is true
return cnt < 10
end
-- Call tictac function every 2 senconds
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, tictac)
-- You can also use an anonymous function like that
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1,
function()
print( "There have been ", cnt, "tic")
-- This callback will never stop
return true
end)
-- Once everything is setup, you can start the main loop
main_loop:run()
-- Next instructions will be still interpreted
print("Main loop is running")
You can find more documentation about LGI here

Resources