components

Store definition

// SPDX-FileCopyrightText: 2020-2023 Jochem Rutgers
//
// SPDX-License-Identifier: MIT

{
	float=3.721 G (m/s^2)
	float=43.34e-3 air molar mass (kg/mol)
	float=210 temperature (K)
	float=600 surface air pressure (Pa)
} environment

{
	float=0.85 motor constant
	float=1.8 mass (kg)
	float=0.4 lift coefficient
	float=1e-4 drag coefficient

	float=0 height (m)
	float=0 speed (m/s)
} helicopter

// All parameters for stored::PID.
// Most fields are optional. If left out, the PID instance will be tuned
// accordingly. The names are predefined, but may add a suffix, like a unit.
{
	// Control frequency
	(float) frequency (Hz)

	// Inputs
	float y
	float setpoint
	bool=true enable

	// Coefficients
	float=0.0005 Kp
	float=60 Ti (s)
	float=30 Td (s)
	float=0 Kff

	// Ti integrator
	float=0.7 int
	float=-inf int low
	float=inf int high

	// Bounds on the output
	float=0 low
	float=1 high
	float=0.1 epsilon

	// Misc control
	bool=true reset
	float=nan override

	// Output
	float u
} pid

Application

// SPDX-FileCopyrightText: 2020-2023 Jochem Rutgers
//
// SPDX-License-Identifier: MIT

/*!
 * \file
 * \brief libstored's components example.
 *
 * When building some control application, e.g., one that drives a motor, you
 * would like to have access to all hardware pins, all ADC conversion
 * parameters, all controllers, etc. A common design pattern is to add these
 * parameters to a store, and instantiate the corresponding components in C++.
 * Then, you can access, override, and tune the application via the store.
 *
 * libstored provides several of such components, such as GPIO pins, Amplifier,
 * and PID controller. This example shows how to instantiate and use such a
 * component class and how it is coupled to your store. You will need at least
 * C++14 for this.
 *
 * And don't crash the helicopter...
 */

#include "ExampleComponents.h"

#include <stored>

#include <chrono>
#include <iostream>

class ExampleComponentsStore
	: public STORE_T(ExampleComponentsStore, stored::ExampleComponentsBase) {
	STORE_CLASS(ExampleComponentsStore, stored::ExampleComponentsBase)
public:
	ExampleComponentsStore() = default;

	// You can change the control frequency dynamically.
	void __pid__frequency_Hz(bool set, float& value)
	{
		if(set) {
			if(std::isnan(value) || value <= 0.1f)
				value = 0.1f;

			m_frequency_Hz = value;
		} else {
			value = m_frequency_Hz;
		}
	}

private:
	float m_frequency_Hz{5.0};
};

static ExampleComponentsStore store;

static float fly(float power)
{
	// Greatly simplified model of a helicopter. The power lets the blades
	// spin. If you have enough lift, you can take off.

	float dt = 1.0f / store.pid__frequency_Hz();
	float G = store.environment__G_m__s_2;
	float air_pressure = store.environment__surface_air_pressure_Pa;
	float air_molar_mass = store.environment__air_molar_mass_kg__mol;
	float temperature = store.environment__temperature_K;

	float height = store.helicopter__height_m;
	float speed = store.helicopter__speed_m__s;

	power = std::max(0.f, std::min(1.f, power));
	if(std::isnan(power))
		power = 0;

	float air_density =
		air_pressure
		* std::exp(-(G * height * air_molar_mass) / (temperature * 8.314462618f))
		/ (air_molar_mass * temperature);

	float lift = .5f * air_density * std::pow(power * store.helicopter__motor_constant, 2.0f)
		     * store.helicopter__lift_coefficient;
	float drag = .5f * air_density * std::pow(speed, 2.0f) * store.helicopter__drag_coefficient;
	float weight = store.helicopter__mass_kg * G;

	float F = lift - weight;
	if(speed > 0)
		F -= drag;
	else
		F += drag;

	float accelleration = F / store.helicopter__mass_kg;

	speed += accelleration * dt;
	height += speed * dt;

	if(height < 0) {
		if(speed < -1)
			std::cout << " ... Crash ... " << std::endl;
		height = 0;
		speed = 0;
	}

	store.helicopter__speed_m__s = speed;
	store.helicopter__height_m = height;

	std::cout << "power throttle: " << power << "  "
		  << "height: " << height << " m  "
		  << "speed: " << speed << " m/s  "
		  << "lift: " << lift << " N  "
		  << "drag: " << drag << " N  "
		  << "F: " << F << " N  "
		  << "acc: " << accelleration << " m/s^2  "
		  << "air density: " << air_density << " kg/m^3" << std::endl;
	return height;
}

