Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
peterschwarz committed Apr 16, 2014
2 parents 09dd1f4 + 9db9034 commit 984ab82
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 22 deletions.
55 changes: 45 additions & 10 deletions src/firmata/core.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns firmata.core
(:require [clojure.core.async :as a :refer [go chan >! >!! <!]]
(:require [clojure.core.async :as a :refer [go chan >! >!! <! alts!! <!!]]
[serial.core :as serial]))

; Message Types
Expand All @@ -13,7 +13,7 @@
(def ^{:private true} SET_PIN_IO_MODE 0xF4)
(def ^{:private true} SYSEX_END 0xF7)
(def ^{:private true} PROTOCOL_VERSION 0xF9)
(def ^{:private true} SYSEX_RESET 0xFF)
(def ^{:private true} SYSTEM_RESET 0xFF)

; SysEx Commands

Expand Down Expand Up @@ -49,6 +49,8 @@
(defprotocol Firmata
(close! [board] "Closes the connection to the board.")

(reset-board! [board] "Sends the reset signal to the board")

; Various queries
(query-firmware [board] "Query the firmware of the board.")

Expand Down Expand Up @@ -100,13 +102,21 @@
"Returns a channel which provides all of the events that have been returned from the board.")

(release-event-channel
[this channel]
[board channel]
"Releases the channel")

(event-publisher
[board]
"Returns a publisher which provides events by [:type :pin]")

(version
[board]
"Returns the currently known version of the board")

(firmware
[board]
"Returns the currently known firmware information")

)

; Number conversions
Expand Down Expand Up @@ -298,6 +308,10 @@
[board report-type address enabled?]
(send-message board [(pin-command report-type address) (if enabled? 1 0)]))

(defn- take-with-timeout
[ch default]
(or (first (alts!! [ch (a/timeout 5000)])) default))

