Skip to content

Commit

Permalink
Merge pull request #119 from quartiq/develop
Browse files Browse the repository at this point in the history
Replacing `develop` with `main`
  • Loading branch information
ryan-summers authored Nov 9, 2022
2 parents 04cab96 + a449acc commit 5ea5b82
Show file tree
Hide file tree
Showing 33 changed files with 2,151 additions and 1,548 deletions.
24 changes: 5 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,22 @@ jobs:
command: fmt
args: --all -- --check

- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/setup-python@v1
- name: Install Pylint
run: |
python -m pip install --upgrade pip
python -m pip install pylint
- name: Run Pylint
working-directory: py/miniconf-mqtt
run: |
python -m pip install .
python -m pylint miniconf
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: thumbv7em-none-eabihf
override: true
components: clippy

- name: Clippy Check
uses: actions-rs/cargo@v1
with:
command: clippy

documentation:
runs-on: ubuntu-latest
steps:
Expand Down
65 changes: 62 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,70 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.6.2](https://github.com/quartiq/miniconf/compare/v0.6.1...v0.6.2) - 2022-11-09

* Renaming and reorganization of the the derive macro

## [0.6.1](https://github.com/quartiq/miniconf/compare/v0.6.0...v0.6.1) - 2022-11-04

* Documentation updates.

## [0.6.0](https://github.com/quartiq/miniconf/compare/v0.5.0...v0.6.0) - 2022-11-04

### Changed
* python module: don't emite whitespace in JSON to match serde-json-core (#92)
* `heapless::String` now implements `Miniconf` directly.
* Python client API is no longer retain by default. CLI is unchanged
* [breaking] Support for `#[derive(MiniconfAtomic)]` was removed.
* Fields in `#[derive(Miniconf)]` are now atomic by default. To recurse, users must
annotate fields with `#[miniconf(defer)]`
* New `miniconf::Option` type has been added. Existing `Option` implementation has been changed to
allow run-time nullability of values for more flexibility.
* New `miniconf::Array` type has been added, replacing the previous [T; N] implementation
* `Miniconf` implementation on most primitive types has been removed as it is no longer required.
* [breaking] The API has changed to be agnostic to usage (e.g. now referring to namespace paths and values
instead of topics and settings). Functions in the `Miniconf` trait have been renamed.
* [breaking] Errors and the Metadata struct have beem marked `#[non_exhaustive]`
* [breaking] `metadata()`, `unchecked_iter_paths()`, `iter_paths()`, `next_path()` are
all associated functions now.
* [breaking] Path iteration has been changed to move ownership of the iteration state into the iterator.
And the path depth is now a const generic.
* [breaking] Path iteration will always return all paths regardless of potential runtime `miniconf::Option`
or deferred `Option` being `None`.
* [breaking] `unchecked_iter_paths()` takes an optional iterator size to be used in `Iterator::size_hint()`.
* MQTT client now publishes responses with a quality of service of at-least-once to ensure
transmission.
* MQTT client no longer uses correlation data to ignore local transmissions.

### Fixed
* Python device discovery now only discovers unique device identifiers. See [#97](https://github.com/quartiq/miniconf/issues/97)
* Python requests API updated to use a static response topic
* Python requests now have a timeout
* Generic trait bound requirements have been made more specific.


## [0.5.0] - 2022-05-12

### Changed
* **breaking** The Miniconf trait for iteration was renamed from `unchecked_iter()` and `iter()` to
`unchecked_iter_settings()` and `iter_settings()` respectively to avoid issues with slice iteration
name conflicts. See [#87](https://github.com/quartiq/miniconf/issues/87)

## [0.4.0] - 2022-05-11

### Added
* Added support for custom handling of settings updates.
* `Option` support added to enable run-time settings tree presence.

### Changed
* [breaking] MqttClient constructor now accepts initial settings values.
* Settings republish will no longer register as incoming configuration requests. See
[#71](https://github.com/quartiq/miniconf/issues/71)
* [breaking] `into_iter()` and `unchecked_into_iter()` renamed to `iter()` and `unchecked_iter()`
respectively to conform with standard conventions.

### Removed
* The client no longer resets the republish timeout when receiving messages.

## [0.3.0] - 2021-12-13

Expand All @@ -24,7 +83,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `miniconf::update()` replaced with `Miniconf::set()`, which is part of the trait and now
directly available on structures.


## [0.2.0] - 2021-10-28

### Added
Expand All @@ -38,7 +96,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Library initially released on crates.io

[Unreleased]: https://github.com/quartiq/miniconf/compare/v0.3.0...HEAD
[0.5.0]: https://github.com/quartiq/miniconf/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/quartiq/miniconf/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/quartiq/miniconf/releases/tag/v0.3.0
[0.2.0]: https://github.com/quartiq/miniconf/releases/tag/v0.2.0
[0.1.0]: https://github.com/quartiq/miniconf/releases/tag/v0.1.0
21 changes: 12 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
[package]
name = "miniconf"
version = "0.3.0"
authors = ["James Irwin <irwineffect@gmail.com>", "Ryan Summers <ryan.summers@vertigo-designs.com"]

# Don't forget to change `miniconf_derive`'s version as well.
version = "0.6.2"

authors = ["James Irwin <irwineffect@gmail.com>", "Ryan Summers <ryan.summers@vertigo-designs.com", "Robert Jördens <rj@quartiq.de>"]
edition = "2018"
license = "MIT"
description = "Lightweight support for run-time settings configuration"
description = "Inspect serde namespaces by path"
repository = "https://github.com/quartiq/miniconf"
keywords = ["settings", "embedded", "no_std", "configuration", "mqtt"]
categories = ["no-std", "config", "embedded", "parsing"]
keywords = ["settings", "serde", "no_std", "json", "mqtt"]
categories = ["no-std", "config", "rust-patterns", "parsing"]

[dependencies]
derive_miniconf = { path = "derive_miniconf" , version = "0.3" }
serde-json-core = "0.4.0"
miniconf_derive = { path = "miniconf_derive" , version = "0.6" }
serde-json-core = "0.5.0"
serde = { version = "1.0.120", features = ["derive"], default-features = false }
log = "0.4"
heapless = { version = "0.7", features = ["serde"] }
minimq = { version = "^0.5.1", optional = true }
smlang = { version = "0.4", optional = true }
minimq = { version = "^0.6.1", optional = true }
smlang = { version = "0.6", optional = true }

[features]
default = ["mqtt-client"]
Expand Down
169 changes: 129 additions & 40 deletions README.md
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].
19 changes: 0 additions & 19 deletions derive_miniconf/Cargo.toml

This file was deleted.

Loading

0 comments on commit 5ea5b82

Please sign in to comment.