int main()
{
	std::cout << "Helicopter flight simulator" << std::endl << std::endl;
	std::cout << "Try to fly this helicopter, using a poorly-tuned (PID) controller."
		  << std::endl;
	std::cout << "Connect via ZMQ and set /pid/setpoint to the desired height." << std::endl;
	std::cout << "For example, set it to 1000, and see the heli take off." << std::endl;
	std::cout << "Tune all parameters at will and see what happens." << std::endl << std::endl;

	// This is the PID controller.
	// This line finds all variables within the /pid/ scope, that are to be
	// used by the PID instance. All lookup is done at compile-time. pid_o
	// holds a set of flags that can be used to leave out unused (optional)
	// parameters.

	constexpr auto pid_o = stored::PID<ExampleComponentsStore>::objects("/pid/");
	// Now, instantiate the PID, tailored to the variables in your store.  The
	// pid_o is also passed to the constructor to prove the addresses of the
	// variables in the store, as found by (constexpr) find() in the store's
	// directory.
	stored::PID<ExampleComponentsStore, pid_o.flags()> pid{pid_o, store};

	// Construct the protocol stack.
	stored::Debugger debugger{"components"};
	debugger.map(store);

	stored::DebugZmqLayer zmqLayer;
	if(zmqLayer.lastError()) {
		perror("Cannot initialize ZMQ layer");
		exit(1);
	}
	zmqLayer.wrap(debugger);

	stored::Poller poller;
	stored::PollableZmqLayer pollableZmq(zmqLayer, stored::Pollable::PollIn);

	if((errno = poller.add(pollableZmq))) {
		perror("Cannot add to poller");
		exit(1);
	}

	// Determine polling/control interval.
	auto dt = std::chrono::milliseconds((long long)(1.0e3f / store.pid__frequency_Hz()));
	auto t = std::chrono::system_clock::now() + dt;

	while(true) {
		auto now = std::chrono::system_clock::now();
		auto rem = std::chrono::duration_cast<std::chrono::microseconds>(t - now);
		auto rem_us = rem.count();

		if(rem_us <= 0) {
			t += std::chrono::milliseconds(
				(long long)(1.0e3f / store.pid__frequency_Hz()));
			// This is where the magic takes place.
			store.pid__y = fly(pid());
			if(!pid.isHealthy())
				std::cout << "Warning: numerically unstable" << std::endl;
			continue;
		}

		if(poller.poll((int)(rem_us / 1000L)).empty()) {
			switch(errno) {
			case EINTR:
			case EAGAIN:
				break;
			default:
				perror("Cannot poll");
				exit(1);
			} // else timeout
		} else if((errno = zmqLayer.recv())) {
			perror("Cannot recv");
			exit(1);
		}
	}
}

Store reference

template<typename Base_, typename Implementation_>
class ExampleComponentsObjects

All ExampleComponentsBase’s objects.

Subclassed by stored::ExampleComponentsBase< ExampleComponentsStore >

Public Types

typedef Base_ Base
typedef Implementation_ Implementation

Public Members

impl::StoreVariable<Base, Implementation, float, 4u, 4> environment__air_molar_mass_kg__mol

