diff --git a/docs/components.md b/docs/components.md index 6b17702..97e0ccd 100644 --- a/docs/components.md +++ b/docs/components.md @@ -153,7 +153,7 @@ when it needs a reference to core component. If the component fails to load `default` will be returned. If `default` is not provided a `ServerError` will be raised. -#### *Server.register_endpoint(uri, request_methods, callback, protocol=["http", "websocket"], wrap_result=True)* +#### *Server.register_endpoint(uri, request_methods, callback, transports=["http", "websocket", "mqtt"], wrap_result=True)* Registers the supplied `uri` with the server. @@ -166,10 +166,11 @@ The `callback` is executed when a request matching the `uri` and a should be able of handling each registered `request_method`. The provided callback must be a coroutine. -The `protocol` is a list containing any combination of `http` and `websocket`. -If `websocket` is selected associated JSON-RPC methods will be generated based -on what is supplied by the `uri` and `request_methods` argument. A unique -JSON_RPC method is generated for each request method. For example: +The `transports` argument is a list containing any combination of +`http`, `websocket` and `mqtt`. JSON-RPC methods for `websocket` and `mqtt` +will be generated based on what is supplied by the `uri` and +request_methods` argument. A unique JSON_RPC method is generated for each +request method. For example: ```python self.server.register_endpoint("/server/example", ["POST"], self._handle_request) ``` @@ -273,32 +274,90 @@ passing the WebRequest on to another request handler. Returns the request argument at the provided `key`. If the key is not present `default` will be returned. If `default` is not provided a -`SeverError` will be raised. +`ServerError` will be raised. #### *WebRequest.get_str(key, default=Sentinel)* Retrieves the request argument at the provided `key` and converts it to a string, returning the result. If the key is not present the `default` value will be returned. If `default` is not provided or if the attempt at -type conversion fails a `SeverError` will be raised. +type conversion fails a `ServerError` will be raised. #### *WebRequest.get_int(key, default=Sentinel)* Retrieves the request argument at the provided `key` and converts it to an integer, returning the result. If the key is not present the `default` value will be returned. If `default` is not provided or if the attempt at -type conversion fails a `SeverError` will be raised. +type conversion fails a `ServerError` will be raised. #### *WebRequest.get_float(key, default=Sentinel)* Retrieves the request argument at the provided `key` and converts it to a float, returning the result. If the key is not present the `default` value will be returned. If `default` is not provided or if the attempt at -type conversion fails a `SeverError` will be raised. +type conversion fails a `ServerError` will be raised. #### *WebRequest.get_boolean(key, default=Sentinel)* Retrieves the request argument at the provided `key` and converts it to a boolean, returning the result. If the key is not present the `default` value will be returned. If `default` is not provided or if the attempt at -type conversion fails a `SeverError` will be raised. +type conversion fails a `ServerError` will be raised. + +### MQTT + +If configured by the user the MQTT component is available for lookup. +Developers may use this to subscribe and publish topics. + +#### *MQTTClient.is_connected()* + +Returns true if Moonraker is currently connected to the Broker, false +otherwise. + +#### *MQTTClient.wait_connection(timeout=None)* + +Blocks until a connection with the broker has been successfully established. +If the optional `timeout` argument is specified then `asyncio.TimeoutError` +will be raised if the timeout has exceeded. + +#### *MQTTClient.publish_topic(topic, payload=None, qos=None, retain=False)* + +Attempts to publish a topic to the Broker. The `payload` may be a bool, int, +float, string, or json encodable (Dict or List). If omitted then an empty +payload is sent. The `qos` may be an integer from 0 to 2. If not specifed +then the QOS level will use the configured default. If `retain` is set to +`True` then the retain flag for the payload will be set. + +Returns a Future that will block until topic is confirmed as published. +For QOS level 0 an exception will be raised if the broker is not connected. + + +#### *MQTTClient.publish_topic_with_response(topic, response_topic, payload=None, qos=None, retain=False, timeout=None)* + +Publishes the supplied `topic` with the arguments specified by `payload`, +`qos`, and `retain`, then subscribes to the `response_topic`. The payload +delivered by the response topic is returned. Note that this method is +a coroutine, it must always be awaited. The call will block until the +entire process has completed unless a `timeout` (in seconds) is specifed. +The `timeout` is applied to both the attempt to publish and the pending +response, so the maximum waiting time would be approximately 2*timeout. + +!!! warning + This should only be used when it is guaranteed that the `response_topic` + does not have a retained value. Otherwise the returned response will + be the retained value. + +#### *MQTTClient.subscribe_topic(topic, callback, qos=None)* + +Subscibes to the supplied `topic` with the specified `qos`. If `qos` is not +supplied the configured default will be used. The `callback` should be a +callable that accepts a `payload` argument of a `bytes` type. The callable +may be a coroutine. The callback will be run each time the subscribed topic +is published by another client. + +Returns a `SubscriptionHandle` that may be used to unsubscribe the topic. + +#### *MQTTClinet.unsubscribe(hdl)* + +Unsubscribes the callback associated with `hdl`. If no outstanding callbacks +exist for the topic then the topic is unsubscribed from the broker. diff --git a/docs/configuration.md b/docs/configuration.md index 03b8dd8..da08b45 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -427,7 +427,8 @@ persistent_files: ``` This second example is for git repositories that have a service that need -updating. +updating. Note that git repos must have at least one tag for Moonraker +to identify its version. ```ini # moonraker.conf @@ -468,3 +469,60 @@ enable_node_updates: # package-lock.json in its root directory then the plugin will fail to load. # Default is False. ``` + +## `[mqtt]` + +Enables an MQTT Client. When configured most of Moonraker's APIs are availble +by publishing JSON-RPC requests to `{instance_name}/moonraker/api/request`. +Responses will be published to `{instance_name}/moonraker/api/response`. See +the [API Documentation](web_api.md#json-rpc-api-overview) for details on +on JSON-RPC. + +It is also possible for other components within Moonraker to use MQTT to +publish and subscribe to topics. + +```ini +address: +# Address of the Broker. This may be a hostname or IP Address. This +# parameter must be provided. +port: +# Port the Broker is listening on. Default is 1883. +username: +# An optional username used to log in to the Broker. Default is no +# username (an anonymous login will be attempted) +password_file: +# An optional path to a text file containing a password used to log in +# to the broker. It is strongly recommended that this file be located +# in a folder not served by Moonraker. It is also recommended that the +# password be unique and not used for other logins, as it is stored in +# plain text. To create a password file, one may ssh in to the device +# and enter the following commands: +# cd ~ +# echo mypassword > .mqttpass +# Then set this option to: +# ~/.mqttpass +# If this option is omitted no password will be used to login. +mqtt_protocol: v3.1.1 +# The protocol to use when connecting to the Broker. May be v3.1, +# v3.1.1, and v5. The default is v3.1.1 +enable_moonraker_api: True +# If set to true the MQTT client will subscribe to API topic, ie: +# {instance_name}/moonraker/api/request +# This can be set to False if the user does not wish to allow API +# requests over MQTT. The default is True. +instance_name: +# An identifer used to create unique API topics for each instance of +# Moonraker on network. This name cannot contain wildcards (+ or #). +# For example, if the instance name is set to my_printer, Moonraker +# will subscribe to the following topic for API requests: +# my_printer/moonraker/api/request +# Responses will be published to the following topic: +# my_printer/moonraker/api/response +# The default is the machine's hostname. +default_qos: 0 +# The default QOS level used when publishing or subscribing to topics. +# Must be an integer value from 0 to 2. The default is 0. +api_qos: +# The QOS level to use for the API topics. If not provided, the +# value specified by "default_qos" will be used. +``` \ No newline at end of file diff --git a/docs/web_api.md b/docs/web_api.md index 91cace0..fc17b0d 100644 --- a/docs/web_api.md +++ b/docs/web_api.md @@ -1,10 +1,10 @@ # -Most API methods are supported over both the Websocket and HTTP transports. -File Transfer and `/access` requests are only available over HTTP. The -Websocket is required to receive server generated events such as gcode -responses. For information on how to set up the Websocket, please see the -Appendix at the end of this document. +Most API methods are supported over the Websocket, HTTP, and MQTT +(if configured) transports. File Transfer and `/access` requests are only +available over HTTP. The Websocket is required to receive server generated +events such as gcode responses. For information on how to set up the +Websocket, please see the Appendix at the end of this document. ### HTTP API Overview @@ -71,10 +71,19 @@ thus using this functionality should be seen as a "last resort." If at all possible clients should attempt to put these arguments in the body of a request. -### Websocket API Overview +### JSON-RPC API Overview -The Websocket API is based on JSON-RPC, an encoded request should look -something like: +The Websocket and MQTT transports use the [JSON-RPC 2.0](https://jsonrpc.org) +protocol. The Websocket transmits objects in a text frame, whereas MQTT +transmits them in the payload of a topic. When MQTT is configured Moonraker +subscribes to an api request topic. After an api request is processed Moonraker +publishes the return value to a response topic. By default these topics are +`{instance_name}/moonraker/api/request` and +`{instance_name}/moonraker/api/response`. The `{instance_name}` should be a +unique identifier for each instance of Moonraker and defaults to the machine's +host name. + +An encoded request should look something like: ```json { "jsonrpc": "2.0", @@ -85,10 +94,19 @@ something like: ``` The `params` field may be left out if the API request takes no arguments. -The `id` should be a unique integer value that has no chance of colliding +The `id` should be a unique value that has no chance of colliding with other JSON-RPC requests. The `method` is the API method, as defined for each API in this document. +!!! tip + MQTT requests may provide an optional `mqtt_timestamp` keyword + argument in the `params` field of the JSON-RPC request. To avoid + potential collisions from time drift it is recommended to specify + the timestamp in microseconds since the Unix Epoch. If provided + Moonraker will use the timestamp to discard duplicate requests. + It is recommended to either provide a timestamp or publish API + requests at a QoS level of 0 or 2. + A successful request will return a response like the following: ```json { @@ -114,7 +132,7 @@ Some errors may not return a request ID, such as an improperly formatted request The `test/client` folder includes a basic test interface with example usage for most of the requests below. It also includes a basic JSON-RPC implementation -that uses promises to return responses and errors (see json-rcp.js). +that uses promises to return responses and errors (see json-rpc.js). ### Printer Administration @@ -303,6 +321,9 @@ POST /printer/objects/subscribe?connection_id=123456789&gcode_move&extruder` request that includes only the `connection_id` argument will cancel the subscription on the specified websocket. + This request is not available over MQTT, as it is not possible to + associate a connected websocket with an MQTT client. + JSON-RPC request: ```json { @@ -2794,7 +2815,7 @@ JSON-RPC request: "jsonrpc": "2.0", "method":"server.history.get_job", "params":{"uid": "{uid}"}, - "id": 4564, + "id": 4564 } ``` Returns: @@ -2849,6 +2870,133 @@ An array of deleted job ids ] ``` +### MQTT APIs + +The following API is available when `[mqtt]` has been configured. + +!!! Note + These requests are not available over the `mqtt` transport as they + are redundant. MQTT clients can publish and subscribe to + topics directly. + +#### Publish a topic + +HTTP request: +```http +POST /server/mqtt/publish +Content-Type: application/json + +{ + "topic": "home/test/pub", + "payload": "hello", + "qos": 0, + "retain": false, + "timeout": 5 +} +``` +JSON-RPC request: +```json +{ + "jsonrpc": "2.0", + "method":"server.mqtt.publish", + "params":{ + "topic": "home/test/pub", + "payload": "hello", + "qos": 0, + "retain": false, + "timeout": 5 + }, + "id": 4564 +} +``` +Only the `topic` parameter is required. Below is an explanation for +each paramater: + +- `topic`: The topic to publish. +- `payload`: Payload to send with the topic. May be a boolean, float, + integer, string, object, or array. All values are converted to strings prior + to publishing. Objects and Arrays are JSON encoded. If omitted an empty + payload is sent. +- `qos`: QOS level to use when publishing the topic. Must be an integer value + from 0 to 2. If omitted the system configured default is used. +- `retain`: If set to `true` the MQTT broker will retain the payload of this + request. Note that only the mostly recently tagged payload is retained. + When other clients first subscribe to the topic they immediately recieve the + retained message. The default is `false`. +- `timeout`: A float value in seconds. By default requests with QoS levels of + 1 or 2 will block until the Broker acknowledges confirmation. This option + applies a timeout to the request, returning a 504 error if the timeout is + exceeded. Note that the topic will still be published if the QoS level is 1 + or 2. + +!!! tip + To clear a retained value of a topic, publish the topic with an empty + payload and `retain` set to `true`. + +Returns: + +The published topic: +```json +{ + "topic": "home/test/pub" +} +``` + +#### Subscribe to a topic + + +HTTP request: +```http +POST /server/mqtt/subscribe +Content-Type: application/json + +{ + "topic": "home/test/sub", + "qos": 0, + "timeout": 5 +} +``` +JSON-RPC request: +```json +{ + "jsonrpc": "2.0", + "method":"server.mqtt.subscribe", + "params":{ + "topic": "home/test/sub", + "qos": 0, + "timeout": 5 + }, + "id": 4564 +} +``` + +Only the `topic` parameter is required. Below is an explanation for +each paramater: + +- `topic`: The topic to subscribe. Note that wildcards may not be used. +- `qos`: QOS level to use when subscribing to the topic. Must be an integer + value from 0 to 2. If omitted the system configured default is used. +- `timeout`: A float value in seconds. By default requests will block + indefinitely until a response is received. This option applies a timeout to + the request, returning a 504 error if the timeout is exceeded. The + subscription will be removed after a timeout. + +!!! note + If the topic was previously published with a retained payload this request + will return with the retained value. + +Returns: + +The subscribed topic and its payload: +```json +{ + "topic": "home/test/pub", + "payload": "test" +} +``` +If the payload is json encodable it will be returned as an object or array. +Otherwise it will be a string. + ### Websocket notifications Printer generated events are sent over the websocket as JSON-RPC 2.0 notifications. These notifications are sent to all connected clients