(defn open-board
"Opens a connection to a board on at a given port name.
The baud rate may be set with the option :baud-rate (default value 57600).
Expand All @@ -306,16 +320,25 @@
[port-name & {:keys [baud-rate event-buffer-size]
:or {baud-rate 57600 event-buffer-size 1024}}]

(let [create-channel #(chan (a/sliding-buffer event-buffer-size))
port (serial/open port-name :baud-rate baud-rate)
(let [board-state (atom {:digital-out (zipmap (range 0 MAX-PORTS) (take MAX-PORTS (repeat 0)))
:digital-in (zipmap (range 0 MAX-PORTS) (take MAX-PORTS (repeat 0)))})

create-channel #(chan (a/sliding-buffer event-buffer-size))
read-ch (create-channel)
write-ch (chan 1)

; Open the serial port and attach ourselves to it
port (serial/open port-name :baud-rate baud-rate)
_ (serial/listen port (firmata-handler {:state board-state :channel read-ch}) false)

; Need to pull these values before wiring up the remaining channels, otherwise, they get lost
_ (swap! board-state assoc :board-version (take-with-timeout read-ch {:type :protocol-version :version "Unknown"}))
_ (swap! board-state assoc :board-firmware (take-with-timeout read-ch {:type :firmware-report :name "Unknown" :version "Unknown"}))

; For sending events to various receivers
mult-ch (a/mult read-ch)
pub-ch (create-channel)
publisher (a/pub pub-ch #(vector (:type %) (:pin %)))
write-ch (chan 1)
board-state (atom {:digital-out (zipmap (range 0 MAX-PORTS) (take MAX-PORTS (repeat 0)))
:digital-in (zipmap (range 0 MAX-PORTS) (take MAX-PORTS (repeat 0)))})]
(serial/listen port (firmata-handler {:state board-state :channel read-ch}) false)
publisher (a/pub pub-ch #(vector (:type %) (:pin %)))]

(a/tap mult-ch pub-ch)

Expand All @@ -331,6 +354,18 @@
(a/close! read-ch)
(serial/close port))

(reset-board!
[this]
(send-message this SYSTEM_RESET))

(version
[this]
(-> @board-state :board-version :version))

(firmware
[this]
(dissoc (:board-firmware @board-state) :type))

(query-firmware
[this]
(send-message this [SYSEX_START REPORT_FIRMWARE SYSEX_END]))
Expand Down
20 changes: 20 additions & 0 deletions src/firmata/shift.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
(ns firmata.shift
(:require [firmata.core :refer [set-digital]]))

(defn shift-out
"Sends a shift out to the board, for sending values to a shift register"
[board latch-pin data-pin clock-pin bit-order value]
{:pre [(or (= bit-order :lsb-first) (= bit-order :msb-first))]}

(set-digital board latch-pin :high)

(doseq [i (range 8)]
(let [shift-by (if (= :lsb-first bit-order) i (- 7 i))]
(set-digital board data-pin (if (= 0 (bit-and value (bit-shift-left 1 shift-by))) :low :high)))

(set-digital board clock-pin :high)
(set-digital board clock-pin :low))

(set-digital board latch-pin :low)

board)
18 changes: 18 additions & 0 deletions src/firmata/util.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
(ns firmata.util
(:require [clojure.core.async :as a]))

(defn arduino-map
"Clojure implemation of the Arduino map function.
http://arduino.cc/en/reference/map"
[x, in-min, in-max, out-min, out-max]
(+ (quot (* (- x in-min) (- out-max out-min)) (- in-max in-min)) out-min))

(defn arduino-constrain
"Clojure implementation of the Arduino constrain function.
http://arduino.cc/en/Reference/Constrain"
[x min max]
(cond (< x min) min
(<= min x max) x
:else max))

(defn to-voltage
"Takes an analog value and converts it to the true voltage value."
[x]
(* x 0.004882814))

(defn to-hex-str
"For debug output"
Expand Down
37 changes: 27 additions & 10 deletions test/firmata/test/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,30 @@
(reduce (fn [^ByteBuffer b ^bytes value] (.put b (to-bytes value))) buffer more)
(ByteArrayInputStream. (.array buffer))))


(defn mock-serial-listen
[handler]
(fn [_ h _]
(reset! handler h)
(h (create-in-stream 0xF9 9 9))
(h (create-in-stream 0xF0 0x79 9 9 "Test Firmware" 0xF7))
nil))

(deftest test-read-events

(let [handler (atom nil)]

(with-redefs [serial/open (fn [name _ rate] {:port name :rate rate})
serial/listen (fn [port h skip?] (reset! handler h))]
serial/listen (mock-serial-listen handler)]

(let [board (open-board "some_board")
evt-chan (event-channel board)]

(testing "board version initialized with board handshake messages"
(is (= "9.9" (version board))))

(testing "board firmware initialized with board handshake messages"
(is (= {:name "Test Firmware" :version "9.9"} (firmware board))))

(testing "read protocol version"
(@handler (create-in-stream 0xF9 2 3))
(if-let [event (<!! evt-chan)]
Expand Down Expand Up @@ -187,11 +199,16 @@

(deftest test-write
(let [write-value (atom nil)]
(with-redefs [serial/open (fn [name _ rate] :port)
serial/listen (fn [port h skip?] nil)
serial/write (fn [port x] (reset! write-value x) nil)]
(with-redefs [serial/open (fn [_ _ _] :port)
serial/listen (mock-serial-listen (atom nil))
serial/write (fn [_ x] (reset! write-value x) nil)]
(let [board (open-board "writable_board")]

(testing "reset board"
(reset-board! board)
(<!! (timeout 10))
(is (= 0xFF @write-value)))

(testing "query protocol version"
(query-version board)
(<!! (timeout 10))
Expand Down Expand Up @@ -307,9 +324,9 @@

(deftest test-i2c-messages
(let [writes (atom [])]
(with-redefs [serial/open (fn [name _ rate] :port)
serial/listen (fn [port h skip?] nil)
serial/write (fn [port x] (swap! writes conj x) nil)]
(with-redefs [serial/open (fn [_ _ _] :port)
serial/listen (mock-serial-listen (atom nil))
serial/write (fn [_ x] (swap! writes conj x) nil)]
(let [board (open-board "writable_board")]

(testing "ic2 request: write"
Expand Down Expand Up @@ -359,8 +376,8 @@

(deftest test-board-close
(let [port (atom nil)]
(with-redefs [serial/open (fn [name _ rate] :port)
serial/listen (fn [port h skip?] nil)
(with-redefs [serial/open (fn [_ _ _] :port)
serial/listen (mock-serial-listen (atom nil))
serial/close (fn [p] (reset! port p) nil)]
(let [board (open-board "writable_board")]

Expand Down
4 changes: 2 additions & 2 deletions test/firmata/test/receiver.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
(chan (a/sliding-buffer 1)))

(defn- send-msg [ch msg]
(is (a/alts!! [[ch msg]
(timeout 100)])))
(is (first(a/alts!! [[ch msg]
(timeout 100)]))))

(defn- mock-board [read-ch]
(let [mult-ch (a/mult read-ch)
Expand Down
149 changes: 149 additions & 0 deletions test/firmata/test/shift.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
(ns firmata.test.shift
(:require [clojure.test :refer :all]
[firmata.core :refer [set-digital]]
[firmata.shift :refer :all]))

(deftest shift-out-test

(let [writes (atom [])
latch-pin 4
data-pin 5
clock-pin 6]
(with-redefs [set-digital (fn [_ pin value] (swap! writes conj {:pin pin :value value}))]

(testing "shift out least significant bit first"

(shift-out :mock-board latch-pin data-pin clock-pin :lsb-first 0x1)

(is (= @writes [{:pin latch-pin :value :high}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin latch-pin :value :low}])))

(reset! writes [])

(testing "shift out least significant bit first - set all high"

(shift-out :mock-board latch-pin data-pin clock-pin :lsb-first 0xFF)

(is (= @writes [{:pin latch-pin :value :high}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin latch-pin :value :low}])))

(reset! writes [])

(testing "shift out most significant bit first"

(shift-out :mock-board latch-pin data-pin clock-pin :msb-first 0x1)

(is (= @writes [{:pin latch-pin :value :high}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :low}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin latch-pin :value :low}])))

(reset! writes [])

(testing "shift out least significant bit first - set all high"

(shift-out :mock-board latch-pin data-pin clock-pin :msb-first 0xFF)

(is (= @writes [{:pin latch-pin :value :high}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin data-pin :value :high}
{:pin clock-pin :value :high}
{:pin clock-pin :value :low}
{:pin latch-pin :value :low}])))

(testing "bad endian-ness"
(is (thrown? AssertionError (shift-out :mock-board latch-pin data-pin clock-pin :whatever 0xFF))))

)))

(run-tests)
Loading

0 comments on commit 984ab82

Please sign in to comment.