3

This might be a silly question, however, I don't know what is happening.

I have a simple script who fetches google time and I need to set it to the time global variable. So, inside the receive event, I print the time fetched and it works properly.

The problem is the variable time always as empty when called outside the event. Here is the code:

-- test.lua
time = ""

function getTime()
  conn = net.createConnection(net.TCP, 0)

  conn:connect(80,'google.com')
  conn:on("connection", function(conn, payload)
    conn:send("HEAD / HTTP/1.1\r\n"..
          "Host: google.com\r\n"..
          "Accept: */*\r\n"..
          "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)"..
          "\r\n\r\n"
    )
  end)

  conn:on("receive", function(conn, payload)
    conn:close()
    time = string.sub(payload,string.find(payload,"Date: ")
       +6,string.find(payload,"Date: ")+35)
    end)
    print("testing: " .. time) -- WORKS!
end

getTime()
print("variable: ".. time)

Here is how I'm calling the function (using nodemcu-uploader terminal):

➜  test nu terminal
--- Miniterm on /dev/cu.wchusbserial1410  115200,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---

> dofile('lib/test.lua')
variable:
> testing: Sat, 20 May 2017 01:37:35 GMT

Any help would be really appreciated! Thanks

hugalves
  • 271
  • 4
  • 15
  • Watch out! This creates memory leaks because you're re-using the `conn` variable in the `connection` and `receive` callbacks. See http://stackoverflow.com/a/37379426/131929. – Marcel Stör May 20 '17 at 19:22
  • @MarcelStör .. setting as `nil` in the end of the event could fix this? – hugalves May 20 '17 at 19:36

2 Answers2

2

It looks like the scope is fine. Check out the order the output prints.

conn:connect and con:on take functions because they are asynchronous. getTime() simply returns before they are called.

James Poag
  • 2,320
  • 1
  • 13
  • 20
  • i did not know this.. do you have any suggestion to handle this? sleep? – hugalves May 20 '17 at 02:50
  • 1
    @hugalves The simplest way is to pass a function to getTime and call it instead of setting time. That function could set time and print. But your program is likely more complex than that. – Tom Blodget May 20 '17 at 11:13
  • 1
    My suggestion is to learn event based programming. Using signals and events will make your code/device more responsive. Blocking makes bad things happen. Read the FAQ, it covers a lot of this: https://nodemcu.readthedocs.io/en/master/en/lua-developer-faq/ – James Poag May 20 '17 at 12:10
  • @TomBlodget sorry, I haven't understand your suggestion.. could you give a code example? – hugalves May 21 '17 at 18:46
1

The NodeMCU programming model is similar to that of Node.js, only in Lua. It is asynchronous and event-driven. Many functions, therefore, have parameters for callback functions.

Source: https://github.com/nodemcu/nodemcu-firmware/#programming-model

That means that functions which accept callback functions as parameters are non-blocking. That it return means that you can't just read a piece of code line-by-line and expect it to be executed in that order.

So, the sequence of events in your original program roughly is this:

  • getTime is triggered but does not block.
  • print("variable: ".. time) is executed. time is still empty at this point.
  • Connection to google.com is established.
  • HEAD request is sent to google.com.
  • Response is receive and on-receive event handler (i.e. anonymous callback function) kicks-in.
  • time is populated.

I see two obvious fixes, one using your global time variable and one without. Both are based on the passing-callback-function-as-parameter pattern.

Note that you should always set up your event listeners (conn:on in your case) before those events are triggered (conn:connect) to avoid missing some events. Your code

conn:connect(80,'google.com')
conn:on("connection"...

only works because conn:connect is non-blocking and because it takes some time until the connection is established. By the time this happens the on-connection event handler has been registered.

Keeping global variable

time = ""

function getTime(cb)
  conn = net.createConnection(net.TCP, 0)

  conn:on("connection", function(socket, payload)
    socket:send("HEAD / HTTP/1.1\r\n" ..
            "Host: google.com\r\n" ..
            "Accept: */*\r\n" ..
            "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)" ..
            "\r\n\r\n")
  end)

  conn:on("receive", function(socket, payload)
    socket:close()
    time = string.sub(payload, string.find(payload, "Date: ")
            + 6, string.find(payload, "Date: ") + 35)
    print("time inside on-receive: " .. time)
    cb()
  end)

  conn:connect(80, 'google.com')
end

function do_something_with_time()
  print("time inside callback: " .. time)
end

getTime(do_something_with_time)

Without global variable

function getTime(cb)
  conn = net.createConnection(net.TCP, 0)

  conn:on("connection", function(socket, payload)
    socket:send("HEAD / HTTP/1.1\r\n" ..
            "Host: google.com\r\n" ..
            "Accept: */*\r\n" ..
            "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)" ..
            "\r\n\r\n")
  end)

  conn:on("receive", function(socket, payload)
    socket:close()
    local time = string.sub(payload, string.find(payload, "Date: ")
            + 6, string.find(payload, "Date: ") + 35)
    print("time inside on-receive: " .. time)
    cb(time)
  end)

  conn:connect(80, 'google.com')
end

function do_something_with_time(time)
  print("time inside callback: " .. time)
end

getTime(do_something_with_time)
Marcel Stör
  • 22,695
  • 19
  • 92
  • 198