Using KAREL Pipe Files

Filed under: FANUC KAREL Programming

I’m currently working on a project where two separate tasks need to pass data to eachother. How do we do that in KAREL? Enter the pipe PIP: device.

The concept of a pipe has been around since the early 1970s, originating as a crucial part of the Unix operating system. Pipes simply provide a mechanism for data to travel from one process to another.

While the KAREL manual does a pretty good job of describing how pipes work and how to use them, I ran into a couple of issues while implementing the provided example. Here’s a quick tutorial showing some of the issues I dealt with along with a working example of how to use KAREL pipes.


The first thing to know about KAREL READ and WRITE statements is that they make use of an input/output buffer.

While you may want to READ through a file just one-byte at a time, it makes more sense for the system to actually read a bigger chunk of the file into a buffer and then deliver the smaller chunks as you request them. This reduces the amount of I/O overhead on the actual filesystem.

Similarly on the WRITE side of things, anything you WRITE will go into the buffer before it’s actually written to disk. The magical carriage return character CR actually causes the buffer to get written (it also writes when full).

Now back to pipes. You would think that the default behavior of a pipe would be to wait on any READ statements (in fact, that’s what the manual describes), but I found that I had to explicitly set the ATR_PIPWAIT attribute to WAIT_USED for this functionality: SET_FILE_ATR(f, ATR_PIPWAIT, WAIT_USED).

It’s probably not a good idea to have READ statements hanging forever, so you should also set the ATR_TIMEOUT attribute. The value here is in milliseconds: SET_FILE_ATR(f, ATR_TIMEOUT, 1000) -- milliseconds.

It’s good practice to use the IO_STATUS built-in to check how your READ operations did. It’s not documented in the manual, but you will get a status of 282 when a READ times out.

Be careful when using KAREL’s “interactive write” mode (SET_FILE_ATR(f, ATR_IA)). This mode will write the contents of the buffer after each WRITE statement without the carriage return. This seems fine and good until you realize that the default READ behavior is to wait until it sees the end of a line.

I found that my READ statements could hang forever despite specifying a timeout when ATR_IA was used. The READ statement would timeout as expected if nothing was written, but it would hang forever if something was written without that precious CR.

It’s probably smart to just avoid this potential issue by not using interactive write mode, but it seems like you could just try and remember to use format specifiers (e.g. READ f(s::3)) which seems to allow the timeout to work.

So without furthur ado, here’s a WORKING example of writing to and reading from a KAREL pipe:

PROGRAM pipe_test
CONST
	ERR_TIMEOUT = 282
VAR
	pipeWrite : FILE
	pipeRead  : FILE
	cons      : FILE
	status    : INTEGER
	result    : STRING[16]
BEGIN
	SET_FILE_ATR(pipeRead, ATR_PIPWAIT, WAIT_USED) -- force READS to wait
	SET_FILE_ATR(pipeRead, ATR_TIMEOUT, (1*1000))  -- READs timeout after 1*1000ms

	OPEN FILE cons ('RW', 'CONS:')

	OPEN FILE pipeWrite('RW', 'PIP:test.dat')
	status = IO_STATUS(pipeWrite)
	WRITE cons('OPEN pipeWrite status: ', status, CR)

	OPEN FILE pipeRead('RO', 'PIP:test.dat')
	status = IO_STATUS(pipeRead)
	WRITE cons('OPEN pipeRead status: ', status, CR)

	WRITE pipeWrite('foo', CR) -- NOTE: CR is required!
	status = IO_STATUS(pipeWrite)
	WRITE cons('write status: ', status, CR)

	WRITE cons('reading...', status, CR)
	READ pipeRead(result)
	status = IO_STATUS(pipeRead)
	WRITE cons('read status: ', status, CR)

	IF status=ERR_TIMEOUT THEN
		WRITE cons('read timed out', CR)
	ENDIF

	IF UNINIT(result) THEN
		WRITE cons('result was UNINIT', CR)
	ELSE
		WRITE cons('read result: ', result, CR)
	ENDIF
END pipe_test

Let’s go over a few things:

First notice that we have to have two FILE descriptors that open the same file. Open is opening the PIP:test.dat file for writing ('RW') while the other is opening for reading only ('RO'). If you try to open the same file for writing without irst closing it, you will get a FILE-018 “File is already opened” error.

Secondly, we made sure to set the ATR_PIPWAIT and ATR_TIMEOUT attributes on the “read end” of our pipe. Setting ATR_PIPWAIT to WAIT_USED will cause READ operations to hang until the read is complete (when it sees a CR). Setting the ATR_TIMEOUT attribute allows us to handle the condition where our READ statements does not get the expected data.

I defined an ERR_TIMEOUT = 282 constant to check this undocumented status value which only seems to come up when a READ times out.

Lastly I made sure to check if the result was UNINIT before printing it to the console. Otherwise you may get a INTP-311 “Uninitialized data is used” error.

Oh yeah, in case you’re not familiar with the CONS: console device, you can connect to your robot via telnet to see this output. You can also pull it up from the robot’s web page via http://robot.ip/MD/CONSLOG.DG or http://robot.ip/MD/CONSTAIL.DG.

Note that this simple example is obviously just one task, but it would be more likely for you to use this to communicate between two tasks started via the RUN_TASK built-in.

I hope this helps you avoid some of the pitfalls I experienced while using KAREL pipes. Let me know if you have any questions or comments.


Want posts like this delivered right to your inbox?

If you liked this post, please sign up for my mailing list!