Hello Guest it is April 24, 2024, 06:58:34 PM

Author Topic: Mach4 Coroutine within a Module  (Read 835 times)

0 Members and 1 Guest are viewing this topic.

Mach4 Coroutine within a Module
« on: March 22, 2020, 06:52:07 PM »
Hello all,
I created a module that backs a Lua panel I added to my Mach4 GUI. The panel allows the user to zero the work coordinates using a Z-axis/corner finding touch plate. I have the code/panel up and running using mcCntlGcodeExecuteWait() to submit gcode to Mach4.

I now want to refactor the code to use a coroutine to submit gcode to Mach4 but I'm having trouble getting this working. Modules have a separate scope from the ScreenScript / PLC Script and I believe this part of my problem. I cannot seem to pass/share the coroutine instance between the Screen Script/PLC Script and my Module.

Here's what I've tried so far

1) Initial Attempt -  create the coroutine within the Module and have the [PLC Script] call coroutine.resume(myCoroutine).
    REASON FOR FAILURE: myCoroutine variable has local scope within the module. The [PLC Script] does not have access to this variable and cannot call coroutine.resume(myCoroutine) as myCoroutine is always nil.

2) Second Attempt - create the coroutine (along with it's function block) within the [Screen Load] Script and call it from the Module and the [PLC Script]. Note that when I instantiate my panel I pass (what I believe to be a global reference to) the coroutine into my Module (see 'Panel Script' below)
    REASON FOR FAILURE: myCoroutine gets instantiated and the PLC Script seems to be able to call coroutine.resume(myCoroutine) just fine but the code
   within my Module that calls coroutine.resume(myCoroutine) (to start the function when the user presses the button) fails complaining that myCoroutine is a
   "bad argument #1 to 'resume' (thread expected)". My best guess here is that at the point my Module is loaded, the coroutine has not been created yet.
   
I've read all the Mach4 General forum threads containing the word 'coroutine' and can't find anything to resolve my confusion.
Any help would be greatly appreciated.
Thanks.

Code: [Select]
-- Screen Load Script
zTouchPlateCoroutine = coroutine.create(function (gCodeString)
local rc = mc.mcCntlGcodeExecute(INST, gCodeString)
coroutine.yield()
if rc ~= mc.MERROR_NOERROR then
error("gcode failed ["..gCodeString.."]", 2)
return "gcode failed", false
else
return "success", true
end
end)
Code: [Select]
-- Panel Script (gets inlined into the Screen Script)
function panelZTouch_Script(...)
    local inst = mc.mcGetInstance()
   
    -- Load the zTouchPlate module
    local profile = mc.mcProfileGetName(inst)
    local path = mc.mcCntlGetMachDir(inst)
    package.path = path .. "\\Modules\\zTouchPlate\\?.lua;"
    package.loaded.zTouchPlate = nil
   
    local ztp = require "zTouchPlate"

    -- Load UI and code to implement this panel
    ztp.create(zTouchPlateCoroutine)
end

Code: [Select]
-- PLC Script
if (zTouchPlateCoroutine ~= nil) and (machState == 0) then --zTouchPlate Coroutine exists and state == idle
    local state = coroutine.status(zTouchPlateCoroutine)
    if state == "suspended" then --zTouchPlate Coroutine is suspended
        coroutine.resume(zTouchPlateCoroutine)
    end
end
Re: Mach4 Coroutine within a Module
« Reply #1 on: March 23, 2020, 01:21:41 PM »
I'll answer my own question in case it helps anyone who comes along later.

The coroutine example code in /Mach4Hobby/LuaExamples/AdvancedExamples/CoroutineExample.mcs demonstrates the use of a coroutine from a button script. The call to the coroutine.resume(co) function is placed in the PLC Script (which is fires every ~50 ms as I understand it). This example, along with the absence of any clarification from the Mach4 Scripting Manual, gives the impression that the PLC Script is the canonical place to resume suspended coroutines. This is not the case. For modules it can and is done differently.

The "Touch Tool" module (/Mach4Hobby/Modules/mcTouchOff.lua) that comes with Mach4 calls coroutine.resume(co) by registering a callback (function) with a wxWidget event named wx.wxEVT_UPDATE_UI on the main UI frame the module creates.

Code: [Select]
UI.frameMain:Connect(
wx.wxEVT_UPDATE_UI,
function(event)
frameMainOnUpdateUI()
event:Skip()
end
)

The callback function (in this case frameMainOnUpdateUI()) is called (by the UI/wxWidgets code) whenever the main frame is "idle". The call to coroutine.resume(co) is placed within this function and is thus called often, resuming the suspended coroutine until it completes. In this case, since the PLC Script is NOT calling coroutine.resume(co) it does not need a reference to the coroutine, which I erroneously thought was required.

In short, I thought the PLC Script was the only/best place to resume suspended coroutines, this is not true for modules.