environment/air molar mass (kg/mol)

impl::StoreVariable<Base, Implementation, float, 0u, 4> environment__G_m__s_2

environment/G (m/s^2)

impl::StoreVariable<Base, Implementation, float, 12u, 4> environment__surface_air_pressure_Pa

environment/surface air pressure (Pa)

impl::StoreVariable<Base, Implementation, float, 8u, 4> environment__temperature_K

environment/temperature (K)

impl::StoreVariable<Base, Implementation, float, 28u, 4> helicopter__drag_coefficient

helicopter/drag coefficient

impl::StoreVariable<Base, Implementation, float, 72u, 4> helicopter__height_m

helicopter/height (m)

impl::StoreVariable<Base, Implementation, float, 24u, 4> helicopter__lift_coefficient

helicopter/lift coefficient

impl::StoreVariable<Base, Implementation, float, 20u, 4> helicopter__mass_kg

helicopter/mass (kg)

impl::StoreVariable<Base, Implementation, float, 16u, 4> helicopter__motor_constant

helicopter/motor constant

impl::StoreVariable<Base, Implementation, float, 76u, 4> helicopter__speed_m__s

helicopter/speed (m/s)

impl::StoreVariable<Base, Implementation, bool, 68u, 1> pid__enable

pid/enable

impl::StoreVariable<Base, Implementation, float, 60u, 4> pid__epsilon

pid/epsilon

impl::StoreFunction<Base, Implementation, ExampleComponentsFunctionMap, 1u> pid__frequency_Hz

pid/frequency (Hz)

impl::StoreVariable<Base, Implementation, float, 56u, 4> pid__high

pid/high

impl::StoreVariable<Base, Implementation, float, 44u, 4> pid__int

pid/int

impl::StoreVariable<Base, Implementation, float, 52u, 4> pid__int_high

pid/int high

impl::StoreVariable<Base, Implementation, float, 48u, 4> pid__int_low

pid/int low

impl::StoreVariable<Base, Implementation, float, 88u, 4> pid__Kff

pid/Kff

impl::StoreVariable<Base, Implementation, float, 32u, 4> pid__Kp

pid/Kp

impl::StoreVariable<Base, Implementation, float, 92u, 4> pid__low

pid/low

impl::StoreVariable<Base, Implementation, float, 64u, 4> pid__override

pid/override

impl::StoreVariable<Base, Implementation, bool, 69u, 1> pid__reset

pid/reset

impl::StoreVariable<Base, Implementation, float, 84u, 4> pid__setpoint

pid/setpoint

impl::StoreVariable<Base, Implementation, float, 40u, 4> pid__Td_s

pid/Td (s)

impl::StoreVariable<Base, Implementation, float, 36u, 4> pid__Ti_s

pid/Ti (s)

impl::StoreVariable<Base, Implementation, float, 96u, 4> pid__u

pid/u

impl::StoreVariable<Base, Implementation, float, 80u, 4> pid__y

pid/y

template<typename Implementation_>
class ExampleComponentsBase : public stored::ExampleComponentsObjects<ExampleComponentsBase<Implementation_>, Implementation_>

Base class with default interface of all ExampleComponents implementations.

Although there are no virtual functions in the base class, subclasses can override them. The (lowest) subclass must pass the Implementation_ template paramater to its base, such that all calls from the base class can be directed to the proper overridden implementation.

The base class cannot be instantiated. If a default implementation is required, which does not have side effects to functions, instantiate stored::ExampleComponents. This class contains all data of all variables, so it can be large. So, be aware when instantiating it on the stack. Heap is fine. Static allocations is fine too, as the constructor and destructor are trivial.

To inherit the base class, you can use the following template:

class ExampleComponents : public stored::store<ExampleComponents, ExampleComponentsBase>::type {
    STORE_CLASS(ExampleComponents, ExampleComponentsBase)
public:
    // Your class implementation, such as:
    ExampleComponents() is_default
    // ...
};

