rasProject_01 / weSweetHome
R. 240
process control / RasPi software by weinert-automation
|
States and state machines. More...
#include "sysBasic.h"
Data Structures | |
struct | state_t |
The structure for state machines. More... | |
Macros | |
#define | newFiveBand(N, C, V, T, R, S) |
Define a five band checker. More... | |
#define | newFloatHyst(N, C, O, R, S) |
Define a hysteresis (Schmitt trigger, float value) as state machine. More... | |
#define | newSeqCont(N, C, n, m, S) |
Define a sequential function chart as state machine. More... | |
#define | newSwitchDeb(N, C, S, R) |
Define a switch to be de-bounced as state machine. More... | |
#define | newTimer(N, C, S) |
Define a timer as state machine. More... | |
Typedefs | |
typedef uint8_t(* | enterState_t) (state_t *const me, uint32_t controlV) |
A state machine's own function to command entry in active state. More... | |
typedef uint8_t(* | enterStateF_t) (state_t *const me, float controlF) |
Substitute or addendum to enterState_t. | |
typedef uint8_t(* | enterStateS_t) (state_t *const me, char const *controlS) |
Substitute or addendum to enterState_t. | |
typedef void(* | genStateText_t) (char *stateText, state_t const *const me, char const *stamp) |
Generate text for state machine status. More... | |
typedef uint8_t(* | leaveState_t) (state_t *const me, uint32_t controlV) |
A state machine's own function to command exit out of active state. More... | |
typedef uint8_t(* | leaveStateF_t) (state_t *const me, float controlF) |
Substitute or addendum to leaveState_t. | |
typedef uint8_t(* | leaveStateS_t) (state_t *const me, char const *controlS) |
Substitute or addendum to leaveState_t. | |
typedef void(* | onStateChange_t) (state_t *const me) |
The applications's call back function for state changes. More... | |
typedef struct state_t | state_t |
The structure for state machines. | |
typedef uint8_t(* | tickCheckState_t) (state_t *const me, uint32_t controlV) |
A machine's own function to be called to trigger / check state. More... | |
typedef uint8_t(* | tickCheckStateF_t) (state_t *const me, float controlF) |
Substitute or addendum to tickCheckState_t. | |
typedef uint8_t(* | tickCheckStateN_t) (state_t *const me) |
Substitute or addendum to tickCheckState_t. | |
Functions | |
uint8_t | fiveBandDoEnter (state_t *const me, float analogueVal) |
Five band checker turn / force ON. More... | |
uint8_t | fiveBandDoLeave (state_t *const me, float analogueVal) |
Five band checker turn / force OFF. More... | |
uint8_t | fiveBandTick (state_t *const me, float controlV) |
Five band checker trigger. More... | |
uint8_t | floatHystDoEnter (state_t *const me, float analogueVal) |
Float value hysteresis turn / force ON. More... | |
uint8_t | floatHystDoLeave (state_t *const me, float analogueVal) |
Float value hysteresis turn / force OFF. More... | |
uint8_t | floatHystTick (state_t *const me, float controlV) |
Float value hysteresis trigger. More... | |
void | genStateText (char *stateText, state_t const *const me, char const *stamp) |
Generate text for state machine status. More... | |
void | logStateReason (state_t const *const me, char const *stamp, char const *cause) |
Log status text with cause and info. More... | |
void | logStateText (state_t const *const me, char const *stamp) |
Log status text. More... | |
uint8_t | seqContDoEnter (state_t *const me, char const *startCommand) |
Sequential control entry. More... | |
uint8_t | seqContDoLeave (state_t *const me, char const *stopCommand) |
Sequential control leave. More... | |
uint8_t | seqContTick (state_t *const me) |
Sequential control tick or check. More... | |
void | setGenStateText (state_t *const me, genStateText_t const fun) |
Set the function to generate text for state machine status. More... | |
void | startStateText (char *stateText, state_t const *const me, char const *stamp) |
Generate status text standard start. More... | |
uint8_t | switchDebDoEnter (state_t *const me, uint32_t controlV) |
Switch de-bounce turn / force ON. More... | |
uint8_t | switchDebDoLeave (state_t *const me, uint32_t controlV) |
Switch de-bounce turn / force OFF. More... | |
uint8_t | switchDebTick (state_t *const me, uint32_t controlV) |
Switch de-bounce trigger. More... | |
uint8_t | switchDebTickAC (state_t *const me, uint32_t controlV) |
Switch de-bounce trigger AC. More... | |
uint8_t | timerDoEnter (state_t *const me, uint32_t secFromNow) |
Timer entry. More... | |
uint8_t | timerDoLeave (state_t *const me, uint32_t ignored) |
Timer leave, that is stop timer. More... | |
uint8_t | timerDoStart (state_t *const me, uint32_t secFromNow) |
Timer unconditional entry and set. More... | |
uint8_t | timerDoStart4ever (state_t *const me) |
Timer unconditional entry and set forever or stop it. More... | |
uint8_t | timerDoTrigger (state_t *const me, uint32_t secFromNow) |
Timer entry or (pro-longing) re-trigger. More... | |
uint8_t | timerEndTrigger (state_t *const me, uint32_t const secUTCend) |
Timer entry or (pro-longing) re-trigger to absolute UTC end. More... | |
uint8_t | timerTickCheck (state_t *const me, uint32_t controlV) |
Timer trigger. More... | |
States and state machines.
Revision history
State machines are implemented in an object oriented way although C is in no way supporting objects. We do this by defining the state in a structure and the behaviour in a set of functions, regarding a defined structure variable plus the functions as an object modelling the state machine in question.
Of course, in C we don't have the object orientation principles, especially no encapsulation. There is no binding of behaviour (functions) to the state (structure variable), which leads to a structure pointer as first parameter of every related function. The only substitute is discipline.
As of Revision 193 there are five types of state machines:
#define newTimer | ( | N, | |
C, | |||
S | |||
) |
Define a timer as state machine.
A state machine of this type "timer" is either running or inactive. It is set active or re-triggered by an amount of seconds from now. The end of the interval is stored as absolute (UTC s) time.
The check/tick function timerTickCheck would, by default be called every second at least near the end of the interval. It might as well be called more seldom if a coarser resolution is applicable and or the the timer end event shall be synchronised with other events, like e.g. second 15 in every second minute.
One strategy would be to have all timers in an array or list and check all timers every second. When this list is kept sorted to active timers with nearest end date first, one can stop checking on the first timer without change to inactive. This sorted list approach is worthwhile with many timers, only.
This macro is the initialisation expression for a timer requiring the unique name and the fitting onStateChange function, only. The other fields are preset as the timer state machine type demands.
Hint: A timer may be made periodic by restarting it accordingly in its onStateChange function.
N | name as string literal |
C | onStateChange function |
S | genStateText function (NULL sets default / standard genStateText) |
#define newSwitchDeb | ( | N, | |
C, | |||
S, | |||
R | |||
) |
Define a switch to be de-bounced as state machine.
A state machine of this type is to be provided with a switch/button input condition of either ON or OFF sampled at regular time intervals. It is constructed with a positive onThreshold (say 4) and a non-negative lower offThreshold (say 2, e.g.).
An internal counter is incremented by ON input when OFF from 0 to onThreshold. On OFF input and when ON the internal counter is decremented form offThreshold to 0.
As might have become obvious the state machine makes the OFF<->ON when these counters reach onThreshold respectively 0;
This macro is the initialisation expression for a switch de-bounce requiring the unique name and the fitting onStateChange function as well as the (uint8_t) values for the off and on threshold.
N | name as string literal |
C | onStateChange function |
S | offThreshold small non-negative integer |
R | onThreshold small positive integer |
#define newFloatHyst | ( | N, | |
C, | |||
O, | |||
R, | |||
S | |||
) |
Define a hysteresis (Schmitt trigger, float value) as state machine.
A state machine of this type is to be provided with an analogue (float) value sampled regularly. It is constructed with an On and an Off threshold value. It must hold onThreshold >= offThreshold; recommended is onThreshold > offThreshold with a significant difference.
The significance of status,subStatus for this state machine is:
0,0 : Off, below
1,0 : On, above
1,1 : On, within from above
0,1 : Off, within from below
0,3 : do not know the level (pre-trigger, reset state)
0,2 : do not know On or Off, within from reset state
As might have become obvious the state machine makes a transition to ON (1,0) when the sampled value becomes > onThreshold and to Off (0,0) when <= offThreshold.
The state change call back function will be called on the following transitions:
To "Off, below" (0,0) from: 1,x and (0,2)
To "On, above" (1,0) from: 0,x and
To "Undefined, between" (0,2) from: reset (0,3)
Note: The call back function might ignore the subStatus; this would take "Undefined, between" as Off and one might see two consecutive transitions to "Off".
This macro is the initialisation expression for a float value hysteresis requiring the unique name and the fitting onStateChange function as well as the (float) values for the off and on threshold.
N | name as string literal |
C | onStateChange function |
O | offThreshold valid float (i.e. non NaN) |
R | onThreshold valid float (R >= S) |
S | genStateText function (NULL sets default / standard genStateText) |
#define newFiveBand | ( | N, | |
C, | |||
V, | |||
T, | |||
R, | |||
S | |||
) |
Define a five band checker.
A state machine of this type is to be fed with an analogue (float) value sampled regularly. This value is compared to four thresholds or borders separating those five bands:
...badLO | critLo | OK | critHi | badHi...
The state machine is to be constructed with four thresholds the un-equality
must hold for. For VDE-AR-N 4105 e.g. one would take
47.7 49.5 -OK- 50.5 51.5 Hz resp. 184 207 -OK- 253 264 V
as thresholds.
The significance of status values for this state machine are:
2: bad Hi
1: critical Hi
0: OK
5: critical Lo
6: bad Lo
8: unknown / reset
subStatus is used as previous state. Hence 8,8 ist the reset state.
The state change call back function will called on all status transitions. realSecOff will be the time stamp of the last transition into OK (0) and realSecOn of the last transition out of it,
This macro is the initialisation expression for a five band checker requiring the unique name and the fitting onStateChange function as well as legal (float) values for the four thresholds.
N | name as string literal |
C | onStateChange function |
V | threshBadLo valid float (i.e. non NaN) |
T | threshCritLo |
R | onThreshold valid float |
S | offThreshold (S > R > T > V) |
#define newSeqCont | ( | N, | |
C, | |||
n, | |||
m, | |||
S | |||
) |
Define a sequential function chart as state machine.
A state machine of this type is simplified two leg sequential function chart with one leg leading via intermediate steps form OFF to ON state and the other leg the other way.
The OFF state is: state_t.status == 0 and state_t.subStatus == 0
Chain to ON is: state_t.status == 0 and state_t.subStatus == 1..n-1
The ON state is: state_t.status == 1 and state_t.subStatus == 0
Chain to OFF is: state_t.status == 1 and state_t.subStatus == 1..m-1
Chain to ON interrupted by seqContDoLeave changes status from 0 to 4.
Chain to OFF interrupted by seqContDoEnter changes status from 1 to 5.
N | name as string literal |
C | onStateChange function |
n | length of chain to ON |
m | length of chain to OFF |
S | genStateText function (NULL sets default / standard genStateText) |
typedef uint8_t(* enterState_t) (state_t *const me, uint32_t controlV) |
A state machine's own function to command entry in active state.
Note: The enter function put in this function pointer may (and should) be called directly on fitting events. Nevertheless, the function put in this function pointer may be used by others, especially by a check/trigger function. Then, be sure not to have the wrong (default) function here.
me | pointer to the machine itself; never null |
controlV | controlValue or extra command for the state; the usage and semantic of this parameter depends on the state machine type |
typedef uint8_t(* leaveState_t) (state_t *const me, uint32_t controlV) |
A state machine's own function to command exit out of active state.
Note: See note at enterState_t.
me | pointer to own state; never null |
controlV | controlValue or extra command for the state; the usage and semantic of this parameter depends on the state implemented |
typedef uint8_t(* tickCheckState_t) (state_t *const me, uint32_t controlV) |
A machine's own function to be called to trigger / check state.
Note: See note at enterState_t.
me | pointer to the machine itself; never null never null |
controlV | additional control value (float for tickCheckStateF_t) |
typedef void(* onStateChange_t) (state_t *const me) |
The applications's call back function for state changes.
This function will only be called when (set and) the own state_t.status really changed. Depending on type (see state_t.typ), this function may also be called when the own state_t.subStatus changed.
me | pointer to the machine itself |
typedef void(* genStateText_t) (char *stateText, state_t const *const me, char const *stamp) |
Generate text for state machine status.
This is the minimal common standard for all status machines.
stateText | a character array supplied to hold the state text to be generated; minimal length 80. |
me | pointer to own state; never null! |
stamp | (time) stamp to be prepended (max. length 23); default " - " |
void setGenStateText | ( | state_t *const | me, |
genStateText_t const | fun | ||
) |
Set the function to generate text for state machine status.
me | pointer to own state machine |
fun | the new text generator function |
void startStateText | ( | char * | stateText, |
state_t const *const | me, | ||
char const * | stamp | ||
) |
Generate status text standard start.
This sets the common standard start of a status text in state text, like:
It ends with a blank at [40] after the colon at [39]. State machine type or instance specific text may be added from [40] or [41] up to (recommended) [76] followed by a terminating 0.
Hint: This function sets a 0 at [41] for robustness, i.e. having stateText always as string.
stateText | a character array supplied to hold the state text to be generated; minimal length 80. |
me | pointer to own state; never null |
stamp | (time) stamp to be prepended (max. length 23); default " - " |
void genStateText | ( | char * | stateText, |
state_t const *const | me, | ||
char const * | stamp | ||
) |
Generate text for state machine status.
This is the minimal common standard for all status machines.
stateText | a character array supplied to hold the state text to be generated; minimal length 80. |
me | pointer to own state; never null! |
stamp | (time) stamp to be prepended (max. length 23); default " - " |
void logStateText | ( | state_t const *const | me, |
char const * | stamp | ||
) |
void logStateReason | ( | state_t const *const | me, |
char const * | stamp, | ||
char const * | cause | ||
) |
Log status text with cause and info.
The status as text will be generated and then be output to outLog. outLog will be flushed.
The text will be:
stamp # state machine name: cause me->infoTxt
me->infoTxt has to be set/ provided by application software and will be output to a maximum / recommended length of 29. A standard format is one field of 7 and two fields of ten characters separated by spaces.
me | pointer to own state; not null |
stamp | (time) stamp to be prepended (max. length 23); default " - " |
cause | the cause of the state change (max. length 6); default me->controlVS |
uint8_t timerTickCheck | ( | state_t *const | me, |
uint32_t | controlV | ||
) |
Timer trigger.
me | pointer to own state; not null |
controlV | current time stamp; mostly cycTaskEventData_t.realSec resp. getAbsS() |
uint8_t timerDoEnter | ( | state_t *const | me, |
uint32_t | secFromNow | ||
) |
Timer entry.
This function starts an inactive timer to end secFromNow s. It does nothing on a timer already active resp. running, especially, it does not re-trigger / prolong the timer's time.
This is the function set as (default) state_t.doEnter and not timerDoStart.
me | pointer to own state; not null |
secFromNow | time to run on from now (s) |
uint8_t timerDoStart | ( | state_t *const | me, |
uint32_t | secFromNow | ||
) |
Timer unconditional entry and set.
This function starts an inactive timer. The timer's end time will be set to (now + secFromNow s) no matter the timers previous state. This unconditional changing the end time of a timer already running is the difference to timerDoEnter.
This is not the function set as (default) state_t.doEnter; it is timerDoEnter.
me | pointer to own state; not null |
secFromNow | time to run on from now (s) |
uint8_t timerDoStart4ever | ( | state_t *const | me | ) |
Timer unconditional entry and set forever or stop it.
This function starts an inactive timer. The timer's end time will be set to 2.2.2106 no matter the timers previous state. This date is considered as for ever in our 21st century. This does effectively stop the timer.
This function is intended to start or keep running a timer not to end before its time is to be set (by timerDoStart) to a sensible end time.
One use case is: A timer running forever is in error state.
This function is, of course, faster than timerDoEnter and timerDoStart.
me | pointer to own state; not null |
uint8_t timerDoTrigger | ( | state_t *const | me, |
uint32_t | secFromNow | ||
) |
Timer entry or (pro-longing) re-trigger.
This function starts an inactive timer to end secFromNow s. IF the timer is active and if (now + secFromNow) is later than the current end, the timer me's runtime will be prolonged accordingly (timer re-trigger).
me | pointer to own state; not null |
secFromNow | time to run on from now (s) |
uint8_t timerEndTrigger | ( | state_t *const | me, |
uint32_t const | secUTCend | ||
) |
Timer entry or (pro-longing) re-trigger to absolute UTC end.
If secUTCend is now or in the past or if it is equal the the end of the already active timer, nothing will be done (returns 1).
This function starts an inactive timer to end at secUTCend. If the timer is active already secUTCend will be taken as new end time.
me | pointer to own state; not null |
secUTCend | end time |
uint8_t timerDoLeave | ( | state_t *const | me, |
uint32_t | ignored | ||
) |
Timer leave, that is stop timer.
Note: This function is usable for other state machine types, too, if appropriate.
me | pointer to own state; not null |
ignored | as the name says |
uint8_t switchDebTick | ( | state_t *const | me, |
uint32_t | controlV | ||
) |
Switch de-bounce trigger.
me | pointer to own state; not null |
controlV | 0: input OFF else: input ON |
uint8_t switchDebTickAC | ( | state_t *const | me, |
uint32_t | controlV | ||
) |
Switch de-bounce trigger AC.
This function does essentially the same as switchDebTick, except for On ticks being counted twice. This is meant for half wave rectified AC signals sampled at multiples of their frequency. Obviously, half or 1/2 + 1 of the samples will always be Off. And, of course, the state machine has to be made with a on chain length being by the number of samples per period higher than the normal switch de-bounce filter time, to avoid spiky ON results.
me | pointer to own state; not null |
controlV | 0: input OFF else: input ON |
uint8_t switchDebDoEnter | ( | state_t *const | me, |
uint32_t | controlV | ||
) |
Switch de-bounce turn / force ON.
me | pointer to own state; not null |
controlV | irrelevant but recorded ob state change |
uint8_t switchDebDoLeave | ( | state_t *const | me, |
uint32_t | controlV | ||
) |
Switch de-bounce turn / force OFF.
Note: this function is usable for other non timer stati, too;
me | pointer to own state; not null |
controlV | irrelevant but recorded ob state change |
uint8_t floatHystTick | ( | state_t *const | me, |
float | controlV | ||
) |
Float value hysteresis trigger.
me | pointer to own state; not null |
controlV | the sampled analogue value |
uint8_t floatHystDoEnter | ( | state_t *const | me, |
float | analogueVal | ||
) |
Float value hysteresis turn / force ON.
me | pointer to own state; not null |
analogueVal | irrelevant, but recorded |
uint8_t floatHystDoLeave | ( | state_t *const | me, |
float | analogueVal | ||
) |
Float value hysteresis turn / force OFF.
Note: this function is usable for other non timer stati, too;
me | pointer to own state; not null |
analogueVal | irrelevant, but recorded; may take an (uint32_t) cast float |
uint8_t fiveBandTick | ( | state_t *const | me, |
float | controlV | ||
) |
Five band checker trigger.
me | pointer to own state; not null |
controlV | the sampled analogue value |
uint8_t fiveBandDoEnter | ( | state_t *const | me, |
float | analogueVal | ||
) |
Five band checker turn / force ON.
This function puts the checker in state bad Hi (2) no matter the parameter value.
me | pointer to own state; not null |
analogueVal | irrelevant, but recorded on state change |
uint8_t fiveBandDoLeave | ( | state_t *const | me, |
float | analogueVal | ||
) |
Five band checker turn / force OFF.
This function puts the checker in state OK (0) no matter the parameter value.
me | pointer to own state; not null |
analogueVal | irrelevant, but recorded on state change |
uint8_t seqContDoEnter | ( | state_t *const | me, |
char const * | startCommand | ||
) |
Sequential control entry.
This function starts the sequence from OFF to ON.
me | pointer to own state; not null |
startCommand | the cause of the state change; 6 characters (max.) to be recorded and else irrelevant for his function |
uint8_t seqContDoLeave | ( | state_t *const | me, |
char const * | stopCommand | ||
) |
Sequential control leave.
This function starts the sequence from ON to OFF.
me | pointer to own state; not null |
stopCommand | It is recorded on state changes and else irrelevant for this function; see description on seqContTick |
uint8_t seqContTick | ( | state_t *const | me | ) |
Sequential control tick or check.
If this sequential control (me) is not in a stable OFF or ON state, that is state_t.status is 0 or 1 and state_t.subStatus is 0, this function should be called at regular intervals or on relevant conditions state changes.
The function must keep or advance the state.
It must react on interrupts by seqContDoEnter or seqContDoLeave recognisable by state_t.status == 5 respectively 4. Note: This is just for the case, that individual the call back function state_t.onStateChange does not handle this "interrupts" — what it really should.
This (basic) implementation advances the sub state from 1 to n-1 respectively m-1. Interrupts by seqContDoEnter or seqContDoLeave are handled by changing to sub state 1 of the opposite leg.
When this basic implementation is not sufficient for a concrete SFC, the application may provide an own tick/check function. However, in most cases seemingly complicated cases — nonlinear chains, wait conditions etc. — the specialised behaviour can most often be implemented by an individual state_t.onStateChange call back function.
me | pointer to own state; not null |