mcx::container::Module
14 minute read
mcx::container::Module
Module: Container
A building block of the Motorcortex components. More…
#include <ct_module.h>
Inherited by mcx::comm::Publisher, mcx::drive::Diagnostics, mcx::drive::Module, mcx::drive::sim::DriveBasic< T >, mcx::ecat::Module, mcx::fbus::Dummy, mcx::log::Module, mcx::parameter_server::Persistence, mcx::user_parameters::Module, mcx::watchdog::Module
Public Types
Name | |
---|---|
enum class | ModuleStates { NOT_INITIALIZED = 0, PHASE_0 = 1, PHASE0 = 2, PHASE0_1 = 3, PHASE1 = 4, PHASE1_2 = 5, PHASE2 = 6, PHASE2_OP = 7, OP = 8, OP_PHASE2 = 9, PHASE2_NOT_INIT = 10} |
enum class | ModuleErrors { NO_ERROR = 0, NO_CALLBACK_DEFINED = 1, CALLBACK_FAILED = 2, WRONG_STATE = 3} |
enum class | ModuleEvents { EMPTY = -1, STOP = 0, START = 1, PAUSE = 2, ACK = 0xFF} |
Public Functions
Name | |
---|---|
Module() Default constructor. |
|
virtual | ~Module() =default Default destructor. |
Module(const Module & orig) =delete Copy constructors are deleted. |
|
Module & | operator=(const Module & ) =delete Copy constructors are deleted. |
void | create(const char * name, parameter_server::Parameter * parameter_server, uint64_t dt_micro_s =0) Executes ‘Create’ phase of initialization. |
void | create(const char * name, parameter_server::Parameter * parameter_server, uint64_t dt_micro_s, parameter_server::UserGroup owner_group, uint32_t permissions =parameter_server::default_permissions) Extended interface with access permissions. |
bool | initPhase1() Executes initialization of the parameter tree phase. |
bool | initPhase2() Initialization, which require parameter tree to be complete and loaded. |
bool | startOp() Final initialization before entering Operation mode. |
bool | stopOp() Stops Operation mode, switches back to PHASE2. |
bool | iterate(const TaskTime & task_time, UserTime * user_time) Iterates an execution cycle of the module. |
void | setName(const char * name) Sets the name of the module. |
const char * | getName() const Returns the name of the module. |
void | setDtMicroSec(uint64_t micro_sec) Sets a cycle time of module and its submodules in microseconds. |
uint64_t | getDtMicroSec() const Returns a cycle time as an integer in microseconds. |
void | setDtSec(double sec) Sets a cycle time of module and its submodules in seconds. |
double | getDtSec() const |
ModuleStates | getState() const Returns an actual state. |
void | setEvent(ModuleEvents event) Command an event. |
ModuleErrors | getError() const Returns active error code. |
Protected Functions
Name | |
---|---|
virtual void | create_(const char * name, parameter_server::Parameter * parameter_server, uint64_t dt_micro_s) =0 User-defined callback, which is executed during ‘Create’ phase. |
virtual bool | initPhase1_() =0 User-defined callback to register parameters in the tree. |
virtual bool | initPhase2_() =0 User-defined callback after the parameter tree is ready. |
virtual bool | startOp_() =0 User-defined callback before entering Operation mode. |
virtual bool | stopOp_() =0 User-defined callback before to stop Operation mode. |
virtual bool | iteratePreOp_(const TaskTime & system_time, UserTime * user_time) User-defined callback which is called during Phase 2. |
virtual bool | iterateOp_(const TaskTime & system_time, UserTime * user_time) =0 User-defined callback which is called during Operation mode. |
void | createSubmodule(Module * module, const char * name) Creates and registers submodule. |
template <typename T > void |
createSubmodules(utils::span< T > module_array, const char * basename) Creates and registers an array of submodules. |
void | setType(const char * name) Sets a type of the module. |
template <size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH> parameter_server::ParamHandle |
addParameter(const char * id, parameter_server::ParameterType param_type, char * str_ptr, size_t length) Helper function to register C-string member variable in the parameter tree. |
template <typename T ,size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH,add_visitable::EnableIfNotVisitable< T > =true> parameter_server::ParamHandle |
addParameter(const char * id, parameter_server::ParameterType param_type, T * value_ptr, size_t length =1, parameter_server::Unit param_unit =parameter_server::Unit::undefined) Helper function to register member variable of in the parameter tree. |
template <typename T ,size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH,add_visitable::EnableIfVisitable< T > =true> parameter_server::GroupHandle |
addParameter(const char * id, parameter_server::ParameterType param_type, T * value_ptr, size_t length =1, parameter_server::Unit param_unit =parameter_server::Unit::undefined) Helper function to register visitable PODs in the parameter tree. |
template <typename T ,size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH> parameter_server::ParamHandle |
addParameter(const char * id, parameter_server::ParameterType param_type, parameter_server::DataType data_type, T * value_ptr, size_t length =1, parameter_server::Unit param_unit =parameter_server::Unit::undefined) Helper function to register types, which cannot be deduced. |
parameter_server::SubHandle | subscribe(const char * path) Helper function to subscribe for the parameter’s update. |
parameter_server::ReqHandle | request(const char * path) Helper function to request a parameter’s value. |
parameter_server::PubHandle | publish(const char * path) Helper function to update parameter’s value. |
parameter_server::Parameter * | getParameterRoot() Returns the root of the parameter tree. |
parameter_server::Parameter * | getLocalBranch() Returns the root of the local branch. |
Friends
Name | |
---|---|
class | Task |
Detailed Description
class mcx::container::Module;
A building block of the Motorcortex components.
State machine of the Motorcortex module
Module: Task:
┌──────────────────────────────┐
│ Not Initialized │
└───┬──────────────────────────┘ ┌─────────────────────────────────────┐
│ (Event: create) │ Creates modules and submodules. │
┌───v───────┐ └───┬─────────────────────────────────┘
│ Phase0 │ │ (Event: configure)
└───┬───────┘ ┌───v─────────────────────────────────┐
│ (Event: initPhase1) │ Add parameters to the tree │
│ │ │
┌───v───────┐ └───┬─────────────────────────────────┘
│ Phase1 │ │ (Event: start)
└───┬───────┘ ┌───v─────────────────────────────────┐
│ (Event: initPhase2) │ Parameter tree is ready, │
│ │ │
┌───v──────────────────────────┐ └───┬─────────────────────────────────┘
│ Phase2 │ │
└───┬──────────────────────^───┘ ┌───v─────────────────────────────────┐
│ (Event: startOp) │ (Event: stopOp) │ Real-time event loop is │
│ │ │ ready to start. │
┌───v──────────────────────┴───┐ └─────────────────────────────────────┘
│ Operation │
└───┬──────────────────────────┘
│ (Event: exit)
┌───v───────┐
│ Destroyed │
└───────────┘```
## Public Types Documentation
### enum ModuleStates
| Enumerator | Value | Description |
| ---------- | ----- | ----------- |
| NOT_INITIALIZED | 0| |
| PHASE_0 | 1| |
| PHASE0 | 2| |
| PHASE0_1 | 3| |
| PHASE1 | 4| |
| PHASE1_2 | 5| |
| PHASE2 | 6| |
| PHASE2_OP | 7| |
| OP | 8| |
| OP_PHASE2 | 9| |
| PHASE2_NOT_INIT | 10| |
[Module](/docs/developing-control-applications/api/classes/classmcx_1_1container_1_1module/) States are available via parameter: 'module_state' or a member function call [getState()](/docs/developing-control-applications/api/classes/classmcx_1_1container_1_1module/#function-getstate).
### enum ModuleErrors
| Enumerator | Value | Description |
| ---------- | ----- | ----------- |
| NO_ERROR | 0| |
| NO_CALLBACK_DEFINED | 1| |
| CALLBACK_FAILED | 2| |
| WRONG_STATE | 3| |
[Module](/docs/developing-control-applications/api/classes/classmcx_1_1container_1_1module/) Errors are available via parameter: 'module_error' or a member function call getErrors().
### enum ModuleEvents
| Enumerator | Value | Description |
| ---------- | ----- | ----------- |
| EMPTY | -1| |
| STOP | 0| |
| START | 1| |
| PAUSE | 2| |
| ACK | 0xFF| |
[Module](/docs/developing-control-applications/api/classes/classmcx_1_1container_1_1module/) Events can be set via parameter: 'module_event' or a member function call [setEvent()](/docs/developing-control-applications/api/classes/classmcx_1_1container_1_1module/#function-setevent).
## Public Functions Documentation
### function Module
```cpp
Module()
Default constructor.
function ~Module
virtual ~Module() =default
Default destructor.
Reimplemented by: mcx::drive::Module::~Module, mcx::log::Module::~Module, mcx::watchdog::Module::~Module, mcx::user_parameters::Module::~Module
function Module
Module(
const Module & orig
) =delete
Copy constructors are deleted.
function operator=
Module & operator=(
const Module &
) =delete
Copy constructors are deleted.
function create
void create(
const char * name,
parameter_server::Parameter * parameter_server,
uint64_t dt_micro_s =0
)
Executes ‘Create’ phase of initialization.
Parameters:
- name - unique name.
- parameter_server - pointer to the root.
- dt_micro_s - cycle time in microseconds.
See: Module::create_
Par: Example Usage:
control::MainControlLoop main_loop;
main_loop.create("Control", ¶m_server, 1000);
Before any operations on the module can be performed, it must be created, assigned a unique name, set its location in the parameter tree and set its cycle time.
function create
void create(
const char * name,
parameter_server::Parameter * parameter_server,
uint64_t dt_micro_s,
parameter_server::UserGroup owner_group,
uint32_t permissions =parameter_server::default_permissions
)
Extended interface with access permissions.
Parameters:
- name - unique name.
- parameter_server - pointer to the root.
- dt_micro_s - cycle time in microseconds.
- owner_group - owner group of the module’s branch.
- permissions - POSIX-like permissions for the parameter access.
See:
- Module::create_
- parameter_server::Permission
Par: Example Usage:
control::MainControlLoop main_loop;
main_loop.create("Control", ¶m_server, 1000, UserGroup::OPERATOR, 770);
Extended interface in addition to the previous one, which can set User Group and Permissions of the module branch.
function initPhase1
bool initPhase1()
Executes initialization of the parameter tree phase.
See: Module::initPhase1_
Return: true on success.
The next step after the Create. During initPhase1 parameters are added to the parameter tree. Parameter values are loaded from the configuration file
function initPhase2
bool initPhase2()
Initialization, which require parameter tree to be complete and loaded.
See: Module::initPhase2_
Return: true on success.
After the tree is built and values are loaded, additional initialization might be required. For example preparing the state machine, where state timeouts are the values from the parameter tree.
function startOp
bool startOp()
Final initialization before entering Operation mode.
See: Module::startOp_
Return: true on success.
Just before entering Operation mode startOp is called. Module could be started and stopped multiple times. startOp could be used to reset counter, state variable etc.
function stopOp
bool stopOp()
Stops Operation mode, switches back to PHASE2.
See: Module::stopOp_
Return: true on success.
User may decide to stop the module by calling stopOp. iterateOp_ is not called anymore, but input requests are still processed. To restart the module startOp should be called.
function iterate
bool iterate(
const TaskTime & task_time,
UserTime * user_time
)
Iterates an execution cycle of the module.
Parameters:
- task_time - various input timers, application and system time.
- user_time - user defined timer, an external clock source.
See: Module::iterateOp_
Return: true on success.
function setName
void setName(
const char * name
)
Sets the name of the module.
Parameters:
- name - name of the module.
function getName
const char * getName() const
Returns the name of the module.
Return: name of the module.
function setDtMicroSec
inline void setDtMicroSec(
uint64_t micro_sec
)
Sets a cycle time of module and its submodules in microseconds.
Parameters:
- micro_sec - new cycle time of the module.
function getDtMicroSec
inline uint64_t getDtMicroSec() const
Returns a cycle time as an integer in microseconds.
Return: cycle time in microseconds
function setDtSec
inline void setDtSec(
double sec
)
Sets a cycle time of module and its submodules in seconds.
Parameters:
- sec - new cycle time of the module.
function getDtSec
inline double getDtSec() const
Return: cycle time in seconds.
Returns a cycle time as a float in seconds.
function getState
ModuleStates getState() const
Returns an actual state.
See: ModuleStates
Return: actual state.
function setEvent
void setEvent(
ModuleEvents event
)
Command an event.
See: ModuleEvents
Requests a state machine to execute an event.
function getError
ModuleErrors getError() const
Returns active error code.
See: ModuleErrors
Return: active error code.
Protected Functions Documentation
function create_
virtual void create_(
const char * name,
parameter_server::Parameter * parameter_server,
uint64_t dt_micro_s
) =0
User-defined callback, which is executed during ‘Create’ phase.
Parameters:
- name - unique name.
- parameter_server - pointer to the root.
- dt_micro_s - cycle time in microseconds.
See: Module::create
Reimplemented by: mcx::drive::sim::DriveBasic::create_, mcx::fbus::Dummy::create_, mcx::log::Module::create_, mcx::user_parameters::Module::create_, mcx::comm::Publisher::create_, mcx::drive::Diagnostics::create_, mcx::drive::Module::create_, mcx::ecat::Module::create_, mcx::ecat::ModuleExtSim::create_, mcx::parameter_server::Persistence::create_, mcx::watchdog::Module::create_
function initPhase1_
virtual bool initPhase1_() =0
User-defined callback to register parameters in the tree.
See: Module::initPhase1
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::comm::Publisher::initPhase1_, mcx::drive::Diagnostics::initPhase1_, mcx::drive::Module::initPhase1_, mcx::drive::sim::DriveBasic::initPhase1_, mcx::ecat::Module::initPhase1_, mcx::ecat::ModuleExtSim::initPhase1_, mcx::fbus::Dummy::initPhase1_, mcx::log::Module::initPhase1_, mcx::parameter_server::Persistence::initPhase1_, mcx::user_parameters::Module::initPhase1_, mcx::watchdog::Module::initPhase1_
function initPhase2_
virtual bool initPhase2_() =0
User-defined callback after the parameter tree is ready.
See: Module::initPhase2
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::comm::Publisher::initPhase2_, mcx::drive::Diagnostics::initPhase2_, mcx::drive::Module::initPhase2_, mcx::drive::sim::DriveBasic::initPhase2_, mcx::ecat::Module::initPhase2_, mcx::ecat::ModuleExtSim::initPhase2_, mcx::fbus::Dummy::initPhase2_, mcx::log::Module::initPhase2_, mcx::parameter_server::Persistence::initPhase2_, mcx::user_parameters::Module::initPhase2_, mcx::watchdog::Module::initPhase2_
This callback could be used for additional initialization. During initPhase2_ tree structure is complete, initial values are NOT loaded.
function startOp_
virtual bool startOp_() =0
User-defined callback before entering Operation mode.
See: Module::startOp
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::comm::Publisher::startOp_, mcx::drive::Diagnostics::startOp_, mcx::drive::Module::startOp_, mcx::drive::sim::DriveBasic::startOp_, mcx::ecat::Module::startOp_, mcx::ecat::ModuleExtSim::startOp_, mcx::fbus::Dummy::startOp_, mcx::log::Module::startOp_, mcx::parameter_server::Persistence::startOp_, mcx::user_parameters::Module::startOp_, mcx::watchdog::Module::startOp_
During startOp_ callback user can safely access initialized parameter tree. startOp_ operation is part of the first iterateOp_ cycle, that is why it should be relatively short. Otherwise fist cycle time will be violated.
function stopOp_
virtual bool stopOp_() =0
User-defined callback before to stop Operation mode.
See: Module::stopOp
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::comm::Publisher::stopOp_, mcx::drive::Diagnostics::stopOp_, mcx::drive::Module::stopOp_, mcx::drive::sim::DriveBasic::stopOp_, mcx::ecat::Module::stopOp_, mcx::ecat::ModuleExtSim::stopOp_, mcx::fbus::Dummy::stopOp_, mcx::log::Module::stopOp_, mcx::parameter_server::Persistence::stopOp_, mcx::user_parameters::Module::stopOp_, mcx::watchdog::Module::stopOp_
function iteratePreOp_
inline virtual bool iteratePreOp_(
const TaskTime & system_time,
UserTime * user_time
)
User-defined callback which is called during Phase 2.
See: Module::iterate
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::log::Module::iteratePreOp_
Optional callback, which is useful for the modules which needs to start iterating before the system switches to OP. For example a Logger module start to print output before OP. Real-time behaviour is this callback is not guaranteed.
function iterateOp_
virtual bool iterateOp_(
const TaskTime & system_time,
UserTime * user_time
) =0
User-defined callback which is called during Operation mode.
See: Module::iterate
Return: user callback should return true on success, otherwise the state machine won’t progress.
Reimplemented by: mcx::comm::Publisher::iterateOp_, mcx::drive::Diagnostics::iterateOp_, mcx::drive::Module::iterateOp_, mcx::ecat::Module::iterateOp_, mcx::ecat::ModuleExtSim::iterateOp_, mcx::parameter_server::Persistence::iterateOp_, mcx::watchdog::Module::iterateOp_, mcx::drive::sim::DriveBasic::iterateOp_, mcx::fbus::Dummy::iterateOp_, mcx::log::Module::iterateOp_, mcx::user_parameters::Module::iterateOp_
function createSubmodule
void createSubmodule(
Module * module,
const char * name
)
Creates and registers submodule.
Parameters:
- module - pointer to the module instance.
- name - name of the module in the parameter tree.
Par: Example Usage:
IIRFilter irr_filter;
createSubmodule(&irr_filter, "MyIIRFilter1");
This is a helper function, which creates a submodule and registers it in the branch as a child node. If submodule is created via createSubmodule, its state machine during initialization and deinitialization will be managed automatically. However the user is responsible to call submodule’s Module::iterate() and trigger its event cycle.
function createSubmodules
template <typename T >
inline void createSubmodules(
utils::span< T > module_array,
const char * basename
)
Creates and registers an array of submodules.
Parameters:
- module_array - iterable object with the array of modules.
- basename - base name of the module in the parameter tree, index is added automatically
Par: Example Usage:
std::array<IIRFilter, 3> irr_filters;
createSubmodule<IIRFilter>(irr_filter, "MyIIRFilter");
This is a helper function, which creates an array of submodule and registers them in the branch as a child nodes. For more information check createSubmodule.
function setType
void setType(
const char * name
)
Sets a type of the module.
Parameters:
- name - type name.
This is a helper function to set the type of the module, which can be used by the client to get additional information. If the type is not set, it will be generated automatically in the following form: Namespace::ClassName
function addParameter
template <size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH>
parameter_server::ParamHandle addParameter(
const char * id,
parameter_server::ParameterType param_type,
char * str_ptr,
size_t length
)
Helper function to register C-string member variable in the parameter tree.
Parameters:
- id - name of the module.
- param_type - IO type of the parameter.
- str_ptr - pointer to a C-string.
- length - maximum length of the C-string.
Template Parameters:
- buffer_size - size of the input buffer (default: DEFAULT_INPUT_BUFFER_LENGTH).
Return: handle to the created parameter.
Par: Example Usage:
char* log[255] = "Hello world!";
...
log_handle = addParameter("logOut", ParamHandle::OUTPUT, log, 255);
function addParameter
template <typename T ,
size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH,
add_visitable::EnableIfNotVisitable< T > =true>
parameter_server::ParamHandle addParameter(
const char * id,
parameter_server::ParameterType param_type,
T * value_ptr,
size_t length =1,
parameter_server::Unit param_unit =parameter_server::Unit::undefined
)
Helper function to register member variable of in the parameter tree.
Parameters:
- id - name of the module.
- param_type - IO type of the parameter.
- value_ptr - pointer to a member variable.
- length - number of elements in case of the array.
- param_unit - measurement unit.
Template Parameters:
- T - a data type of the registered variable.
- buffer_size - size of the input buffer. (default: DEFAULT_INPUT_BUFFER_LENGTH).
Return: handle to the created parameter.
Par: Example Usage:
double input1_ = 1.34;
input1_handle_ = addParameter("input1_", ParamHandle::INPUT, &input1_);
...
double input2_[2] = {2.3, 4.5};
input2_handle_ = addParameter("input2_", ParamHandle::INPUT, input2_, 2);
A Helper function, which automatically deduce standard numerical types.
function addParameter
template <typename T ,
size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH,
add_visitable::EnableIfVisitable< T > =true>
parameter_server::GroupHandle addParameter(
const char * id,
parameter_server::ParameterType param_type,
T * value_ptr,
size_t length =1,
parameter_server::Unit param_unit =parameter_server::Unit::undefined
)
Helper function to register visitable PODs in the parameter tree.
Parameters:
- id - name of the module.
- param_type - IO type of the parameter.
- value_ptr - pointer to a member variable.
- length - number of elements in case of the array (Only length = 1 is supported).
- param_unit - measurement unit.
Template Parameters:
- T - a data type of the registered variable.
- buffer_size - size of the input buffer. (default: DEFAULT_INPUT_BUFFER_LENGTH).
Return: handle to the created parameter.
Par: Example Usage:
struct foo {
BEGIN_VISITABLES(foo);
VISITABLE(bool, b);
VISITABLE(int, i);
VISITABLE(double, d);
END_VISITABLES;
};
foo input1_{};
input1_handle_ = addParameter("input1_", ParamHandle::INPUT, &input1_);
A Helper function, which automatically deduce visitable PODs.
function addParameter
template <typename T ,
size_t buffer_size =parameter_server::DEFAULT_INPUT_BUFFER_LENGTH>
parameter_server::ParamHandle addParameter(
const char * id,
parameter_server::ParameterType param_type,
parameter_server::DataType data_type,
T * value_ptr,
size_t length =1,
parameter_server::Unit param_unit =parameter_server::Unit::undefined
)
Helper function to register types, which cannot be deduced.
Parameters:
- id - name of the module.
- param_type - IO type of the parameter.
- data_type - a data type from the parameter_server::DataType
- value_ptr - pointer to a member variable.
- length - number of elements in case of the array.
- param_unit - measurement unit.
Template Parameters:
- T - a data type of the registered variable.
- buffer_size - size of the input buffer. (default: DEFAULT_INPUT_BUFFER_LENGTH).
Return: handle to the created parameter.
Par: Example Usage:
struct {
const char* text{"All work and no play makes Jack a dull robot"};
double numbers[2]{4,5};
...
} blob;
input1_handle_ = addParameter("internal_data", ParamHandle::OUTPUT,
DataType::USER_TYPE, &blob);
input1_handle_.setOwner(UserGroup::SYSTEM, 770); // Available for the
// internal use only.
function subscribe
parameter_server::SubHandle subscribe(
const char * path
)
Helper function to subscribe for the parameter’s update.
See: parameter_server::SubHandle
Return: a subscription handle.
Note: A better approach is to use Module::addParameter inside the modules to create inputs and outputs, which are linked by Parameter::link outside the modules in the application specific configuration code.
function request
parameter_server::ReqHandle request(
const char * path
)
Helper function to request a parameter’s value.
See: parameter_server::ReqHandle
Return: a request handle.
Note: A better approach is to use Module::addParameter inside the modules to create inputs and outputs, which are linked by Parameter::link outside the modules in the application specific configuration code.
function publish
parameter_server::PubHandle publish(
const char * path
)
Helper function to update parameter’s value.
See: parameter_server::PubHandle
Return: a request handle.
Note: A better approach is to use Module::addParameter inside the modules to create inputs and outputs, which are linked by Parameter::link outside the modules in the application specific configuration code.
function getParameterRoot
inline parameter_server::Parameter * getParameterRoot()
Returns the root of the parameter tree.
Return: a pointer to the root of the parameter tree.
function getLocalBranch
inline parameter_server::Parameter * getLocalBranch()
Returns the root of the local branch.
Return: a pointer to the parameter, which represents the module.
Friends
friend Task
friend class Task(
Task
);
Updated on 2022-04-05 at 16:21:27 +0200