{"id":20081930,"url":"https://github.com/eval/otarta","last_synced_at":"2026-02-10T17:39:06.387Z","repository":{"id":66250909,"uuid":"156701843","full_name":"eval/otarta","owner":"eval","description":"An MQTT-library for ClojureScript","archived":false,"fork":false,"pushed_at":"2022-09-23T12:50:49.000Z","size":118,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-06T00:37:59.173Z","etag":null,"topics":["clojure","clojurescript","mqtt"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eval.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-08T12:13:23.000Z","updated_at":"2022-12-09T11:31:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c006134-ab29-46ac-894c-c81c0682455b","html_url":"https://github.com/eval/otarta","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/eval/otarta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eval%2Fotarta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eval%2Fotarta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eval%2Fotarta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eval%2Fotarta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eval","download_url":"https://codeload.github.com/eval/otarta/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eval%2Fotarta/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29309593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-10T16:09:25.305Z","status":"ssl_error","status_checked_at":"2026-02-10T16:08:52.170Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","mqtt"],"created_at":"2024-11-13T15:40:58.535Z","updated_at":"2026-02-10T17:39:06.369Z","avatar_url":"https://github.com/eval.png","language":"Clojure","readme":"# Otarta\n\n*Canonical repository: https://gitlab.com/eval/otarta*\n\n\n[![cljdoc badge](https://img.shields.io/badge/cljdoc-latest-blue.svg)](https://cljdoc.org/d/eval/otarta/CURRENT)\n[![cljdoc badge](https://img.shields.io/badge/cljdoc-0.3.1-blue.svg)](https://cljdoc.org/d/eval/otarta/0.3.1)\n[![discuss at Clojurians-Zulip](https://img.shields.io/badge/clojurians%20zulip-otarta-brightgreen.svg)](https://clojurians.zulipchat.com/#narrow/stream/151157-Otarta)\n[![pipeline status](https://gitlab.com/eval/otarta/badges/master/pipeline.svg)](https://gitlab.com/eval/otarta/commits/master)\n[![Clojars Project](https://img.shields.io/clojars/v/eval/otarta.svg)](https://clojars.org/eval/otarta)\n\n\nAn MQTT-library for ClojureScript.\n\n_NOTE: this is pre-alpha software with an API that will change (see the [CHANGELOG](./CHANGELOG.md) for breaking changes)_\n\n## Installation\n\nLeiningen:\n```clojure\n[eval/otarta \"0.3.1\"]\n```\n\nDeps:\n```clojure\neval/otarta {:mvn/version \"0.3.1\"}\n```\n\n## Examples\n\n* [CI-Dashboard](https://eval.gitlab.io/ci-dashboard/) (source: https://gitlab.com/eval/ci-dashboard)\n\n## Usage\n\nThe following code assumes:\n- being in a browser (ie `js/WebSockets` exists. For Node.js [see below](README.md#nodejs).)\n- a websocket-enabled MQTT-broker on `localhost:9001` (eg via `docker run --rm -ti -p 9001:9001 toke/mosquitto`)\n\n```clojure\n(ns example.core\n  (:require-macros [cljs.core.async.macros :refer [go go-loop]])\n  (:require [cljs.core.async :as a :refer [\u003c!]]\n            [otarta.core :as mqtt]))\n\n(defonce client (mqtt/client \"ws://localhost:9001/mqtt#weather-sensor\"))\n\n(defn subscription-handler [ch]\n  (go-loop []\n    (when-let [m (\u003c! ch)]\n      ;; example m: {:topic \"temperature/current\" :payload \"12.1\" :retain? false :qos 0}\n      (prn \"Received:\" m)\n      (recur))))\n\n(go\n  (let [[err {sub-ch :ch}] (\u003c! (mqtt/subscribe client \"temperature/#\"))]\n    (if err\n      (println \"Failed to subscribe:\" err)\n      (do\n        (println \"Subscribed!\")\n        (subscription-handler sub-ch))))\n\n  (mqtt/publish client \"temperature/current\" \"12.1\"))\n```\n\n### client\n\n### broker-url\n\nThe first argument (the `broker-url`) should be of the form `ws(s):://(user:pass@)host.org:1234/path(#some/root/topic)`.\n\nThe fragment contains the `root-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`).\n\nWhen 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 the *root of the broker*) you can provide a `default-root-topic`:\n```clojure\n(mqtt/client config.broker-url {:default-root-topic \"weather-sensor\"})\n```\nThe client will then treat the broker-url `ws://localhost:9001/mqtt` like `ws://localhost:9001/mqtt#weather-sensor`.\nWhen `config.broker-url` does contain a `root-topic`, the `default-root-topic` is ignored (but gives a nice hint as to what the `root-topic` could look like, eg `acceptance/weather-sensor`).\n\n\n### messages\n\nMessages have the following shape:\n\n```clojure\n{:topic \"temperature/current\" ;; topic relative to `root-topic`\n :payload \"12.1\"    ;; formatted payload\n :retain? false     ;; whether this message was from the broker's store or 'real-time' from publisher\n :qos 0}            ;; quality of service (0: at most once, 1: at least once, 2: exactly once) \n```\n\n#### retain?\n\nNOTE: `retain?` is not so much a property of the sent message, but tells you *when* 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.  \n\n### formats\n\nWhen publishing or subscribing you can specify a format. Available formats are: `string` (default), `raw`, `json`, `edn` and `transit`:\n```clojure\n(go\n  (let [[err {sub-ch :ch}] (\u003c! (mqtt/subscribe client \"temperature/#\" {:format :transit}))]\n    (if err\n      (println \"Failed to subscribe:\" err)\n      (do\n        (prn (\u003c! sub-ch))))) ;; prints: {:created-at #inst \"2018-09-27T13:13:21.932-00:00\", :value 12.1}\n\n  (mqtt/publish client \"temperature/current\" {:created-at (js/Date.) :value 12.1} {:format :transit}))\n```\n\nIncoming messages with a payload that is not readable, won't appear on the subscription-channel.  \nSimilarly, when formatting fails when publishing, you'll receive an error:\n\n```clojure\n(let [[err _] (\u003c! (mqtt/publish client \"foo\" #\"not transit!\" {:format :transit}))]\n  (when err\n    (println err)))\n```\n\nYou can provide your own format:\n```clojure\n(ns example.core\n  (:require [otarta.format :as mqtt-fmt]))\n\n(defn extract-temperature []\n  ...)\n\n;; this format piggybacks on the string-format\n;; after which extract-temperature will get the relevant data.\n;; Otarta will catch any exceptions that occur when reading/writing.\n(def custom-format\n  (reify mqtt-fmt/PayloadFormat\n    (-read [_fmt buff]\n      (-\u003e\u003e buff (mqtt-fmt/-read mqtt-fmt/string) extract-temperature))\n    (-write [_fmt v]\n      (-\u003e\u003e v (mqtt-fmt/-write mqtt-fmt/string)))))\n```\n\n### Node.js\n\nYou should provide a W3C compatible websocket when running via Node.js.  \nI've had good experience with [this websocket-library (\u003e= v1.0.28)](https://www.npmjs.com/package/websocket).\n\nWith the library included in your project (see https://clojurescript.org/guides/webpack for details), the following will initialize `js/WebSocket`:\n\n```clojure\n(ns example.core\n  (:require [websocket]))\n\n(set! js/WebSocket (.-w3cwebsocket websocket))\n```\n\n## Limitations\n\n- only QoS 0\n- only clean-session\n- no reconnect built-in\n- untested for large payloads (ie more than a couple of KB)\n\n## Development\n\n### Testing\n\nVia [cljs-test-runner](https://github.com/Olical/cljs-test-runner/):\n\nVia [cljs-test-runner](https://github.com/Olical/cljs-test-runner/):\n\n```bash\n# once\n$ clojure -Atest\n\n# watching\n$ clojure -Atest-watch\n\n# specific tests\n(deftest ^{:focus true} only-this-test ...)\n$ clojure -Atest-watch -i :focus\n\n# more options:\n$ clojure -Atest-watch --help\n```\n\n### Figwheel\n\n```bash\n# start figwheel\n$ make figwheel\n\n# wait till compiled and then from other shell:\n$ node target/app.js\n\n# then from emacs:\n# M-x cider-connect with host: localhost and port: 7890\n# from repl:\nuser\u003e (figwheel/cljs-repl)\n;; prompt changes to:\ncljs.user\u003e\n;; to quickly see what otarta can do:\n;; - evaluate the otarta.main namespace\n;; - then eval the comment-section of otarta.main line by line\n```\n\nSee [CIDER docs](https://cider.readthedocs.io/en/latest/interactive_programming/) what you can do.\n\n\n## Release\n\n### Install locally\n\n- (ensure no CLJ_CONFIG and MAVEN_OPTS env variables are set - this to target ~/.m2)\n- ensure dependencies in pom.xml up to date\n  - clj -Spom\n- ensure pom.xml with new version\n  - cp pom.xml{.template,}\n  - gsed -i 's/$RELEASE_VERSION/1.2.3/' pom.xml\n- make mvn-install\n- testdrive locally\n\n### Deploy to Clojars\n\n- create (pre-)tag\n- push to CI\n\n\n## License\n\nCopyright (c) 2018 Gert Goet, ThinkCreate  \nCopyright (c) 2018 Alliander N.V. \nSee [LICENSE](./LICENSE).  \n\nFor licenses of third-party software that this software uses, see [LICENSE-3RD-PARTY](./LICENSE-3RD-PARTY).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feval%2Fotarta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feval%2Fotarta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feval%2Fotarta/lists"}