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.