Machine Tending strategy

We’re setting up a new UR3e system to place parts on a gage, then place them on a rack.
We have several systems doing similar activities now, but those systems do everything sequentially with a single gripper.

On this one we want to rack the part which has just been measured while the new part is on the gage being measured, so we have it set up with dual grippers.

Since this is a fairly common process, I thought someone might have some suggestions -

Process consists of:

  • Pick the new part up with gripper #1
  • Pick the measured part off the gage with gripper #2
  • Place the new part on the gage
  • Activate the gage cycle (advance an actuator, communicate with gage, retract actuator, repeat a couple times to verify)
  • While gage is cycling, rack the previously measured part.
  • Return to home position to repeat the process.

It’s clear that I’ll have to use at least one thread so that the gage cycle and racking operations can happen simultaneously - does it matter which? Should all or most of the steps be within threads?

I haven’t really worked with threads - several of our existing systems use threads for various things, but they were written by others. Seems like the one or two times I did work with threads a while back, they were kinda finicky.

In my mind, this is still a sequential operation. At least once the robot gets into the full swing. You will always have a slightly different “first run” because there’s nothing in the gage. So here’s how I picture it, ignoring the slightly different first run:

  1. Grab a part
  2. Wait for gage completion
  3. Remove verified part
  4. Load new part
  5. Cycle gage <— Gage operates independently from robot, so you could think of this like a “Thread”
  6. Go rack the gage-fit part
  7. Grab a new part
  8. Wait for gage <— This would be where you “Join” your 2 “Threads” The robot and gage sync back up

So there isn’t ever any actually Threads being used here. Unless of course all of the gage is being driven by the robot too. I’m picturing this as a standalone gage driven by a PLC or something and the robot merely sends it a cycle start and waits for a cycle complete signal from the gage. But if the robot is in charge of firing the outputs for the actuators and stuff, then yeah that WOULD be a thread.

In this case we’re using the robot to control the gage - it’s simple enough that I don’t think it would make sense to split it off to a PLC, and I think our options for communicating with the gage are either a PC or Linux without having to reinvent some wheels.
We need to pull an actual value from the gage rather than just good/bad signals.

Ultimately it may still be better (faster) to run it sequentially with a single gripper, as the gaging cycle time may well end up shorter than the time it takes to pull back, rotate the gripper, and then move back into the gage.
For now, though, we’ll go the dual gripper route.

I think on the “first run” issue, I may still have it try to pick up a part just in case there’s one left there. If there’s not one on the gage, I can have it think about whether there’s supposed to be one or not, and notify the operator if necessary.

OK gotcha! Well then yes, you would want to spin off a Thread to control the gage. The trick I really like using to control threads is to put them in a “Do nothing” loop unless signaled by the main program. That looks like this:

In this way, the thread is always ready to go do work, and you can signal it on/off easily from the main program. Obviously you cannot control the robot from both a thread and the main program, but you should be able to fire outputs and things necessary for the gage routine.

Thanks - helpful stuff.

So I’m continuing to work on this program.
One issue I have with the gage is that it seems to accumulate some readings and other “stuff” in its memory which need to be purged before being able to get the current reading. This stuff seems to show up when the gage is turned on, and other times when it is triggered or has an error message. These seem to just sit in its output queue.
I’m looking to set up a loop which issues read commands to the gage until it’s empty.
That’s pretty straightforward, but I’m struggling trying to get it to move on once all the unwanted strings have been flushed.
Once it’s empty, the robot is sitting waiting for something to come from the gage - I need it to break out of the loop and go on to a new trigger/read command sequence.

I first tried with a script approach, which conceptually would look something like this:

def read_gage():

global read_flag = True


timeout = run gage_timeout()  		#start timer

while read_flag:   			#start loop to read/flush unwanted strings
	sleep(0.5)
	if timed_out = True:
		break
	else:
		global gagestring_x = serialRead("gage1")	#read unwanted string from gage
	end
end
kill timeout

