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 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
-
impl::StoreVariable<Base, Implementation, float, 4u, 4> environment__air_molar_mass_kg__mol
-
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
orstored::store_t
. Alternatively, useSTORE_T(...)
instead, providing the template parameters ofstored::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.
-
enumerator ObjectCount
-
typedef Implementation_ Implementation
Type of the actual implementation, which is the (lowest) subclass.
-
typedef uintptr_t Key
Type of a key.
See also
-
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
forstored::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
-
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
-
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
-
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 >
-
enum [anonymous]
-
class ExampleComponentsStore : public stored::ExampleComponentsBase<ExampleComponentsStore>