- Notifications
You must be signed in to change notification settings - Fork1
An MQTT-library for ClojureScript
License
eval/otarta
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Canonical repository:https://gitlab.com/eval/otarta
An MQTT-library for ClojureScript.
NOTE: this is pre-alpha software with an API that will change (see theCHANGELOG for breaking changes)
Leiningen:
[eval/otarta"0.3.1"]
Deps:
eval/otarta {:mvn/version"0.3.1"}
The following code assumes:
- being in a browser (ie
js/WebSockets
exists. For Node.jssee below.) - a websocket-enabled MQTT-broker on
localhost:9001
(eg viadocker run --rm -ti -p 9001:9001 toke/mosquitto
)
(nsexample.core (:require-macros [cljs.core.async.macros:refer [go go-loop]]) (:require [cljs.core.async:as a:refer [<!]] [otarta.core:as mqtt]))(defonceclient (mqtt/client"ws://localhost:9001/mqtt#weather-sensor"))(defnsubscription-handler [ch] (go-loop [] (when-let [m (<! ch)];; example m: {:topic "temperature/current" :payload "12.1" :retain? false :qos 0} (prn"Received:" m) (recur))))(go (let [[err {sub-ch:ch}] (<! (mqtt/subscribe client"temperature/#"))] (if err (println"Failed to subscribe:" err) (do (println"Subscribed!") (subscription-handler sub-ch)))) (mqtt/publish client"temperature/current""12.1"))
The first argument (thebroker-url
) should be of the formws(s):://(user:pass@)host.org:1234/path(#some/root/topic)
.
The fragment contains theroot-topic
and indicates the topic relative to which the client publishes and subscribes. This allows for pointing the client to a specific subtree of the broker (eg where it has read/write-permissions, or where it makes sense given the stage:ws://some-broker/mqtt#acceptance/sensor1
).
When you write a client that receives its broker-url from outside (ie as an environment variable), it might lack a root-topic. In order to prevent unwanted effects in that case (eg the client subscribing to "#" essentially subscribing to theroot of the broker) you can provide adefault-root-topic
:
(mqtt/client config.broker-url {:default-root-topic"weather-sensor"})
The client will then treat the broker-urlws://localhost:9001/mqtt
likews://localhost:9001/mqtt#weather-sensor
.Whenconfig.broker-url
does contain aroot-topic
, thedefault-root-topic
is ignored (but gives a nice hint as to what theroot-topic
could look like, egacceptance/weather-sensor
).
Messages have the following shape:
{:topic"temperature/current";; topic relative to `root-topic`:payload"12.1";; formatted payload:retain?false;; whether this message was from the broker's store or 'real-time' from publisher:qos0};; quality of service (0: at most once, 1: at least once, 2: exactly once)
NOTE:retain?
is not so much a property of the sent message, but tells youwhen you received it: typically you receive messages with{:retain? true}
directly after subscribing. But when you're subscribed and a message is published with the retain-flag set, the message you'll received has{:retain? false}
. This as you received it 'first hand' from the publisher, not from the broker's store.
When publishing or subscribing you can specify a format. Available formats are:string
(default),raw
,json
,edn
andtransit
:
(go (let [[err {sub-ch:ch}] (<! (mqtt/subscribe client"temperature/#" {:format:transit}))] (if err (println"Failed to subscribe:" err) (do (prn (<! sub-ch)))));; prints: {:created-at #inst "2018-09-27T13:13:21.932-00:00", :value 12.1} (mqtt/publish client"temperature/current" {:created-at (js/Date.):value12.1} {:format:transit}))
Incoming messages with a payload that is not readable, won't appear on the subscription-channel.
Similarly, when formatting fails when publishing, you'll receive an error:
(let [[err _] (<! (mqtt/publish client"foo"#"not transit!" {:format:transit}))] (when err (println err)))
You can provide your own format:
(nsexample.core (:require [otarta.format:as mqtt-fmt]))(defnextract-temperature []...);; this format piggybacks on the string-format;; after which extract-temperature will get the relevant data.;; Otarta will catch any exceptions that occur when reading/writing.(defcustom-format (reify mqtt-fmt/PayloadFormat (-read [_fmt buff] (->> buff (mqtt-fmt/-read mqtt-fmt/string) extract-temperature)) (-write [_fmt v] (->> v (mqtt-fmt/-write mqtt-fmt/string)))))
You should provide a W3C compatible websocket when running via Node.js.
I've had good experience withthis websocket-library (>= v1.0.28).
With the library included in your project (seehttps://clojurescript.org/guides/webpack for details), the following will initializejs/WebSocket
:
(nsexample.core (:require [websocket]))(set! js/WebSocket (.-w3cwebsocket websocket))
- only QoS 0
- only clean-session
- no reconnect built-in
- untested for large payloads (ie more than a couple of KB)
Viacljs-test-runner:
Viacljs-test-runner:
# once$ clojure -Atest# watching$ clojure -Atest-watch# specific tests(deftest ^{:focus true} only-this-test ...)$ clojure -Atest-watch -i :focus# more options:$ clojure -Atest-watch --help
# start figwheel$ make figwheel# wait till compiled and then from other shell:$ node target/app.js# then from emacs:# M-x cider-connect with host: localhost and port: 7890# from repl:user> (figwheel/cljs-repl);; prompt changes to:cljs.user>;; to quickly see what otarta can do:;; - evaluate the otarta.main namespace;; -theneval the comment-section of otarta.main line by line
SeeCIDER docs what you can do.
- (ensure no CLJ_CONFIG and MAVEN_OPTS env variables are set - this to target ~/.m2)
- ensure dependencies in pom.xml up to date
- clj -Spom
- ensure pom.xml with new version
- cp pom.xml{.template,}
- gsed -i 's/$RELEASE_VERSION/1.2.3/' pom.xml
- make mvn-install
- testdrive locally
- create (pre-)tag
- push to CI
Copyright (c) 2018 Gert Goet, ThinkCreate
Copyright (c) 2018 Alliander N.V.SeeLICENSE.
For licenses of third-party software that this software uses, seeLICENSE-3RD-PARTY.
About
An MQTT-library for ClojureScript