Inside Bill's Brain

This web page is a collection of email messages from Bill Gribble and others concerning the machines in the UTCS Robot Lab and use of the wheelchair and other robots.

Index


Checking out and compiling the argus source code

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: "Micheal S. Hewett" <hewett@cs.utexas.edu> Subject: Re: Significant changes to SPLAT in CVS References: <87d7tbk6g9.fsf@flophouse.localnet> <38306FC9.15361C40@cs.utexas.edu> From: Bill Gribble <grib@cs.utexas.edu> Date: 15 Nov 1999 14:53:06 -0600 You need to tell CVS to use ssh, not rsh (rsh is such a huge security hole that we uninstalled it): export CVS_RSH=ssh cvs update -Pd b.g.
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: robotics@cs.utexas.edu Subject: Bugs in knowledge base fixed From: Bill Gribble <grib@cs.utexas.edu> Date: 01 Dec 1999 09:51:19 -0600 [...] We've upgraded the compiler recently, so you may need to 'make clean' before building to avoid complaints about missing system header files. b.g.
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: robotics@cs.utexas.edu Subject: flat-interface & midline follower for splat in CVS. From: Bill Gribble <grib@cs.utexas.edu> Date: 14 Nov 1999 13:29:37 -0600 Getting the code ---------------- You need to have SSH set up so that you can log into argus without a password. If you don't know how to do this, send me email. If you already have an argus source tree checked out: $ cd ${argus_top_level} $ cvs update -Pd If you don't have an argus source tree checked out: $ export CVSROOT=argus.csres.utexas.edu:/usr/src/CVS $ cvs checkout argus:all Building the code ----------------- You need to build a couple of servers to run SPLAT. It's not too hard. This should work without a hitch on lab machines, but we require fairly up-to-date versions of g++ (2.95.2 or better) and we use threads heavily so I don't know how well it will compile on CS machines. 1. Edit ${argus_top_level}/Makefile.defs There's a line near the top that should say SOURCE_ROOT=/home/grib/devel/argus Change that to be the top level of your argus source directory (the directory that Makefile.defs is in). 2. Build the 'kbase', 'dobjects', and 'corelib' modules. $ cd kbase $ make all corelib and dobjects will get built automatically (there's a module dependency system). Running the code ---------------- I have provided a file called test-flat.scm in the splat/ directory which shows off the midline following control law. 1. Run flat and the java display. Mike has good instructions for this on his web pages. note the port number that Flat is listening for connections on (it's usually 1069). 2. Run the distributed object name-server/router (I suggest you open a new xterm for this): $ cd ${argus_top_level}/dobjects $ ./switchboard --port 14000 3. Run a knowledge base server (I suggest a new xterm for this also). $ cd ${argus_top_level}/kbase $ ./kb-server --swb localhost:14000 If you are running the kb-server on a machine different from the one you ran switchboard on, substitute the switchboard host name for 'localhost' in the --swb argument to kb-server. If you wish to load a default knowledge base, use the argument --kb <kb-dump-name> to load it. 4. Launch rscheme and load the test-flat file. $ cd ${argus_top_level}/splat $ ../corelib/argusrs.devel test-flat.scm You'll see it loading a bunch of support files, compiling several SPLATs, and doing some other stuff. 5. Go for it. top[1]=> (test-flat-midline-following <speed-in-m/s> <switchboard-port> <flat-server-port>) for example, top[1]=> (test-flat-midline-following 0.50 "localhost:14000" "localhost:1069") NOTES: ------ ******************************************************************* The wheelchair's coordinate system is different than the one used natively in Flat! ******************************************************************* I handle the translations from flat's coordinates automatically in the <flat-client> class, but when you are writing new control laws you MUST USE THE NEW COORDINATE SYSTEM, because you're talking to a level of interface that only knows wheelchair coordinates. It is: - positive X axis along the robot's direction of travel - positive Y axis sticking out the robot's left ear - positive angles counterclockwise when viewed from above - displacements in METERS, velocities METERS/SEC, angles RADIANS, angular velocities RADIANS/SEC. The midline-following controller is in the file follow-hallway.scm. It implements a "little red wagon" controller as we discussed in the meeting last week. Right now, its termination conditions are rudimentary and there's no safety-expert or other sanity checker. Nevertheless it works pretty well. I'll be modifying it more in the next few days. The flat client interface is in flat-client.scm. <flat-client> and <wheelchair-client> are subclasses of <mobile-robot>, and their interfaces are the same, so if you substituted a (make-wheelchair-client) for the (make-flat-client) in test-flat.scm you would be controlling the wheelchair with the same splat. Enjoy, Bill Gribble
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: "Micheal S. Hewett" <hewett@cs.utexas.edu> Subject: Re: argus compile errors From: Bill Gribble <grib@cs.utexas.edu> Don't [compile] on argus. You are bound to be in some pain. You should never log into argus or janus... they has about as much power as a pocket calculator and are just being used as CVS server and firewall. The problem on shadow is that compile tools aren't synced as they should be with the rest of the machines. We use automatic dependency generation (look for files called foo.d), which generates references to system and local header files. Since your home dir shows up on multiple machines, if the paths to system header files aren't the same (they aren't if the compiler version is different) you have problems. Try doing a 'make clean' to get rid of the .d files, and start over. Compiling this stuff takes a long time. We have tried to streamline it as much as possible, but plan for a build from scratch of corelib, dobjects, and kbase to take 10-15 minutes on the fast machines. Please try again [...], after a 'make clean', on glare or eclipse and let me know if it still breaks. b.g.

