Razor 9DOF IMU/Clojure

Something I put together to visualize the data coming from a Razor 9DOF IMU, in case I need it later.

(ns razor.core
  (:import (java.awt Color Dimension Font BasicStroke)
           (java.awt.event WindowListener)
           (javax.swing JFrame JPanel)
           (java.text DecimalFormat)
           (org.jfree.chart ChartPanel JFreeChart)
           (org.jfree.chart.plot  CompassPlot)
           (org.jfree.data.general DefaultValueDataset)
           (org.jfree.chart.plot.dial DialPlot StandardDialFrame
                                      StandardDialScale DialPointer$Pointer
                                      DialValueIndicator DialBackground DialCap)
           (java.io BufferedReader InputStreamReader)
           (gnu.io SerialPort CommPortIdentifier
                   SerialPortEventListener SerialPortEvent)))

(defn razor [dev]
  (let [state (atom [0 0 0])
        id (first
            (filter #(= dev (.getName %))
                    (enumeration-seq (CommPortIdentifier/getPortIdentifiers))))
        port (doto (.open id "clojure" 1)
               (.setSerialPortParams 57600
                                     SerialPort/DATABITS_8
                                     SerialPort/STOPBITS_1
                                     SerialPort/PARITY_NONE))
        stream (.getInputStream port)
        reader (BufferedReader. (InputStreamReader. stream))
        parser (fn [_ line]
                 (map read-string (.split (.replaceAll line "!ANG:" "") ",")))
        listener (proxy [SerialPortEventListener] [] 
                   (serialEvent 
                     [event]
                     (if (= (.getEventType event) SerialPortEvent/DATA_AVAILABLE)
                       (while (pos? (.available stream))
                         (swap! state parser (.readLine reader))))))]
    (.addEventListener port listener)
    (.notifyOnDataAvailable port true)
    [port state]))

(defn dial [name dataset min max]
  (let [dial-frame (doto (StandardDialFrame.)
                     (.setBackgroundPaint Color/LIGHT_GRAY)
                     (.setStroke (BasicStroke. 2)))

        scale (doto (StandardDialScale. min max 300 300 30 4)
                (.setMajorTickPaint Color/WHITE)
                (.setMinorTickPaint Color/WHITE)
                (.setTickLabelPaint Color/WHITE)
                (.setTickLabelFont (Font. nil Font/BOLD 15))
                (.setTickLabelFormatter (DecimalFormat. "# °"))
                (.setTickRadius 0.9)
                (.setTickLabelOffset 0.2))

        pointer (doto (DialPointer$Pointer. 0)
                  (.setFillPaint Color/GRAY)
                  (.setOutlinePaint (Color. 0 true)))

        indicator (doto (DialValueIndicator. 0)
                    (.setFont (Font. nil Font/BOLD 14))
                    (.setNumberFormat (DecimalFormat. "#.# °"))
                    (.setRadius 0.85)
                    (.setPaint Color/WHITE)
                    (.setOutlinePaint (Color. 0 true))
                    (.setBackgroundPaint (Color. 0 true)))

        plot (doto (DialPlot. dataset)
               (.setBackground (DialBackground. Color/DARK_GRAY))
               (.setDialFrame dial-frame)
               (.setCap (DialCap.))
               (.addScale 0 scale)
               (.mapDatasetToScale 0 0)
               (.addPointer pointer)
               (.addLayer indicator))]
    (doto (ChartPanel.
           (doto (JFreeChart. name JFreeChart/DEFAULT_TITLE_FONT plot false)
             (.setBackgroundPaint (Color. 0x999999))))
      (.setPreferredSize (Dimension. 300 300)))))

(defn compass [name dataset]
  (let [plot (CompassPlot. dataset)]
    (doto plot
      (.setSeriesNeedle 0 5)
      (.setSeriesPaint 0 Color/GRAY)
      (.setSeriesOutlinePaint 0 Color/GRAY)
      (.setRosePaint Color/DARK_GRAY)
      (.setRoseCenterPaint Color/DARK_GRAY)
      (.setRoseHighlightPaint Color/WHITE))
    (doto (ChartPanel.
           (doto (JFreeChart. name JFreeChart/DEFAULT_TITLE_FONT plot false)
             (.setBackgroundPaint (Color. 0x999999))))
      (.setPreferredSize (Dimension. 300 300)))))

(defn frame [dev]
  (let [[port state] (razor dev)
        pitch (DefaultValueDataset. 0.0)
        yaw (DefaultValueDataset. 0.0)
        roll (DefaultValueDataset. 0.0)]
    (add-watch state "update-dials"
               (fn [k r o n]
                 (let [[rl ph yw] n]
                   (.setValue roll (int rl))
                   (.setValue pitch (int ph))
                   (.setValue yaw yw))))
    (doto (JFrame.)
      (.add (doto (JPanel.)
              (.setBackground (Color. 0x999999))
              (.add (compass "Yaw" yaw))
              (.add (dial "Roll" roll -180 180))
              (.add (dial "Pitch" pitch -90 90))))
      (.addWindowListener (proxy [WindowListener] []
                            (windowActivated [e])
                            (windowClosing [e]
                              (.close port))
                            (windowDeactivated [e])
                            (windowDeiconified [e])
                            (windowIconified [e])
                            (windowOpened [e])
                            (windowClosed [e])))
      (.setTitle "Razor IMU")
      (.pack)
      (.setVisible true))))
(frame "/dev/tty.usbserial-A600e1hM")