-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #119 from quartiq/develop
Replacing `develop` with `main`
- Loading branch information
Showing
33 changed files
with
2,151 additions
and
1,548 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,149 @@ | ||
# MiniConf | ||
|
||
# Miniconf | ||
[![crates.io](https://img.shields.io/crates/v/miniconf.svg)](https://crates.io/crates/miniconf) | ||
[![docs](https://docs.rs/miniconf/badge.svg)](https://docs.rs/miniconf) | ||
[![QUARTIQ Matrix Chat](https://img.shields.io/matrix/quartiq:matrix.org)](https://matrix.to/#/#quartiq:matrix.org) | ||
![Continuous Integration](https://github.com/vertigo-designs/miniconf/workflows/Continuous%20Integration/badge.svg) | ||
|
||
MiniConf is a `no_std` minimal run-time settings configuration tool designed to be run on top of | ||
any communication means. It was originally designed to work with MQTT clients and provides a default | ||
implementation using [minimq](https://github.com/quartiq/minimq) as the MQTT client. | ||
[![Continuous Integration](https://github.com/vertigo-designs/miniconf/workflows/Continuous%20Integration/badge.svg)](https://github.com/quartiq/miniconf/actions) | ||
|
||
# Design | ||
Miniconf enables lightweight (`no_std`) partial serialization (retrieval) and deserialization | ||
(updates, modification) within a hierarchical namespace by path. The namespace is backed by | ||
structs and arrays of serializable types. | ||
|
||
Miniconf provides an easy-to-work-with API for quickly adding runtime-configured settings to any | ||
embedded project. This allows any internet-connected device to quickly bring up configuration | ||
interfaces with minimal implementation in the end-user application. | ||
Miniconf can be used as a very simple and flexible backend for run-time settings management in embedded devices | ||
over any transport. It was originally designed to work with JSON ([serde_json_core](https://docs.rs/serde-json-core)) | ||
payloads over MQTT ([minimq](https://docs.rs/minimq)) and provides a comlete [MQTT settings management | ||
client](MqttClient) and a Python reference implementation to ineract with it. | ||
|
||
MiniConf provides a `Miniconf` derive macro for creating a settings structure, e.g.: | ||
## Example | ||
```rust | ||
use miniconf::Miniconf; | ||
use serde::{Serialize, Deserialize}; | ||
|
||
#[derive(Miniconf)] | ||
struct NestedSettings { | ||
inner: f32, | ||
#[derive(Deserialize, Serialize, Copy, Clone, Default)] | ||
enum Either { | ||
#[default] | ||
Bad, | ||
Good, | ||
} | ||
|
||
#[derive(Miniconf)] | ||
struct MySettings { | ||
initial_value: u32, | ||
internal: NestedSettings, | ||
#[derive(Deserialize, Serialize, Copy, Clone, Default, Miniconf)] | ||
struct Inner { | ||
a: i32, | ||
b: i32, | ||
} | ||
``` | ||
|
||
# Settings Paths | ||
#[derive(Miniconf, Default)] | ||
struct Settings { | ||
// Atomic updtes by field name | ||
foo: bool, | ||
enum_: Either, | ||
struct_: Inner, | ||
array: [i32; 2], | ||
option: Option<i32>, | ||
|
||
A setting value must be configured via a specific path. Paths take the form of variable names | ||
separated by slashes - this design follows typical MQTT topic design semantics. For example, with | ||
the following `Settings` structure: | ||
```rust | ||
#[derive(Miniconf)] | ||
struct Data { | ||
inner: f32, | ||
// Exposing elements of containers | ||
// ... by field name | ||
#[miniconf(defer)] | ||
struct_defer: Inner, | ||
// ... or by index | ||
#[miniconf(defer)] | ||
array_defer: [i32; 2], | ||
// ... or deferring to two levels (index and then inner field name) | ||
#[miniconf(defer)] | ||
array_miniconf: miniconf::Array<Inner, 2>, | ||
|
||
// Hiding paths by setting the Option to `None` at runtime | ||
#[miniconf(defer)] | ||
option_defer: Option<i32>, | ||
// Hiding a path and deferring to the inner | ||
#[miniconf(defer)] | ||
option_miniconf: miniconf::Option<Inner> | ||
} | ||
|
||
#[derive(Miniconf)] | ||
struct Settings { | ||
initial_value: u32, | ||
internal: Data, | ||
let mut settings = Settings::default(); | ||
let mut buf = [0; 64]; | ||
|
||
// Atomic updates by field name | ||
settings.set("foo", b"true")?; | ||
assert_eq!(settings.foo, true); | ||
|
||
settings.set("enum_", br#""Good""#)?; | ||
settings.set("struct_", br#"{"a": 3, "b": 3}"#)?; | ||
settings.set("array", b"[6, 6]")?; | ||
settings.set("option", b"12")?; | ||
settings.set("option", b"null")?; | ||
|
||
// Deep access by field name in a struct | ||
settings.set("struct_defer/a", b"4")?; | ||
// ... or by index in an array | ||
settings.set("array_defer/0", b"7")?; | ||
// ... or by index and then struct field name | ||
settings.set("array_miniconf/1/b", b"11")?; | ||
|
||
// If a deferred Option is `None` it is hidden at runtime | ||
settings.set("option_defer", b"13").unwrap_err(); | ||
settings.option_defer = Some(0); | ||
settings.set("option_defer", b"13")?; | ||
settings.set("option_miniconf/a", b"14").unwrap_err(); | ||
*settings.option_miniconf = Some(Inner::default()); | ||
settings.set("option_miniconf/a", b"14")?; | ||
|
||
// Serializing an element by path | ||
let len = settings.get("foo", &mut buf)?; | ||
assert_eq!(&buf[..len], b"true"); | ||
|
||
// Iterating over all elements | ||
for path in Settings::iter_paths::<3, 32>().unwrap() { | ||
let len = settings.get(&path, &mut buf)?; | ||
if path.as_str() == "option_miniconf/a" { | ||
assert_eq!(&buf[..len], b"14"); | ||
} | ||
} | ||
|
||
# Ok::<(), miniconf::Error>(()) | ||
``` | ||
|
||
## MQTT | ||
There is an [MQTT-based client](MqttClient) that implements settings management over the [MQTT | ||
protocol](https://mqtt.org) with JSON payloads. A Python reference library is provided that | ||
interfaces with it. | ||
|
||
```sh | ||
# Discover the complete unique prefix of an application listening to messages | ||
# under the topic `quartiq/application/12345` and set its `foo` setting to `true`. | ||
python -m miniconf -d quartiq/application/+ foo=true | ||
``` | ||
|
||
We can access `Data::inner` with the path `internal/inner`. | ||
## Design | ||
For structs with named fields, Miniconf offers a [derive macro](derive.Miniconf.html) to automatically | ||
assign a unique path to each item in the namespace of the struct. | ||
The macro implements the [Miniconf] trait that exposes access to serialized field values through their path. | ||
All types supported by [serde_json_core] can be used as fields. | ||
|
||
Elements of homogeneous [core::array]s are similarly accessed through their numeric indices. | ||
Structs, arrays, and Options can then be cascaded to construct a multi-level namespace. | ||
Namespace depth and access to individual elements instead of the atomic updates | ||
is configured at compile (derive) time using the `#[miniconf(defer)]` attribute. | ||
`Option` is used with `#[miniconf(defer)]` to support paths that may be absent (masked) at | ||
runtime. | ||
|
||
While the [Miniconf] implementations for [core::array] and [core::option::Option] by provide | ||
atomic access to their respective inner element(s), [Array] and | ||
[Option] have alternative [Miniconf] implementations that expose deep access | ||
into the inner element(s) through their respective inner [Miniconf] implementations. | ||
|
||
## Formats | ||
The path hierarchy separator is the slash `/`. | ||
|
||
Values are serialized into and deserialized from JSON. | ||
|
||
Settings may only be updated at the terminal node. That is, you cannot configure | ||
`<device-id>/settings/internal` directly. If this is desired, instead derive `MiniconfAtomic` on the | ||
`struct Data` definition. In this way, all members of `struct Data` must be updated simultaneously. | ||
## Transport | ||
Miniconf is designed to be protocol-agnostic. Any means that can receive key-value input from | ||
some external source can be used to modify values by path. | ||
|
||
# Settings Values | ||
## Limitations | ||
Deferred (non-atomic) access to inner elements of some types is not yet supported. This includes: | ||
* Complex enums (other than [core::option::Option]) | ||
* Tuple structs (other than [Option], [Array]) | ||
|
||
MiniConf relies on using [`serde`](https://github.com/serde-rs/serde) for defining a | ||
de/serialization method for settings. Currently, MiniConf only supports serde-json de/serialization | ||
formats, although more formats may be supported in the future. | ||
## Features | ||
* `mqtt-client` Enabled the MQTT client feature. See the example in [MqttClient]. |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.