serialWrite("gage1", "M1?)		                        #trigger gage
global gagestring = serialRead("gage1") 	#read the desired output string

end

thread gage_timeout():
sleep(4)
timed_out = True
sync
end

However, I still run into the problem that the controller will be sitting at the first “serialRead” command, and won’t see the break command when the timeout thread actually times out. (serialRead and serialWrite commands are defined in our URCap).

I then tried doing this with Polyscope, thinking that checking the “Check expression continuously” box would allow it to break the loop and move on. However, I get a compile error with this - " break statement not within loop".


image

Again, I think this is something that is pretty common - once I flush the unwanted strings, the controller is just sitting waiting for more, so I need to break out and move on. Not sure of the best way to do that.

Yeah so sounds like you may need to nail down your terminating condition here. How do YOU know when you’ve flushed all the garbage strings out of the gage? Does it eventually just return an empty string when there’s no more messages? If so, you shouldn’t be waiting for some fixed amount of time, you should be waiting until you get that empty string result.

It looks like you are attempting to just call serialRead() somewhat continuously for 4 seconds and just assuming that at the end of this time, you’ve cleared all the trash out. I can’t recall exactly how Wait statements work within Threads… I felt like at a time they may have only controlled the main program? Don’t quote me on that. Regardless, I don’t think you’ll want a “Halt” inside a thread.

I’m a little confused at the script. It seems a tad overly complex? If one thread’s job is just to sit off to the side and count to 4, why not just incorporate that logic into the main thread? You may be falling victim to a scoping issue here where despite being named the same, the variables are defined in different places so they aren’t actually the same in memory.

thread flushGageThread():
  while(flushGage == False): // If I'm not signaling the gage to flush, do nothing.
    sync()
  end
  // If it makes it here, that means I must have signaled for the thread to run
  readCount = 0  // 0 out the count so we start fresh
  while(readCount < 8):  // With a 0.5 second sleep, running 8 times will be executing for 4 seconds
    sleep(0.5)
    global gagestring_x = serialRead("gage1") // Flush a string
    readCount = readCount + 1 // Increment the loop
  end
  flushGage = False // Turn the thread back off so it doesn't continuously call serialRead. You need to signal it again to start.
  sync()
end
threadHandle = run flushGageThread() // Run the thread. Again it won't actually do anything until you write "flushGage" to true

//Somwhere in your main program:
flushGage = True  //<-- This will turn the thread on so it will call serial read for 4 seconds
//Do something (or wait) for gage to report a valid command?

Alternatively, if it’s your/your company’s URCAP, consider writing a fushQueue method in there, or bake it into the existing serialRead() command if it’s truly useless garbage.

As you mention, the terminating condition seems to be the issue.
I’m not seeing anything specific that I can look for.
My intent with the timeout was that I would loop with the read statements, one string per cycle, until the program hangs because it’s waiting for another string, then after 4s it would time out and I could shut down the loop.

Ultimately if I stick with this strategy I’d like that 4s to be much shorter, and the .5s wait between readings to be small or zero - these values just give me some time to watch the Variables page & see what’s happening as it runs.

I still wonder if/how I could get that read command to stop waiting for an input (without shutting down the whole program) to let me trigger the gage for the string I want.

On the older gages, there are separate “start measurement”, “stop measurement” and “get measurement” command strings, so we issue an “F2” and a read, then an “F3” and a read, then finally an “M” and a read, which gives us the value we’re looking for.
The newer gages don’t have the start & stop commands, which probably serve to handle the flushing. Those were RS232 interfaces, while the new ones use USB with a smart cable.

The other option I’m exploring is getting a flush routine defined within the URCap - one of our young programmers actually wrote the URCap to communicate with the USB connection, and he had started on a flush function, but it isn’t working, so I’m trying to get him to look at that again. He’s in another division a couple hours from here, so it’s a bit more difficult to track him down.

I may also try reconnecting the gage to my laptop and looking at the strings to see if there’s something that I can consistently use to tell me it’s done, but I doubt it.

I might be able to get some more info from the manufacturer (Mahr), but my primary contact doesn’t seem to knowledgeable about the specifics of the communication protocols.

Ah I see. That makes more sense. l would definitely try to get in touch with the CAP writer and have him either work on the flush function, or else change the way the serialRead() command is working. I was under the impression that this command was capable of returning an empty string. But it seems like the serialRead() command itself is waiting infinitely for something to be there. If I were writing it, I would definitely allow it to return an empty string, since this would allow me to check for that in the robot program exactly like you’re trying to do:

readString = "NotAnEmptyString"
While(readString != "")
readString = serialRead()
sync()

Assuming you allow serialRead() to return an empty string when the gage no longer has anything left in its buffer, this is all you’d need to do to flush the stream.

Good point. That programmer will probably be coming down in the next few days to work on this. I’ll pass that along as an option in case he can’t get the flush routine to work.

If serialRead() function just blocks when there is no more to read, then thread running that function can be killed to interrupt it.
This is how “check expression continuously” works in polyscope GUI.

In following example background thread is started that reads messages from gage, and communicates back to the main thread
that messages are still available. When it will be stuck at serialRead() for more than half second then main thread will kill it, and move to send an M1? message.

global flush_counter = 0
thread flush_gage_buffer():
  while True:   			#start loop to read/flush unwanted strings
    serialRead("sog")	#read unwanted string from gage
    flush_counter = flush_counter + 1
  end
end
prev_flush_counter = flush_counter
flush_gage_buffer_th = run flush_gage_buffer()
sleep(0.5)

# each individual read should not take more than 0.5 seconds, so loop can tell that 
# background thread is still reading buffered messages
while(prev_flush_counter != flush_counter):
  prev_flush_couter = flush_couter
  sleep(0.5)
end
kill flush_gage_buffer_th

serialWrite("sog", "M1?")		                        #trigger gage
global gagestring = serialRead("sog") 	#read the desired output string

mmi - thanks for looking at this.

I’ve been trying your code out, but still having issues, which are likely related to the details of how the gage actually stores & releases data.

I took your code, and made the following adjustments:

  1. pulled the thread out of the middle and put it at the end
  2. The rest of the code is within a function – “read_gage_test”
  3. the gage ID parameter passed to the serial read & write functions is actually “sog” rather than “gage1”
  4. I placed an “end” statement between the sleep and kill statements, as otherwise the write/read sequence was repeating (sometimes indefinitely, sometimes stopping after 10 - 50 cycles).
  5. Added the gage_write_count variable to track the write/read sequence.

So, here’s my version of your code:

def read_gage_test():

global flush_counter = 0
global gage_write_count = 0

prev_flush_counter = flush_counter
flush_gage_buffer_th = run flush_gage_buffer()
sleep(0.5)

/# each individual read should not take more than 0.5 seconds, so loop can tell that
/# background thread is still reading buffered messages

while(prev_flush_counter != flush_counter):
    sleep(0.5)
end	                   # ** inserted to end the while loop here

kill flush_gage_buffer_th

serialWrite("sog", "M1?")		         #trigger gage

gage_write_count = gage_write_count + 1    #***** track progress here

global gagestring = serialRead("sog") 	#read the desired output string

end

thread flush_gage_buffer():
while True: #start loop to read/flush unwanted strings
serialRead(“sog”) #read unwanted string from gage
flush_counter = flush_counter + 1
end

end

and here’s the Polyscope program:

image

Questions:

  1. Is anything I did above different from your intent with that code?
  2. Should there be a sync() within the thread? - I’ve tried with and without, doesn’t seem to make a difference.

Unfortunately, when I run this program, I still don’t get the expected results:

If the gage buffer is empty when I start this program, the flush_counter is zero and the gage_write_count variables goes to 1, but I don’t get anything in the gagestring variable (it’s not even created in the Variables window), and the program still hangs. Multiple write commands don’t seem to make a difference.
I had a slightly different version of this code which would give me values - not always the string I was looking for, which depended on me using the gage’s trigger button before running the program - I’ll try to remember what that code version looked like to get a better picture of what’s happening.
I also occasionally get an error which says “Unable to transport XML to server and get XML response back” with the URCap info attached.
It’s looking like there are details within the gage and URCap which still need to be fleshed out.
I’m waiting for ourinternal URCap programmer to get back to me, and hopefully I’ll hear from the Mahr tech guy in Germany - was apparently out on vacation last week, hopefully is back this week.

So here’s the version which seems to work, combined with a physical trigger.
I added a couple counters to help track the program flow, and made the timers all variables.

Key to solving this was to use an output from the controller to trigger the footswitch input on the gage, which will release the serialRead() function once it hangs when no more data is received. There might be a way to do that within that URCap function which would make this kinda clunky approach unnecessary, so I’ll keep pushing our programmer to look into that.

I’m still playing with the timeout & trigger pulse times - looks like they can be pretty short.

Hopefully this will continue to work correctly once I put it inside the thread which will activate the rest of the gage process. (advance & retract an air cylinder a few times & average a few readings).

# Flush gage buffer and pull current gage value

def read_gage_test():

global flush_counter = 0
global gage_write_count = 0
global flush_loop_ctr = 0
global gage_timeout = 0.2
global trigger_pulse = 0.1
global write_wait = 0.25
global flush_max = 3


global flush_prev_counter = flush_counter
global flush_gage_buffer_th = run flush_gage_buffer()
sleep(gage_timeout)

/# loop flush_max times if gage is not empty on first try

while(flush_prev_counter != flush_counter):  
    sleep(gage_timeout)
    flush_loop_ctr = flush_loop_ctr+1
    if flush_loop_ctr > flush_max:
	flush_counter = 0
    end		# end if
end		#end while 

/# Stop thread
kill flush_gage_buffer_th

/# Trigger gage (footswitch) - needs 2X for some reason
sleep(trigger_pulse)
set_standard_digital_out(6,True)
sleep(trigger_pulse)
set_standard_digital_out(6,False)
sleep(trigger_pulse)

serialWrite("sog", "M1?")		         #trigger gage
sleep(write_wait)

gage_write_count = gage_write_count + 1    	#track progress here

global gagestring = serialRead("sog") 		# read the desired output string

end # end read_gage function

# Thread to flush gage buffer

thread flush_gage_buffer():
while True: #start loop to read/flush unwanted strings
serialRead(“sog”) #read unwanted string from gage
flush_counter = flush_counter + 1
end
sync()
end #end thread

I’ve actually made a mistake in the code (except missing “end”) - forgot to save flush_couter in prev_flush_counter in while loop.
Your code is similar, it just waits fixed amount of time.
I have possibly simpler suggestion:

global flushing = True
thread flush_gage_buffer():
  while True:   			#start loop to read/flush unwanted strings
    serialRead("sog")	#read unwanted string from gage
    flushing = True
  end
end

# initialize to True every time before starting the thread
flushing = True
flush_gage_buffer_th = run flush_gage_buffer()

# each individual read should not take more than 0.5 seconds, so loop can tell that 
# background thread is still reading buffered messages
while(flushing):
  # if  background thread is still looping then it will reset it to True within half second, otherwise if it
  # is blocked on serialRead then flushing will stay False, and while loop will exit
  flushing = False
  sleep(0.5)
end
kill flush_gage_buffer_th

serialWrite("sog", "M1?")		                        #trigger gage
global gagestring = serialRead("sog") 	#read the desired output string