Hello Guest it is December 06, 2019, 02:43:09 AM

Author Topic: Script with a short pause in the middle (non-blocking)  (Read 289 times)

0 Members and 1 Guest are viewing this topic.

Script with a short pause in the middle (non-blocking)
« on: March 14, 2019, 11:48:21 AM »
Interesting question here...

I am writing a communications script (serial) to some servo drives. I want to abstract the communications so I can use a call like this anywhere in mach.

CurrentPosition = DriveCommunications (Position), where position is a parameter code to be read from the drive.

When DriveCommunications() is called, it assembles an ASCII string complete with a checksum and writes it over the serial port. It then analyzes the response, checks for errors, and returns the result.  The whole thing is a bit more complicated, but that's the just of it.

Once a transmission is sent to the drive, a brief delay (a couple milliseconds) must be enacted to allow the drive to respond before reading the serial buffer. I successfully added a delay using wxTimer for a different script, but that calls a new function which makes returning a value from DriveCommunications() impossible AFAIK. I could also just use a plain old delay, but I don't want to block other functions from executing during this time. I am hoping to get this to the point where I can stream data from the drives and not interfere with other Mach functions.

My question is, is there a way to break out of the middle of a function, but return to execute the remaining code after a short while?

Offline Chaoticone

*
  • *
  •  5,633 5,633
  • Precision Chaos
    • View Profile
Re: Script with a short pause in the middle (non-blocking)
« Reply #1 on: March 14, 2019, 12:13:35 PM »
Check out the coroutine in the ref. manual. http://www.lua.org/manual/5.3/manual.html#6.2
 
In the default wx4 or wx6 screens check out the Ref all button script, RefAll function in the screen load script and the bit that resumes it in the PLC script.
;D If you could see the things I have in my head, you would be laughing too. ;D

My guard dog is not what you need to worry about!
Re: Script with a short pause in the middle (non-blocking)
« Reply #2 on: March 14, 2019, 01:59:25 PM »
Wow, that is powerful.

Here is what I am thinking, simplified for readability

Code: [Select]
---------------------------------------------------------------
-- Screen Load Script --
---------------------------------------------------------------
--Create a Timer which resumes the co-routine when expired

Panel = wx.wxPanel (wx.NULL, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize ) --Starts a Timer to handle drive response
Timer = wx.wxTimer(Panel)
Panel:Connect(wx.wxEVT_TIMER, function(event)
coroutine.resume(DriveComm) --Restart Communications without giving new parameters, we want the old ones to persist
end)

DriveComm = coroutine.create(DriveCommunications(a, b, c, d)

    local saveA = a   --We want to save the parameters passed into the function so they persist after the Yield and Resume
    local saveB = b
    local saveC = c
    local saveD = d

    Calculate transmission

    Send Transmission

    Timer:Start(5) --Start a 5ms timer

    coroutine.yield(U3K) --pause script execution, coroutine becomes suspended

    Read Drive Response

    return data1

    return data2

end )


So I can simply call the coroutine/function by adding the code below to anything like a signal scrpt, plc script, button script, etc. The coroutine should be created in a suspended state at the top of the function.

Code: [Select]
done, data1, data2 = coroutine.resume(DriveComm, a, b, c, d)


It sounds like once you reach the end of the coroutine, it becomes dead and cannot be called again. This is not ideal since I want to periodically call this as long as it is not already running. How do you restart a dead coroutine? Online references aren't yet clear to me about this.

Thanks!
« Last Edit: March 14, 2019, 02:03:06 PM by mcardoso »

Offline Chaoticone

*
  • *
  •  5,633 5,633
  • Precision Chaos
    • View Profile
Re: Script with a short pause in the middle (non-blocking)
« Reply #3 on: March 14, 2019, 03:33:12 PM »
Just check to see if it exist. If not, create it again just as you did earlier. You need to test it but that should be pretty close.

