Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"dead_mario.h": "c",
"unity.h": "c",
"string.h": "c"
}
},
"cSpell.words": [
"CFSM"
]
}
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Implementing State pattern in C
#
# Copyright (C) 2024 Haju Schulz <haju@schulznorbert.de>
# Copyright (C) 2024-2025 Haju Schulz <haju@schulznorbert.de>
#
cmake_minimum_required(VERSION 3.11)
set (CMAKE_C_STANDARD 99)
Expand All @@ -15,9 +15,9 @@ project(CFSM
)

# ******************************************************************************
# Enable strict compiler warnings
# Enable strict compiler warnings
# ******************************************************************************

if (MSVC)
# warning level 4
add_compile_options(/W4)
Expand All @@ -28,7 +28,7 @@ endif()

add_subdirectory(src)

set (CFSM_EXAMPLE_MARIO_SRC
set (CFSM_EXAMPLE_MARIO_SRC
"examples/mario/main.c"
"examples/mario/mario.c"
"examples/mario/states/small_mario.c"
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (C) 2024 Haju Schulz <haju@schulznorbert.de>
Copyright (C) 2024-205 Haju Schulz <haju@schulznorbert.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ constructs.
![State Pattern](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/nhjschulz/cfsm/master/doc/cfsm_statepattern.puml)

The state pattern builds on

* A context that delegates operations to one of various state objects,
which is currently the active state.
* A number of state objects that implement context operations to provide
Expand All @@ -52,6 +53,7 @@ use cases:
![State Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/nhjschulz/cfsm/master/doc/cfsm_context.puml)

There are operations to execute

1. When the FSM "enters" a state, meaning it becomes the current active one.
2. When the FSM "leaves" a state, meaning some other state becomes active
3. When a cyclic processing in the active state shall take place
Expand Down Expand Up @@ -88,12 +90,13 @@ typedef struct cfsm_Ctx {
```

Notes:
* All operations in a state are optional, with the exception of the enter
operation. A state that cannot be entered is pointless.
* Operations that are not defined in a state are ignored by CFSM.
* Supporting "other" operations can be done by adding new, or
changing existing functions pointers in the context. CFSM
is primarily an implementation pattern, not a fixed function library.

* All operations in a state are optional, with the exception of the enter
operation. A state that cannot be entered is pointless.
* Operations that are not defined in a state are ignored by CFSM.
* Supporting "other" operations can be done by adding new, or
changing existing functions pointers in the context. CFSM
is primarily an implementation pattern, not a fixed function library.

### CFSM States

Expand Down Expand Up @@ -128,8 +131,8 @@ void SuperMario_onEnter(cfsm_Ctx * fsm)
State transitions are triggered by calling the ```cfsm_transition()```
API function. The call can originate

1. from the CFSM using application to enter a specific state
2. from inside the state operation handlers
1. From the CFSM using application to enter a specific state.
2. From inside the state operation handlers.

The following interaction diagram shows what happens during a state
transition from state "Mario" to "SuperMario":
Expand Down Expand Up @@ -191,10 +194,10 @@ Here are the transitions shown as a table:
The example uses CFSM to implement the Mario state machine in the following
way:

* Each Mario character is a state implemented in an own C-File.
* The collection of items or the monster hits are modelled as events.
* The process operation prints a character specific message.
* The enter/leave operations print a message to visualize these transitions.
* Each Mario character is a state implemented in an own C-File.
* The collection of items or the monster hits are modelled as events.
* The process operation prints a character specific message.
* The enter/leave operations print a message to visualize these transitions.

The main loop of the example implements a small menu where events get
fired based on user input to simulate the game.
Expand Down Expand Up @@ -285,7 +288,7 @@ different state implementing modules.

Here is a transcript of first game simulation steps:

```
```txt
$ cfsm_mario.exe
(1) SmallMario_onEnter()...
(2) SmallMario_onProces(): It's me, Mario!
Expand Down Expand Up @@ -350,17 +353,16 @@ other modules to transition into it.
The first two lines are the actions that the enter handler performs.
In this example it

* prints a message to show the user it got called. In real code such
a call would not be there or would be using some debug logging api.
* updates our Mario to become a small one.
* prints a message to show the user it got called. In real code such
a call would not be there or would be using some debug logging pi.
* updates our Mario to become a small one.

The final 3 lines update the CFSM context to delegate operations
to the SmallMario state.

Note that unused handlers don't need to be set to NULL. The
``cfsm_transition()`` API has done this before calling the
enter operation. Only the needed handlers must be set here
(if any).
```cfsm_transition()``` API has done this before calling the enter
operation. Only needed handlers must be set here (if any).

#### The Small Mario Leave Operation

Expand Down Expand Up @@ -402,7 +404,7 @@ event signal operation is implemented as a switch over the event ids.
The call to ``mario_updateCoins()`` extracts the coin awards into
a helper function. The amount of coins depend on the event, not
on the state. Directly implementing it inside the states would cause
code dublication.
code duplication.

The individual event cases trigger a transition from small to another
Mario dependent on the event. Noteworthy is the slightly more complex
Expand Down Expand Up @@ -450,7 +452,7 @@ not needed and therefore also not present at all.
The CFSM pattern is surprisingly simple. There are no complex logic
sequences or loops in CFSM. Processing boils down to a NULL checked
function pointer call to delegate operation requests to state
dependend handlers. This simplicity makes the pattern also usable
dependent handlers. This simplicity makes the pattern also usable
for functional safety applications. The functionality is easy to
test and review.

Expand All @@ -476,4 +478,3 @@ CFSM achieves the following benefits
code duplication. This is usually addressed in OO Languages by introducing
base classes for states. C-Language doesn't offer such concepts and
CFSM does not try to mimic such OO behavior in C-language.

34 changes: 17 additions & 17 deletions examples/UnoBlink/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Arduino Blink with CFSM

This is a boilerplate code example for
[CFSM](https://github.com/nhjschulz/cfsm) based applications.
It is build on top of Arduino and implements a CFSM version of
[CFSM](https://github.com/nhjschulz/cfsm) based applications.
It is build on top of Arduino and implements the CFSM version of
the "led-blink" sketch, the embedded system version of
"hello world". It gets build using
"hello world". It gets build using
[PlatformIO](https://platformio.org/) and is configured for
Arduino UNO. But it will run on any Arduino supported platform
with an onboard led by updating ```platform.ini```.
with an onboard LED by updating ```platform.ini```.

## Blinking with a State Machine

The blink sketch toggles a led on or off, resulting in
The blink sketch toggles a LED on or off, resulting in
two distinct states that can be mapped to FSM states. The
first is representing "On", the second "Off". The transitions
between the states happen after fixed time intervals. In
Expand All @@ -31,14 +31,14 @@ different transition methods for going to on or off.

## CFSM Blink Example

The example is based on PlatformIO 's Arduino template with a main module
implementing the ```setup()``` and ```loop()``` methods.
The example is based on PlatformIO 's Arduino template with a main module
implementing the ```setup()``` and ```loop()``` methods.
It includes CFSM as an external library using the ```lib_deps``` variable in
```platformio.ini```.

~~~{.ini}
lib_deps =
nhjschulz/CFSM@^0.2.0
nhjschulz/CFSM@^1.0.0
~~~

The example is following this SW architecture:
Expand All @@ -51,31 +51,32 @@ The example is following this SW architecture:

![Setup Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/nhjschulz/cfsm/master/examples/UnoBlink/doc/BlinkSetup.puml)

### Arduino Loop Transition into OffState
### Arduino Loop Transition into OffState

The sequence starts while CFSM is in OnState:

![Setup Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/nhjschulz/cfsm/master/examples/UnoBlink/doc/BlinkOnToOff.puml)

## Arduino Loop Transition into OnState
## Arduino Loop Transition into OnState

The sequence starts while CFSM is in OffState:

![Setup Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/nhjschulz/cfsm/master/examples/UnoBlink/doc/BlinkOffToOn.puml)

Note that there is a little difference between the transitions:
* "On" state processes the transition inside the process operation.
* "Off" state does not use the process operation. The state works event based. That's why
CFSM instantly returns on the process() request in this state.
Note the little difference between the transitions:

* "On" state processes the transition inside the process operation.
* "Off" state does not use the process operation. The state works event based. That's why
CFSM instantly returns on the process() request in this state.

## Running the Example

The onboard led will toggle on and off after every second
when the example got flashed. Serial monitor prints
will print the progress also to a serial port. The
will print the progress also to a serial port. The
output looks like this:

~~~
~~~txt
OnState: enter()
OnState: LED On time has expired !
OnState: leave()
Expand All @@ -86,4 +87,3 @@ OffState: leave()
OnState: enter()
...
~~~

13 changes: 8 additions & 5 deletions examples/UnoBlink/doc/BlinkClass.puml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- blinkFsm : cfsm_Ctx
+setup()
+loop()

}


Expand All @@ -25,8 +25,8 @@


class BlinkData <<struct>> {
- ledPin : int
- turnOnTimeMillis : uint64_t
+ ledPin : int
+ turnOnTimeMillis : uint64_t
}


Expand All @@ -44,7 +44,7 @@ package "CFSM" {
+onProcess()
+onEvent()
}

}

cfsm_Ctx - Operations
OnState .u-|> Operations
Expand All @@ -53,6 +53,9 @@ OffState .u-|> Operations
Main .l.> cfsm_Ctx : \n\nSignal LED_ON\n every 2 seconds
Main .> OnState : transition to\nin setup()

states .> BlinkData: <<use>>
OffState .> BlinkData: <<use>>
OnState .> BlinkData: <<use>>

Main .d..> BlinkData: <<use>>

@enduml
6 changes: 4 additions & 2 deletions examples/UnoBlink/doc/BlinkOffToOn.puml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@startuml CFSM stateAlias1 --> stateAlias2 : messageOrCond2 <<stereotype1>>
@startuml CFSM state LED on

autoactivate on

participant Arduino
Expand All @@ -25,11 +26,12 @@ Cfsm <- OffState : transition(OnState)
Cfsm -> OffState : onLeave()
Cfsm <-- OffState
Cfsm -> OnState: OnEnter()
OnState -> Led #LightGray: turned on
OnState -> Led #LightGray: turn on
Cfsm <-- OnState
Cfsm --> OffState
Cfsm <-- OffState
Main <-- Cfsm
Arduino <-- Main
end

@enduml
21 changes: 18 additions & 3 deletions examples/UnoBlink/doc/BlinkOnToOff.puml
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
@startuml CFSM stateAlias1 --> stateAlias2 : messageOrCond2 <<stereotype1>>
autoactivate on
@startuml CFSM state LED off


participant Arduino
participant Main
participant Cfsm
participant OnState
participant OffState
Actor Led

activate Led #LightGray
Arduino -> Main : loop()
activate Main
Main -> Cfsm : process()
activate Cfsm
Cfsm -> OnState : onProcess()
activate OnState
alt 1 second since enter of OnState
OnState -> Cfsm: transition(OffState)
activate Cfsm
Cfsm -> OnState: onLeave()
activate OnState
Cfsm <-- OnState
deactivate OnState
Cfsm -> OffState : onEnter()
OffState <-- Led : turned off
activate OffState
OffState -> Led : turn off
deactivate Led
Cfsm <-- OffState
deactivate OffState
Cfsm --> OnState
deactivate Cfsm
Cfsm <-- OnState
deactivate OnState
Main <-- Cfsm
deactivate Cfsm
Arduino <-- Main
deactivate Main
end

@enduml
5 changes: 3 additions & 2 deletions examples/UnoBlink/doc/BlinkSetup.puml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@startuml CFSM stateAlias1 --> stateAlias2 : messageOrCond2 <<stereotype1>>
@startuml CFSM state

autoactivate on

participant Arduino
Expand All @@ -13,7 +14,7 @@ Main -> Cfsm : init fsm context
Main <-- Cfsm
Main -> Cfsm : transition(OnState)
Cfsm -> OnState : onEnter()
OnState -> Led #LightGray : turned on
OnState -> Led #LightGray : turn on
Cfsm <-- OnState
Main <-- Cfsm
Arduino <-- Main
Expand Down
9 changes: 5 additions & 4 deletions examples/UnoBlink/doc/BlinkState.puml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
@startuml Blink State Machine

state OnState
state OffState

OnState -l-> OffState: one Second\npassed
OffState --> OnState: Event\nLED_ON
state OnState: /enter turn on LED
state OffState: /leave turn off LED

OnState --> OffState: one Second\npassed
OffState --> OnState: Event(LED_ON)

[*] -> OnState

Expand Down
Loading