Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit ad9b8ebccf9d4c253210bd06ab0404bbb8da2fc2 @samaaron committed Mar 15, 2011
Showing with 215 additions and 0 deletions.
  1. +6 −0 .gitignore
  2. +82 −0 README
  3. +4 −0 project.clj
  4. +117 −0 src/serial_port.clj
  5. +6 −0 test/serial_port/test/core.clj
@@ -0,0 +1,6 @@
+pom.xml
+*jar
+lib
+classes
+.cake
+META-INF
82 README
@@ -0,0 +1,82 @@
+# serial-port
+
+A simple library for serial port communication with Clojure.
+
+## Installation
+
+The easiest way to to install serial-port is using cake. Cake has great support for native dependencies and serial-port makes use of RxTx which requires native libraries. With cake, you just need to add serial-port to your list of depenendencies in your project.clj and then run `cake deps`.
+
+## Usage
+
+### Using the library
+
+Just make sure you pull in the serial-port namespace using something like:
+
+ (use 'serial-port)
+
+### Finding your port identifier
+
+In order to connect to your serial device you need to know the path of the file it presents itself on. Serial-port provides a simple function to list these paths out:
+
+ => (list-ports)
+
+ 0 : /dev/tty.usbmodemfa141
+ 1 : /dev/cu.usbmodemfa141
+ 2 : /dev/tty.Bluetooth-PDA-Sync
+ 3 : /dev/cu.Bluetooth-PDA-Sync
+ 4 : /dev/tty.Bluetooth-Modem
+ 5 : /dev/cu.Bluetooth-Modem
+
+In this case, we have an Arduino connected to `/dev/tty.usbmodemfa141`.
+
+### Connecting with a port identifier
+
+When you know the path to the serial port, connecting is just as simple as:
+
+ (open "/dev/tty.usbmodemfa141")
+
+However, you'll want to bind the result so you can use it later:
+
+ (def port (open "/dev/tty.usbmodemfa141"))
+
+### Reading bytes
+
+The simplest way to read bytes from the connection is to use `on-byte`. This allows you to register a hander fn which will be called for each byte received. So, to print out each byte just do the following:
+
+ (on-byte port #(println %))
+
+It's also possible to register a handler for every n bytes. The monome communicates by sending pairs of bytes, one byte to describe whether a button was pressed or released, and another to describe the coordinates of the button. You can register a hander to receive pairs of bytes as follows:
+
+ (on-n-bytes port 2 (fn [[action coords]] ...))
+
+If you wish to get raw access to the `InputStream` this is possible with the function `listen`. This allows you to specify a handler that will get called every time there is data available on the port and will pass your handler the `InputStream` to allow you to directly `.read` bytes from it. Both `on-bytes` and `on-n-bytes` generate such handlers acting as a proxy between your specified handler and the incoming data events.
+
+When the handler is first registered, the bytes that have been buffered on the serial port are dropped by default. This can be changed by passing false to `on-byte`, `on-n-bytes` or `listen` as an optional last argument.
+
+Only one listener may be registered at a time. If you want to fork the incoming datastream to a series of streams, you might want to consider using lamina. You can then register a handler which simply enqueues the incoming serial data to a lamina channel which you may then fork and map according to your whim.
+
+Finally, you may remove your listener with `remove-listener`.
+
+### Writing bytes
+
+The simplest way to write bytes is by passing a byte array to `write`:
+
+ (write port my-byte-array)
+
+There are a couple of convenience functions available if you're dealing with plain Integers. `write-int` allows you to write a simple integer to the serial port and `write-int-seq` allows you to pass a sequence of integers which are then converted to a byte array which is subsequently written to the serial port:
+
+ (write-int port 20)
+ (write-int-seq port [20 10 2 100])
+
+
+### Closing the port
+
+Simply use the `close` function:
+
+ (close port)
+
+## License
+
+Copyright (C) 2010 Sam Aaron
+
+Distributed under the Eclipse Public License, the same as Clojure.
@@ -0,0 +1,4 @@
+(defproject serial-port "1.0.7"
+ :description "Simple serial port comms library. Wraps RxTx."
+ :dependencies [[org.clojure/clojure "1.2.0"]
+ [rxtx22 "1.0.5"]])
@@ -0,0 +1,117 @@
+(ns serial-port
+ (:import
+ (gnu.io CommPortIdentifier
+ SerialPort
+ SerialPortEventListener
+ SerialPortEvent)
+ (java.io OutputStream
+ InputStream)))
+
+(def PORT-OPEN-TIMEOUT 2000)
+(defrecord Port [path raw-port out-stream in-stream])
+
+(defn port-ids
+ "Returns a seq representing all port identifiers visible to the system"
+ []
+ (enumeration-seq (CommPortIdentifier/getPortIdentifiers)))
+
+(defn port-at
+ "Returns the name of the serial port at idx."
+ [idx]
+ (.getName (nth (port-ids) idx)))
+
+(defn list-ports
+ "Print out the available ports with an index number for future reference
+ with (port-at <i>)."
+ []
+ (loop [ports (port-ids)
+ idx 0]
+ (when ports
+ (println idx ":" (.getName (first ports)))
+ (recur (next ports) (inc idx)))))
+
+(defn close
+ "Closes an open port."
+ [port]
+ (let [raw-port (:raw-port port)]
+ (.removeEventListener raw-port)
+ (.close raw-port)))
+
+(defn open
+ "Returns an opened serial port. Allows you to specify the baud-rate.
+ (open \"/dev/ttyUSB0\")
+ (open \"/dev/ttyUSB0\" 9200)"
+ ([path] (open path 115200))
+ ([path baud-rate]
+ (try
+ (let [uuid (.toString (java.util.UUID/randomUUID))
+ port-id (first (filter #(= path (.getName %)) (port-ids)))
+ raw-port (.open port-id uuid PORT-OPEN-TIMEOUT)
+ out (.getOutputStream raw-port)
+ in (.getInputStream raw-port)
+ _ (.setSerialPortParams raw-port baud-rate
+ SerialPort/DATABITS_8
+ SerialPort/STOPBITS_1
+ SerialPort/PARITY_NONE)]
+
+ (Port. path raw-port out in))
+ (catch Exception e
+ (throw (Exception. (str "Sorry, couldn't connect to the port with path " path )))))))
+
+(defn write
+ "Write a byte array to a port"
+ [port bytes]
+ (.write ^OutputStream (:out-stream port) ^bytes bytes))
+
+(defn- compose-byte-array [bytes]
+ (byte-array (count bytes) (map #(.byteValue ^Integer %) bytes)))
+
+(defn write-int-seq
+ "Write a seq of Integers as bytes to the port."
+ [port ints]
+ (write port (compose-byte-array ints)))
+
+(defn write-int
+ "Write an Integer as a byte to the port."
+ [port int]
+ (write port (compose-byte-array (list int))))
+
+(defn listen
+ "Register a function to be called for every byte received on the specified port."
+ ([port handler] (listen port handler true))
+ ([port handler skip-buffered]
+ (let [raw-port (:raw-port port)
+ in-stream (:in-stream port)
+ listener (reify SerialPortEventListener
+ (serialEvent [_ event] (when (= SerialPortEvent/DATA_AVAILABLE (.getEventType event))
+ (handler in-stream))))]
+
+ (if skip-buffered
+ (let [to-drop (.available in-stream)]
+ (.skip in-stream to-drop)))
+
+ (.addEventListener raw-port listener)
+ (.notifyOnDataAvailable raw-port true))))
+
+(defn remove-listener
+ "De-register the listening fn for the specified port"
+ [port]
+ (.removeEventListener (:raw-port port)))
+
+(defn on-n-bytes
+ "Partitions the incoming byte stream into seqs of size n and calls handler passing each partition."
+ ([port n handler] (on-n-bytes port n handler true))
+ ([port n handler skip-buffered]
+ (listen port (fn [^InputStream in-stream]
+ (if (>= (.available in-stream) n)
+ (handler (doall (repeatedly n #(.read in-stream))))))
+ skip-buffered)))
+
+(defn on-byte
+ "Calls handler for each byte received"
+ ([port handler] (on-byte port handler true))
+ ([port handler skip-buffered]
+ (listen port (fn [^InputStream in-stream]
+ (handler (.read in-stream)))
+ skip-buffered)))
+
@@ -0,0 +1,6 @@
+(ns serial-port.test.core
+ (:use [serial-port.core] :reload)
+ (:use [clojure.test]))
+
+(deftest replace-me ;; FIXME: write
+ (is false "No tests have been written."))

0 comments on commit ad9b8eb

Please sign in to comment.