Some compilers or tools may get confused by the inheritance using stored::store or stored::store_t. Alternatively, use STORE_T(...) instead, providing the template parameters of stored::store as macro arguments.

See also

stored::ExampleComponents

See also

stored::ExampleComponentsData

Subclassed by stored::ExampleComponentsDefaultFunctions< ExampleComponentsBase< ExampleComponents > >

Public Types

enum [anonymous]

Values:

enumerator ObjectCount

Number of objects in the store.

enumerator VariableCount

Number of variables in the store.

enumerator FunctionCount

Number of functions in the store.

enumerator BufferSize

Buffer size.

typedef Implementation_ Implementation

Type of the actual implementation, which is the (lowest) subclass.

typedef uintptr_t Key

Type of a key.

See also

bufferToKey()

typedef Map<String::type, Variant<Implementation>>::type ObjectMap

Map as generated by map().

typedef ExampleComponentsObjects<ExampleComponentsBase, Implementation_> Objects
typedef ExampleComponentsBase root

We are the root, as used by STORE_CLASS.

typedef ExampleComponentsBase self

Define self for stored::store.

Public Functions

inline ~ExampleComponentsBase()
void __pid__frequency_Hz(bool set, float &value)

Callback for pid/frequency (Hz)

inline Key bufferToKey(void const *buffer) const noexcept

Converts a variable’s buffer to a key.

A key is unique for all variables of the same store, but identical for the same variables across different instances of the same store class. Therefore, the key can be used to synchronize between instances of the same store. A key does not contain meta data, such as type or length. It is up to the synchronization library to make sure that these properties are handled well.

For synchronization, when hookEntryX() or hookEntryRO() is invoked, one can compute the key of the object that is accessed. The key can be used, for example, in a key-to-Variant map. When data arrives from another party, the key can be used to find the proper Variant in the map.

This way, data exchange is type-safe, as the Variant can check if the data format matches the expected type. However, one cannot process data if the key is not set yet in the map.

inline Type::type bufferToType(void const *buffer) noexcept

Return the type of the variable, given its buffer.

