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
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
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
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 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_TIMEOUT attributes on the “read end” of our pipe. Setting
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
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.