Code: [Select]
local state = coroutine.status (DriveComm)
if (state == "dead") then
    DriveComm = coroutine.create(DriveCommunications(a, b, c, d)
end

Or, as in the PLC script.......
Code: [Select]
if (DriveComm == nil) then
;D If you could see the things I have in my head, you would be laughing too. ;D

My guard dog is not what you need to worry about!
Re: Script with a short pause in the middle (non-blocking)
« Reply #4 on: March 15, 2019, 03:07:06 PM »
Chaoticone,

Thanks for the thoughts! I would prefer to keep the resetting of the coroutine out of the PLC script since I would like to reserve that for operations which MUST be on a periodic task. This would also avoid delays between when the coroutine becomes dead and when the PLC script runs. Two questions:

1) Can the first chunk of code you posted previously be placed in the ScreenLoad script? Will it detect the change in coroutine state there?

2) Can the function called by the coroutine be defined separate from the creation of the coroutine?  For example, is this allowed?

Code: [Select]
function DoThis(x)
--stuff here
end

placeholder = coroutine.create(Do this(x))


Offline Chaoticone

*
  • *
  •  5,633 5,633
  • Precision Chaos
    • View Profile
Re: Script with a short pause in the middle (non-blocking)
« Reply #5 on: March 15, 2019, 04:38:28 PM »

1) Can the first chunk of code you posted previously be placed in the ScreenLoad script? Will it detect the change in coroutine state there?


Yes and no. The code could be run in the screen load script as a function. However, something has to call that function when needed.

Quote
2) Can the function called by the coroutine be defined separate from the creation of the coroutine?  For example, is this allowed?

Absolutely, that is exactly how the ref all home button and function in the screen load script work. I did that for an example so others could see at least one way to use a coroutine.
;D If you could see the things I have in my head, you would be laughing too. ;D

My guard dog is not what you need to worry about!
Re: Script with a short pause in the middle (non-blocking)
« Reply #6 on: March 25, 2019, 09:21:53 AM »
Well ok... I went off on a spiritual soul searching (learning coroutines) journey and I am back... With a few questions.

Long term goal: Write and read serial data to Ultra 3000 servo drives over RS485. Use data for Mach 4 functions like spindle load monitoring, absolute homing, etc.

I have gotten serial communications working and when I get this whole thing perfect I will make a write-up for anyone who wants to use ASCII serial. I have gotten coroutines working, but I am a little confused on how to handle the code structure.

Here is what I want (pseudo code)

Code: [Select]
------------------------
--PLC Script, 50ms --
------------------------
myTorque = servoGetTorque(address)
>>Copy torque data to register to use for on screen bar graph


------------------------
--Screen Load Script --
------------------------
wxTimer function()
    >>resume coroutine
end

appendChecksum()
    >>appends a checksum to the serial string for transmission
end

coroutineFunction()
    >>append checksum to transmission string
    >>Transmit String
    >>Start Timer, roughly 5ms
    >>Yield Coroutine
    >>Coroutine resumes here
    >>Read serial buffer
    >>Check data checkum
    >>Return data
end

function servoGetTorque(address)
    >>Create transmission string
    local state = coroutine.status (DriveComm)
    if (state == "dead") then
        >>create coroutine, pass in transmission string
    end
    Return torque data when entire coroutine is finished
end

The one problem I haven't yet figured out is that the coroutine returns when it is suspended. So if I were to use the above function, it would return after the transmission. The second half of the code (reading the serial buffer) returns to the background timer resume coroutine call. This makes getting the data myTorque impossible, because when servoGetTorque() returns, we haven't yet read the serial port.

I really want to abstract all the communications to calls like myTorque = servoGetTorque(address), so can anyone suggest how to handle the coroutine returning to a different location.
Re: Script with a short pause in the middle (non-blocking)
« Reply #7 on: March 25, 2019, 09:32:09 AM »
If the above post is not clear, let me know and I will try to explain better.