From: David Mattli
Subject: learning lisp, sockets, concurrency, cells. zombies mentioned
Date: 
Message-ID: <873ad250u6.fsf@emily.mattli.us>
I've decided to use commie lisp to write a sweet app but I have a few
questions.

So this app handles gps input and for that I'm using gpsd. If you
haven't heard of gpsd it's an awesome little program that handles all of
the incompatible nastiness spewed forth by gpsen and processes it into a
consistent text format. You just open a socket to the gpsd daemon, send
a 'w', and it spits back a time/position/velocity report(prefixed by
"O=")  or a timestamp(prefixed by "Y=") whenever the gps gets a fix.

Below is the code I've put together to represent the gps state and the
connection to the daemon.

I've wrapped the connection in a closure, which when called spits out
the latest fix. This relies on the buffering on the socket to work, is
that a bad thing? In particular how do socket buffers overflow? I would
probably have to add some error handling to ignore partial fix lines to
handle this case, right?

ok ok But in general my app will be handling events from several
ends. The user will be pushing buttons, the gps will be spewing fixes,
and the internal logic will be processing the fixes and other data, eg
zombie locations. How do lispers handle concurrency? Threads, locks, and
shared state? Right now my gui just has a timer that occasionally (every
500ms) grabs the latest fix from the closure and this seems to work well
enough. However as I add functionality to the app (zombie tracking) I'm
wondering how this will all fit.

I've been drinking the Tilton cells koolaid and I'm using cells-gtk for
the gui, so I was thinking I could just specify everything in cells and
wrap it all in a lock and use a seperate thread for the gps and other
data sources that push the constraint network when they have updated
data.

Here's the gps code. I'm using usocket. Is there a better way to read
the values than read-from-string? Let me know what sucks or I'll never
learn. haha

Thanks! 
David

(defconstant +o-string-values+
  '(tag
    timestamp
    time-error
    latitude
    longitude
    altitude
    horizontal-error-estimate
    vertical-error-estimate
    course-over-ground
    speed-over-ground
    climb
    estimated-error-in-course-over-ground
    estimated-error-in-speed-over-ground
    estimated-error-in-climb-sink
    mode))

(defun y-stringp (string)
  (if (> (length string) 6)
      (char= (elt string 5)
	     #\Y)))

(defun o-stringp (string)
  (if (> (length string) 6)
      (char= (elt string 5)
	     #\O)))

(defun make-gps-watcher-connection (&optional
				    (host "localhost")
				    (port 2947))
  "Connects to the specified gpsd and returns a function which, when called,
returns two string values representing the latest gps state."
  (let ((s (handler-case
	       (socket-connect host port)
	     (connection-refused-error () nil)))
	(ostring "")
	(ystring ""))
    (when s
      (write-line "w" (socket-stream s))
      (force-output (socket-stream s))
      (lambda (&optional (close nil))
	(if close
	    (socket-close s)
	    (do ((string "" (read-line (socket-stream s))))
		((not (listen (socket-stream s)))
		 (values ostring ystring))
	      (cond ((o-stringp string)
		     (setf ostring string))
		    ((y-stringp string)
		     (setf ystring string)))))))))

(defun read-number-list (list)
  (let ((*read-default-float-format* 'double-float))
    (loop for elt in list
       for c = (aref elt 0)
       collect (if (or (digit-char-p c)
		       (char-equal c #\-))
		   (read-from-string elt)))))

(defun gps-to-alist (connection)
  "Returns an alist of the gps values returned by connection."
  (let ((tokens (remove-if (lambda (x)
			     (char-equal x #\Return))
			   (cadr (split-sequence #\=
						 (funcall connection))))))
    (pairlis +o-string-values+
	     (read-number-list
	      (split-sequence #\Space tokens)))))

; Represents gps state
(defclass gps ()
; TODO: Add the rest of the slots...
   ((connection :accessor connection)
    (timestamp :accessor timestamp)
    (time-error :accessor time-error)
    (latitude :accessor latitude)
    (longitude :accessor longitude)
    (altitude :accessor altitude)
    (horizontal-error-estimate :accessor ho))) 

(defun update-gps (gps)
  (let ((vals (gps-to-alist (connection gps))))
    (setf (timestamp gps)
	  (assoc 'timestamp vals))
    (setf (latitude gps)
	  (assoc 'latitude vals))
    (setf (longitude gps)
	  (assoc 'longitude vals))
    (setf (altitude gps)
	  (assoc 'altitude vals))))

(defmethod print-object ((object gps) stream)
  (print-unreadable-object (object stream :type t)
    (with-slots (timestamp latitude longitude altitude) object
      (format stream "~% time ~d ~% latitude ~d ~% longitude ~d ~% altitude ~d"
	      (cdr timestamp)
	      (cdr latitude)
	      (cdr longitude)
	      (cdr altitude)))))