Moonraker's external API documentation has been significantly overhauled in an effort to improve clarity and readability. All parameters and responses are documented with specifications. Tables and other elements are used to make documentation more visible and less verbose. Spelling and other corrections have also been added. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
366 lines
14 KiB
Markdown
366 lines
14 KiB
Markdown
## Components
|
|
|
|
Components in Moonraker are used to extend Moonraker's functionality,
|
|
similar to "extras" in Klipper. Moonraker divides components into
|
|
two categories, "core" components and "optional" components. A core
|
|
component gets its configuration from the `[server]` section and is
|
|
loaded when Moonraker starts. For example, the `file_manager` is a
|
|
core component. If a core component fails to load Moonraker will
|
|
exit with an error.
|
|
|
|
Optional components must be configured in `moonraker.conf`. If they
|
|
have no specific configuration, a bare section, such as `[octoprint_compat]`
|
|
must be present in `moonraker.conf`. Unlike with core components,
|
|
Moonraker will still start if an optional component fails to load.
|
|
Its failed status will be available for clients to query and present
|
|
to the user.
|
|
|
|
### Basic Example
|
|
|
|
Components exist in the `components` directory. The below example
|
|
shows how an `example.py` component might look:
|
|
```python
|
|
# Example Component
|
|
#
|
|
# Copyright (C) 2021 Eric Callahan <arksine.code@gmail.com>
|
|
#
|
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
|
|
|
class Example:
|
|
def __init__(self, config):
|
|
self.server = config.get_server()
|
|
self.name = config.get_name()
|
|
|
|
# Raises an error if "example_int_option" is not configured in
|
|
# the [example] section
|
|
self.example_int_opt = config.getint("example_int_option")
|
|
|
|
# Returns a NoneType if "example_float_option is not configured
|
|
# in the config
|
|
self.example_float_opt = config.getfloat("example_float_option", None)
|
|
|
|
self.server.register_endpoint("/server/example", ['GET'],
|
|
self._handle_example_request)
|
|
|
|
async def request_some_klippy_state(self):
|
|
klippy_apis = self.server.lookup_component('klippy_apis')
|
|
return await klippy_apis.query_objects({'print_stats': None})
|
|
|
|
async def _handle_example_request(self, web_request):
|
|
web_request.get_int("required_request_param")
|
|
web_request.get_float("optional_request_param", None)
|
|
state = await self.request_some_klippy_state()
|
|
return {"example_return_value": state}
|
|
|
|
def load_component(config):
|
|
return Example(config)
|
|
|
|
```
|
|
If you have created a "Klippy extras" module then the above should look
|
|
look familiar. Moonraker attempts to use similar method for adding
|
|
extensions, making easier Klipper contributors to add new functionality
|
|
to Moonraker. Be aware that there is no "Reactor" object in Moonraker,
|
|
it uses `asyncio` for coroutines. Like Klippy, you should not write
|
|
code that blocks the main thread.
|
|
|
|
### The ConfigHelper Object
|
|
|
|
As shown above, each component is passed a config object. This object
|
|
will be a `ConfigHelper` type, which is an object that wraps a
|
|
configuration section to simply access to the native `ConfigParser`.
|
|
A `ConfigHelper` should never be directly instantiated.
|
|
|
|
#### *ConfigHelper.get_server()*
|
|
|
|
Returns the primary [server](#the-server-object) instance.
|
|
|
|
#### *ConfigHelper.get_name()*
|
|
|
|
Returns the configuration section name associated with this `ConfigHelper`.
|
|
|
|
#### *ConfigHelper.get(option_name, default=Sentinel)*
|
|
|
|
Returns the value of the option`option_name` as a string. If
|
|
the option does not exist, returns `default`. If `default` is
|
|
not provided raises a `ConfigError`.
|
|
|
|
#### *ConfigHelper.getint(option_name, default=Sentinel)*
|
|
|
|
Returns the value of the option`option_name` as an integer. If
|
|
the option does not exist, returns `default`. If `default` is
|
|
not provided raises a `ConfigError`.
|
|
|
|
#### *ConfigHelper.getfloat(option_name, default=Sentinel)*
|
|
|
|
Returns the value of the option`option_name` as a float. If
|
|
the option does not exist, returns `default`. If `default` is
|
|
not provided raises a `ConfigError`.
|
|
|
|
#### *ConfigHelper.getboolean(option_name, default=Sentinel)*
|
|
|
|
Returns the value of the option`option_name` as a boolean. If
|
|
the option does not exist, returns `default`. If `default` is
|
|
not provided raises a `ConfigError`.
|
|
|
|
#### *ConfigHelper.has_section(section_name)*
|
|
|
|
Returns True if a section matching `section_name` is in the configuration,
|
|
otherwise False.
|
|
|
|
Note that a ConfigHelper object also implements `__contains__`,
|
|
which is an alias for `has_section`, ie: `section_name in config_instance`
|
|
|
|
#### *ConfigHelper.getsection(section_name)*
|
|
|
|
Returns a Config object for the section matching `section_name`. If the
|
|
section does not exist in the configuration raises a `ConfigError`.
|
|
|
|
Note that a ConfigHelper object also implements `__getitem__`,
|
|
which is an alias for `get_section`, ie: `config_instance[section_name]`
|
|
|
|
#### *ConfigHelper.get_options()*
|
|
|
|
Returns a dict mapping options to values for all options in the Config
|
|
object.
|
|
|
|
#### *ConfigHelper.get_prefix_sections(prefix)*
|
|
|
|
Returns a list section names in the configuration that start with `prefix`.
|
|
These strings can be used to retrieve ConfigHelpers via
|
|
[get_section()](#confighelpergetsectionsection_name).
|
|
|
|
### The Server Object
|
|
|
|
The server instance represents the central management object in Moonraker.
|
|
It can be used to register endpoints, register notifications, look up other
|
|
components, send events, and more.
|
|
|
|
#### *Server.lookup_component(component_name, default=Sentinel)*
|
|
|
|
Attempts to look up a loaded component, returning the result. If
|
|
the component has not been loaded, `default` will be returned.
|
|
If `default` is not provided a `ServerError` will be raised.
|
|
|
|
#### *Server.load_component(config, component_name, default=Sentinel)*
|
|
|
|
Attempts to load an uninitialized component and returns the result. It is
|
|
only valid to call this within a a component's `__init__()` method, and
|
|
should only be necessary if one optional component relies on another. Core components will always be loaded before optional components, thus an optional
|
|
component may always call
|
|
[lookup_component()](#serverlookup_componentcomponent_name-defaultsentinel)
|
|
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, transports=["http", "websocket", "mqtt"], wrap_result=True)*
|
|
|
|
Registers the supplied `uri` with the server.
|
|
|
|
The `request_methods` argument should be a list of strings containing any
|
|
combination of `GET`, `POST`, and `DELETE`.
|
|
|
|
The `callback` is executed when a request matching the `uri` and a
|
|
`request_method` is received. The callback function will be passed a
|
|
`WebRequest` object with details about the request. This function
|
|
should be able of handling each registered `request_method`. The
|
|
provided callback must be a coroutine.
|
|
|
|
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)
|
|
```
|
|
would register a JSON-RPC method like:
|
|
```
|
|
server.example
|
|
```
|
|
|
|
However, if multiple requests methods are supplied, the generated JSON-RPC
|
|
methods will differ:
|
|
```python
|
|
self.server.register_endpoint("/server/example", ["GET", "POST", "DELETE"],
|
|
self._handle_request)
|
|
```
|
|
would register:
|
|
```
|
|
server.get_example
|
|
server.post_example
|
|
server.delete_example
|
|
```
|
|
|
|
The `wrap_result` argument applies only to the `http` protocol. In Moonraker
|
|
all http requests return a result with a JSON body. By default, the value returned
|
|
by a `callback` is wrapped in a dict:
|
|
```python
|
|
{"result": return_value}
|
|
```
|
|
It is only necessary to set this to false if you need to return a body that
|
|
does not match this result. For example, the `[octoprint_compat]` component
|
|
uses this functionality to return results in a format that match what
|
|
OctoPrint itself would return.
|
|
|
|
#### *Server.register_event_handler(event, callback)*
|
|
|
|
Registers the provided `callback` method to be executed when the
|
|
provided `event` is sent. The callback may be a coroutine, however it
|
|
is not required.
|
|
|
|
#### *Server.send_event(event, \*args)*
|
|
|
|
Emits the event named `event`, calling all callbacks registered to the
|
|
event. All positional arguments in `*args` will be passed to each
|
|
callback. Event names should be in the form of
|
|
`"module_name:event_description"`.
|
|
|
|
#### *Server.register_notification(event_name, notify_name=None)*
|
|
|
|
Registers a websocket notification to be pushed when `event_name`
|
|
is emitted. By default JSON-RPC notification sent will be in the form of
|
|
`notify_{event_description}`. For example, when the server sends the
|
|
`server:klippy_connected` event, the JSON_RPC notification will be
|
|
`notify_klippy_connected`.
|
|
|
|
If a `notify_name` is provided it will override the `{event_description}`
|
|
extracted from the `event_name`. For example, if the `notify_name="k_connected"`
|
|
were specified when registering the `server:klippy_connected` event, the
|
|
websocket would emit a `notify_k_connected` notification.
|
|
|
|
#### *Server.get_host_info()*
|
|
|
|
Returns a tuple of the current host name of the PC and the port Moonraker
|
|
is serving on.
|
|
|
|
#### *Server.get_klippy_info()*
|
|
|
|
Returns a dict containing the values from the most recent `info` request to
|
|
Klippy. If Klippy has never connected this will be an empty dict.
|
|
|
|
### The WebRequest Object
|
|
|
|
All callbacks registered with the
|
|
[register_endpoint()](#serverregister_endpointuri-request_methods-callback-transportshttp-websocket-mqtt-wrap_resulttrue)
|
|
method are passed a WebRequest object when they are executed. This object
|
|
contains information about the request including its endpoint name and arguments
|
|
parsed from the request.
|
|
|
|
#### *WebRequest.get_endpoint()*
|
|
|
|
Returns the URI registered with this request, ie: `/server/example`.
|
|
|
|
#### *WebRequest.get_action()*
|
|
|
|
Returns the request action, which is synonymous with its HTTP request
|
|
method. Will be either `GET`, `POST`, or `DELETE`. This is useful
|
|
if your endpoint was registered with multiple request methods and
|
|
needs to handle each differently.
|
|
|
|
#### *WebRequest.get_connection()*
|
|
|
|
Returns the associated Websocket connection ID. This will be `None`
|
|
for HTTP requests when no associated websocket is connected to
|
|
the client.
|
|
|
|
#### *WebRequest.get_args()*
|
|
|
|
Returns a reference to the entire argument dictionary. Useful if
|
|
one request handler needs to preprocess the arguments before
|
|
passing the WebRequest on to another request handler.
|
|
|
|
#### *WebRequest.get(key, default=Sentinel)*
|
|
|
|
Returns the request argument at the provided `key`. If the key is not
|
|
present `default` will be returned. If `default` is not provided a
|
|
`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 `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 `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 `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 `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
|
|
or until the specified timeout has exceeded. Returns true if the connection
|
|
was successful established, or False on timeout. If no timeout is specified
|
|
then this method will block indefinitely until a connection has been
|
|
established.
|
|
|
|
#### *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 specified
|
|
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 specified.
|
|
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)*
|
|
|
|
Subscribes 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.
|
|
|
|
#### *MQTTClient.unsubscribe(hdl)*
|
|
|
|
Unsubscribes the callback associated with `hdl`. If no outstanding callbacks
|
|
exist for the topic then the topic is unsubscribed from the broker.
|