{"id":23671000,"url":"https://github.com/wickedshell/clj-mavlink","last_synced_at":"2025-09-01T23:31:47.958Z","repository":{"id":57713467,"uuid":"85527485","full_name":"WickedShell/clj-mavlink","owner":"WickedShell","description":"Clojure MAVLink Bindings","archived":false,"fork":false,"pushed_at":"2024-04-06T00:51:38.000Z","size":267,"stargazers_count":8,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-25T07:07:58.799Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WickedShell.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}},"created_at":"2017-03-20T02:34:32.000Z","updated_at":"2024-05-16T04:44:54.000Z","dependencies_parsed_at":"2023-01-31T05:15:54.918Z","dependency_job_id":null,"html_url":"https://github.com/WickedShell/clj-mavlink","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WickedShell%2Fclj-mavlink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WickedShell%2Fclj-mavlink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WickedShell%2Fclj-mavlink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WickedShell%2Fclj-mavlink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WickedShell","download_url":"https://codeload.github.com/WickedShell/clj-mavlink/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231722040,"owners_count":18416600,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-12-29T09:45:48.260Z","updated_at":"2024-12-29T09:45:48.811Z","avatar_url":"https://github.com/WickedShell.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clj-mavlink\n\n[![Build Status](https://semaphoreci.com/api/v1/wickedshell/clj-mavlink/branches/master/badge.svg)](https://semaphoreci.com/wickedshell/clj-mavlink)\n[![Clojars Project](https://img.shields.io/clojars/v/clj-mavlink.svg)](https://clojars.org/clj-mavlink)\n\nClojure [MAVLink](https://mavlink.io/en/) bindings.\n\n## Usage\n\nAdd the following to your `project.clj` dependencies:\n\n```\n[clj-mavlink \"0.1.2\"]\n```\n\n### Using the library\n\nPull in the `mavlink.core`, for example\n\n``` clojure\n(:require [mavlink.core :as mavlink])\n```\n\n`clj-mavlink` builds a MAVLink interface from XML [MAVLink specifications](https://mavlink.io/en/messages/).\nThe clj-mavlink interface reads message hash maps from a clojure async channel,\nencodes them, and then writes the encoded message onto an `OutputStream` connected to the autopilot.\nThe clj-mavlink interface reads an `InputStream` connected to the autopilot,\ndecodes messages, and writes the resulting message hash map to a clojure\nasync channel (see clojure.core.async).\n\n### Building the clj-mavlink database\n\nThe first step is to bring the MAVLink XML files into your project.\n`mavlink/parse` takes an xml-sources specification and returns the clj-mavlink database.\n\nFor example:\n``` clojure\n(defonce ^:const ardupilotmega-xml \"ardupilotmega.xml\")\n(defonce ^:const common-xml \"common.xml\")\n(defonce ^:const uavionix-xml \"uAvionix.xml\")\n\n(defonce mavlink-info\n         (delay\n           (mavlink/parse\n             {:xml-sources [{:xml-file ardupilotmega-xml\n                             :xml-source (-\u003e ardupilotmega-xmls\n                                             io/resource io/input-stream)}\n                            {:xml-file common-xml\n                             :xml-source (-\u003e common-xml\n                                             io/resource io/input-stream)}\n                            {:xml-file uavionix-xml\n                             :xml-source (-\u003e uavionix-xml\n                                             io/resource io/input-stream)}]})))\n\n```\n\n`:xml-sources` is a vector of hash-maps. Each hash map specifies the name of\nthe source file and the `InputStream` to read that file. The clj-mavlink database\ncan be reused any number of times to open MAVLink interfaces, i.e. channels, between\nthe application and the autopilot.\n\n### Opening a channel\n\n`mavlink/open-channel` establishes a communication interface between the\nautopilot and the application. `open-channel` takes a hash-map of options to configure\nthe channel.\n\nFor example this code open a MAVLink 1 protocol connection, encoded messages\nwill use the specified system id and component id (unless it is overridden by the\nmessage hash-map), the `:decode-input-stream` and `encode-output-link` are defined by\nthe communication protocol (e.g. a serial link or TCP link). `:autopilot-send` and\n`:autopilot-receive` are clojure.core.async channels that the application uses to\nsend message hash maps to be encoded and to receive decoded messages as message hash-maps.\n\nNote that there is set up to create all the fields needed for opening a MAVLink channel,\nas well as clean up when the channel is closed.\n\n\n``` clojure\n(mavlink/open-channel @mavlink-info\n                      {:protocol :mavlink1\n                       :system-id your-sysid\n                       :component-id your-component-id\n                       :link-id 0\n                       :decode-input-stream decode-input\n                       :decode-output-channel autopilot-receive\n                       :encode-input-channel autopilot-send\n                       :encode-output-link encode-output\n                       :report-error #(println \"clj-mavlink error:\\n\" %1)\n                       :exception-handler #(println \"clj-mavlink exception:\\n\" %1)\n                       :signing-options {:secret-key nil ; no signing\n                                         :secret-keyset nil ; secret-keyset\n                                         :accept-message-handler  ; log but don't accept the message\n                                           #(log/error \"clj-mavlink/accept-message:\\n\" %1)\n                                         }\n                       :tlog-stream tlog-stream})\n```\n\nSee the `mavlink/open-channel` doc string for more information on the options.\n\n#### A note about the protocol\n\nThe messages will be encoded as specified in the `mavlink/open-channel` options hash-map.\nIn this example, it encodes as MAVLink 1.0 messages. However, if a MAVLink 2.0 message is\ndecoded, the encoding will automatically switch to MAVLink 2.0, as if the protocol had\nbeen specified `:mavlink2`. Messages will be signed based on whether or not the `:secret-key`\nof the `:signing-options` is not `nil`. If a signed MAVLink 2.0 message is decoded, then the\nsignature is verified by first trying the `:secret-key` (if it is not `nil`), if it doesn't verify,\nthen the keys in the secret-keyset is tried until a match is found, in which case the secret-key is \nset to that key so that encoded messages are signed with that key. If no matching key is found\nto verify the signature, the message is dropped.\n\nIf the parser is running with MAVLink 2.0 decoding and signing keys and a MAVLink\n1.0 or MAVLink 2.0 message without a valid signing key is recieved then the\n`:accept-message-handler` in `:signing-options` is used to determine if a message\nshould be accepted and emitted into the decoded message channel and tlog. The entire message\nwill not have been decoded at this point, just the headers.\n\nThe parser can be switched to MAVLink 2.0 only decoding by sending the following message on the outgoing channel\n``` clojure\n{:message'id :clj-mavlink\n :protocol :mavlink2}\n```\n\n### Messages\n\nThe format of the message hash maps to send (encode and output) and received\n(input and decode). The bindings are for the `message id` and the fields of the message.\n\n##### message ids\n\n`:message'id` holds a keyword of the name of the message. This is based on the `\u003cmessage\u003e` \nname attribute, for example `name=HEARTBEAT\"`. The name is converted to lower case and all underscores\nare replaced with hyphens. Examples: `:heartbeat :sys-status :system-time`\n\n##### fields\n\nfields are defined by their name attribute of the `\u003cfield\u003e` tag. The field names are simply converted to keywords.\nExamples from the heartbeat message: `:custom-mode :type :autopilot :base-mode :system-status`\n\nValid values are defined in the MAVLink specification, for example `uint16_t char[16] uint64_t int8_t`\nBecause of the way clojure builds hash-maps, all numeric values will be held as boxed numbers, so there\nis no need to worry about the type of the value beyond an in integer, a float, character, an array, or a string.\nclj-mavlink will perform the appropriate conversions to send/receive the values appropriately.\nNote that char arrays can be specified as strings. Arrays are specified as vectors.\n\nNote that unspecified fields will be given a value of 0.\n\n##### system'id component'id sequence'id link'id\n\nA message can override the default `system-id`, `component-id`, `sequence-id` and `link-id` by specifying it as a keyword value binding in a message hash-map, for example\n\n``` clojure\n{:message-id :heartbeat\n :system'id 0xff\n :sequence'id 0}\n```\n\nNote that a side effect of specifying the `sequence id` is to reset the last used sequence id to this\nvalue. Succeeding messages sequence id's will increment from that value.\n\n#### Enumerated types\n\nOne of the big benefits of clj-mavlink is that enumerated type values are supported as keywords.\nFor example, the `:type` field of the heartbeat message is an enumerated type defined in the\nMAVLink XML as `MAV_TYPE`, the `MAV_TYPE \u003cenum\u003e` has 29 different `\u003centry\u003e`'s, for example,\nvalue 0 is `MAV_TYPE_GENERIC`; clj-mavlink defines a keyword for the enum group, `:mav-type`,\nand for the each enum, `:mav-type-generic` the same as it did for message ids,\nconvert to lower case and replace underscores with hyphens.\n\nSome messages, for example the command messages define different uses for\ntheir fields based on the value of another field, for command messages the :command field\ndefines what the values for the param fields are and for some of those fields, the value\nis defined in an enum group.\n\nEnums can be used to bind the value of the these fields in outgoing messages. However,\ndecoding cannot determine the enum group to use, if any, based on the `:command`. For that,\nclj-mavlink provides a function get-enum which takes a MAVLink database, a group id,\nand a value and returns the enum for the value in that group, or nil if it doesn't exist.\nThis gives you the option of binding the field to a value or an enum.\nYou can define your own get-enum function\nto hide the MAVLink database (and possibly the enum group id) from the rest of your code, for example:\n\n``` clojure\n(def get-enum\n  (memoize (fn [group-id ^long v]\n             (mavlink/get-enum @mavlink-info group-id v))))\n```\n\n#### Sending messages\n\nThis example shows building and sending a `heartbeat` message. Remember,\nthe `system id` and `component id` and encoding protocol were specified in the open channel call.\nThe `sequence id` is automatically calculated and added to the message.\nAny of these fields can be overridden by providing them in the message to be encoded.\n\n``` clojure\n(let [msg message {:message-id :heartbeat\n                   :custom-mode 0\n                   :type :mav-type-gcs\n                   :autopilot :mav-autopilot-invalid\n                   :base-mode 0\n                   :system-status :mav-state-uninit})]\n  (async/\u003e!! autopilot-send msg))\n```\n\n#### Receiving messages\n\nThere are different approaches to receiving and distributing messages, the approach shown here\nuses clojure.async publish/subscribe.\n\nA message publisher is defined for the entire application based on the :message-id:\n\n``` clojure\n(defonce message-publisher (async/pub autopilot-receive\n                                      :message-id))\n```\n\nWhen a module wishes to receive messages, it subscribes to that particular message id:\n\n``` clojure\n(async/sub message-publisher :heartbeat connect-heartbeat-chan)\n```\n\n``` clojure\n(let [msg (async/\u003c!! connect-heartbeat-chan)]\n  ; process the message here\n  )\n```\n\n## License\n\nCopyright © 2017 Michael du Breuil\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwickedshell%2Fclj-mavlink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwickedshell%2Fclj-mavlink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwickedshell%2Fclj-mavlink/lists"}