├── .gitignore ├── project.clj ├── LICENSE ├── resources ├── examples │ ├── led.clj │ ├── button-led.clj │ ├── pwm.clj │ └── photoresistor.clj └── wishield │ ├── handler.h │ └── wishield.pde ├── README.markdown └── src └── clodiuno ├── wishield.clj ├── core.clj └── firmata.clj /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | native/ 3 | classes/ 4 | *.jar 5 | .lein-deps-sum 6 | target/ 7 | pom.xml -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clodiuno "0.0.4-SNAPSHOT" 2 | :description "Clojure API for Arduino." 3 | :dependencies [[org.clojure/clojure "1.3.0"] 4 | [rxtx22 "1.0.6"]]) 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | "THE BEER-WARE LICENSE" (Revision 42): 2 | 3 | wrote this file. As long as you retain this notice you 4 | can do whatever you want with this stuff. If we meet some day, and you think 5 | this stuff is worth it, you can buy me a beer in return. 6 | -------------------------------------------------------------------------------- /resources/examples/led.clj: -------------------------------------------------------------------------------- 1 | (ns led 2 | (:use :reload-all clodiuno.core) 3 | (:use :reload-all clodiuno.firmata)) 4 | 5 | (def board (arduino :firmata "/dev/tty.usbserial-A6008nhh")) 6 | 7 | (pin-mode board 13 OUTPUT) 8 | 9 | (doseq [_ (range 5)] 10 | (digital-write board 13 HIGH) 11 | (Thread/sleep 1000) 12 | (digital-write board 13 LOW) 13 | (Thread/sleep 1000)) 14 | 15 | (close board) 16 | -------------------------------------------------------------------------------- /resources/examples/button-led.clj: -------------------------------------------------------------------------------- 1 | (ns button-led 2 | (:use :reload-all clodiuno.core) 3 | (:use :reload-all clodiuno.wishield)) 4 | 5 | ;; Button connected to pin 6 6 | ;; LED connected ro pin 3 7 | ;; http://www.arduino.cc/en/Tutorial/Button 8 | 9 | (def board (arduino :wishield "10.0.2.100" 1000)) 10 | 11 | (pin-mode board 3 OUTPUT) 12 | (pin-mode board 6 INPUT) 13 | 14 | (while true 15 | (let [i (digital-read board 6)] 16 | (println i) 17 | (digital-write board 3 i))) 18 | 19 | ;;(close board) 20 | -------------------------------------------------------------------------------- /resources/examples/pwm.clj: -------------------------------------------------------------------------------- 1 | (ns pwm 2 | (:use :reload-all clodiuno.core) 3 | (:use :reload-all clodiuno.wishield)) 4 | 5 | ;; Potentiometer connected to pin 5 6 | ;; LED connected to pin 3 7 | 8 | (defn map-int [x in-min in-max out-min out-max] 9 | (+ (/ (* (- x in-min) (- out-max out-min)) (- in-max in-min)) out-min)) 10 | 11 | (def board (arduino :wishield "10.0.2.100" 1000)) 12 | 13 | (pin-mode board 3 PWM) 14 | (pin-mode board 5 ANALOG) 15 | 16 | (while true 17 | (analog-write board 3 (map-int (analog-read board 5) 0 1023 0 255))) 18 | 19 | ;;(close board) 20 | -------------------------------------------------------------------------------- /resources/examples/photoresistor.clj: -------------------------------------------------------------------------------- 1 | (ns photoresistor 2 | (:use :reload-all clodiuno.core) 3 | (:use :reload-all clodiuno.firmata)) 4 | 5 | ;; refer 6 | ;; http://nakkaya.com/2009/10/29/connecting-a-photoresistor-to-an-arduino/ 7 | ;; for circuit diagram. 8 | 9 | (def photo-pin 0) 10 | (def led-pin 13) 11 | (def threshold 250) 12 | (def board (arduino :firmata "/dev/tty.usbserial-A600aeCj")) 13 | 14 | (pin-mode board 13 OUTPUT) 15 | 16 | ;;start receiving data for photo-pin 17 | (enable-pin board :analog photo-pin) 18 | 19 | (doseq [_ (range 1000000)] 20 | (let [val (analog-read board photo-pin)] 21 | (if (> val threshold) 22 | (digital-write board led-pin HIGH) 23 | (digital-write board led-pin LOW)))) 24 | 25 | (close board) 26 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Clojure API for Arduino. 2 | 3 | To install, merely add the following to your 'project.clj'. 4 | 5 | ```clojure 6 | [clodiuno "0.0.4-SNAPSHOT"] 7 | 8 | ``` 9 | 10 | For examples, check out the [project 11 | homepage](http://nakkaya.com/clodiuno.html). 12 | 13 | # Usage 14 | 15 | ## Create board 16 | 17 | ```clojure 18 | (ns clj-arduino 19 | (:require [clodiuno.core :refer :all]) 20 | (:require [clodiuno.firmata :refer :all])) 21 | 22 | (def board (arduino :firmata "/path/to/port")) 23 | ; or 24 | (def board (arduino :firmata "/path/to/port" :baudrate 9600)) 25 | ; default baudrate is 57600 26 | ``` 27 | 28 | 29 | # Issues 30 | 31 | If you got NoSuchPortException then you need add your port to env var containing ports. 32 | ```clojure 33 | (System/setProperty "gnu.io.rxtx.SerialPorts" "/path/to/your/port") 34 | ``` 35 | -------------------------------------------------------------------------------- /src/clodiuno/wishield.clj: -------------------------------------------------------------------------------- 1 | (ns clodiuno.wishield 2 | #^{:author "Nurullah Akkaya", 3 | :doc "WiShield Library for Clojure."} 4 | (:use clodiuno.core) 5 | (:import (java.text DecimalFormat) 6 | (java.net Socket) 7 | (java.io PrintWriter InputStreamReader BufferedReader))) 8 | 9 | ;;Pins 10,11,12,13 are "mandatory" for SPI communications 10 | ;;as is pin 2 OR 8 depending on your jumper setting. 11 | ;;Pin 9 (LED) and Pin 7 (Dataflash) can be freed by removing the jumper. 12 | 13 | (def pin-format (DecimalFormat. "00")) 14 | (def pwm-format (DecimalFormat. "000")) 15 | 16 | (defn- send-command [conn cmd] 17 | (doto (:out conn) 18 | (.println cmd) 19 | (.flush)) 20 | (.readLine (:in conn))) 21 | 22 | (defmethod enable-pin :wishield [board type pin]) 23 | 24 | (defmethod disable-pin :wishield [board type pin]) 25 | 26 | (defmethod pin-mode :wishield [board pin mode] 27 | (let [pin (.format pin-format pin) 28 | mode (cond (= mode INPUT) "i" 29 | (= mode OUTPUT) "o" 30 | (= mode ANALOG) "a" 31 | (= mode PWM) "p" 32 | (= mode SERVO) "s" 33 | :default (throw (Exception. "Invalid Mode.")))] 34 | (send-command board (str "pm" pin mode)))) 35 | 36 | (defmethod digital-write :wishield [board pin value] 37 | (let [pin (.format pin-format pin) 38 | value (cond (= value HIGH) "h" 39 | (= value LOW) "l" 40 | :default (throw (Exception. "Invalid Value.")))] 41 | (send-command board (str "dw" pin value)))) 42 | 43 | (defmethod analog-write :wishield [board pin value] 44 | (send-command 45 | board (str "aw" (.format pin-format pin) (.format pwm-format value)))) 46 | 47 | (defmethod analog-read :wishield [board pin] 48 | (read-string 49 | (send-command board (str "ar" (.format pin-format pin))))) 50 | 51 | (defmethod digital-read :wishield [board pin] 52 | (read-string 53 | (send-command board (str "dr" (.format pin-format pin))))) 54 | 55 | (defmethod close :wishield [board] 56 | (let [{:keys [in out socket]} board] 57 | (send-command board "bye") 58 | (.close in) 59 | (.close out) 60 | (.close socket))) 61 | 62 | (defmethod arduino :wishield [interface ip port] 63 | (let [socket (Socket. ip port) 64 | in (BufferedReader. (InputStreamReader. (.getInputStream socket))) 65 | out (PrintWriter. (.getOutputStream socket))] 66 | (.readLine in) 67 | {:interface :wishield :in in :out out :socket socket})) 68 | -------------------------------------------------------------------------------- /resources/wishield/handler.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int servo_enabled[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 5 | Servo servo[20]; 6 | 7 | int parsePin(char* msg){ 8 | char pin[] = {msg[2],msg[3]}; 9 | return atoi(pin); 10 | } 11 | 12 | int cmdCmpr(char* msg, char* expected){ 13 | if (msg[0] == expected[0] && msg[1] == expected[1]) 14 | return true; 15 | else 16 | return false; 17 | } 18 | 19 | char* setPinMode(char* msg){ 20 | int pin = parsePin(msg); 21 | char mode = msg[4]; 22 | 23 | if (mode == 'o'){ 24 | digitalWrite(pin, LOW); // disable PWM 25 | pinMode(pin, OUTPUT); 26 | return "OK\n"; 27 | }else if (mode == 'i'){ 28 | pinMode(pin, INPUT); 29 | return "OK\n"; 30 | }else if (mode == 'a'){ 31 | digitalWrite(pin + 14, LOW); // disable internal pull-ups 32 | pinMode(pin + 14, INPUT); 33 | return "OK\n"; 34 | }else if (mode == 'p'){ 35 | pinMode(pin, OUTPUT); 36 | return "OK\n"; 37 | }else if (mode == 's'){ 38 | servo[pin].attach(pin); 39 | servo_enabled[pin] = 1; 40 | return "OK\n"; 41 | }else 42 | return "Error\n"; 43 | } 44 | 45 | char* setDigitalWrite(char* msg){ 46 | int pin = parsePin(msg); 47 | char mode = msg[4]; 48 | 49 | if (mode == 'h'){ 50 | digitalWrite(pin, HIGH); 51 | return "OK\n"; 52 | }else if (mode == 'l'){ 53 | digitalWrite(pin, LOW); 54 | return "OK\n"; 55 | }else 56 | return "Error\n"; 57 | } 58 | 59 | char* setAnalogWrite(char* msg){ 60 | int pin = parsePin(msg); 61 | char val[] = {msg[4], msg[5], msg[6]}; 62 | 63 | if (servo_enabled[pin] == 1) 64 | servo[pin].write(atoi(val)); 65 | analogWrite(pin, atoi(val)); 66 | 67 | return "OK\n"; 68 | } 69 | 70 | char* getDigitalRead(char* msg){ 71 | int pin = parsePin(msg); 72 | 73 | char buff [20]; 74 | itoa(digitalRead(pin),buff,10); 75 | 76 | strncat(buff,"\n",1); 77 | return buff; 78 | } 79 | 80 | char* getAnalogRead(char* msg){ 81 | int pin = parsePin(msg); 82 | 83 | char buff [20]; 84 | itoa(analogRead(pin),buff,10); 85 | 86 | strncat(buff,"\n",1); 87 | return buff; 88 | } 89 | 90 | char* process(char* msg){ 91 | if (cmdCmpr(msg,"pm")) 92 | return setPinMode(msg); 93 | else if (cmdCmpr(msg,"dw")) 94 | return setDigitalWrite(msg); 95 | else if (cmdCmpr(msg,"aw")) 96 | return setAnalogWrite(msg); 97 | else if (cmdCmpr(msg,"dr")) 98 | return getDigitalRead(msg); 99 | else if (cmdCmpr(msg,"ar")) 100 | return getAnalogRead(msg); 101 | else 102 | return "Unknown Command.\n"; 103 | } 104 | -------------------------------------------------------------------------------- /src/clodiuno/core.clj: -------------------------------------------------------------------------------- 1 | (ns clodiuno.core) 2 | 3 | (def INPUT 0) ;;pin to input mode 4 | (def OUTPUT 1) ;;pin to output mode 5 | (def ANALOG 2) ;;pin to analog mode 6 | (def PWM 3) ;; pin to PWM mode 7 | (def SERVO 4) ;; attach servo to pin (pins 2 - 13) 8 | (def HIGH 1) ;;high value (+5 volts) to a pin in a call to digital-write 9 | (def LOW 0) ;;low value (0 volts) to a pin in a call to digital-write 10 | 11 | (defmulti arduino 12 | "Connect to board." 13 | (fn [type & _] type)) 14 | 15 | (defmulti enable-pin 16 | "Tell firmware to start sending pin readings." 17 | (fn [type _ _] (type :interface))) 18 | 19 | (defmulti disable-pin 20 | "Tell firmware to stop sending pin readings." 21 | (fn [type _ _] (type :interface))) 22 | 23 | (defmulti pin-mode 24 | "Configures the specified pin to behave either as an input or an output." 25 | (fn [type _ _] (type :interface))) 26 | 27 | (defmulti digital-write 28 | "Write a HIGH or a LOW value to a digital pin." 29 | (fn [type _ _] (type :interface))) 30 | 31 | (defmulti digital-read 32 | "Read a HIGH or a LOW value from a digital pin." 33 | (fn [type _] (type :interface))) 34 | 35 | (defmulti analog-read 36 | "Reads the value from the specified analog pin." 37 | (fn [type _] (type :interface))) 38 | 39 | (defmulti analog-write 40 | "Write an analog value (PWM-wave) to a digital pin." 41 | (fn [type _ _] (type :interface))) 42 | 43 | (defmulti set-sampling-interval 44 | "Set the Sampling Interval, that is, how often analog data and I2C data is reported to the client" 45 | (fn [type _] (type :interface))) 46 | 47 | (defmulti i2c-init 48 | "You need to call this before doing ANY I2C work. 49 | delay: microseconds to wait between receiving a firmata read 50 | request and sending it to the slave. Needed by some devices like 51 | WiiNunchuck. Default is 19ms" 52 | (fn [type & _] (type :interface))) 53 | 54 | (defmulti i2c-blocking-read 55 | "Make a single read request to a I2C device in a blocking way" 56 | (fn [type & _] (type :interface))) 57 | 58 | (defmulti i2c-write 59 | "Write to I2C device" 60 | (fn [type & _] (type :interface))) 61 | 62 | (defmulti i2c-start-reading 63 | "Read continously from I2C device. This will register an slave-addr+register 64 | entry in the Board. The board then issues I2C read requests at sampling-interval 65 | freq, and reports back to the host. You can access these lectures using i2c-read. 66 | Will read indefinitely until i2c-stop-reading is called for that particular device. " 67 | (fn [type & _] (type :interface))) 68 | 69 | (defmulti i2c-stop-reading 70 | "Stop reading continuously from I2C device" 71 | (fn [type & _] (type :interface))) 72 | 73 | (defmulti i2c-read 74 | "Read from I2C device. Before using this function you need to start reading using i2c-start-reading" 75 | (fn [type _ _] (type :interface))) 76 | 77 | (defmulti close 78 | "Close serial interface." 79 | (fn [type] (type :interface))) 80 | -------------------------------------------------------------------------------- /resources/wishield/wishield.pde: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "handler.h" 4 | 5 | #define WIRELESS_MODE_INFRA 1 6 | #define WIRELESS_MODE_ADHOC 2 7 | 8 | // Wireless configuration parameters ---------------------------------------- 9 | // IP address of WiShield 10 | unsigned char local_ip[] = {192,168,2,220}; 11 | // router or gateway IP address 12 | unsigned char gateway_ip[] = {192,168,2,1}; 13 | // subnet mask for the local network 14 | unsigned char subnet_mask[] = {255,255,255,0}; 15 | // max 32 bytes 16 | const prog_char ssid[] PROGMEM = {"Home"}; 17 | 18 | 19 | // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2 20 | unsigned char security_type = 3; 21 | 22 | // WPA/WPA2 passphrase 23 | // max 64 characters 24 | const prog_char security_passphrase[] PROGMEM = {"12345678"}; 25 | 26 | // WEP 128-bit keys 27 | // sample HEX keys 28 | prog_uchar wep_keys[] PROGMEM = { 29 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 30 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // Key 0 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 1 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 2 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Key 3 37 | }; 38 | 39 | // setup the wireless mode 40 | // infrastructure - connect to AP 41 | // adhoc - connect to another WiFi device 42 | unsigned char wireless_mode = WIRELESS_MODE_INFRA; 43 | 44 | unsigned char ssid_len; 45 | unsigned char security_passphrase_len; 46 | //--------------------------------------------------------------------------- 47 | 48 | void setup(){ 49 | WiFi.init(); 50 | } 51 | 52 | void loop(){ 53 | WiFi.run(); 54 | } 55 | 56 | extern "C" { 57 | 58 | #include "uip.h" 59 | #include 60 | #include "uipopt.h" 61 | #include "psock.h" 62 | 63 | static int handle_connection(struct socket_app_state *s); 64 | 65 | void socket_app_init(void){ 66 | /* We start to listen for connections on TCP port 1000. */ 67 | uip_listen(HTONS(1000)); 68 | } 69 | 70 | void socket_app_appcall(void){ 71 | struct socket_app_state *s = &(uip_conn->appstate); 72 | 73 | if(uip_connected()) { 74 | PSOCK_INIT(&s->p, 75 | (unsigned char*)s->inputbuffer, 76 | sizeof(s->inputbuffer)); 77 | } 78 | 79 | handle_connection(s); 80 | } 81 | 82 | #define PSOCK_SEND_STR(psock, str) \ 83 | PT_WAIT_THREAD(&((psock)->pt), \ 84 | psock_send(psock, (unsigned char*)str, strlen(str))) 85 | 86 | static int handle_connection(struct socket_app_state *s){ 87 | 88 | PSOCK_BEGIN(&s->p); 89 | PSOCK_SEND_STR(&s->p,"Connected.\n"); 90 | 91 | for(;;){ 92 | memset(s->inputbuffer, 0x00, sizeof(s->inputbuffer)); 93 | PSOCK_READTO(&s->p, '\n'); 94 | 95 | if(strcmp(s->inputbuffer,"bye\n") == 0) 96 | break; 97 | 98 | PSOCK_SEND_STR(&s->p, process(s->inputbuffer)); 99 | } 100 | 101 | PSOCK_CLOSE(&s->p); 102 | PSOCK_END(&s->p); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/clodiuno/firmata.clj: -------------------------------------------------------------------------------- 1 | (ns clodiuno.firmata 2 | (:refer-clojure :exclude [byte]) 3 | #^{:author "Nurullah Akkaya", 4 | :doc "Firmata Library for Clojure."} 5 | (:use clodiuno.core) 6 | (:import (java.io InputStream) 7 | (gnu.io SerialPort CommPortIdentifier 8 | SerialPortEventListener SerialPortEvent 9 | NoSuchPortException))) 10 | 11 | (def DIGITAL-MESSAGE 0x90) ;;send data for a digital port 12 | (def ANALOG-MESSAGE 0xE0) ;;send data for an analog pin (or PWM) 13 | (def REPORT-ANALOG 0xC0) ;;enable analog input by pin # 14 | (def REPORT-DIGITAL 0xD0) ;;enable digital input by port 15 | (def SET-PIN-MODE 0xF4) ;;set a pin to INPUT/OUTPUT/PWM/etc 16 | (def REPORT-VERSION 0xF9) ;;report firmware version 17 | (def SYSTEM-RESET 0xFF) ;;reset from MIDI 18 | (def START-SYSEX 0xF0) ;;start a MIDI SysEx message 19 | (def END-SYSEX 0xF7) ;;end a MIDI SysEx message 20 | 21 | ;; SYSEX extended commands 22 | (def RESERVED-COMMAND 0x00) ;;2nd SysEx data byte is a chip-specific command (AVR, PIC, TI, etc). 23 | (def ANALOG-MAPPING-QUERY 0x69) ;;ask for mapping of analog to pin numbers 24 | (def ANALOG-MAPPING-RESPONSE 0x6A) ;;reply with mapping info 25 | (def CAPABILITY-QUERY 0x6B) ;;ask for supported modes and resolution of all pins 26 | (def CAPABILITY-RESPONSE 0x6C) ;;reply with supported modes and resolution 27 | (def PIN-STATE-QUERY 0x6D) ;;ask for a pin's current mode and value 28 | (def PIN-STATE-RESPONSE 0x6E) ;;reply with a pin's current mode and value 29 | (def EXTENDED-ANALOG 0x6F) ;;analog write (PWM, Servo, etc) to any pin 30 | (def SERVO-CONFIG 0x70) ;;set max angle, minPulse, maxPulse, freq 31 | (def STRING-DATA 0x71) ;;a string message with 14-bits per char 32 | (def SHIFT-DATA 0x75) ;;shiftOut config/data message (34 bits) 33 | (def I2C-REQUEST 0x76) ;;I2C request messages from a host to an I/O board 34 | (def I2C-REPLY 0x77) ;;I2C reply messages from an I/O board to a host, only for read/read-continously 35 | (def I2C-CONFIG 0x78) ;;Configure special I2C settings such as power pins and delay times 36 | (def REPORT-FIRMWARE 0x79) ;;report name and version of the firmware 37 | (def SAMPLING-INTERVAL 0x7A) ;;sampling interval 38 | (def SYSEX-NON-REALTIME 0x7E) ;;MIDI Reserved for non-realtime messages 39 | (def SYSEX-REALTIME 0x7F) ;;MIDI Reserved for realtime messages 40 | 41 | ;; Taken from StandardFirmata.ino 42 | (def I2C-WRITE 2r00000000) 43 | (def I2C-READ 2r00001000) 44 | ;; read-continously indicates that the firmware should continuously 45 | ;; read the device at the rate specified by the sampling interval. 46 | ;; firmware implementation should support read continuous mode for 47 | ;; several I2C devices simultaneously. Sending the stop reading 48 | ;; command will end read continuous mode for that particular device. 49 | (def I2C-READ-CONTINUOUSLY 2r00010000) 50 | (def I2C-STOP-READING 2r00011000) 51 | (def I2C-READ-WRITE-MODE-MASK 2r00011000) 52 | (def I2C-10BIT-ADDRESS-MODE-MASK 2r00100000) 53 | 54 | 55 | (def arduino-port-count 7) 56 | 57 | (defn- byte [v] 58 | (.byteValue (- v 256))) 59 | 60 | ;; 61 | ;; Serial Setup 62 | ;; 63 | 64 | (defn- port-identifier 65 | "Given a port name return its identifier." 66 | [port-name] 67 | (try 68 | (let [ports (CommPortIdentifier/getPortIdentifiers)] 69 | (loop [port (.nextElement ports) 70 | name (.getName port)] 71 | (if (= name port-name) 72 | port (recur (.nextElement ports) (.getName port))))) 73 | (catch Exception e (throw (NoSuchPortException.))))) 74 | 75 | (defn- open 76 | "Open serial interface." 77 | [identifier baudrate] 78 | (doto (.open identifier "clojure" 1) 79 | (.setSerialPortParams baudrate 80 | SerialPort/DATABITS_8 81 | SerialPort/STOPBITS_1 82 | SerialPort/PARITY_NONE))) 83 | 84 | (defmethod close :firmata [conn] 85 | (.close (:port @conn))) 86 | 87 | (defn- listener 88 | "f will be called whenever there is data availible on the stream." 89 | [f] 90 | (proxy [SerialPortEventListener] [] 91 | (serialEvent 92 | [event] 93 | (if (= (.getEventType event) SerialPortEvent/DATA_AVAILABLE) 94 | (f))))) 95 | 96 | (defn- write-bytes [conn & bs] 97 | (let [out (.getOutputStream (:port @conn))] 98 | (doseq [b bs] 99 | (.write out b)) 100 | (.flush out))) 101 | 102 | (defn- lsb [b] 103 | (bit-and b 0x7F)) 104 | 105 | (defn- msb [b] 106 | (bit-and (bit-shift-right b 7) 0x7F)) 107 | 108 | (defn- bytes-to-int [lsb msb] 109 | (bit-or (bit-shift-left (bit-and msb 0x7F) 7) 110 | (bit-and lsb 0x7F))) 111 | 112 | (defn- write-data [conn data] 113 | (when (not (empty? data)) 114 | (apply write-bytes conn 115 | (mapcat (fn [b] [(lsb b) (msb b)]) 116 | data)))) 117 | 118 | (defn- bits [n] 119 | (map #(bit-and (bit-shift-right n %) 1) (range 8))) 120 | 121 | (defn- numb [bits] 122 | (int (BigInteger. (apply str bits) 2))) 123 | 124 | (defn- assoc-in! [r ks v] 125 | (dosync (alter r assoc-in ks v))) 126 | 127 | (defn- i2c-request [conn slave-addr mode data] 128 | {:pre [(<= slave-addr 127)]} ;; Current arduino firmata doesn't support 10-bit addressing. 129 | (doto conn 130 | (write-bytes START-SYSEX 131 | I2C-REQUEST 132 | (lsb slave-addr) 133 | (bit-or (bit-and (msb slave-addr) 134 | (bit-not I2C-READ-WRITE-MODE-MASK)) 135 | mode) 136 | ) 137 | (write-data data) 138 | (write-bytes END-SYSEX) 139 | ) 140 | ) 141 | 142 | ;; 143 | ;; Firmata Calls 144 | ;; 145 | 146 | (defmethod enable-pin :firmata [conn type pin] 147 | (cond (= type :analog) (write-bytes conn (bit-or REPORT-ANALOG pin) 1) 148 | (= type :digital) (write-bytes conn (bit-or REPORT-DIGITAL (int (/ pin 8))) 1) 149 | :default (throw (Exception. "Unknown pin type.")))) 150 | 151 | (defmethod disable-pin :firmata [conn type pin] 152 | (cond (= type :analog) (write-bytes conn (bit-or REPORT-ANALOG pin) 0) 153 | (= type :digital) (write-bytes conn (bit-or REPORT-DIGITAL (int (/ pin 8))) 0) 154 | :default (throw (Exception. "Unknown pin type.")))) 155 | 156 | (defmethod pin-mode :firmata [conn pin mode] 157 | (write-bytes conn SET-PIN-MODE pin mode)) 158 | 159 | (defmethod digital-write :firmata [conn pin value] 160 | (let [port (int (/ pin 8)) 161 | vals ((@conn :digital-out) port) 162 | beg (take (mod pin 8) vals) 163 | end (drop (inc (mod pin 8)) vals) 164 | state (concat beg [value] end)] 165 | (assoc-in! conn [:digital-out port] state) 166 | (write-bytes conn (bit-or DIGITAL-MESSAGE port) (numb (reverse state)) 0))) 167 | 168 | (defmethod digital-read :firmata [conn pin] 169 | (let [port (int (/ pin 8)) 170 | vals ((@conn :digital-in) port)] 171 | (first (drop (mod pin 8) vals)))) 172 | 173 | (defmethod analog-read :firmata [conn pin] 174 | ((@conn :analog) pin)) 175 | 176 | (defmethod analog-write :firmata [conn pin val] 177 | (write-bytes conn (bit-or ANALOG-MESSAGE (bit-and pin 0x0F)) (bit-and val 0x7F) (bit-shift-right val 7))) 178 | 179 | ;; default 19 (ms) 180 | (defmethod set-sampling-interval :firmata [conn delay] 181 | (write-bytes conn START-SYSEX SAMPLING-INTERVAL (lsb delay) (msb delay) END-SYSEX) 182 | ) 183 | 184 | ;; default delay = 19 (ms) 185 | (defmethod i2c-init :firmata [conn & {:keys [delay] :or {delay 19}}] 186 | (write-bytes conn START-SYSEX I2C-CONFIG (lsb delay) (msb delay) END-SYSEX)) 187 | 188 | (defmethod i2c-blocking-read :firmata [conn slave-addr register bytes-to-read & {:keys [timeout]}] 189 | ;; Note, Firmata protocol doesn't allows you to distingish between 190 | ;; read replies and read-continously reports, both are marked as I2C_REPLY 191 | ;; If a report comes for that slave-addr/register it will be taken 192 | ;; as the response of the i2c-blocking-read command. 193 | ;; 194 | ;; Register can be nil 195 | (let [reply (promise)] 196 | (assoc-in! conn [:i2c :last-blocking-read] {:slave-addr slave-addr 197 | :register (or register -1) 198 | :response reply}) 199 | 200 | (i2c-request conn slave-addr I2C-READ (if register [register bytes-to-read] [bytes-to-read])) 201 | (if timeout 202 | (deref reply timeout nil) 203 | @reply))) 204 | 205 | (defmethod i2c-write :firmata 206 | ([conn slave-addr data] (i2c-request conn slave-addr I2C-WRITE data)) 207 | ([conn slave-addr register data] (i2c-request conn slave-addr I2C-WRITE (concat [register] data)))) 208 | 209 | (defmethod i2c-start-reading :firmata [conn slave-addr register bytes-to-read] 210 | (i2c-request conn slave-addr I2C-READ-CONTINUOUSLY [register bytes-to-read])) 211 | 212 | (defmethod i2c-stop-reading :firmata [conn slave-addr] 213 | (i2c-request conn slave-addr I2C-STOP-READING [])) 214 | 215 | (defmethod i2c-read :firmata [conn slave-addr register] 216 | (get-in @conn [:i2c slave-addr register])) 217 | 218 | 219 | (defn- read-multibyte [in] 220 | (let [lsb (.read in) 221 | msb (.read in) 222 | val (bit-or (bit-shift-left msb 7) lsb)] 223 | [lsb msb val])) 224 | 225 | (defn- read-to-sysex-end [in] 226 | (loop [buffer []] 227 | (let [b (.read in)] 228 | (if (= b END-SYSEX) 229 | buffer 230 | (recur (conj buffer b)))))) 231 | 232 | (defn- multibytes-to-ints [list] 233 | (for [[lsb msb] (partition 2 list)] (bytes-to-int lsb msb))) 234 | 235 | (defn- handle-sysex [conn in] 236 | (let [cmd (.read in)] 237 | (cond 238 | (= cmd REPORT-FIRMWARE) (let [major-version (.read in) 239 | minor-version (.read in) 240 | firmware-name (apply str (map char (read-to-sysex-end in)))] 241 | (assoc-in! conn [:firmware] {:version [major-version minor-version] 242 | :name firmware-name})) 243 | (= cmd STRING-DATA) (let [packet (read-to-sysex-end in) 244 | msg (apply str (map char packet)) 245 | msg-callback (get-in @conn [:callbacks :msg])] 246 | (when msg-callback 247 | (msg-callback msg))) 248 | 249 | (= cmd I2C-REPLY) (let [packet (read-to-sysex-end in) 250 | [slave-addr 251 | register 252 | & data] (multibytes-to-ints packet) 253 | r (get-in @conn [:i2c :last-blocking-read])] 254 | (if (and r (= (:slave-addr r) slave-addr) 255 | (= (:register r) register) 256 | (not (realized? (:response r)))) 257 | (deliver (:response r) data) 258 | (assoc-in! conn [:i2c slave-addr register] data)) 259 | ) 260 | :unknown-cmd (read-to-sysex-end in) ;; discard 261 | ))) 262 | 263 | (defn- process-input 264 | "Parse input from firmata." 265 | [conn in] 266 | (while (> (.available in) 2) 267 | (let [data (.read in)] 268 | (cond 269 | (= (bit-and data 0xF0) ANALOG-MESSAGE) (let [pin (bit-and data 0x0F) 270 | [_ _ val] (read-multibyte in)] 271 | (assoc-in! conn [:analog pin] val)) 272 | 273 | (= (bit-and data 0xF0) DIGITAL-MESSAGE) (let [port (bit-and data 0x0F) 274 | [lsb msb val] (read-multibyte in)] 275 | (assoc-in! conn [:digital-in port] (bits val))) 276 | 277 | (= data REPORT-VERSION) (assoc-in! conn [:version] [(.read in) (.read in)]) 278 | 279 | (= data START-SYSEX) (handle-sysex conn in))))) 280 | 281 | 282 | (defmethod arduino :firmata [type port & {:keys [baudrate msg-callback] :or {baudrate 57600}}] 283 | (let [port (open (port-identifier port) baudrate) 284 | conn (ref {:port port :interface :firmata})] 285 | 286 | (doto port 287 | (.addEventListener (listener #(process-input conn (.getInputStream (:port @conn))))) 288 | (.notifyOnDataAvailable true)) 289 | 290 | (write-bytes conn REPORT-VERSION) 291 | 292 | (while (nil? (:version @conn)) 293 | (Thread/sleep 100)) 294 | 295 | (dotimes [i arduino-port-count] 296 | (assoc-in! conn [:digital-out i] (repeat 8 0))) 297 | 298 | (dotimes [i arduino-port-count] 299 | (assoc-in! conn [:digital-in i] (repeat 8 0))) 300 | 301 | (assoc-in! conn [:i2c] {:last-blocking-read nil}) 302 | (assoc-in! conn [:callbacks] {:msg msg-callback}) 303 | 304 | conn)) 305 | --------------------------------------------------------------------------------