├── .gitignore ├── resources ├── bootloader.bin ├── espruino_esp32.bin ├── partitions_espruino.bin └── esprit-esp32-board.edn ├── co-repl.edn ├── src ├── cljs │ └── repl │ │ └── esprit.clj └── esprit │ ├── board.clj │ ├── indicators.cljs │ ├── make_rom.clj │ ├── repl.cljs │ ├── flash.clj │ ├── board.cljs │ └── repl.clj ├── doc ├── cljdoc.edn ├── board-flashing.md ├── building-espruino.md ├── project-setup.md └── getting-started.md ├── deps.edn ├── project.clj ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /target 3 | /pom.xml.asc 4 | /pom.xml 5 | /.cpcache 6 | .nrepl-port 7 | -------------------------------------------------------------------------------- /resources/bootloader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfikes/esprit/HEAD/resources/bootloader.bin -------------------------------------------------------------------------------- /resources/espruino_esp32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfikes/esprit/HEAD/resources/espruino_esp32.bin -------------------------------------------------------------------------------- /co-repl.edn: -------------------------------------------------------------------------------- 1 | {:optimizations :simple 2 | :target :none 3 | :browser-repl false 4 | :process-shim false} 5 | -------------------------------------------------------------------------------- /resources/partitions_espruino.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfikes/esprit/HEAD/resources/partitions_espruino.bin -------------------------------------------------------------------------------- /src/cljs/repl/esprit.clj: -------------------------------------------------------------------------------- 1 | (ns cljs.repl.esprit 2 | (:require [esprit.repl :as esprit])) 3 | 4 | (def repl-env esprit/repl-env) 5 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree [["Readme" {:file "README.md"}] 2 | ["Getting Started" {:file "doc/getting-started.md"}] 3 | ["Project Setup" {:file "doc/project-setup.md"}] 4 | ["Building and Board Flashing" {:file "doc/board-flashing.md"}] 5 | ["Building Espruino" {:file "doc/building-espruino.md"}]]} 6 | -------------------------------------------------------------------------------- /resources/esprit-esp32-board.edn: -------------------------------------------------------------------------------- 1 | {:esprit.indicators/read-led 2 | {:type :output-analog 3 | :pin "D14" 4 | :value 1} 5 | :esprit.indicators/eval-led 6 | {:type :output-analog 7 | :pin "D33" 8 | :value 0.2 9 | :freq 1} 10 | :esprit.indicators/print-led 11 | {:type :output-analog 12 | :pin "D32" 13 | :value 1} 14 | :esprit.indicators/conn-led 15 | {:type :output-analog 16 | :pin "D13" 17 | :value 1}} 18 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojurescript {:mvn/version "1.10.764"} 2 | com.github.rickyclarkson/jmdns {:mvn/version "3.4.2-r353-1"} 3 | yogthos/config {:mvn/version "1.1.7"} 4 | org.clojure/tools.cli {:mvn/version "1.0.194"}} 5 | :paths ["src" "resources"] 6 | :aliases {:make-repl {:main-opts ["-m" "cljs.main" "-co" "co-repl.edn" "-c" "esprit.repl"]} 7 | :make-rom {:main-opts [ "-m" "esprit.make-rom"]}}} 8 | -------------------------------------------------------------------------------- /src/esprit/board.clj: -------------------------------------------------------------------------------- 1 | (ns esprit.board 2 | (:require [clojure.java.io :as io] 3 | [clojure.edn :as edn] 4 | [config.core :refer [env]])) 5 | 6 | (defmacro read-board-file [] 7 | (list 'quote (edn/read-string (slurp (io/resource (or (:board-file env) 8 | (do (binding [*out* *err*] 9 | (println "NOTE: Defaulting to Esprit board config")) 10 | "esprit-esp32-board.edn"))))))) 11 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject esprit "1.0.0" 2 | :description "ClojureScript on the ESP32 using Espruino" 3 | :url "https://github.com/mfikes/esprit" 4 | :source-paths ["src" "resources"] 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :dependencies [[org.clojure/clojure "1.10.1" :scope "provided"] 8 | [org.clojure/clojurescript "1.10.764" :scope "provided"] 9 | [com.github.rickyclarkson/jmdns "3.4.2-r353-1"] 10 | [yogthos/config "1.1.7"] 11 | [org.clojure/tools.cli "1.0.194"]]) 12 | -------------------------------------------------------------------------------- /src/esprit/indicators.cljs: -------------------------------------------------------------------------------- 1 | (ns esprit.indicators 2 | (:require [esprit.board :as board])) 3 | 4 | (def ^:private print-led (::print-led board/items)) 5 | (def ^:private eval-led (::eval-led board/items)) 6 | (def ^:private read-led (::read-led board/items)) 7 | (def ^:private conn-led (::conn-led board/items)) 8 | 9 | (defn- pwm [led on-duty-cycle freq] 10 | (when led 11 | (js/analogWrite led (- 1 on-duty-cycle) #js {:freq freq}))) 12 | 13 | (defn- turn-off [led] 14 | (when led 15 | (.write led true))) 16 | 17 | (defn- turn-on [led] 18 | (when led 19 | (.write led false))) 20 | 21 | (defn indicate-eval [eval?] 22 | (if eval? 23 | (pwm eval-led 0.8 2) 24 | (turn-off eval-led))) 25 | 26 | (defn indicate-connections 27 | [connections] 28 | (if (pos? connections) 29 | (turn-on conn-led) 30 | (pwm conn-led 0.02 1))) 31 | 32 | (defn indicate-joining-wifi [] 33 | (pwm conn-led 0.5 120)) 34 | 35 | (defn indicate-no-wifi-creds [] 36 | (pwm conn-led 0.5 2)) 37 | 38 | (defn indicate-read [] 39 | (when read-led 40 | (js/digitalPulse read-led true #js [100 100]))) 41 | 42 | (defn indicate-print [] 43 | (when print-led 44 | (js/digitalPulse print-led true #js [100 100]))) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Esprit 2 | This repository contains support for ClojureScript on the ESP32 WROVER (or [Esprit board](https://github.com/mfikes/esprit-board)) using Espruino. 3 | 4 | If you have any questions about this stuff, be sure to check out the `#esprit` [Clojurians Slack](https://clojurians.net) channel. 5 | 6 | ## Videos 7 | 8 | ### ClojureScript on Microcontrollers 9 | [![ClojureScript on Microcontrollers](http://img.youtube.com/vi/u1jr4v7dhoo/0.jpg)](http://www.youtube.com/watch?v=u1jr4v7dhoo "ClojureScript on Microcontrollers") 10 | 11 | ### Contextual Electronics Podcast with Chris Gammell 12 | [![Contextual Electronics Podcast with Chris Gammell](http://img.youtube.com/vi/xYVsge81rmk/0.jpg)](https://www.youtube.com/watch?v=xYVsge81rmk "Contextual Electronics Podcast with Chris Gammell") 13 | 14 | ### Apropos 15 | [![Apropos](http://img.youtube.com/vi/J0wF92Zvq2c/0.jpg)](http://www.youtube.com/watch?v=J0wF92Zvq2c "Apropos") 16 | 17 | ### Live coding the ESP32 with ClojureScript in Emacs 18 | [![Live coding the ESP32 with ClojureScript in Emacs](http://img.youtube.com/vi/WYSL-fD_ogA/0.jpg)](http://www.youtube.com/watch?v=WYSL-fD_ogA "Live coding the ESP32 with ClojureScript in Emacs") 19 | 20 | ## Usage 21 | Check out the docs! 22 | [![cljdoc badge](https://cljdoc.org/badge/esprit/esprit)](https://cljdoc.org/d/esprit/esprit/CURRENT) 23 | 24 | ## Known issues 25 | 26 | See https://github.com/mfikes/esprit/issues for known issues. 27 | -------------------------------------------------------------------------------- /src/esprit/make_rom.clj: -------------------------------------------------------------------------------- 1 | (ns esprit.make-rom 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as string])) 5 | 6 | (defn -main [] 7 | (let [main-js (-> (slurp "out/main.js") 8 | (string/replace "goog.NONCE_PATTERN_=/^[\\w+/_-]+[=]{0,2}$/" "goog.NONCE_PATTERN_=null") 9 | (string/replace "/^((https:)?\\/\\/[0-9a-z.:[\\]-]+\\/|\\/[^/\\\\]|[^:/\\\\%]+\\/|[^:/\\\\%]*[?#]|about:blank#)/i" "null") 10 | (string/replace "/^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i" "null") 11 | (string/replace "a: {" "{") 12 | (string/replace "a:for" "for") 13 | (string/replace "goog.uri.utils.splitRe_=/^(?:([^:/?#.]+):)?(?:\\/\\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\\?([^#]*))?(?:#([\\s\\S]*))?$/" "goog.uri.utils.splitRe_=null") 14 | (string/replace "= /^(?:([^:/?#.]+):)?(?:\\/\\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\\?([^#]*))?(?:#([\\s\\S]*))?$/" "= null")) 15 | main-js (str "Array.prototype.concat = [].concat;\n" main-js) 16 | _ (spit "out/main-modified.js" main-js) 17 | bytes (.getBytes main-js "UTF-8") 18 | size (count bytes) 19 | size-bytes (byte-array (map #(bit-and (bit-shift-right size %) 0xff) [0 8 16 24]))] 20 | (with-open [os (io/output-stream "out/main.bin")] 21 | (.write os size-bytes) 22 | (.write os (.getBytes ".bootcde")) 23 | (.write os (byte-array (replicate 20 0))) 24 | (.write os bytes)) 25 | (println "ROM created; you can flash it to your ESP32 by executing the following:") 26 | (println "clj -M -m esprit.flash -f out/main.bin"))) 27 | -------------------------------------------------------------------------------- /src/esprit/repl.cljs: -------------------------------------------------------------------------------- 1 | (ns esprit.repl 2 | (:require 3 | [clojure.string :as string] 4 | [esprit.indicators :as ind])) 5 | 6 | (ind/indicate-eval false) 7 | 8 | (def connections (atom 0)) 9 | 10 | (def ^:private net (js/require "net")) 11 | 12 | (def ^:private wifi (js/require "Wifi")) 13 | 14 | (defn- write [c o] 15 | (ind/indicate-eval false) 16 | (doto c 17 | (.write (.stringify js/JSON o)) 18 | (.write "\0")) 19 | (ind/indicate-print)) 20 | 21 | (defn eval-data [data] 22 | (try 23 | (ind/indicate-eval true) 24 | #js {:status "success" 25 | :value (js/eval data)} 26 | (catch :default ex 27 | #js {:status "exception" 28 | :value (str ex) 29 | :stacktrace (.-stack ex)}))) 30 | 31 | (defn- handle-repl-connection [c] 32 | (.log js/console "New REPL Connection") 33 | (ind/indicate-connections (swap! connections inc)) 34 | (.on c "close" 35 | (fn [] 36 | (.log js/console "REPL disconnected") 37 | (ind/indicate-connections (swap! connections dec)))) 38 | (let [buffer (atom "")] 39 | (.on c "data" 40 | (fn [data] 41 | (ind/indicate-read) 42 | (if-not (string/ends-with? data "\0") 43 | (swap! buffer str data) 44 | (let [data (str @buffer data)] 45 | (reset! buffer "") 46 | (cond 47 | (string/starts-with? data "(function (){try{return cljs.core.pr_str") 48 | (write c (eval-data data)) 49 | 50 | (= data ":cljs/quit") 51 | (.end c) 52 | 53 | :else 54 | (write c #js {:status "success" 55 | :value "true"})))))))) 56 | 57 | (def ^:private server (.createServer net handle-repl-connection)) 58 | 59 | (defn- display-connect-info [] 60 | (.log js/console "Establish an Esprit REPL by executing") 61 | (.log js/console (str "clj -M -m cljs.main -re esprit -ro '{:endpoint-address \"" (.. wifi getIP -ip) "\"}' -r"))) 62 | 63 | (defn ensure-cljs-user [] 64 | (when-not (exists? js/cljs.user) 65 | (set! (.-user js/cljs) #js {}))) 66 | 67 | (defn- start-server [port] 68 | (.log js/console "Ready for REPL Connections") 69 | (ind/indicate-connections 0) 70 | (display-connect-info) 71 | (ensure-cljs-user) 72 | (.listen server port)) 73 | 74 | (.on wifi "connected" #(start-server 53000)) 75 | 76 | (goog-define wifi-ssid "") 77 | (goog-define wifi-password "") 78 | 79 | (if (seq wifi-ssid) 80 | (do 81 | (ind/indicate-joining-wifi) 82 | (.connect wifi wifi-ssid #js {:password wifi-password} (fn []))) 83 | (ind/indicate-no-wifi-creds)) 84 | -------------------------------------------------------------------------------- /src/esprit/flash.clj: -------------------------------------------------------------------------------- 1 | (ns esprit.flash 2 | (:require [clojure.java.io :as io] 3 | [clojure.tools.cli :refer [parse-opts]] 4 | [config.core :refer [env]]) 5 | (:import [java.io File] 6 | [java.lang ProcessBuilder])) 7 | 8 | (def memory-layout 9 | {"bootloader.bin" 0x1000 10 | "partitions_espruino.bin" 0x8000 11 | "espruino_esp32.bin" 0x10000 12 | "main.bin" 0x320000}) 13 | 14 | (defn run-and-print [& cmd-and-args] 15 | (.. (doto (ProcessBuilder. cmd-and-args) 16 | (.inheritIO)) 17 | (start) 18 | (waitFor))) 19 | 20 | (defn erase 21 | "Erase entire ESP32 flash" 22 | ([] 23 | (if-let [port (:serial-port env)] 24 | (erase port) 25 | (run-and-print "esptool.py" "erase_flash"))) 26 | ([port] 27 | (run-and-print "esptool.py" "--port" port "erase_flash"))) 28 | 29 | (defn flash 30 | "Flash `bin` to the `addr` location" 31 | ([bin addr] 32 | (if-let [port (:serial-port env)] 33 | (flash bin addr port) 34 | (run-and-print "esptool.py" "--baud" "2000000" "write_flash" (str addr) bin))) 35 | ([bin addr port] 36 | (run-and-print "esptool.py" "--baud" "2000000" "--port" port "write_flash" (str addr) bin))) 37 | 38 | (defn resource-to-tmp 39 | "Copy a file from the classpath `resource` to a temp file and return the File obj" 40 | [resource] 41 | (with-open [in (io/input-stream (io/resource resource))] 42 | (let [temp-file (File/createTempFile resource nil)] 43 | (io/copy in temp-file) 44 | temp-file))) 45 | 46 | (defn bootstrap 47 | "Flash the Espruino core, partition table, and bootloader to the ESP32" 48 | ([] 49 | (if-let [port (:serial-port env)] 50 | (bootstrap port) 51 | (doseq [[file addr] memory-layout 52 | :when (not (= file "main.bin"))] 53 | (flash (str (resource-to-tmp file)) addr)))) 54 | ([port] 55 | (doseq [[file addr] memory-layout 56 | :when (not (= file "main.bin"))] 57 | (flash (str (resource-to-tmp file)) addr port)))) 58 | 59 | (def cli-options 60 | ;; An option with a required argument 61 | [["-b" "--bootstrap"] 62 | ["-e" "--erase"] 63 | ["-p" "--port PORT" "ESP32's Serial Port"] 64 | ["-h" "--help"] 65 | ["-f" "--flash BINARY" "JS binary to flash to ESP32" 66 | :validate [#(.exists (io/file %)) "Binary does not exists"]]]) 67 | 68 | (defn -main [& args] 69 | (let [opts (parse-opts args cli-options)] 70 | (cond 71 | (:errors opts) (run! println (:errors opts)) 72 | (:erase (:options opts)) (if-let [port (:port (:options opts))] 73 | (erase port) 74 | (erase)) 75 | (:bootstrap (:options opts)) (if-let [port (:port (:options opts))] 76 | (bootstrap port) 77 | (bootstrap)) 78 | (:help (:options opts)) (println (:summary opts)) 79 | (:flash (:options opts)) (if-let [port (:port (:options opts))] 80 | (flash (:flash (:options opts)) (get memory-layout "main.bin") port) 81 | (flash (:flash (:options opts)) (get memory-layout "main.bin")))))) 82 | -------------------------------------------------------------------------------- /src/esprit/board.cljs: -------------------------------------------------------------------------------- 1 | (ns esprit.board 2 | (:require-macros [esprit.board :refer [read-board-file]])) 3 | 4 | ; Read in the board file 5 | (defonce board (read-board-file)) 6 | 7 | (defn board-item [{type :type 8 | :as item}] 9 | (case type 10 | ; Basic Pins 11 | :output-analog (let [{pin-name :pin} item] 12 | (let [pin (js/Pin pin-name)] 13 | (.mode pin "output") 14 | (when-let [val (:value item)] 15 | (js/analogWrite pin val #js{:freq (:freq item) 16 | :soft (:soft item) 17 | :forceSoft (:force-soft item)})) 18 | pin)) 19 | :output-digital (let [{pin-name :pin} item] 20 | (let [pin (js/Pin pin-name)] 21 | (.mode pin "output") 22 | (when-let [val (:value item)] 23 | (.write pin val)) 24 | pin)) 25 | :input (let [{pin-name :pin} item] 26 | (doto (js/Pin pin-name) 27 | (.mode "input"))) 28 | :analog (let [{pin-name :pin} item] 29 | (doto (js/Pin pin-name) 30 | (.mode "analog"))) 31 | :input-pullup (let [{pin-name :pin} item] 32 | (doto (js/Pin pin-name) 33 | (.mode "input_pullup"))) 34 | :input-pulldown (let [{pin-name :pin} item] 35 | (doto (js/Pin pin-name) 36 | (.mode "input_pulldown"))) 37 | :opendrain (let [{pin-name :pin} item] 38 | (let [pin (js/Pin pin-name)] 39 | (.mode pin "opendrain") 40 | (when-let [val (:value item)] 41 | (.write pin val)) 42 | pin)) 43 | :opendrain-pullup (let [{pin-name :pin} item] 44 | (let [pin (js/Pin pin-name)] 45 | (.mode pin "opendrain_pullup") 46 | (when-let [val (:value item)] 47 | (.write pin val)) 48 | pin)) 49 | ; Peripherals 50 | :serial (let [{baud :baud} item] 51 | (doto js/Serial2 52 | (.setup baud #js{:rx (:rx item) 53 | :tx (:tx item) 54 | :ck (:ck item) 55 | :cts (:cts item) 56 | :bytesize (:bytesize item) 57 | :parity (:parity item) 58 | :stopbits (:stopbits item) 59 | :flow (:flow item)}))) 60 | :software-serial (let [{baud :baud} item] 61 | (doto (js/Serial.) 62 | (.setup baud #js{:rx (:rx item) 63 | :tx (:tx item) 64 | :bytesize (:bytesize item) 65 | :stopbits (:stopbits item)}))))) 66 | 67 | (defonce items (into {} (map (fn [[key item]] [key (board-item item)])) board)) 68 | -------------------------------------------------------------------------------- /doc/board-flashing.md: -------------------------------------------------------------------------------- 1 | # Building and Board Flashing 2 | 3 | To flash binaries to your board, you will need a copy of [esptool][1] 4 | Additionally, as the ESP32 does not have native USB, you may need to install drivers for the USB to UART adapter that is on your board. For the Esprit board, you will need the Silicon Labs CP2102N [drivers][2]. 5 | 6 | ## esprit.flash 7 | Esprit provides a wrapper for esptool to give some convenient utilities to flashing esprit-specific binaries. The flags are given in each section below, and can also be shown via the command-line `-h` or `--help` argument to `clj -M -m esprit.flash`. All of the `esprit.flash` commands take an optional `-p` or `--port` option to specify the upload port, but esptool can usually find the correct port without it. At the project level, the port can be defined with the `:serial-port` config option in `config.edn` on the classpath. 8 | 9 | ## Erasing 10 | Given an ESP32 in an unknown state, it may be a good idea to completely erase the flash to start from a clean slate, this can be done with: 11 | ```bash 12 | esptool.py erase_flash 13 | ``` 14 | Or with the provided functionality 15 | ```bash 16 | clj -M -m esprit.flash --erase 17 | ``` 18 | 19 | ## Bootstraping 20 | The Esprit project is built atop the Espruino JS interpreter, and as such, Espruino must be uploaded to the ESP32 first. This phase of flashing "bootstraping" is only needed once, unless of course the underlying Espruino version gets updated. The bootstrap payload consists of `bootloader.bin`, `partitions_espruino.bin`, and `espruino_esp32.bin`. 21 | To learn more how these are built, see [Building Espruino][3]. 22 | 23 | ```bash 24 | esptool.py --baud 2000000 write_flash / 25 | 0x1000 bootloader.bin / 26 | 0x8000 partitions_espruino.bin / 27 | 0x10000 espruino_esp32.bin 28 | ``` 29 | To bootstrap using the included binaries, run 30 | ```bash 31 | clj -M -m esprit.flash --bootstrap 32 | ``` 33 | 34 | ## Building 35 | A few compiler options need to be set to get a working build of Esprit: 36 | ```clojure 37 | {:optimizations :simple 38 | :target :none 39 | :browser-repl false 40 | :process-shim false} 41 | ``` 42 | If you are using the Esprit REPL, you can bake in WiFi credentials by setting the additional compiler options: 43 | ```clojure 44 | {:closure-defines {esprit.repl/wifi-ssid "MySSID" 45 | esprit.repl/wifi-password "MyWiFiPassword"} 46 | ``` 47 | Using the CLI tools, this can be done by directly calling the options with 48 | ```bash 49 | clj -M -m cljs.main -co '{:closure-defines {esprit.repl/wifi-ssid "MySSID" esprit.repl/wifi-password "MyWiFiPassword"} :optimizations :simple :target :none :browser-repl false :process-shim false}' -c 50 | ``` 51 | Or with an EDN file on the classpath and 52 | ```bash 53 | clj -M -m cljs.main -co @compile-options.edn -c 54 | ``` 55 | 56 | ## Flashing 57 | Assuming some ClojureScript project has been built to `out/main.js`, a binary can be created with `clj -M -m esprit.make-rom`. This command will create a binary suitable for writing to the `js_code` partition in `out/main.bin`. This can be written with 58 | ```bash 59 | esptool.py --baud 2000000 write_flash 0x320000 out/main.bin 60 | ``` 61 | To flash with the built in functionality, run 62 | ```bash 63 | clj -M -m esprit.flash --flash out/main.bin 64 | ``` 65 | 66 | [1]: https://github.com/espressif/esptool 67 | [2]: https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers 68 | [3]: https://cljdoc.org/d/esprit/esprit/CURRENT/doc/building-espruino -------------------------------------------------------------------------------- /doc/building-espruino.md: -------------------------------------------------------------------------------- 1 | # Building Espruino 2 | 3 | ## Background 4 | We need to make a custom build of Espruino for use with Esprit. The real trick is to set up Espruino such that it has enough space to hold `cljs.core` and all your program code when running a REPL. This ends up at least around 900KB. 5 | 6 | Now for those unfamiliar, the ESP32 is configured with a partition table - allocating different sections of memory to the program code, non-volatile storage, the bootloader, etc. These partitions can be arbitrarily resized. Additionally, one can make partitions that don't mean anything to the ESP32, but can yield pointers in the C code that help manage the separate parts of memory. 7 | 8 | As modern ESP32s ship with TONS of flash, we want to make the partition that Espruino calls `js_code` as large as possible to take full advantage of the chip. This presents a couple challenges. 9 | 10 | 1. Espruino makes an assumption in their ESP32 builds that the chips are all of the 4MB variant, so there are a few more things to change than just the partition table. 11 | 2. The build process for Espruino is a bit challenging to successfully complete, involving challenges that are sometimes a bit esoteric and a bit more advanced. 12 | 13 | For the these reasons, we offer pre-built custom versions of the Espruino runtime for use with Esprit and these are directly accessed from the downloaded JAR and used by the Esprit flashing tools. 14 | 15 | But, if you want or need to build your own variant of Espruino, this page documents the process so that it is reproducible. 16 | 17 | ## Building 18 | 1. Clone the EspruinoBuildTools [repo][1] 19 | 2. In `esp32/build/app` modify `partitions_espruino.csv` as such 20 | ```patch 21 | -js_code,data,0,0x320000,256K 22 | -storage,data,0,0x360000,640K 23 | +js_code,data,0,0x320000,3M 24 | +storage,data,0,0x620000,1920K 25 | ``` 26 | > Note: We want the partition table to take up as much space as it can, 8MB as is the case for the ESP32. A normal data partition cannot be larger than 3MB as discussed [here][2]. It throws errors when trying to call `esp_partition_mmap`. We wish this could be bigger, as the the `js_code` partition is where our code will be burned. We believe code can be stored and loaded from `storage` as well, but that hasn’t been sorted yet. 27 | 3. Now in the same directory, modify `sdkconfig`, the result of a`menuconfig` run, to let the compiler know that our target does in fact have 8MB of flash. This should also match the total size of the partition table. 28 | ```patch 29 | -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 30 | -CONFIG_ESPTOOLPY_FLASHSIZE_8MB= 31 | +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= 32 | +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y 33 | -CONFIG_ESPTOOLPY_FLASHSIZE="4MB" 34 | +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" 35 | ``` 36 | 4. Run `. build-idf.sh` from the parent directory `esp32/build`, which should build the IDF as well as clone the main `Espruino` library. Be careful as these scripts move you around in directories, and can leave things in a weird state if they fail. 37 | 5. Move to the `esp32/build/Espruino` directory and check out the latest release branch, `git checkout RELEASE_2V06`. Note that currently the `master` branch has too many JS errors down the road when when trying to load in CLJS. 38 | 6. In `esp32/build/Espruino/boards` modify `ESP32.py` to increase the number of 4096-length pages to match the `js_code` partition. 3MB/4096 = 768 39 | ```patch 40 | - 'pages' : 64, 41 | + 'pages' : 768, 42 | ``` 43 | 7. In `esp32/build/Espruino/targets/esp32` modify `main.c` as follows in order to allocate more JavaScript space in Espruino (this is needed to accommodate loading all of `cljs.core`): 44 | ```patch 45 | - if(heapVars > 20000) heapVars = 20000; //WROVER boards have much more RAM, so we set a limit 46 | + if(heapVars > 55000) heapVars = 55000; //WROVER boards have much more RAM, so we set a limit 47 | ``` 48 | 8. Back up to `esp32/build`, and then run `. build-partition.sh` and `. build-tgz.sh`. Those should have taken care of building everything, with results symlinked in the `esp32/build/Espruino` directory. 49 | 9. Grab the `bootloader.bin`, `espruino_esp32.bin` and `partitions_espruino.bin` from `esp32/build/Espruino` and move them to your project directory and use them as usual when [flashing][3]. 50 | 51 | [1]: https://github.com/espruino/EspruinoBuildTools 52 | [2]: https://github.com/espressif/esp-idf/issues/1184 53 | [3]: https://cljdoc.org/d/esprit/esprit/CURRENT/doc/building-and-board-flashing -------------------------------------------------------------------------------- /doc/project-setup.md: -------------------------------------------------------------------------------- 1 | # Project Setup 2 | 3 | Eventually, you will want to move away from the REPL and start building up firmware proper. Additionally, as Esprit will work on any ESP32 with at least 8MB of flash, you may want to create your own printed circuit board (PCB) for your project. Esprit provides functionality to support this kind of development. An example skeleton project is setup [here](https://github.com/kiranshila/esprit-skeleton) as a reference. 4 | 5 | ## REPL 6 | The Esprit REPL must be explicitly included in your project to enable REPL support at runtime. Simply `(:require [esprit.repl])` in your project's namespace somewhere. Note: if your project is built with `:advanced` optimizations, the REPL code will probably be removed. 7 | 8 | ## Live Coding 9 | 10 | ### CLI Tools 11 | To connect to the running REPL using the Clojure CLI tools, simply run the following, replacing the endpoint address with the correct IP. 12 | ```bash 13 | clj -M -m cljs.main -re esprit -ro '{:endpoint-address "10.0.0.1"}' -r 14 | ``` 15 | 16 | Additionally, the endpoint address can be provided in a `config.edn` file in the classpath with the `:endpoint-address` setting. Then, the connection command would be 17 | ```bash 18 | clj -M -m cljs.main -re esprit -r 19 | ``` 20 | 21 | ### Emacs/CIDER 22 | By leveraging CIDER's piggieback library, we can jack-in to a remote REPL by adding a custom cljs-init form. This can be set in the `cider-custom-cljs-repl-init-form` variable. This variable must be set to 23 | ```clojure 24 | (do (require 'esprit.repl) 25 | (cider.piggieback/cljs-repl 26 | (esprit.repl/repl-env))) 27 | ``` 28 | Additionally, CIDER's default printing function must be changed to `pr` as we don't provide support for `cljs.pprint` at this time. 29 | This is set in the `cider-print-fn` variable, and must be set to `'pr`. 30 | 31 | Finally, following `cider-jack-in-cljs`, the REPL environment is set to `custom`. 32 | 33 | An example `.dir-locals.el` would then be setup as 34 | 35 | ```lisp 36 | ((nil 37 | (cider-custom-cljs-repl-init-form . "(do (require 'esprit.repl) 38 | (cider.piggieback/cljs-repl 39 | (esprit.repl/repl-env)))") 40 | (cider-default-cljs-repl . custom) 41 | (cider-print-fn . pr))) 42 | ``` 43 | 44 | Esprit's REPL environment's endpoint address can be set in `config.edn` with the `:endpoint-address` setting. The IP of the running Esprit instance is displayed upon successful connection to WiFi. 45 | 46 | ### Other Editors 47 | Contributions welcome! 48 | 49 | ## Board Configuration 50 | Esprit provides a board customization mechanism that can setup various "board items". These items can be pins and peripherals. See [[esprit.board/board-item]] for the currently supported items. 51 | 52 | The board file is simply an EDN file on your classpath, and is configured in `config.edn` by the `:board-file` setting. If this is not set, the default Esprit board pin definitions will be configured. 53 | 54 | For example, say you create a board configuration named `my-board.edn` in your classpath with the following contents: 55 | 56 | ```clojure 57 | {:my-library.file/my-led 58 | {:type :output-digital 59 | :pin "D20" 60 | :value 0}} 61 | ``` 62 | 63 | In `config.edn` (also in your classpath), you would then set 64 | 65 | ```clojure 66 | {:board-file "my-board.edn"} 67 | ``` 68 | 69 | In your project or library, you can then use the `esprit.board/items` map to access the pre-initialized Espruino objects. For example, in `my-library/file.cljs` one could now use `my-led` as: 70 | 71 | ```clojure 72 | (def my-led (::my-led esprit.board/items)) 73 | ``` 74 | 75 | ### Blinkenlights 76 | By default, Esprit supports a few LEDs that provide indications for the different stages of the REPL (Read, Eval, and Print) as well as the connection status. To enable them, in your `board.edn` file, supply pin definitions for the following keywords: 77 | * `:esprit.indicators/read-led` 78 | * `:esprit.indicators/eval-led` 79 | * `:esprit.indicators/print-led` 80 | * `:esprit.indicators/conn-led` 81 | 82 | The initial value for these LEDs depends on the board's hardware setup, but for the Esprit board with the LEDs in the open-drain configuration (logical low turns the LEDs on), the `board.edn` file would be set up as follows: 83 | 84 | ```clojure 85 | {:esprit.indicators/read-led 86 | {:type :output-analog 87 | :pin "D14" 88 | :value 1} 89 | :esprit.indicators/eval-led 90 | {:type :output-analog 91 | :pin "D33" 92 | :value 0.2 93 | :freq 1} 94 | :esprit.indicators/print-led 95 | {:type :output-analog 96 | :pin "D32" 97 | :value 1} 98 | :esprit.indicators/conn-led 99 | {:type :output-analog 100 | :pin "D13" 101 | :value 1}} 102 | ``` 103 | 104 | Of course, if these pins are not provided in the `board.edn` file, the blinkenlight functionality is disabled. 105 | -------------------------------------------------------------------------------- /doc/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Welcome to the Esprit project - ClojureScript for Microcontrollers (well, just the ESP32 for now). 4 | 5 | ## Forward 6 | Esprit attempts to provide a functional ClojureScript environment running atop the [Espruino](https://www.espruino.com/) JavaScript interpreter. As Espruino isn't quite feature-complete, some modifications to ClojureScript are necessary. This project attempts to automate that process and provide some functionality for Clojure-friendly abstractions to hardware-level configurations. 7 | 8 | If you haven't already, check out the [Clojure/north talk](https://youtu.be/u1jr4v7dhoo) and the [Apropos episode](https://youtu.be/J0wF92Zvq2c). For help, check out the #esprit channel on the [Clojurians Slack](http://clojurians.net/). 9 | 10 | ## Prerequisites 11 | To run Esprit, you will need an ESP32. However, not every ESP32 is created equal. Specifically, we require modules with at least 8MB of flash. The ESP32 WROVER module with 8MB of PSIRAM has been shown to work great. Additionally, you can use the purpose-built [Esprit board](https://github.com/mfikes/esprit-board), which comes pre-configured with battery management and blikenlights galore. 12 | 13 | All of the interaction with the board itself is done with Espressif's [esptool.py](https://github.com/espressif/esptool). Make sure this is installed, and on your path before proceeding. (If using Homebrew, you can do `brew install esptool`.) 14 | 15 | We are assuming that the device is plugged in to your machine via USB. 16 | 17 | > Note: If you are using the Esprit board, you will need the Silicon Labs CP2102N USB to UART chip [drivers](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers) if they are not already installed on your computer. Also note that, even though the Esprit board might be visible to your computer via USB, it is recommended that the latest Silicon Labs drivers be installed to ensure reliable operation. 18 | 19 | ## I haz board, how REPL? 20 | Alright hot shot, wanna jump in head first? 21 | Create a `deps.edn` file with ClojureScript and Esprit 22 | ```clojure 23 | {:deps {org.clojure/clojurescript {:mvn/version "1.10.764"} 24 | esprit/esprit {:mvn/version "1.0.0"}}} 25 | ``` 26 | Build a js file, containing the Esprit REPL, baking WiFi credentials into it (change `MySSID` and `MyWiFiPassword`): 27 | ```sh 28 | clj -M -m cljs.main -co '{:closure-defines {esprit.repl/wifi-ssid "MySSID" esprit.repl/wifi-password "MyWiFiPassword"} :optimizations :simple :target :none :browser-repl false :process-shim false}' -c esprit.repl 29 | ``` 30 | > Normally we'd just have the Espruino persist the WiFi info via its existing capability to do so, but this is currently not reliable with this particular modified build, while baking it in as illustrated above works every time. 31 | 32 | Then make a ROM binary from the compiled ClojureScript using 33 | ```sh 34 | clj -M -m esprit.make-rom 35 | ``` 36 | Now that your binary is ready, we now have to prepare the board for upload. 37 | 38 | > Note: These steps require `esptool.py` on your `PATH`. By default, this tool will search for the USB port where the ESP32 is connected, and this can be a bit slow. The search can be skipped by creating a `config.edn` file specifying the port. For example, if you are using the Esprit board, its contents would be: `{:serial-port "/dev/cu.SLAB_USBtoUART"}`. 39 | 40 | First, erase whatever is on your board 41 | ```sh 42 | clj -M -m esprit.flash --erase 43 | ``` 44 | Now bootstrap the Espruino runtime 45 | ```sh 46 | clj -M -m esprit.flash --bootstrap 47 | ``` 48 | Finally, flash your binary 49 | ```sh 50 | clj -M -m esprit.flash --flash out/main.bin 51 | ``` 52 | Open up the ESP32's com port, say with `screen` at the default baudrate of 115200. 53 | 54 | ```sh 55 | screen 115200 56 | ``` 57 | 58 | In the above, insert the serial port for your board. For example, with the Esprit board, you would connect via `screen /dev/cu.SLAB_USBtoUART 115200`. 59 | 60 | After connecting via `screen`, it may be a good idea to reset your board in case you may have missed any messages printed during boot. (On the Esprit board, this can be done by pressing the reset button.) 61 | 62 | It can take about 15 seconds to load the ClojureScript runtime. (The Esprit board will flash its EVAL LED while this is occcuring.) 63 | 64 | Then the code will attempt to join the WiFi. (The Esprit board will dimly light the CONN LED while this is occuring, and once connected to WiFi it will switch to doing short pulses until a REPL connection is established.) 65 | 66 | Once the device is connected to WiFi, it will print a message like the following to the serial port like: 67 | ``` 68 | Ready for REPL Connections 69 | Establish an Esprit REPL by executing 70 | clj -M -m cljs.main -re esprit -ro '{:endpoint-address "10.0.0.1"}' -r 71 | ``` 72 | 73 | Copy this command, and then exit your terminal session (in screen this is done via Ctrl-a, k, y), and then issue the copied command to start the REPL. 74 | 75 | At this point, the REPL connection traffic is managed via TCP over WiFi. If your board is powered by some alternate means (LiPo battery, for example), you can disconnect the USB cable and the REPL will remain live. 76 | -------------------------------------------------------------------------------- /src/esprit/repl.clj: -------------------------------------------------------------------------------- 1 | (ns esprit.repl 2 | (:require 3 | [clojure.java.io :as io] 4 | [cljs.compiler :as comp] 5 | [cljs.repl :as repl] 6 | [clojure.data.json :as json] 7 | [config.core :refer [env]] 8 | [clojure.string :as str]) 9 | (:import 10 | (java.net Socket) 11 | (java.lang StringBuilder) 12 | (java.io BufferedReader BufferedWriter IOException) 13 | (javax.jmdns JmDNS ServiceListener) 14 | (java.net URI))) 15 | 16 | (defn set-logging-level [logger-name level] 17 | "Sets the logging level for a logger to a level." 18 | {:pre [(string? logger-name) (instance? java.util.logging.Level level)]} 19 | (.setLevel (java.util.logging.Logger/getLogger logger-name) level)) 20 | 21 | (def esprit-bonjour-name-prefix 22 | "The prefix used in Esprit Bonjour service names." 23 | "Esprit ") 24 | 25 | (defn esprit-bonjour-name? [bonjour-name] 26 | "Returns true iff a given name is an Esprit Bonjour service name." 27 | {:pre [(string? bonjour-name)]} 28 | (.startsWith bonjour-name esprit-bonjour-name-prefix)) 29 | 30 | (defn bonjour-name->display-name 31 | "Converts an Esprit Bonjour service name to a display name 32 | (stripping off esprit-bonjour-name-prefix)." 33 | [bonjour-name] 34 | {:pre [(esprit-bonjour-name? bonjour-name)] 35 | :post [(string? %)]} 36 | (subs bonjour-name (count esprit-bonjour-name-prefix))) 37 | 38 | (defn name-endpoint-map->choice-list [name-endpoint-map] 39 | "Takes a name to endpoint map, and converts into an indexed list." 40 | {:pre [(map? name-endpoint-map)]} 41 | (map vector 42 | (iterate inc 1) 43 | (sort-by first name-endpoint-map))) 44 | 45 | (defn print-discovered-devices [name-endpoint-map opts] 46 | "Prints the set of discovered devices given a name endpoint map." 47 | {:pre [(map? name-endpoint-map) (map? opts)]} 48 | (if (empty? name-endpoint-map) 49 | (println "(No devices)") 50 | (doseq [[choice-number [bonjour-name _]] (name-endpoint-map->choice-list name-endpoint-map)] 51 | (println (str "[" choice-number "] " (bonjour-name->display-name bonjour-name)))))) 52 | 53 | (defn setup-mdns 54 | "Sets up mDNS to populate atom supplied in name-endpoint-map with discoveries. 55 | Returns a function that will tear down mDNS." 56 | [reg-type name-endpoint-map] 57 | {:pre [(string? reg-type)] 58 | :post [(fn? %)]} 59 | (let [mdns-service (JmDNS/create) 60 | service-listener 61 | (reify ServiceListener 62 | (serviceAdded [_ service-event] 63 | (let [type (.getType service-event) 64 | name (.getName service-event)] 65 | (when (and (= reg-type type) (esprit-bonjour-name? name)) 66 | (.requestServiceInfo mdns-service type name 1)))) 67 | (serviceRemoved [_ service-event] 68 | (swap! name-endpoint-map dissoc (.getName service-event))) 69 | (serviceResolved [_ service-event] 70 | (let [type (.getType service-event) 71 | name (.getName service-event)] 72 | (when (and (= reg-type type) (esprit-bonjour-name? name)) 73 | (let [entry {name (let [info (.getInfo service-event)] 74 | {:address (.getHostAddress (.getAddress info)) 75 | :port (.getPort info)})}] 76 | (swap! name-endpoint-map merge entry))))))] 77 | (.addServiceListener mdns-service reg-type service-listener) 78 | (fn [] 79 | (.removeServiceListener mdns-service reg-type service-listener) 80 | (.close mdns-service)))) 81 | 82 | (defn discover-and-choose-device 83 | "Looks for Esprit devices advertised via Bonjour and presents 84 | a simple command-line UI letting user pick one, unless 85 | choose-first-discovered? is set to true in which case the UI is bypassed" 86 | [choose-first-discovered? opts] 87 | {:pre [(map? opts)]} 88 | (let [reg-type "_http._tcp.local." 89 | name-endpoint-map (atom {}) 90 | tear-down-mdns 91 | (loop [count 0 92 | tear-down-mdns (setup-mdns reg-type name-endpoint-map)] 93 | (if (empty? @name-endpoint-map) 94 | (do 95 | (Thread/sleep 100) 96 | (when (= 20 count) 97 | (println "\nSearching for devices ...")) 98 | (if (zero? (rem (inc count) 100)) 99 | (do 100 | (tear-down-mdns) 101 | (recur (inc count) (setup-mdns reg-type name-endpoint-map))) 102 | (recur (inc count) tear-down-mdns))) 103 | tear-down-mdns))] 104 | (try 105 | (Thread/sleep 500) ;; Sleep a little more to catch stragglers 106 | (loop [current-name-endpoint-map @name-endpoint-map] 107 | (println) 108 | (print-discovered-devices current-name-endpoint-map opts) 109 | (when-not choose-first-discovered? 110 | (println "\n[R] Refresh\n") 111 | (print "Choice: ") 112 | (flush)) 113 | (let [choice (if choose-first-discovered? "1" (read-line))] 114 | (if (= "r" (.toLowerCase choice)) 115 | (recur @name-endpoint-map) 116 | (let [choices (name-endpoint-map->choice-list current-name-endpoint-map) 117 | choice-ndx (try (dec (Long/parseLong choice)) (catch NumberFormatException _ -1))] 118 | (if (< -1 choice-ndx (count choices)) 119 | (second (nth choices choice-ndx)) 120 | (recur current-name-endpoint-map)))))) 121 | (finally 122 | (.start (Thread. tear-down-mdns)))))) 123 | 124 | (defn socket 125 | [host port] 126 | {:pre [(string? host) (number? port)]} 127 | (let [socket (doto (Socket. host port) (.setKeepAlive true)) 128 | in (io/reader socket) 129 | out (io/writer socket)] 130 | {:socket socket :in in :out out})) 131 | 132 | (defn close-socket 133 | [s] 134 | {:pre [(map? s)]} 135 | (.close (:socket s))) 136 | 137 | (defn fn-ify 138 | "Wraps bare try-catch into a fn as to properly return pr_str" 139 | [js] 140 | (str "(function (){try{return " (subs js 4 (dec (count js))) "}})()")) 141 | 142 | (defn process 143 | "Process outgoing JS to make compatible with Espruino" 144 | [js] 145 | (if (str/starts-with? js "try{cljs.core.pr_str.call") 146 | (fn-ify js) 147 | js)) 148 | 149 | (defn write 150 | [out js] 151 | (:pre [(instance? BufferedWriter out) (string? js)]) 152 | (.write out (process js)) 153 | (.write out (int 0)) ;; terminator 154 | (.flush out)) 155 | 156 | (defn read-messages 157 | [in response-promise opts] 158 | {:pre [(instance? BufferedReader in) (map? opts)]} 159 | (loop [sb (StringBuilder.) c (.read in)] 160 | (cond 161 | (= c -1) (do 162 | (if-let [resp-promise @response-promise] 163 | (deliver resp-promise :eof)) 164 | :eof) 165 | (= c 1) (do 166 | (print (str sb)) 167 | (flush) 168 | (recur (StringBuilder.) (.read in))) 169 | (= c 0) (do 170 | (deliver @response-promise (str sb)) 171 | (recur (StringBuilder.) (.read in))) 172 | :else (do 173 | (.append sb (char c)) 174 | (recur sb (.read in)))))) 175 | 176 | (defn start-reading-messages 177 | "Starts a thread reading inbound messages." 178 | [repl-env opts] 179 | {:pre [(map? repl-env) (map? opts)]} 180 | (.start 181 | (Thread. 182 | #(try 183 | (let [rv (read-messages (:in @(:socket repl-env)) (:response-promise repl-env) opts)] 184 | (when (= :eof rv) 185 | (close-socket @(:socket repl-env)))) 186 | (catch IOException e 187 | (when-not (.isClosed (:socket @(:socket repl-env))) 188 | (.printStackTrace e))))))) 189 | 190 | (def not-conected-result 191 | {:status :error 192 | :value "Not connected."}) 193 | 194 | (defn esprit-eval 195 | "Evaluate a JavaScript string in the Espruino REPL process." 196 | [repl-env js] 197 | {:pre [(map? repl-env) (string? js)]} 198 | (let [{:keys [out]} @(:socket repl-env) 199 | response-promise (promise)] 200 | (if out 201 | (do 202 | (reset! (:response-promise repl-env) response-promise) 203 | (write out js) 204 | (let [response @response-promise] 205 | (if (= :eof response) 206 | not-conected-result 207 | (let [result (json/read-str response 208 | :key-fn keyword)] 209 | (merge 210 | {:status (keyword (:status result)) 211 | :value (:value result)} 212 | (when-let [raw-stacktrace (:stacktrace result)] 213 | {:stacktrace raw-stacktrace})))))) 214 | not-conected-result))) 215 | 216 | (defn load-javascript 217 | "Load a Closure JavaScript file into the Espruino REPL process." 218 | [repl-env provides url] 219 | (esprit-eval repl-env 220 | (str "goog.require('" (comp/munge (first provides)) "')"))) 221 | 222 | (defn- set-up-socket 223 | [repl-env opts address port] 224 | {:pre [(map? repl-env) (map? opts) (string? address) (number? port)]} 225 | (when-let [socket @(:socket repl-env)] 226 | (close-socket socket)) 227 | (reset! (:socket repl-env) 228 | (socket address port)) 229 | ;; Start dedicated thread to read messages from socket 230 | (start-reading-messages repl-env opts)) 231 | 232 | (defn tear-down 233 | [repl-env] 234 | (when-let [socket @(:socket repl-env)] 235 | (close-socket socket))) 236 | 237 | (defn setup 238 | [repl-env opts] 239 | {:pre [(map? repl-env) (map? opts)]} 240 | (try 241 | (let [_ (set-logging-level "javax.jmdns" java.util.logging.Level/OFF) 242 | [bonjour-name endpoint] (if-let [endpoint-address (:endpoint-address (:options repl-env))] 243 | ["Esprit ESP32 WROVER" {:address endpoint-address :port 53001}] 244 | (discover-and-choose-device (:choose-first-discovered (:options repl-env)) opts)) 245 | endpoint-address (:address endpoint) 246 | endpoint-port (:port endpoint)] 247 | (println (str "\nConnecting to " (bonjour-name->display-name bonjour-name) " ...\n")) 248 | (set-up-socket repl-env opts endpoint-address (dec endpoint-port)) 249 | {}) 250 | (catch Throwable t 251 | (tear-down repl-env) 252 | (throw t)))) 253 | 254 | (defrecord EspritEnv [response-promise bonjour-name webdav-mount-point socket options] 255 | repl/IReplEnvOptions 256 | (-repl-options [this] 257 | {:require-foreign true}) 258 | repl/IJavaScriptEnv 259 | (-setup [repl-env opts] 260 | (setup repl-env opts)) 261 | (-evaluate [repl-env _ _ js] 262 | (esprit-eval repl-env js)) 263 | (-load [repl-env provides url] 264 | (load-javascript repl-env provides url)) 265 | (-tear-down [repl-env] 266 | (tear-down repl-env))) 267 | 268 | (defn repl-env* 269 | [options] 270 | {:pre [(or (nil? options) (map? options))]} 271 | (->EspritEnv (atom nil) (atom nil) (atom nil) (atom nil) (into {:endpoint-address 272 | (:endpoint-address env) 273 | :choose-first-discovered 274 | (:choose-first-discovered env)} 275 | options))) 276 | 277 | (defn repl-env 278 | "Esprit REPL environment." 279 | [& {:as options}] 280 | (repl-env* options)) 281 | 282 | (defn -main 283 | "Launches the Esprit REPL." 284 | [] 285 | (repl/repl (repl-env))) 286 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | --------------------------------------------------------------------------------