Support for coroutines missing in Lua interpreter?


I just wrote my first Lua script for Ardupilot and ran into what’s probably a typical problem: needing to wait for a reply from a serial peripheral can’t be synchronous because the scripting interpreter will terminate the script for going beyond its time slice.

I thought I’d use a coroutine to keep state of where my script is up to and resume it each time my loop function is called, however I get an error that coroutine is an unknown global.

The Lua 5.3.5 interpreter has coroutines in the standard library, so I’m not sure what Ardupilot has done to make them unavailable, or whether that was intentional?

Does anyone know? Rewriting things to not need coroutines will need a nasty kind of state machine so I can connect responses to requests…

Coroutines have been disabled by internationally. This is to keep scripts contained in a single low priority thread so they don’t affect the operation of the main flight code.

If you can post your script and what its trying to do we might be able to suggest some less nasty approaches.

That’s a pity, I already figured out how to turn them back on and got all excited :grinning:

     lua_pushstring(L, "package");
     lua_settable(L, -3);
+    lua_pushstring(L, "coroutine");
+    luaopen_coroutine(L);
+    lua_settable(L, -3);

Here’s my (WIP) script: Ardupilot ODrive control · GitHub

It’s essentially using the ODrive ASCII protocol to send throttleLeft and throttleRight to a two channel ODrive.

I realise I can use PWM to control this board, but for other reasons (error handling, ESC telemetry, watchdog) I want to do it over the UART.

The issue is the protocol is request-response to fetch things like RPM, bus voltage, etc. I need to send the command to get it, then wait until I get a line back, which can be milliseconds later. That’s too long for a blocking wait, but if I return from the main loop to be called later, I won’t know which command I’ve issued that I’m waiting for a response from. I know I can restructure the code to make it step through a sequence of things it wants to do and keep track of that, I just thought coroutines were a slightly less ugly approach…

Keen for your thoughts though.

ps: I realise I’m calling methods that don’t (yet) exist on the AP_ESC_Telem binding…

and having patched the sandbox to include coroutines, I can confirm they behave as I’d expect. Here’s a minimal example:

local function foo()
    gcs:send_text(0, "I'm doing some work")
    gcs:send_text(0, "I'm back for round two")
    gcs:send_text(0, "and now I'm done")
    return "hi!"

local co = nil

local function loop()
    if co == nil or coroutine.status(co) == "dead" then
        co = coroutine.create(foo)


    return loop, 1000

return loop, 100

in my mind this is a relatively nice way to structure code if you have to continuously give up execution flow. I’m not sure it circumvents the sandboxing or resource protections that AP_Scripting is wrapping around this, keen to hear why you think that might be the case. I can imagine it might chew up a bit more stack space or have other side effects, though the limits on scripts should still be applicable.

Adding coroutine support to the sandbox seems to have added 6k to my SITL build FWIW.