Tools to use a thread like a finite-state automaton-like program that PLC and automation programmers like.
The class has a basic weakeup period that is set as a constructor argument, in milliseconds. Then, it can be filled with objects whose timing method is activated periodically (the period is set when adding, the parent class' timer must be its divisor). A modification that implements typical functionality of a finite state automaton is available. When all is set up, the resume() method is called to activate it all. Each wakeup gets the same input and time for all automatons, that are fired in the sequence they were added in.
Input and output are be accessed in a synchronised way, either between wakeups or delayed until the wakeup finishes. The returned value is a smart pointer that keeps the wakeup from occurring until destroyed. You may want to copy it.
Most classes expect two template arguments, one for the input structure given to the state machines, one for the output structures given to the state machines. Class StateMachine
that comfortably implements state machines accepts the type describing its state (meant to be an enum) as the third template argument.
A basic object that can be inserted into the system, it only has the basic interface providing time, input and output. Overload its tick(const Input&, Output&)
method for the loop, it doesn't need to be initalised. Use StateMachine
for more features.
It provides a makeTimer()
method that returns a timer that has a time()
method to get the time from its creation in milliseconds and always returns time 0 if default constructed or its method deactivate()
was called (this can be checked using its active()
method). Its other methods are lastPeriod()
that returns the time since last tick in milliseconds and frameTime()
that returns the time of that tick.
A more advanced object to represent a state machine. Its third template parameter can be anything (intended for an enum
) that represents a state. It has a state()
method that reads its state and a state(State)
method that sets its state. The time spent in the current state can be checked using timeInState()
.
It also has all the functionality of TimedObject
.
A smart pointer that holds lock over a returned structure until it's destroyed.
A basic class that holds the state machines. It can be paused using the pause()
method and resumed using the unpause()
method. It starts paused. While it's paused, its contents can be modified with methods addTimedObject()
and removeTimedObject()
.
The input and output structures can be obtained using the input()
and output()
methods that return ProtectedReturn
type smart pointers that hold locks over the structures until destroyed. These methods are therefore thread-safe.
Here is a commeted example of a heating unit program:
// Declare input and output structures
struct Input {
float temperature;
};
struct Output {
float power;
};
// Declare a PID controller class
class TemperatureController : public TimedObject<Input, Output> {
const float proportional_ = 0.3f;
const float integral_ = 0.02f;
const float differential_ = -0.2f;
float integralTotal = 0;
float previous_ = 0;
public:
virtual void tick(const Input &in, Output &out)
{
float difference = desired_ - in.temperature;
float needed = difference * proportional_ + integral_ * integralTotal + differential_ * (difference - previous_);
if (needed < 0.0f)
out.power = 0.0f;
else if (needed > 100.0f)
out.power = 100.0f;
else {
out.power = needed;
integralTotal += difference;
}
previous_ = difference;
}
float desired_ = 0;
};
// Declare states for a class that would control the heating and cooling process
enum TemperatureProgrammerState {
STARTING = 0,
HEATING,
HOT,
COOLING,
COOL
};
// Declare the class for controlling the process, using the states set above
class TemperatureProgrammer : public StateMachine<Input, Output, TemperatureProgrammerState> {
float ramp_ = 0.005f;
float max_ = 100.0f;
int hotTime_ = 10000;
float finish_ = 20.0f;
float coolRamp_ = 0.005f;
std::shared_ptr<TemperatureController> controller_;
public:
TemperatureProgrammer(std::shared_ptr<TemperatureController> controller) : controller_(controller)
{
state(STARTING);
}
virtual void tick(const Input &in, Output &out)
{
switch(state()) {
case STARTING:
state(HEATING);
break;
case HEATING: {
float wanted = timeInState() * ramp_;
if (wanted > max_) {
wanted = max_;
state(HOT);
}
controller_->desired_ = wanted;
break;
}
case HOT:
controller_->desired_ = max_;
if (timeInState() > hotTime_) {
state(COOLING);
}
break;
case COOLING: {
float wanted = max_ - timeInState() * ramp_;
if (wanted < finish_) {
wanted = finish_;
state(COOL);
}
controller_->desired_ = wanted;
break;
}
case COOL:
break;
}
}
};
// Create the manager that controls all the state machines, initialise the Input/Output structures, set the minimal period to 100 ms
// It starts in paused state
StateMachineManager<Input, Output> manager(Input{ 20 }, Output{ 0 }, 100);
// Create instances of the classes and insert them into the manager
// It can be done only while the manager is paused
std::shared_ptr<TemperatureController> controller = std::make_shared<TemperatureController>();
manager.addTimedObject(200, controller);
manager.addTimedObject(500, std::make_shared<TemperatureProgrammer>(controller));
// Start the manager, this has to be done from the thread that created it
manager.unpause();
// Periodic reading of output and setting of input for the next turn
for (int i = 0; i < 400; i++) {
std::this_thread::sleep_for (std::chrono::milliseconds(100));
auto out = manager.output(); // These two operations are thread-safe because of locks
auto in = manager.input();
in->temperature = 20 + (in->temperature - 20) * 0.95f + out->power;
std::cout << "Power: " << out->power << " temperature " << in->temperature << " desired " << controller->desired_ << std::endl;
// Destroying the returned structures unlocks the structures
}
// When the manager falls out of scope, it is safely destroyed
Only the thread that created it can pause it, unpause it and destroy it. Reading output and setting input is thread safe and can be done from any thread.