inline Variant<Implementation> find(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept

Finds an object with the given name.

Returns:

the object, or an invalid stored::Variant if not found.

template<typename T>
inline Function<T, Implementation> function(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept

Finds a function with the given name.

The function, when it exists, must have the given (fixed) type.

inline Implementation const &implementation() const noexcept

Returns the reference to the implementation.

inline Implementation &implementation() noexcept

Returns the reference to the implementation.

template<typename F>
inline void list(F &&f) noexcept

Calls a callback for every object in the longDirectory().

See also

stored::list()

template<typename F>
inline void list(F f, void *arg, char const *prefix, String::type *nameBuffer) noexcept

Calls a callback for every object in the longDirectory().

See also

stored::list()

template<typename F>
inline void list(F f, void *arg, char const *prefix = nullptr) noexcept

Calls a callback for every object in the longDirectory().

See also

stored::list()

inline uint8_t const *longDirectory() const noexcept

Retuns the long directory.

When not available, the short directory is returned.

inline ObjectMap map(char const *prefix = nullptr)

Create a name to Variant map for the store.

Generating the map may be expensive and the result is not cached.

inline char const *name() const noexcept

Returns the name of store, which can be used as prefix for stored::Debugger.

inline uint8_t const *shortDirectory() const noexcept

Returns the short directory.

template<typename T>
inline Variable<T, Implementation> variable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept

Finds a variable with the given name.

The variable, when it exists, must have the given (fixed) type.

Public Members

impl::StoreVariable<Base, Implementation, float, 4u, 4> environment__air_molar_mass_kg__mol

environment/air molar mass (kg/mol)

impl::StoreVariable<Base, Implementation, float, 0u, 4> environment__G_m__s_2

environment/G (m/s^2)

impl::StoreVariable<Base, Implementation, float, 12u, 4> environment__surface_air_pressure_Pa

environment/surface air pressure (Pa)

impl::StoreVariable<Base, Implementation, float, 8u, 4> environment__temperature_K

environment/temperature (K)

impl::StoreVariable<Base, Implementation, float, 28u, 4> helicopter__drag_coefficient

helicopter/drag coefficient

impl::StoreVariable<Base, Implementation, float, 72u, 4> helicopter__height_m

helicopter/height (m)

impl::StoreVariable<Base, Implementation, float, 24u, 4> helicopter__lift_coefficient

helicopter/lift coefficient

impl::StoreVariable<Base, Implementation, float, 20u, 4> helicopter__mass_kg

helicopter/mass (kg)

impl::StoreVariable<Base, Implementation, float, 16u, 4> helicopter__motor_constant

helicopter/motor constant

impl::StoreVariable<Base, Implementation, float, 76u, 4> helicopter__speed_m__s

helicopter/speed (m/s)

impl::StoreVariable<Base, Implementation, bool, 68u, 1> pid__enable

pid/enable

impl::StoreVariable<Base, Implementation, float, 60u, 4> pid__epsilon

pid/epsilon

impl::StoreFunction<Base, Implementation, ExampleComponentsFunctionMap, 1u> pid__frequency_Hz

pid/frequency (Hz)

impl::StoreVariable<Base, Implementation, float, 56u, 4> pid__high

pid/high

impl::StoreVariable<Base, Implementation, float, 44u, 4> pid__int

pid/int

impl::StoreVariable<Base, Implementation, float, 52u, 4> pid__int_high

pid/int high

impl::StoreVariable<Base, Implementation, float, 48u, 4> pid__int_low

pid/int low

impl::StoreVariable<Base, Implementation, float, 88u, 4> pid__Kff

pid/Kff

impl::StoreVariable<Base, Implementation, float, 32u, 4> pid__Kp

pid/Kp

impl::StoreVariable<Base, Implementation, float, 92u, 4> pid__low

pid/low

impl::StoreVariable<Base, Implementation, float, 64u, 4> pid__override

pid/override

impl::StoreVariable<Base, Implementation, bool, 69u, 1> pid__reset

pid/reset

impl::StoreVariable<Base, Implementation, float, 84u, 4> pid__setpoint

pid/setpoint

impl::StoreVariable<Base, Implementation, float, 40u, 4> pid__Td_s

pid/Td (s)

impl::StoreVariable<Base, Implementation, float, 36u, 4> pid__Ti_s

pid/Ti (s)

impl::StoreVariable<Base, Implementation, float, 96u, 4> pid__u

pid/u

impl::StoreVariable<Base, Implementation, float, 80u, 4> pid__y

pid/y

Public Static Functions

template<typename T>
static inline constexpr FreeFunction<T, Implementation> freeFunction(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept

Finds a function with the given name.

The function, when it exists, must have the given (fixed) type. It is returned as a free function; it is not bound yet to a specific store instance. This function is constexpr for C++14.

template<typename T>
static inline constexpr FreeVariable<T, Implementation> freeVariable(char const *name, size_t len = std::numeric_limits<size_t>::max()) noexcept

Finds a variable with the given name.

The variable, when it exists, must have the given (fixed) type. It is returned as a free variable; it is not bound yet to a specific store instance. This function is constexpr for C++14.

static inline constexpr char const *hash() noexcept

Returns a unique hash of the store.

Friends

friend class impl::StoreFunction
friend class impl::StoreVariable
friend class impl::StoreVariantF
friend class impl::StoreVariantV
friend class stored::FreeVariable
friend class stored::Variant< void >
class ExampleComponentsStore : public stored::ExampleComponentsBase<ExampleComponentsStore>

Public Functions

ExampleComponentsStore() = default
inline void __pid__frequency_Hz(bool set, float &value)