Interface between a Planner and the SPLAT level

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu Subject: Generating splats from planner output From: Bill Gribble <grib@cs.utexas.edu> Date: 23 Apr 1999 12:11:53 -0500 Emilio, After our conversation Wednesday I've written a sample of what I think planner output might look like given the architecture we discussed. It includes a number of new splat primitives that aren't implemented yet, but aren't too difficult. The basic concept is that the LISP process starts the SPLAT scheme process via whatever the Common LISP system() equivalent is, and keeps an input-port and an output-port corresponding to the Scheme process' standard output and standard input, respectively. The Lisp process prints Scheme forms to the Scheme process, and the Scheme process prints Lisp forms back to the Lisp process. The Lisp process needs to periodically read from that port, process the input, and perhaps write a response. The lisp->scheme interface is going straight to the Scheme reader, so you can send any valid Scheme form and do what you want with the output. There is a simple API for doing the main function (creating and running plans) which will have predictable output and will be easier to deal with than if you send arbitrary scheme code. (make-plan-splat <id> <body>) Special form which does the following: - create a single-method splat (the method consisting of <body>) with no preconditions or fancy stuff, ready to run. - associates <id> with the splat for future input/output tagging. <id> can be a symbol, a number, anything that can be an association list tag. (start-plan-splat <id>) starts the specified splat running. (abort-plan-splat <id> <retval>) causes the splat to exit immediately and return <retval>. When significant things happen, the splat can spit out forms on its output-port. These will be valid lisp lists, which the lisp reader should read and process as it wishes. There are two main kinds of output that the splat side can generate: splat-status-report and splat-replan-request. splat-status-report forms come periodically to let the lisp side know what's going on in the plan. They can be safely ignored, except that you want to keep looking for a 'completed-task report so you know you can go on to the next thing. (splat-status-report <id> 'in-progress) (splat-status-report <id> 'completed-subtask 1) (splat-status-report <id> 'completed-task #t) ; for success (splat-status-report <id> 'completed-task #f) ; for failure When the splat needs a new plan, it can ask for one: (splat-replan-request <id> <task>) where <task> is a string that contains whatever information the LISP planner needs to figure out what plan to generate. Since the planner generates the code which asks for the replan, it can embed its own internal representation of the task or goal in the string. In the following example, (replan-and-resume) is a special form which generates the splat-replan-request with the string ":go :here taylor-outside-door" as the <task>. It will expand to the splat that is generated by the planner. Here is what should be sent by the planner to the Scheme reader to instantiate a plan for traveling from the robot lab out of Taylor Hall. The (reference-shared-object <tag>) forms create data connections to the shared knowledge base that we discussed. The path specifiers are arbitrary, but I conceived of it as being knowledge-base::context::identifier I discussed with Ben how the Algernon knowledge base could be taught to use the shared knowledge base for a subset of its knowledge (those things which are changed or used by the Splat engine); I'll send another email with a summary of that discussion. (simple-splat (let ((elevator (reference-shared-object "objects::taylor-hall::4th-floor-elevator")) (door (reference-shared-object "objects::taylor-hall::robot-lab-door")) (hallway (reference-shared-object "paths::taylor-hall::4th-floor-hallway"))) (sequence-actions (go-thru-doorway ;; door to go through door ;; meters after door to stop 1.0) (follow-hallway-until-near-object ;; which hallway hallway ;; which direction 'positive ;; which object elevator ;; how near when it's time to stop (meters) 4.0) (enter-elevator elevator) (travel-in-elevator elevator 'down 2) (replan-and-resume ":GO :HERE TAYLOR-OUTSIDE-DOOR")))) The plan primitives in use are go-thru-doorway <door> <terminate-distance> Visually locate the specified door, trave through it, and stop when you are <terminate-distance> meters past the center of the door. follow-hallway-until-near-object <hallway> <direction> <object> <distance> Travel the specified direction down the hallway until within <distance> meters of <object>. enter-elevator <elevator> duh. travel-in-elevator <elevator> <direction> <number of floors> duh, again replan-and-resume <goal-spec> This is the interesting one. it spits a splat-replan-request to the Scheme process' output, waits for a new (make-plan-splat ) to come across, and then continues executing the plan. The <goal-spec> is whatever data the planner wants in order to be able to make the plan. Using a combination of laser rangefinder and vision information these plan primitives are within the grasp of our current software framework, though they will all require significant work to realize. The internal structure of these primitives doesn't need to be visible to the planner, but I'll be happy to discuss how they will work. Thanks, Bill Gribble

SPLAT control laws available

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: robotics@cs.utexas.edu Subject: Significant changes to SPLAT in CVS From: Bill Gribble <grib@cs.utexas.edu> Date: 15 Nov 1999 11:31:02 -0600 - Removed a bunch of archaic files from the archive to avoid confusion when you're poking around the splat code. use 'cvs update -Pd' and remove any files that CVS warns you are "no longer pertinent". - Changed follow-hallway control law a bit. Made the "wagon handle" in the little-red-wagon controller proportionate in length to the robot's travel speed. This is based on the "highway driving insight": the faster one drives on the highway, the farther ahead one looks to adjust steering. - If you want to write test SPLATs for flat, there are a couple of working rangefinder-based and open-loop control laws: (follow-hallway-to-end robot travel-speed) Get this by adding (depend 'follow-hallway) to your file. (turn-in-place robot angle speed grab-mutex?) Get this by adding (depend 'turn-in-place) to the top of your scheme file. Pass #t to grab-mutex if you have not acquired access to the robot's controller before calling turn-in-place. You get the mutex by calling (semaphore-wait (base-control-mutex robot)) See follow-hallway.scm for an example. (drive-distance/speed robot distance speed grab-mutex?) This is an open-loop drive command. No sensors at all (except odometry) You can get it by (depend 'follow-hallway) also. - Changed/added SPLAT syntax: (parallel-actions <a1> <a2> ...) has been renamed to (parallel-actions/all <a1> <a2> ...), and a new syntax form (parallel-actions/any <a1> <a2> ...) has been added. (parallel-actions/all (wait-a-while 2) (wait-a-while 4) (wait-a-while 6)) will take 6 seconds to complete; (parallel-actions/any (wait-a-while 2) (wait-a-while 4) (wait-a-while 6)) will take 2. Of course (sequence-actions (wait-a-while 2) (wait-a-while 4) (wait-a-while 6)) will take 12 seconds. New syntax form (with-predicate-true <pred> <action>) has been added at Emilio's request: (with-predicate-true (predicate/vars <fluent-list> <body>) <action>) Evaluates the splat <action>, terminating <action> if the predicate defined in <body> ever becomes false. <body> is evaluated at startup, and any time the value of a fluent in <fluent-list> changes. Example: (let ((continue? (make-splat-fluent #t eq?))) (with-predicate-true (predicate/vars (continue?) (get-fluent continue)) (parallel-actions/all (wait-a-while 10) (sequence-actions (wait-a-while 5) (set-fluent! continue? #f))))) will run 5 seconds and then be terminated when continue? is set to #f. A similar syntax form, (repeat-until-true <pred> <action>), has been around a while but isn't documented: (repeat-until-true (predicate/vars <fluent-list> <body>) <action>) Repeatedly evaluates the splat <action> until the predicate defined in <body> becomes true. <body> is evaluated at startup, and any time the value of a fluent in <fluent-list> changes. Example: (let ((loop-count (make-splat-fluent 0 eq?))) (repeat-until-true (predicate/vars (loop-count) (eq? (get-fluent loop-count) 10)) (sequence-actions (wait-a-while 1) (set-fluent! loop-count (+ 1 (get-fluent loop-count)))))) will take 10 seconds to run. - Lots of bug fixes. b.g.

Reading Laser Rangefinder data

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: robotics@cs.utexas.edu Subject: Added get-current-ranges-right/get-current-ranges-left to <mobile-robot> From: Bill Gribble <grib@cs.utexas.edu> Date: 15 Nov 1999 12:49:48 -0600 At Emilio's request, I added functions to get the current rangefinder readings. These are available on both <flat-client> and <wheelchair-client>. (get-current-ranges-right <mobile-robot>) (get-current-ranges-left <mobile-robot>) b.g.

Wheelchair bounding box

From: Mike Hewett Date: Tue, 28 Dec 1999 15:03:22 -0600 The maximum bounding box, with all the wheels splayed out is 1.3081 x 0.847725 (meters) We also need the location of the origin of the wheelchair reference frame in this box. As near as I can determine, it is located as shown in the diagram below: (front) 0.847725 ____________________________ | | | | | | | | | | | | | | | 0.4238625 | | ........... X | | . | 1.3081 | . | | . | | . | | . | | . | | . | | . 0.6604 | | . | | . | | . | | . | | . | ____________________________ I don't know what it should be called. How about something like: Tinman::bounds width 0.847725 height 1.3081 front 0.6477 back 0.6604 left 0.4238625 right 0.4238625

Calibrating the Laser Rangefinders

If the laser rangefinders are moved, their locations and angles relative to the robot need to be determined and added to the knowledge base. Below is one way to do it. Note that the wheelchair's coordinate system has its origin at the central pivot point of the wheelchair, its X-axis projects forward and its Y-axis projects to the left.

It is important for these measurements to be very accurate. We need about two decimals of precision in the angular measurements.

  1. Using tape or chalk, outline a rectangle on the floor to contain the wheelchair. The rectangle's bounds are described above. Include a vertical line from the front of the wheelchair to the center. You need at least a few meters of space in front of the wheelchair. Make sure you know whether the actual boundary is the front, center or back of the line.
  2. Position the wheelchair in the rectangle.
  3. Using a string with a weight on it, mark a spot on the floor directly under the central axis of each rangefinder.
  4. Position a couple of thin objects a couple of meters in front of the wheelchair. Each one should be on a line from the wheelchair's origin through one of the rangefinder locations. Use a long string to position the objects.
  5. Take a scan from each rangefinder to locate the object in front of it. The rangefinder reading has an (r, theta) value. Convert the reading into a coordinate system where zero degrees is straight forward from the rangefinder and angles increase to the left.
  6. Move the wheelchair and, using the marks on the floor, find the location of each rangefinder, as (x, y) and (r, theta) in the wheelchair's coordinate system.
  7. The rangefinder's angle relative to the wheelchair's coordinate system is (theta(rangefinder) - theta(reading)).
  8. Store these values in the kbase (argus/kbase/robot-lab-kb.dump) in the variable 'calibration::laser-rangefinders':
  9. Make a sanity check on the values:
  10. Check in the revised kbase.
If these values are incorrect, the robot will not be able to follow the wall at a specific set-point. It will be off by a few hundred millimeters. Also, the Flat laser display will have a parallax effect or a slight rotation if the rangefinder angles in the .flat file do not correspond to the actual angles. Mike Hewett 13 Aug 2000
Subject: notes on laser segment display Date: 22 Dec 1999 14:29:00 -0600 From: Bill Gribble <grib@cs.utexas.edu> To: robotics@cs.utexas.edu Since extract-min-max-segments can be called with a variety of arguments, I had to pick a set of arguments to use when calling it in the dashboard program. Which is to say that the line segments being extracted may not be the ones that you are seeing if you are using different arguments than dashboard does. Dashboard gets its arguments from the knowledge base so that you can tune the segment extraction parameters by hand while watching the display interactively. The frame that holds the arguments used is called runtime-state::display::segment-params. Below is a printout of the frame showing what the default values are and what the names of the slots are. vulcan> ./kb-tool --swb localhost:14000 print-frame runtime-state::display::segment-params Frame runtime-state::display::segment-params k-colinear-range ==> 0.10 k-colinear-theta ==> 0.05 k-discontinuity-thresh ==> 0.10 k-squid-std-dev ==> 0.02 k-squid-winsize ==> 4 Use kb-tool to assign new values to the slots and the new values will be used the next time the dashboard display is updated. b.g.
Subject: new rangefinder interface for segments Date: 21 Dec 1999 14:39:47 -0600 From: Bill Gribble <grib@cs.utexas.edu> To: robotics@cs.utexas.edu I've finished porting the segment-extraction code to run in C++. It works on test data, but I need to test it on the real rangefinders before handing it over to you. I just want to let you know what the interface is so you can start modifying your code. The wheelchair-client (and flat-client after this afternoon) have a new method called "extract-min-max-segments". Its arguments are: (extract-min-max-segments min-angle ;; start of the window to examine (radians, 0 is ahead) max-angle ;; end of window (both are in wheelchair frame of reference) k-discontinuity-thresh ;; threshold range-diff (in meters) ;; for discontinuity betw. adjacent pts k-squid-window-size ;; window size in samples for squid binning k-squid-window-std-dev ;; std deviation for qualitative slope ;; (slope mag. < std dev are UNKNOWN) k-colinear-range-thresh ;; threshold of range difference for two ;; segments to be combined as colinear k-colinear-theta-thresh ;; threshold of segment angle for colinear ;; segment combination ) It returns a vector of <sick-segment-info>, where (class <sick-segment-info> (<object>) (seg-range) ;; range in meters to intersection of fit line and ;; perpendicular segment from origin (seg-theta) ;; orientation of segment (in radians) (0 is straight ahead) (seg-sigma) ;; standard deviation of samples in segment window (p-min) ;; a <sick-scan-point> representing the minimum-theta end of the ;; segment (p-max) ;; a <sick-scan-point> representing the maximum-theta end of the ;; segment ) In case it's not clear, the segments are represented by the r-theta representation of the line that best fits the segment data (using a least-squares approximation) and the two endpoints of the segment. Two colinear segments will have the same r and theta, but different endpoints, so merging colinear segments (which is done automatically) is just a matter of comparing the r and theta of the extracted segments. I'll send another email when this stuff is checked in to CVS. b.g.

Coda file system problems

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: "Micheal S. Hewett" <hewett@cs.utexas.edu> Subject: Re: Wheelchair stuff From: Bill Gribble <grib@cs.utexas.edu> Date: 30 Nov 1999 07:32:19 -0600 "Micheal S. Hewett" <hewett@cs.utexas.edu> writes: > Emilio and I couldn't run the dashboard today because > the system couldn't find libimage and libvideo. Maybe > he needed to update his argus files... This is a symptom of Coda getting full. I'll reset it. [...] b.g.

Resetting the wheelchair internals

> Also, I pushed the Reset button on the Tupperware box > once in order to stop the wheelchair and after that > we could never get the wheelchair to move, although all > the sensor data was coming through. How do we recover > from pushing Reset? Never push the reset button. It's a pain to get back to a running state. I'll show you how to do it at the meeting. The 100% sure way to stop the wheelchair is to (1) turn the green button off, then (2) kill the tinman-server process.
Return-Path: <grib@cs.utexas.edu> From: Bill Gribble <grib@cs.utexas.edu> To: robotics@cs.utexas.edu Subject: loading software on the tupperware box. Message-Id: <E11stwD-0006gq-00@glare.csres.utexas.edu> Date: Tue, 30 Nov 1999 14:31:49 -0600 In case anyone should ever happen to accidentally reset the tupperware box, or if (as has happened sporadically in the past) the tupperware should stop updating its odometry after it's been up for several days, here's how to reload the software. 1. Log in to vulcan. 2. Look in the argus/robots/tinman directory. You need a downloadable object file called 'tinman-onboard-server'. If it doesn't exist, build it. You have to put the Tupperware compiler in your path first: $ export PATH=${PATH}:/usr/local/arc/linuxbin $ cd .. $ make tinman-onboard-server $ cd tinman 3. Reset the Tupperware to its factory default program. You have done this if the green LEDs marked "led/tpu0" thru "led/tpu3" are flashing sequentially in a pattern best described as "Cylon eye" (cf. Battlestar Galactica). If you don't see this pattern, press the red Reset button. If that doesn't work, turn the "computer" and "sensor" switches off (in that order) and back on (in the reverse order), then press the Reset button. 3. Launch "arc", the tinman interface program. It takes one argument, the serial port to use (the Tupperware box is connected to /dev/ttyS1). NOTE: arc screws up the terminal interface something terrible, so you always want to run it exactly as follows so that your terminal gets reset automatically: $ export PATH=${PATH}:/usr/local/arc/linuxbin $ arc -port /dev/ttyS1 ; stty sane 4. Now you're in a shell talking to the tinman. type "ps" to see a list of the programs that get run by default (a very dumb program to avoid obstacles using the sonars). You need to load the new onboard server that we use: arc> ramload tinman-onboard-server 5. Run the onboard server. This is the tricky part. You have to type two commands to "arc": arc> run tinman-onboard-server arc> quit The trick is that the time between hitting "return" on the first command and hitting "return" on the second command has to be within a narrow window of opportunity. My estimate is that 2.5 sec should elapse between hitting "return" on the run command and "return" on the quit command. 6. Make sure it worked: if you got the timing right, your "arc" session should be quit and back to the unix shell, with no weird characters printed to the display. If you look at the LEDs on the front of the Tupperware box, there should no longer be the "cylon eye" pattern, "led/tpu0" should be blinking at about 1 Hz, and the "COM 1" LED in the upper left corner of the Tupp should be flashing green fairly rapidly. Good luck! Emilio has seen this process, so you can also ask him if it's too confusing. b.g.

Using the log server

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: Emilio Remolina <eremolin@cs.utexas.edu> Subject: medusa & log-stream info From: Bill Gribble <grib@cs.utexas.edu> Date: 01 Dec 1999 22:34:36 -0600 > 1) I want to use your log-server. What do I have to do? To make log entries: 1. create a <log-source-client> object. The first arg is always "LogStream", the second arg is the name of the log. Multiple clients can connect to the same log; their entries are sequenced and timestamped correctly. 2. Call (<log-source-client>-make-entry logger "string to log" timestamp) to make a log entry. 3. Call <log-source-client>-rotate-log to create a new "volume" of log entries. The idea is that if you have multiple data sets you call rotate-log between sets, and the log server stores each data set separately. EXAMPLE: (depend 'LogSourceClient) (let ((logger (make-<log-source-client> "LogStream" "test log"))) (<log-source-client>-make-entry logger "first entry" (time)) (<log-source-client>-make-entry logger "second entry" (time)) (<log-source-client>-rotate-log logger) (<log-source-client>-make-entry logger "entry in new volume" (time))) To save log entries to a file: With the log server still running, use the program mathtools/log-tool. 'log-tool list --swb localhost:14000' will show you a summary of all the stuff in the log server. 'log-tool' will show you a summary of the things the log tool can do. 'log-tool dump-all --swb localhost:14000' will dump every volume of every log to separate files. If you're worried about something crashing before you get the data saved to disk, you can call (<log-source-client>-dump-current-volume logger "filename"). The volume number gets appended to the filename so you get a set of files if you periodically rotate the logs. Format of files: timestamp sequence# entry timestamp is a floating point value representing time in seconds (to a precision of microseconds) since Jan 1 1970. sequence# is the number of this entry in this volume of this log (the first entry in any volume is sequence #0). The entry runs until the end of the line.

Running the whole system (by Mike)

From Bill's instructions to Emilio

It is best to do each of these in a separate Xterm, i.e. start 8 Xterms using:

and then start a program in each window dobjects/switchboard --port 14000 kbase/kb-server --swb localhost:14000 --kb kbase/robot-lab-kb.dump mathtools/log-server --swb localhost:14000 servers/sick-server 10 --swb localhost:14000 <i>wait for the sensors to synchronize. Restart if necessary.</i> servers/tinman-server --swb localhost:14000 servers/frame-server --rate 10 --swb localhost:14000 servers/dashboard-server --servicedb localhost:14000 servers/ptu-server --swb localhost:14000 If you have problems with backspace/erase on the lab machines, do 'stty erase '^?'.

Switching the wheelchair from wall current to battery power (by Mike)

From verbal instructions by Bill

Do this exactly in the order shown. To put the wheelchair back on wall power, do the steps in the reverse order.

  1. Check the Ethernet cable going into the computer on the back of the wheelchair. If necessary, remove the cable going from the wall and replace with the cable from the wireless Ethernet box.
  2. Look under the front center of the wheelchair. There is a large power converter near the bottom. On the left side is a metal switch. Switch it ON (UP).
  3. On top of the power converter is a smaller black box. On top is a metal switch. Switch it to the LEFT. (The brown plastic switch in front controls power to the sensors. Normally you don't change its position.)
  4. The power strip on the wheelchair is plugged into the wall current. Unplug it and put the cord somewhere on the wheelchair out of the way.
  5. Unplug the battery charger from the wheelchair. This is a round plug on the right rear side of the wheelchair. The plug is partially blocked by the computer, so be careful when maneuvering it.

Using sockets in RScheme (by Rob)

Return-Path: <rlb@cs.utexas.edu> To: Bill Gribble <grib@cs.utexas.edu> Cc: eremolin@cs.utexas.edu Subject: Re: Sockets in Scheme From: Rob Browning <rlb@cs.utexas.edu> Date: 14 Nov 1999 14:58:03 -0600 Emilio's request jogged my memory and I went back and looked at that stuff. So that in the future people don't have to go around reading raw sgml and hunting in the source, I converted/wrote up some text docs for the relevant functions. [function] (inet-server (port <fixnum>)) => (fd <fixnum>) This procedure establishes a socket on port to listen for connections from the internet. The listen queue is set to 3. [function] (make-service (fd <fixnum>)) => (service <service>) This procedure arranges to listen for connections on fd, which was presumably created using something like inet-server. Use get-next-client to dequeue connecting clients. [function] (get-next-client (service <service>)) => (fd <fixnum) (peer <socket-addr>) This procedure extracts the next client connecting to service. It returns the socket, fd, and the peer socket name. [function] (inet-client (host <hostspec>) (port <fixnum>)) => (fd <fixnum>) This procedure establishes a TCP/IP (stream) connection to the the given port on the given host. The host may be specified as either a <string> or an <inet-addr>. If host{/Phrase} is a <string>, it is resolved using string->hostaddr. [function] (string->hostaddr (spec <string>)) => (host <inet-addr) This procedure converts a string into an <inet-addr>. If the first character of the string is a digit, then the string is interpreted as a dotted IP address (e.g., 204.71.200.72). Otherwise, it is interpreted as a host name and resolved using gethostbyname. If the host name cannot be found, or it has no address, or if the resolver library encounters some other error, a <resolver-error> condition is signalled. [function] (remote-port-owner (fd <fixnum>)) => (type <string>) (info <string>) This procedure contacts the ident server on the machine which is the peer of the given socket. If a recognizable ident response is returned, the response type and additional info are returned as two values. (define svc (make-service (inet-server 5050))) (remote-port-owner (get-next-client svc)) "USERID" "UNIX : dkolbly"{/ComputerOutput} If the peer host is not running an ident server, this procedure signals an error (an <os-error>, "Connection refused"). [function] (open-mbox-input-port (fd <fixnum>)) => (port <input-port>) This procedure creates a safe wrapper around the given fd, allowing it to be used as a standard input-port. This wrapper is thread-aware, so all IO will be thread-blocking rather than process blocking. AFAIK it is not thread-safe. If you have multiple threads using the port, you will need suitable synchornization mechanisms. [function] (open-queued-output (fd <fixnum>)) => (port <output-port>) This procedure creates a safe wrapper around the given fd, allowing it to be used as a standard output-port. This wrapper is thread-aware, so all IO will be thread-blocking rather than process blocking. AFAIK it is not thread-safe. If you have multiple threads using the port, you will need suitable synchronization mechanisms. [function] (flush-output-port (port <output-port>)) => <none> Flush the output port. You may need this when using the above server/client functions (among other times) in order to guarantee timely responses. -- Rob Browning <rlb@cs.utexas.edu> PGP=E80E0D04F521A094 532B97F5D64E3930

Remote interface to RScheme

Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: robotics@cs.utexas.edu Subject: Forgot one change From: Bill Gribble <grib@cs.utexas.edu> Date: 15 Nov 1999 12:11:05 -0600 - Added support for Common LISP interface via TCP. To use it, add the following to an RScheme file: (depend 'lisp-interface) (start-lisp-interface portno) where "portno" is an integer specifying the TSP port to listen on. Connecting to this port gives you a simple read-eval-print interface to the calling RScheme process. You can test it by using "telnet localhost <portno>" from a shell, or making a TCP port connection in Lisp. b.g.
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: hewett@cs.utexas.edu Subject: test-lisp.scm From: Bill Gribble <grib@cs.utexas.edu> Date: 16 Nov 1999 11:49:38 -0600 (depend 'lisp-interface) (depend 'shared-frame) (define (start-server swb port) (service-db-set-server swb) (start-lisp-interface port)) ;; example: (start-server "localhost:14000" 12345)

Using Bill's KB

Return-Path: <grib@cs.utexas.edu> To: hewett@cs.utexas.edu, eremolin@cs.utexas.edu Subject: interface to knowledgebase From: Bill Gribble <grib@cs.utexas.edu> Date: 13 Oct 1999 10:53:35 -0500 I think the easiest thing to do is just use a remote REPL accessible through a TCP connection, and you pass scheme forms to get information from the KB. So you would just pass it something like (define frame-1 (make-shared-frame "lfor::robot-lab")) (get-slot frame-1 "outgoing-links") I'm including the Scheme definition of the KB interface. The class is called <shared-frame>. The object-id parameter is just a string; I've been using a naming convention which uses :: and tries to break the KB namespace up into logical chunks. The prefixes I'm using now are calibration calibration::video (frame) (video card params like brightness) calibration::turn/drive (frame) (calibration of low-level control) calibration::camera (frame) (calibration of imaging geometry) calibration::colors calibration::colors::red (frame) calibration::colors::blue (frame) etc. runtime-state runtime-state::robot runtime-state::robot::configuration (frame) (position,pan/tilt pos,etc) lfor lfor::robot-lab (frame) objects I think there's more stuff but that gives you the idea. This code's a little over-complicated because I'm planning to implement knowledgebase server push sometime pretty soon. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; shared-frame.scm ;;; represents a frame in the shared knowledge base ;;; ;;; CVS Identifier: ;;; $Id: shared-frame.scm,v 1.4 1999/09/23 22:03:35 grib Exp $ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (depend 'KBPointerClient) (depend 'KBStorageClient) (support 'shared-frame) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; <shared-frame> class ... wraps up a KBPointer and lets you ;;; do stuff like waiting for a slot to change, waiting for the ;;; frame to change, etc. Server push not yet implemented so ;;; the second kbpointerclient is useless. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-class <shared-frame> (<object>) (object-id init-value: #f init-keyword: #f) (kb-rw-pointer init-value: #f init-keyword: #f) (kb-wait-pointer init-value: #f init-keyword: #f)) (define (make-shared-frame object-id) (let ((rval (make <shared-frame>))) (set-kb-rw-pointer! rval (make-<kbpointer-client> "KnowledgeBase" object-id)) (set-kb-wait-pointer! rval (make-<kbpointer-client> "KnowledgeBase" object-id)) rval)) (define (get-slot (self <shared-frame>) (slot <string>)) (with-input-from-string (<kbpointer-client>-get-slot (kb-rw-pointer self) slot) (lambda () (read)))) (define (set-slot! (self <shared-frame>) (slot <string>) value) (<kbpointer-client>-set-slot (kb-rw-pointer self) slot (with-output-to-string (lambda () (display value))))) (define (ref-object (self <shared-frame>) (object-id <string>)) (<kbpointer-client>-ref-object self string)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; <kb-storage> class ... wraps up KBStorageClient. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-class <kb-storage> (<object>) (client init-value: #f init-keyword: #f)) (define (make-kb-storage) (let ((rval (make <kb-storage>))) (set-client! rval (make-<kbstorage-client> "KBStorage")) rval)) (define (dump-kb (self <kb-storage>) filename) (<kbstorage-client>-dump-kb (client self) filename)) (define (restore-kb (self <kb-storage>) filename) (<kbstorage-client>-restore-kb (client self) filename))
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: "Micheal S. Hewett" <hewett@cs.utexas.edu> Subject: Re: Your KB connection From: Bill Gribble <grib@cs.utexas.edu> Date: 05 Nov 1999 18:11:22 -0600 This interface will work fine with a couple of name changes. frame-p ==> shared-frame? slots-of-frame ==> slots The argument of make-shared-frame is a string: (define my-frame (make-shared-frame "my-frame")) Be aware that shared-frame? probably has different semantics than you expect: (shared-frame? foo) returns true if the symbol foo points to an object of type <shared-frame>. I am guessing you meant it to be "does frame <frame> exist in the KB?" and there's currently no way to do that. Right now, make-shared-frame ends up automatically creating the frame if it doesn't exist. Is it important to have the predicate? Also a couple of synchronization primitives. set-slot! and get-slot are guaranteed atomic, but if you need synchronized multiple-slot access you have to lock and unlock the frame: (acquire-read <frame>) (release-read <frame>) (acquire-write <frame>) (release-write <frame>) If it's not obvious from the names, synchronization is "multiple reader, single writer" with FIFO ordering over all readers and writers. i.e. a writer can't get access until all readers have been released. [...] b.g.
Return-Path: <grib@cs.utexas.edu> Sender: grib@cs.utexas.edu To: "Micheal S. Hewett" <hewett@cs.utexas.edu> Subject: Re: frames and <shared-frame>s References: <3831A399.5E2A8497@cs.utexas.edu> From: Bill Gribble <grib@cs.utexas.edu> Date: 16 Nov 1999 14:04:32 -0600 "Micheal S. Hewett" <hewett@cs.utexas.edu> writes: > It seems that I have to send the form: > (get-slot (make-shared-frame "calibration::turn-drive") > "drive-default") > Is that correct? If so, that will create a lot of instances > in a web browser type of application. Yes, that's right. Most applications don't use the KB like a web browser would, so that doesn't bother me. You can minimize the resource usage by explicitly cleaning up after yourself: (let* ((frame-ptr (make-shared-frame "calibration::turn/drive")) (slot-value (get-slot frame-ptr "drive-default"))) (finalize frame-ptr) slot-value) b.g.

Adding a new sensor to the wheelchair

Subject: [unfinished] intro to distributed object system Date: 22 Dec 1999 15:13:38 -0600 From: Bill Gribble <grib@cs.utexas.edu> To: robotics@cs.utexas.edu Just in case it comes in handy. I'll finish it later but here you have an introduction to the introduction. b.g. =================================================================== Let's say you have a new piece of hardware that you have installed on the robot.. a GPS receiver, for example. This receiver has "read" semantics (you can ask it where you are) and (though we might not use it) "write" semantics (you can set waypoints and configure the thing in various ways). Since the GPS probably has a serial-port interface, it's easy to write a class to talk to it. Let's make it a trivially simple class with just a few methods : class GPSReceiver { public: GPSReceiver(const string & serial_port_name); ~GPSReceiver(); gps_pos_t get_current_position(); void set_waypoint(gps_pos_t waypoint); private: SerialPort _port; }; Obviously, you can only instantiate a GPSReceiver object on the same machine that the GPS receiver is plugged in to. But you might want access to the data from anywhere, so you want to use the distributed object system. I'll go through two different ways of doing this, one straightforward but not exactly what you want, and one that's a little more of a hassle up front but does just what you want. ======================== == the straightforward path ======================== The most straightforward way is to make the GPSReceiver class distributable and provide it as a service on the machine with the physical thing plugged into it. This means that anyone can ask the switchboard where the GPSReceiver class can be instantiated, then cause one to be created there, and keep a "stub" object that can call methods on it. First you need to make a 'distribution template' which describes the class in a parsable abstract form. This goes in the file classname.do2 , so here we create GPSReceiver.do2 : --------------------- (define-distributed-type 'gps-pos-t '(c++ "gps_pos_t" "gps_pos_t" "GPSReceiver.h") '(scheme "<gps-pos>" "gps-pos" "gps-pos")) (define-distributed-object "GPSReceiver" '(constructurs (("serial-port-name" (reference-to string) const))) '(methods ("get_current_position" () gps-pos-t) ("set_waypoint" (("waypoint_pos" gps-pos-t)) #f))) --------------------- define-distributed-type ----------------------- define-distributed-type is used to tell the distributed object system about objects that may be used as arguments or return values in the methods of a distributed object. All the standard types are defined already. We use explicit naming conventions for sizes to get around compiler assumptions, so don't expect "int" to work... use "int_s4" (signed, 4-byte) instead. standard types: int_u1, int_s1 ("unsigned char", "char") int_u2, int_s2 ("unsigned short", "short") int_u4, int_s4 ("unsigned int", "int") real_8 ("double") string STL rope<char> class... a great string class. time struct timeval bool boolean pointer typedef void * pointer; vector<string> STL vector of string You also get pointers to, references to, and fixed-length C vectors of whatever type for free. If you have a new type foo that you need to send as an argument or retval, you need to : 1. put it in a define-distributed-type form in the .do2 file 2. define functions called DO_get_foo and DO_send_foo according to instructions below. The fields in the define-distributed type form are: (define-distributed-type <class-name-symbol> '(c++ <c++-class-name> <embeddable-c++-class-name> <c++-func-hdr>) '(scheme <scm-class-name> <embeddable-scm-class-name> <scm-func-hdr>)) The "embeddable" names are ones used when automatically generating function names that include types in the function names; you might have to refer to a c++ type as parent_type::child_type, but you can't make a function with that in its name; you might put the line '(c++ "parent_type::child_type" "parent_type_child_type" "parent_comm.h") the header file is the one that contains the prototypes for DO_get_foo and DO_send_foo.

Stopping the wheelchair (by Ben)

This is from Ben's brain, not Bill's. From: "Benjamin J. Kuipers" <kuipers@cs.utexas.edu> Date: Wed, 1 Dec 1999 23:37:52 -0600 Emilio, This afternoon, we briefly discussed the problem of stopping the robot smoothly at a desired point, in spite of the weird control at low speeds. I have a suggestion. Note that, if velocity is directly controllable, the first-order control law dx/dt = -f(x) brings the system to rest at x=0 for any f in M0+ (that is, any monotonically increasing f such that f(0)=0. Since any f will work, we can pick the f that has the performance properties we want. The linear controller you have been using is f(x) = kx. Try it with f(x) = k*sqrt(x). Solving this analytically, with an initial condition x(0)=1, you get the predicted behavior x(t) = (1 - kt/2)^2 which is a parabolic drop from x=1 to x=0. You can solve for velocity explicitly, too: v(t) = k^2 t/2 - k which actually means that you could implement the control signal as an open loop change in velocity: select the right value for k, then change velocity linearly to zero over the selected time interval. Then you are no longer constrained by sensing. If you estimated the situation right to start with, you just stop. (The open loop approach also avoids the singularity as f(x) -> 0.) Caveat: I have assumed that velocity is directly, and instaneously, controllable, which is only an approximation. It only bites you if the initial velocity is quite far from -f(x) for the initial value of x. The controller will probably have to have another component to handle cases like that. This should be very easy to implement and test, using the forward range sensor to test how close to the desired point the wheelchair actually stopped. (Aim for 1.00 m from the wall.) Cheers, Ben

Approaches to control (by Ben)

This is from Ben's brain, not Bill's. Return-Path: <kuipers@cs.utexas.edu> From: "Benjamin J. Kuipers" <kuipers@cs.utexas.edu> Date: Thu, 2 Dec 1999 10:02:27 -0600 Emilio (and everyone), Last night's message about stopping control may clarify an interesting contrast between different approaches to control. (1) Feedback control: x'(t) = -f(x(t)). At each time-step, sense x, and set x' appropriately to obey the law. (2) Open-loop control: v(t) = k^2 t/2 - k. (This may also be what people call "feed-forward control", but I'm not sure.) After first observing x(0), calculate appropriate values of k, and the value of t such that x(t)=0, then control v(t) on the above schedule, independent of further observations. (3) Model-predictive control: Repeat (2) at each time-step, with updated observations of x(t) and updated values of k and t. This gives you a schedule to follow between observations, plus updating the control policy as new information arrives. Feedback control is robust in the face of sensory and motor errors. Open-loop control avoids inline processing costs of sensory input. Model-predictive control combines the benefits. Cheers, Ben

[Robotics Home]
Author: Micheal Hewett