OOSMOS User's Guide
1 Object-Oriented + State Machine + Operating System
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Example
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine Tables
3.2.1 State Machine Declarations
3.2.2 State Machine Initialization
3.2.3 Simple State Machine Example
3.2.4 Moderate State Machine Example
3.2.5 Orthogonal State Machine Example
3.3 Creating the Code for States
4 Async APIs
4.1 Async API Rules and Restrictions
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
1 Object-Oriented + State Machine + Operating ...
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Example
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine Tables
3.2.1 State Machine Declarations
3.2.2 State Machine Initialization
3.2.3 Simple State Machine Example
3.2.4 Moderate State Machine Example
3.2.5 Orthogonal State Machine Example
3.3 Creating the Code for States
4 Async APIs
4.1 Async API Rules and Restrictions
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
1 Object-Oriented + State Machine + Oper...
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Example
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine Tables
3.2.1 State Machine Declarations
3.2.2 State Machine Initialization
3.2.3 Simple State Machine Example
3.2.4 Moderate State Machine Example
3.2.5 Orthogonal State Machine Example
3.3 Creating the Code for States
4 Async APIs
4.1 Async API Rules and Restrictions
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
1 Object-Oriented + State Machine ...
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Example
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine Table...
3.2.1 State Machine Declarations
3.2.2 State Machine Initialization
3.2.3 Simple State Machine Example
3.2.4 Moderate State Machine Example
3.2.5 Orthogonal State Machine Example
3.3 Creating the Code for States
4 Async APIs
4.1 Async API Rules and Restrictions
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
1 Object-Oriented + State Machin...
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Exampl...
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine Tab...
3.2.1 State Machine Declarations
3.2.2 State Machine Initialization
3.2.3 Simple State Machine Example
3.2.4 Moderate State Machine Example
3.2.5 Orthogonal State Machine Examp...
3.3 Creating the Code for States
4 Async APIs
4.1 Async API Rules and Restrictio...
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
1 Object-Oriented + State Ma...
1.1 Object-Oriented
1.1.1 C vs C++ Encapsulation
1.1.1.1 C++
1.1.1.2 C
1.2 State Machine
1.3 Operating System
1.3.1 Bare Board Loop Examples
1.3.2 Thread-Cooperative Loop Ex...
1.3.3 Thread Hybrid Loop Example
2 Examples
2.1 Making Changes
2.2 Arduino Examples
2.3 Windows and Linux Examples
2.4 MPLAB X Examples
3 States
3.1 Drawing The Statechart
3.2 Creating the State Machine...
3.2.1 State Machine Declarations
3.2.2 State Machine Initializati...
3.2.3 Simple State Machine Examp...
3.2.4 Moderate State Machine Exa...
3.2.5 Orthogonal State Machine E...
3.3 Creating the Code for Stat...
4 Async APIs
4.1 Async API Rules and Restri...
5 Events
5.1 Predefined Events
5.1.1 oosmos_COMPLETE
5.1.2 oosmos_DEFAULT
5.1.3 oosmos_ENTER
5.1.4 oosmos_EXIT
5.1.5 oosmos_INSTATE
5.1.6 oosmos_TIMEOUT
5.2 User Defined Events
6 Queues
6.1 Event Queue Example
6.2 Work Queue Example
6.3 Asynchronous Calls
7 The bool Data Type
8 Object Allocation
8.1 OOSMOS Object Allocation
8.1.1 Example
8.1.2 Alternative Allocation
9 The OOSMOS Name Space
The following sections describe the various elements of OOSMOS.
Note: Experienced programmers may be able to go directly to the Portfolio of Examples and read through some of the source code and have enough of an idea of what's going on to be able to effectively modify existing examples to suit their needs.

1 Object-Oriented + State Machine + Operating System

OOSMOS stands for:
  • Object-Oriented.
  • State Machine.
  • Operating System.
OOSMOS takes a novel approach to all three of these characteristics, described below.

1.1 Object-Oriented

OOSMOS conventions promote strict encapsulation and information hiding.
Files module.h and module.c, below, show how it is done.
The important line is line 5 in module.h. This is a typedef that declares a type called module that is of incomplete structure type struct moduleTag. This line makes the type module accessible to the user but not its size, so only pointers of type module can be created.
Now refer to line 5 of file module.c. This declaration completes the type, but it is inside the implementation file module.c, not the user-facing interface file module.h. That way, all the implementation details are hidden from the user. We think this approach is superior to the way a customary C++ class is organized. See section C vs C++ Encapsulation below for an example.
1
2
3
4
5
6
7
8
9
10
 
#ifndef module_h
#define module_h
 
typedef struct moduleTag module;
 
extern module * moduleNew(void);
 
#endif
 
module.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
#include <stdlib.h>
#include "module.h"
 
struct moduleTag {
int m_Member;
};
 
extern module * moduleNew(void)
{
module * pModule = malloc(sizeof(module));
 
pModule->m_Member = 0;
 
return pModule;
}
 
module.c

1.1.1 C vs C++ Encapsulation

Here is an example of how the OOSMOS encapsulation structure is superior to the same thing written in classic C++.

1.1.1.1 C++

File module.hpp, below, is a C++ header file that defines a simple interface to a class called module. Note that the implementation of module in file module.cpp needs to retain a Windows handle in the class. Accordingly, a private member m_Handle is added in module.hpp in a private section of the class. Because we refer to type HWND, we need to include Windows.h. This is very unfortunate for at least two reasons:
  1. Because Windows.h is included in the module.h header file, anyone that includes module.h will now get their namespace polluted by all the symbols in Windows.h.
  2. Any change to the implementation that necessitates a change to the size or structure of the class will ripple into the user space, requiring a recompile of all dependent source files.
Now skip ahead and let's take a look at the same thing in OOSMOS-structured C.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
#ifndef module_hpp
#define module_hpp
 
#include "Windows.h"
 
class module
{
public:
void Init();
 
private:
HWND m_Handle;
};
 
#endif
 
module.hpp
1
2
3
4
5
6
7
8
9
 
#include <stddef.h>
#include "module.hpp"
 
void module::Init()
{
m_Handle = NULL;
}
 
module.cpp

1.1.1.2 C

Now refer to C files module.h and module.c, below.
In OOSMOS-structured C, we do not include Windows.h in the header file. There is no need because all the implementation details are maintained within module.c, so module.c includes Windows.h, not module.h.
Therefore, the user's namespace is not polluted with symbols they shouldn't see and when the implementation changes, only module.c needs to be changed and only that file needs to be recompiled.
1
2
3
4
5
6
7
8
9
10
 
#ifndef module_h
#define module_h
 
typedef struct moduleTag module;
 
extern void moduleInit(module * pModule);
 
#endif
 
module.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
#include <stddef.h>
#include "module.h"
 
#include "Windows.h"
 
struct moduleTag
{
HWND m_Handle;
};
 
extern void moduleInit(module * pModule)
{
pModule->m_Handle = NULL;
}
 
module.c

1.2 State Machine

OOSMOS defines three types of objects:
  • Passive objects are traditional objects that have no asynchronous behavior and thus do not require a state machine.
    A good illustration of a passive object is the LinearRegression program in the Examples directory.
  • Active objects have very simple asynchronous behavior requirements and don't require the features provided by a full hierarchical state machine. Instead, they register the address of a callback function to call each time through the OOSMOS control loop.
    A good illustration of an Active object is the switch class called sw in the Classes directory.
  • State Machine objects are built around the full hierarchical state machine capability of OOSMOS which gives you the ability to nest states, specify state entry and exit code and use orthogonal states also known as "and" states.
    In addition, State Machine objects support Async calls that allow you to write code much like you would in a threaded operating system. That is, as a continuous sequence of calls that perform asynchronous operations. (See the async example.)

1.3 Operating System

All operating systems are endless loops. OOSMOS is no different in this respect. Traditionally, the main loop is implemented inside the OS. In OOSMOS, the main loop is in user space in order to provide maximum control and visibility.
OOSMOS can be used in one of two ways:
  1. As the one-and-only operating system in an environment that doesn't already have an operating system.
    This is sometimes called a "bare board." In this environment the main loop can take 100% of the CPU because it is not sharing the processor with any other entity.
    Examples are Arduino and PIC32.
  2. Cooperatively with an existing operating system.
    On these platforms, we cannot assume that we "own" the CPU, so we take this into account in our loop. Specifically, in the loop, we run all the state machines in the system and then we relinquish control for a period of time. We like to think of this as periodically "taking a sip" from the processor. The amount of time to delay will differ from milliseconds to possibly multiple seconds depending on how responsive you need your OOSMOS objects to be.
    Examples are Windows and Linux.

1.3.1 Bare Board Loop Examples

For an environment where you control the main program and own the CPU, you will call oosmos_RunStateMachines in a tight loop dedicating 100% of the CPU to OOSMOS objects. Your system will be maximally responsive in this situation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
extern int main()
{
//
// Create objects, many of which will have state machines...
//
 
while (true) {
oosmos_RunStateMachines();
}
 
return 0;
}
 
Has control of main()
The same loop applies to Arduino-style environments, conforming naturally to Arduino's setup and loop callbacks.
1
2
3
4
5
6
7
8
9
10
11
12
13
 
extern void setup()
{
//
// Create objects, many of which will have state machines...
//
}
 
extern void loop()
{
oosmos_RunStateMachines();
}
 
setup() and loop() environment

1.3.2 Thread-Cooperative Loop Example

When OOSMOS is used with threaded operating systems like Linux or Windows, it would be impolite to consume 100% of the CPU. Instead, we periodically "take a small sip" from the processor and then issue a wait for a period of time.
From our experience, OOSMOS used in this way takes very little CPU time.
1
2
3
4
5
6
7
8
9
10
11
12
 
extern int main()
{
//
// Create objects, many of which will have state machines...
//
while (true) {
oosmos_RunStateMachines();
oosmos_DelayMS(50);
}
}
 
OOSMOS on Windows or Linux

1.3.3 Thread Hybrid Loop Example

If you have an existing threaded operating system running on a multiple core processor and you want your OOSMOS objects to be highly responsive, you could create a thread and use processor affinity to assign that thread to one of the cores and then execute oosmos_RunStateMachines on that thread in a tight loop.
For example, if you have a four core CPU and you dedicate one of the cores to a thread that runs only OOSMOS objects, you would take 25% of the total available CPU power for OOSMOS and the remaining 75% of CPU headroom would be available to all other processes and their threads.
1
2
3
4
5
6
7
8
9
10
11
 
extern int main()
{
//
// Create objects, many of which will have state machines...
//
while (true) {
oosmos_RunStateMachines();
}
}
 
OOSMOS on a dedicated core in a threaded operating system

2 Examples

OOSMOS is packaged such that you can download and unzip the OOSMOS_1.4.zip or OOSMOS_1.4.tar.gz file, change to any example directory that interests you and then be able to build and run right away without having to do any configuration.
For most platforms, there is a bld.py script that compiles examples by referring to common master files relative to the current directory.
Arduino-style environments have no way to refer to files outside of the current directory, so we copy common source files from where they are maintained into the various example directories by running the populate.py script in the top-level oosmos directory. All the files that are copied are made read-only at their destination as an indication that the master writable version of that file resides elsewhere. Most master files are located in either the Classes or Classes/Tests directory.
Use populate.py (below) as your roadmap of where maintained files reside. For example, lines 67 through 77 populate all the Blink examples for all platforms. Take line 67 for example. This line copies pin and toggle classes from the Classes directory into the Examples/Blink/Arduino/BlinkExample directory. Files oosmos.h and oosmos.c are copied as well. Additionally, during the copy, all .c files are renamed .cpp because Arduino "sketches" are compiled by a C++ compiler.
1
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
[...]
 
#
# Blink
#
Copy('Classes', 'h c', 'Examples/Blink/Arduino/BlinkExample', 'h cpp', 'pin toggle')
Copy('Classes', 'h c', 'Examples/Blink/ESP8266/BlinkExample', 'h cpp', 'pin toggle')
Copy('Classes', 'h c', 'Examples/Blink/ChipKit/BlinkExample', 'h cpp', 'pin toggle')
oosmos.CopyFileReadOnly('Examples/Blink/Arduino/BlinkExample/BlinkExample.ino', 'Examples/Blink/ChipKit/BlinkExample/BlinkExample.ino')
Copy('Classes', 'h c', 'Examples/Blink/Galileo/BlinkExample', 'h cpp', 'pin toggle')
oosmos.CopyFileReadOnly('Examples/Blink/Arduino/BlinkExample/BlinkExample.ino', 'Examples/Blink/Galileo/BlinkExample/BlinkExample.ino')
Copy('Classes', 'h c', 'Examples/Blink/mbed', 'h cpp', 'pin toggle')
Copy('Classes', 'h c', 'Examples/Blink/MSP430/BlinkExample', 'h cpp', 'pin toggle')
Copy('Classes', 'h c', 'Examples/Blink/LightBlueBean/BlinkExample', 'h cpp', 'pin toggle')
 
Copy('Classes', 'h c', 'Examples/Blink/RaspberryPi', 'h c', 'pin prt toggle')
 
[...]
oosmos/populate.py

2.1 Making Changes

To change a file, first make it writable, then make your changes, test, and then copy the modified file to where the master is maintained.
After that, you'll want to run the populate.py script to propagate your changes into all the Example directories.

2.2 Arduino Examples

For Arduino, you can navigate to any of the Arduino examples, load the .ino file, and all the dependent files that are co-located in the same directory will be loaded as tabs in the IDE.
Blink example loaded into the Arduino IDE
Once loaded, you'll then click on either the verify button (to compile) or upload button (to run).
Below is a list of all the files required to build the Blink example. Note that all the files are read-only except for BlinkExample.ino, so only BlinkExample.ino is mastered in this directory. All other files are copied by populate.py into this directory from the directory indicated.
1
2
3
4
5
6
7
8
9
 
BlinkExample.ino - Maintained in the current directory. (R/W)
oosmos.cpp - Copied from oosmos/source/oosmos.c (R/O)
oosmos.h - Copied from oosmos/source/oosmos.h (R/O)
pin.cpp - Copied from oosmos/Classes/pin.c (R/O)
pin.h - Copied from oosmos/Classes/pin.h (R/O)
toggle.cpp - Copied from oosmos/Classes/toggle.c (R/O)
toggle.h - Copied from oosmos/Classes/toggle.h (R/O)
 
Arduino Blink Files

2.3 Windows and Linux Examples

For both Windows and Linux examples, we provide a bld.py script to compile and link all the files of the example from the command line.
In order to run the script, you'll need to make sure that you have a version of Python installed. We have tested bld.py with Python versions 2.6, 2.7, 3.4 and 3.5.
On Windows, bld.py expects to find the Microsoft Visual Studio cl.exe compiler in the path. We use the free "Express" version of Visual Studio available from Microsoft.
On Linux, the compilation system is supplied by default, so you won't need to install any additional software in order to compile and link.
We also provide a clean.py script to help you keep things tidy.
Note that nearly all the Linux examples are mastered in the Windows example directories and then copied (via populate.py) into the corresponding Linux directories.
You may also notice a file called d.py in some of the Windows example directories. It's simply a quick way to launch the debugger from the command line.

2.4 MPLAB X Examples

For MPLAB X (PIC32), we supply the project such that you can navigate to any PIC32 directory, open the .X file associated with the project, then build, run, or debug.

3 States

States are where your objects spend time. Typically you will execute a stretch of code as you enter a state and then stay in the state waiting for an event to occur or a condition to be satisfied. That event could be a certain amount of time going by or the completion of some asynchronous operation that you started on entry, or some condition for which your poll is satisfied.
The names of your states should reflect what the object is doing while it is waiting. Good examples: Running, Initializing, Connecting; that is, words that end in ing. There are also states that will reflect a physical reality. For example, On, Moving, Latched, or Open.
An example where you might use both types of names is where you may have an outer state that is Ready and an inner state called Injecting.

3.1 Drawing The Statechart

It is important to draw a statechart. It helps you and everyone involved visualize the behavior of your object. There are a number of tools you can use to do this:
  1. UMLet - We highly recommend this tool. Once you get the hang of it, it's remarkable how fast you can capture and modify your design. All the statecharts on this website are done using UMLet.
  2. IBM Rhapsody Modeler - This is a great tool, but it is somewhat unclear if it is free or a trial. (This is not to be confused with full-blown Rhapsody, which is an excellent but expensive modeling tool.)
  3. Visio - We've attempted to draw state machines with Visio but, in our experience, it is frustrating, time-consuming and you can never quite get what you want.
  4. Hand Drawn - Certainly you can draw statecharts by hand, but if you commit one of these statecharts to code, make sure you either take a picture of it or scan it in so you can keep the statechart image associated with its object implementation.

3.2 Creating the State Machine Tables

Once you've drawn a statechart, you will then hand-translate it into the corresponding implementation file in C.
Each state machine has two parts:
  1. State machine table declarations that will go in your class (struct) declaration.
  2. State machine initialization statements that will go in the constructor for your class. (The "New" function.)

So, for example, the statechart in Figure 1, after hand-translation, becomes the state machine declarations in Snippet 1, lines 33-35 and the state machine initialization in lines 78-82.
Figure 1. toggle State Chart
1
22
23
30
31
32
33
34
35
36
37
38
39
40
41
42
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
[GPLv2]
 
 
 
struct toggleTag
{
oosmos_sStateMachineNoQueue(StateMachine);
oosmos_sLeaf On_State;
oosmos_sLeaf Off_State;
 
pin * m_pPin;
int m_TimeOnMS;
int m_TimeOffMS;
};
 
 
 
extern toggle * toggleNew(pin * pPin, const int TimeOnMS, const int TimeOffMS)
{
oosmos_Allocate(toggle, pToggle, toggleMAX, NULL);
 
// StateName Parent Default
// ===============================================
oosmos_StateMachineInitNoQueue(pToggle, StateMachine, NULL, On_State);
oosmos_LeafInit (pToggle, On_State, StateMachine );
oosmos_LeafInit (pToggle, Off_State, StateMachine );
 
pToggle->m_pPin = pPin;
pToggle->m_TimeOnMS = TimeOnMS;
pToggle->m_TimeOffMS = TimeOffMS;
 
oosmos_DebugCode(
oosmos_Debug(&pToggle->StateMachine, true, NULL);
)
 
return pToggle;
}
Snippet 1. toggle State Machine Declaration and Initialization

3.2.1 State Machine Declarations

Here are all the state declaration types you can use to build a state machine. These will go in your class declaration. (Refer to any state machine object in the Examples directory.)
  • oosmos_sStateMachine and oosmos_sStateMachineNoQueue - these declare the state machine (as opposed to its states). These are the only state declarations that take arguments.
    Use oosmos_sStateMachine if your object will react to events. That is, it needs to have an event queue.
    Use oosmos_sStateMachineNoQueue if your object does not react to events.
    oosmos_sStateMachine takes three arguments. The first argument is the name of the state machine (StateMachine). The second argument should be the type of the event that your object can accept. For most cases, this will be oosmos_sEvent. The third argument is the number of events allocated for the queue and should be the maximum number of queued events you think you will receive before your object is able to run and consume them. The right number will depend on the number of objects to which you subscribe.
    The one and only argument to oosmos_sStateMachineNoQueue is the name of the state machine. For now, this should always be StateMachine.
  • oosmos_sLeaf - A leaf state is a state that contains no other states. This is the most common type of state that you'll create.
  • oosmos_sComposite - A composite state is a state that can contain other states.
  • oosmos_sChoice - This is exactly the same as an oosmos_sLeaf state. We use a different name to help self-document.
  • oosmos_sFinal - A final state is a special state that aids in determining whether a state region is complete or not. (See the oosmos_COMPLETE event.)
  • oosmos_sOrtho - This state introduces an orthogonal state. The only valid immediate child of this state is an oosmos_sOrthoRegion state.
  • oosmos_sOrthoRegion - This is similar to an oosmos_sComposite in that it can contain other states, but it must only be an immediate child of oosmos_sOrtho.
For readability, we recommend indenting the declarations to match the state machine drawing but align the state names in a single column.

3.2.2 State Machine Initialization

Initializing a state machine is a straightforward transformation of the state machine drawing and your state machine declarations. The format is very consistent. The only added complexity is that you are now specifying the parent of your state as well as the default state if the state has inner states.
To aid with readability, we indent the statements to match the state machine drawing (just like we did in the state machine declarations) and then columnarize the pointer to the object, state name, parent state and default state (if applicable). We also add the column headings in a comment for maximum readability.
The following sections have examples of translating simple, moderate and orthogonal state machines.

3.2.3 Simple State Machine Example

Figure 2. toggle State Chart
1
22
23
30
31
32
33
34
35
36
37
38
39
40
41
42
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
[GPLv2]
 
[...]
 
struct toggleTag
{
oosmos_sStateMachineNoQueue(StateMachine);
oosmos_sLeaf On_State;
oosmos_sLeaf Off_State;
 
pin * m_pPin;
int m_TimeOnMS;
int m_TimeOffMS;
};
 
 
 
extern toggle * toggleNew(pin * pPin, const int TimeOnMS, const int TimeOffMS)
{
oosmos_Allocate(toggle, pToggle, toggleMAX, NULL);
 
// StateName Parent Default
// ===============================================
oosmos_StateMachineInitNoQueue(pToggle, StateMachine, NULL, On_State);
oosmos_LeafInit (pToggle, On_State, StateMachine );
oosmos_LeafInit (pToggle, Off_State, StateMachine );
 
pToggle->m_pPin = pPin;
pToggle->m_TimeOnMS = TimeOnMS;
pToggle->m_TimeOffMS = TimeOffMS;
 
oosmos_DebugCode(
oosmos_Debug(&pToggle->StateMachine, true, NULL);
)
 
return pToggle;
}
toggle.c - toggle State Machine Declaration and Initialization

3.2.4 Moderate State Machine Example

Figure 3. Keyer State Chart
1
22
23
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
[GPLv2]
 
[...]
 
struct keyerTag
{
oosmos_sStateMachineNoQueue(StateMachine);
oosmos_sLeaf Idle_State;
oosmos_sComposite Dah_State;
oosmos_sLeaf Dah_Tone_State;
oosmos_sComposite Dit_State;
oosmos_sLeaf Dit_Tone_State;
 
pin * m_pDahPin;
pin * m_pDitPin;
pin * m_pSpeakerPin;
 
int m_DitTimeMS;
int m_DahTimeMS;
int m_SpaceTimeMS;
 
struct {
struct {
bool m_DitWasPressed;
} Dah;
 
struct {
bool m_DahWasPressed;
} Dit;
} m_StateData;
};
 
 
 
extern keyer * keyerNew(pin * pDahPin, pin * pDitPin, pin * pSpeakerPin, const int WPM)
{
oosmos_Allocate(pKeyer, keyer, 2, NULL);
 
// State Name Parent Default
// =====================================================
oosmos_StateMachineInitNoQueue(pKeyer, StateMachine, NULL, Idle_State );
oosmos_LeafInit (pKeyer, Idle_State, StateMachine );
oosmos_CompositeInit (pKeyer, Dah_State, StateMachine, Dah_Tone_State);
oosmos_LeafInit (pKeyer, Dah_Tone_State, Dah_State );
oosmos_CompositeInit (pKeyer, Dit_State, StateMachine, Dit_Tone_State);
oosmos_LeafInit (pKeyer, Dit_Tone_State, Dit_State );
 
pKeyer->m_pDahPin = pDahPin;
pKeyer->m_pDitPin = pDitPin;
pKeyer->m_pSpeakerPin = pSpeakerPin;
pKeyer->m_DitTimeMS = 1200 / WPM;
pKeyer->m_DahTimeMS = pKeyer->m_DitTimeMS * 3;
pKeyer->m_SpaceTimeMS = pKeyer->m_DitTimeMS;
 
oosmos_DebugCode(
oosmos_Debug(&pKeyer->StateMachine, true, NULL);
)
 
return pKeyer;
}
keyer.c - keyer State Machine Declaration and Initialization

3.2.5 Orthogonal State Machine Example

Figure 4. Control State Chart
1
22
23
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
[GPLv2]
 
[...]
 
struct controlTag
{
motor * m_pMotor;
pump * m_pPump;
bool m_Option1;
bool m_Option2;
 
oosmos_sStateMachine (StateMachine, oosmos_sEvent, 3);
oosmos_sLeaf StartingUp_State;
oosmos_sOrtho Operational_State;
oosmos_sOrthoRegion Operational_MotorControl_State;
oosmos_sLeaf Operational_MotorControl_Idle_State;
oosmos_sLeaf Operational_MotorControl_Moving_State;
oosmos_sOrthoRegion Operational_PumpControl_State;
oosmos_sLeaf Operational_PumpControl_Idle_State;
oosmos_sLeaf Operational_PumpControl_Pumping_State;
oosmos_sLeaf StopPressed_State;
oosmos_sLeaf Termination_State;
};
 
[...]
 
extern control * controlNew(void)
{
key * pStopKey;
key * pMoveKey;
key * pQuitKey;
 
key * pPumpKey;
key * pUpKey;
key * pDownKey;
key * pOption1Key;
key * pOption2Key;
 
oosmos_Allocate(pControl, control, 1, NULL);
 
pStopKey = keyNew('s');
pMoveKey = keyNew('m');
pQuitKey = keyNew('q');
 
pPumpKey = keyNew('p');
pUpKey = keyNew('u');
pDownKey = keyNew('d');
pOption1Key = keyNew('1');
pOption2Key = keyNew('2');
 
{
oosmos_sQueue * const pControlEventQueue = &pControl->EventQueue;
 
keySubscribePressedEvent(pStopKey, pControlEventQueue, StopPressedEvent);
keySubscribeReleasedEvent(pStopKey, pControlEventQueue, StopReleasedEvent);
keySubscribePressedEvent(pMoveKey, pControlEventQueue, MovePressedEvent);
keySubscribePressedEvent(pPumpKey, pControlEventQueue, PumpPressedEvent);
keySubscribePressedEvent(pQuitKey, pControlEventQueue, QuitPressedEvent);
keySubscribePressedEvent(pOption1Key, pControlEventQueue, Option1PressedEvent);
keySubscribePressedEvent(pOption2Key, pControlEventQueue, Option2PressedEvent);
}
 
pControl->m_pMotor = motorNew();
pControl->m_pPump = pumpNew(pUpKey, pDownKey);
pControl->m_Option1 = false;
pControl->m_Option2 = false;
 
// StateName Parent Default
// ======================================================================================================================
oosmos_StateMachineInit (pControl, StateMachine, NULL, StartingUp_State );
oosmos_LeafInit (pControl, StartingUp_State, StateMachine );
oosmos_OrthoInit (pControl, Operational_State, StateMachine );
oosmos_OrthoRegionInitNoCode(pControl, Operational_MotorControl_State, Operational_State, Operational_MotorControl_Idle_State);
oosmos_LeafInit (pControl, Operational_MotorControl_Idle_State, Operational_MotorControl_State );
oosmos_LeafInit (pControl, Operational_MotorControl_Moving_State, Operational_MotorControl_State );
oosmos_OrthoRegionInitNoCode(pControl, Operational_PumpControl_State, Operational_State, Operational_PumpControl_Idle_State );
oosmos_LeafInit (pControl, Operational_PumpControl_Idle_State, Operational_PumpControl_State );
oosmos_LeafInit (pControl, Operational_PumpControl_Pumping_State, Operational_PumpControl_State );
oosmos_LeafInit (pControl, StopPressed_State, StateMachine );
oosmos_LeafInit (pControl, Termination_State, StateMachine );
 
oosmos_DebugCode(
oosmos_Debug(&pControl->StateMachine, true, EventNames);
)
 
return pControl;
}
control.c - control State Machine Declaration and Initialization

3.3 Creating the Code for States

You implement your state-specific event handling logic in a static subroutine within your class implementation file using the name of state with the suffix of _Code. If your state does not have any logic to implement, you can use the NoCode form of the initializer function and then simply don't create a subroutine for that state.
Event handling subroutines have the same pattern. Read through the following description while referring to Code Snippet 2, below.
Line 2: The event function name is comprised of the name of the state (Idle_State in this case) followed by _Code.
Line 4: This line assigns the opaque object pointer (pObject in this case) to a local specialized object pointer. In this case pTest. If you are programming exclusively in C, you technically don't need the cast. If you are using C++, then the cast is required. Note that we picked pObject for the opaque pointer name but you can use pThis if you prefer.
Line 6: This is the event code switch statement that must take this form.
Line 7: Your event cases. As an example, we used oosmos_ENTER but you should only specify an event when you have code to handle it.
Line 8: Your event handling code. You should always return true from an event that you handle. Note that many OOSMOS API functions return true as a convenience.
Line 11: Always return false at the end of your event handling function (which indicates that this event was not handled). Be careful — not all compilers warn you if you don't return a value from a function.
1
2
3
4
5
6
7
8
9
10
11
12
13
 
static bool Idle_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
test * pTest = (test *) pObject;
 
switch (pEvent->Code) {
case oosmos_ENTER:
return oosmos_Transition(pRegion, &pTest->Pumping_State);
}
 
return false;
}
 
Code Snippet 2.

4 Async APIs

The OOSMOS Async APIs allow you to program much like you would in a threaded environment. That is, in a natural, sequential manner.
For example, if you wanted to toggle a few LEDs all at different rates using threads, you'd likely create a thread per LED so you could write the following natural pseudo-code:
1
2
3
4
5
6
7
8
9
 
while (true) {
pinOn(MyPin);
delay(OnTime);
 
pinOff(MyPin);
delay(OffTime);
}
 
Threaded toggle example
That's a lot of overhead just to toggle a few LEDs.
Alternatively, using OOSMOS Async APIs, you can program in a similarly natural way but without the memory and processor overhead that comes with using threads and their stacks. Take a look at lines 48-54 below (from the toggle class) to see how you can do the same thing in OOSMOS with no additional threads.
1
22
23
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[GPLv2]
 
[...]
 
struct toggleTag
{
oosmos_sStateMachineNoQueue(StateMachine);
oosmos_sLeaf Running_State;
 
pin * m_pPin;
int m_TimeOnMS;
int m_TimeOffMS;
};
 
static bool Running_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
toggle * pToggle = (toggle *) pObject;
 
switch (pEvent->Code) {
case oosmos_INSTATE:
oosmos_AsyncBegin(pRegion);
while (true) {
pinOn(pToggle->m_pPin);
oosmos_AsyncDelayMS(pRegion, pToggle->m_TimeOnMS);
 
pinOff(pToggle->m_pPin);
oosmos_AsyncDelayMS(pRegion, pToggle->m_TimeOffMS);
}
oosmos_AsyncEnd(pRegion);
return true;
}
 
return false;
}
 
 
toggle's use of oosmos_AsyncDelayMS.
Note that the code reads about the same as in the threaded example, but only with the modest overhead of the memory specified in lines 31 through 39.
You may wonder how this could possibly work... Well, using some C trickery, the two oosmos_AsyncDelayMS calls do not actually block the processor. Instead, they continuously return from the event handling function and then reenter it until the number of specified milliseconds have transpired. Then they drop through to the next statement.
If you are curious about the mechanics of how this is done, the technique is described in this Wikipedia article.
The full set of OOSMOS Async APIs are: See the async on the Examples page to see an example usage of each one.

4.1 Async API Rules and Restrictions

  • All use of Async APIs must be bracketed by oosmos_AsyncBegin and oosmos_AsyncEnd calls.
  • Only one asynchronous sequence may exist per oosmos_INSTATE event.
  • Any control variables (like loop counters) cannot be on the stack, they must reside in the object. For an example, see the implementation of the matrix class.
  • No switch statements can be used inside the oosmos_AsyncBegin and oosmos_AsyncEnd calls. Use if/then/else instead.

5 Events

Events allow your states to react. There are a small number of events that are predefined and generated internally from within OOSMOS, like oosmos_TIMEOUT and oosmos_ENTER. Then there are user-defined events that you create in order to use either internally or for you to subscribe to events that happen inside another object.

5.1 Predefined Events

OOSMOS predefines six events that it generates internally. The following sections explain what each of them does, along with an example.

5.1.1 oosmos_COMPLETE

This event code is generated when all inner states have entered a Final state.
Here is an example usage. First the Statechart...
Figure 5. Completion State Chart
And then the code snippet...
1
22
23
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[GPLv2]
 
[...]
 
static bool Ortho_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
test * pTest = (test *) pObject;
 
switch (pEvent->Code) {
case oosmos_COMPLETE:
return oosmos_Transition(pRegion, &pTest->CompleteState);
}
 
return false;
}
 
[...]
oosmos/Examples/Ortho/Windows/Completion.c

5.1.2 oosmos_DEFAULT

This event code is generated once for the default state before the state is entered.

5.1.3 oosmos_ENTER

The oosmos_ENTER event is generated once on entry to a state.
Here is an example usage from the toggle class:
1
22
23
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[GPLv2]
 
[...]
 
static bool On_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
toggle * pToggle = (toggle *) pObject;
 
switch (pEvent->Code) {
case oosmos_ENTER:
pinOn(pToggle->m_pPin);
return oosmos_StateTimeoutMS(pRegion, pToggle->m_TimeOnMS);
case oosmos_EXIT:
pinOff(pToggle->m_pPin);
return true;
case oosmos_TIMEOUT:
return oosmos_Transition(pRegion, &pToggle->Off_State);
}
 
return false;
}
 
[...]
oosmos/Classes/toggle.c_state

5.1.4 oosmos_EXIT

The oosmos_EXIT event is generated once on exit from a state.
Here is an example usage from the toggle class:
1
22
23
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[GPLv2]
 
[...]
 
static bool On_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
toggle * pToggle = (toggle *) pObject;
 
switch (pEvent->Code) {
case oosmos_ENTER:
pinOn(pToggle->m_pPin);
return oosmos_StateTimeoutMS(pRegion, pToggle->m_TimeOnMS);
case oosmos_EXIT:
pinOff(pToggle->m_pPin);
return true;
case oosmos_TIMEOUT:
return oosmos_Transition(pRegion, &pToggle->Off_State);
}
 
return false;
}
 
[...]
oosmos/Classes/toggle.c_state

5.1.5 oosmos_INSTATE

This event code is generated continuously for all current states in the system. You can use this event to poll for a particular condition, or you can use the powerful Async APIs.
1
22
23
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[GPLv2]
 
[...]
 
static bool Running_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
toggle * pToggle = (toggle *) pObject;
 
switch (pEvent->Code) {
case oosmos_INSTATE:
oosmos_AsyncBegin(pRegion);
while (true) {
pinOn(pToggle->m_pPin);
oosmos_AsyncDelayMS(pRegion, pToggle->m_TimeOnMS);
 
pinOff(pToggle->m_pPin);
oosmos_AsyncDelayMS(pRegion, pToggle->m_TimeOffMS);
}
oosmos_AsyncEnd(pRegion);
return true;
}
 
return false;
}
 
[...]
oosmos/Classes/toggle.c

5.1.6 oosmos_TIMEOUT

The oosmos_TIMEOUT event is generated when the state's timeout has expired. You must establish the state's timeout upon entry to the state as shown in line 49 of code Snippet 6 below.
Here is an example from the toggle class:
1
22
23
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[GPLv2]
 
[...]
 
static bool On_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
toggle * pToggle = (toggle *) pObject;
 
switch (pEvent->Code) {
case oosmos_ENTER:
pinOn(pToggle->m_pPin);
return oosmos_StateTimeoutMS(pRegion, pToggle->m_TimeOnMS);
case oosmos_EXIT:
pinOff(pToggle->m_pPin);
return true;
case oosmos_TIMEOUT:
return oosmos_Transition(pRegion, &pToggle->Off_State);
}
 
return false;
}
 
[...]
Snippet 6: oosmos/Classes/toggle.c_state

5.2 User Defined Events

With OOSMOS, you can define your own events to allow your state machine to react to events that occur in other objects. OOSMOS does this using a publish/subscribe mechanism.
To show you how to set this up, we use the switchtest class as the subscriber which will subscribe to events that occur within (and are published by) a sw (switch) object.
An example is the switchtest class in the Classes/Tests directory (see code snippet 7 below). In this scenario, switchtest wants to react to a button being pressed as well as when that button is released.
How it's done:
The events we define are OpenEvent and CloseEvent, declared in the enum on lines 33-36. Then we subscribe to the corresponding events of the sw object on lines 69-70.
When the button controlled by the switch is pressed, a CloseEvent will be sent to our switchtest object which is handled starting on line 51. Then when the button is released, the OpenEvent will be sent to us and is handled starting on line 49.
1
22
23
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
[GPLv2]
 
#includes...
 
typedef enum {
OpenEvent = 1,
ClosedEvent,
} eEvents;
 
struct switchtestTag
{
oosmos_sStateMachine(StateMachine, oosmos_sEvent, 2);
oosmos_sLeaf Idle_State;
};
 
static bool Idle_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
switchtest * pSwitchTest = (switchtest *) pObject;
 
switch (pEvent->Code) {
case OpenEvent:
return prtFormatted("Switch %p opened\n", pSwitchTest);
case ClosedEvent:
return prtFormatted("Switch %p closed\n", pSwitchTest);
}
 
return false;
}
 
extern switchtest * switchtestNew(pin * pPin)
{
oosmos_Allocate(pSwitchTest, switchtest, MAX_SWITCHTESTS, NULL);
 
// StateName Parent Default
// =====================================================
oosmos_StateMachineInit(pSwitchTest, StateMachine, NULL, Idle_State);
oosmos_LeafInit (pSwitchTest, Idle_State, StateMachine );
 
sw * pSwitch = swNew(pPin);
 
swSubscribeOpenEvent(pSwitch, &pSwitchTest->EventQueue, OpenEvent, NULL);
swSubscribeCloseEvent(pSwitch, &pSwitchTest->EventQueue, ClosedEvent, NULL);
 
return pSwitchTest;
}
Snippet 7: oosmos/Classes/Tests/switchtest.c

6 Queues

OOSMOS uses queues to serialize work for an object. OOSMOS supports two types of queues: Event queues and Work queues.
  • Event queues are used by OOSMOS to post events to an object's state machine. The state machine reacts to events based on the current state. If the current state (or any of its parents) does not handle the event, the event is thrown away. This is a fundamental difference between event queues and work queues. Events are posted to an event queue in one of two ways:
    • oosmos_SubscriberListNotify() is called by a publisher to notify all of its subscriber objects of the occurrence of an event.
    • oosmos_SendEvent() is called by an object method to push an event to its own state machine.
  • Work queues are not directly associated with states. Data remains in the queue until some logic within your object pops it off the queue in order to act on it. You can think of a work queue as you might think of a server. Work is queued up to be processed at a later time. OOSMOS does nothing implicitly; you push the work and pop it when your logic dictates.
    An example of the use of a work queue is a UART interrupt handler pushing a received character to the associated UART object for processing once the interrupt handler returns and the OOSMOS main loop runs the UART object's state machine.
An OOSMOS object can have a queue associated with its state machine in order to react to events. It can also have one or more work queues associated with it that are not state-sensitive.
In other words, work queued up to a work queue can remain there, unconsumed, until something within the object pops it. OOSMOS has multiple patterns where queues play a role:
  • Publish/Subscribe Event Pattern - This pattern allows an object to publish the occurrence of an internal event to any number of interested subscribers that may react to that event.
  • Work Queue Pattern - Where data and/or directives are pushed onto a queue for an object to process later.
  • Asynchronous Call Pattern - The ability to enqueue an event onto an event queue or work onto a work queue. Used to implement asynchronous calls.

6.1 Event Queue Example

control State Machine

6.2 Work Queue Example

1
22
23
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
188
189
190
198
199
200
201
202
203
204
205
211
212
213
219
220
221
222
223
224
225
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
[GPLv2]
 
[...]
 
static void RunReceiverStateMachine(void * pObject)
{
uart * pUART = (uart *) pObject;
uint8_t Byte;
 
DisableInterrupt(pUART);
const bool PopSuccess = oosmos_QueuePop(&pUART->m_ReceiveDataQueue, &Byte, sizeof(Byte));
EnableInterrupt(pUART);
 
if (PopSuccess) {
uart_sReceivedByteEvent ReceivedByteEvent = { { 0, NULL }, Byte };
oosmos_SubscriberListNotifyWithArgs(pUART->m_ReceivedByteEventSubscribers, ReceivedByteEvent);
}
}
 
static void RunSenderStateMachine(void * pObject)
{
uart * pUART = (uart *) pObject;
 
switch (pUART->m_SendState) {
case sendAwaitingCharToSend_State: {
const bool PopSuccess = oosmos_QueuePop(&pUART->m_SendDataQueue, &pUART->m_CharToSend, sizeof(pUART->m_CharToSend));
 
if (PopSuccess)
pUART->m_SendState = sendAwaitingReady_State;
 
break;
}
case sendAwaitingReady_State:
if (UARTTransmitterIsReady(pUART->m_PlibUartID)) {
UARTSendDataByte(pUART->m_PlibUartID, pUART->m_CharToSend);
pUART->m_SendState = sendAwaitingComplete_State;
}
 
break;
case sendAwaitingComplete_State:
if (UARTTransmissionHasCompleted(pUART->m_PlibUartID))
pUART->m_SendState = sendAwaitingCharToSend_State;
 
break;
}
}
 
static void RunStateMachine(void * pObject)
{
RunSenderStateMachine(pObject);
RunReceiverStateMachine(pObject);
}
 
static void ISR(const int UartModule)
{
uart * pUART = UartList;
int Count;
 
for (Count = 1; Count <= UartCount; pUART++, Count++) {
if (UartModule <= pUART->m_UartModule)
break;
}
 
const int PlibUartId = pUART->m_PlibUartID;
 
if (INTGetFlag(INT_SOURCE_UART_RX(PlibUartId))) {
while (UARTReceivedDataIsAvailable(PlibUartId)) {
const uint8_t Byte = UARTGetDataByte(PlibUartId);
oosmos_QueuePush(&pUART->m_ReceiveDataQueue, &Byte, sizeof(Byte));
}
 
INTClearFlag(INT_SOURCE_UART_RX(PlibUartId));
}
}
 
static int GetPeripheralClock(void)
{...}
 
extern void uartPrintf(uart * pUART, const char * pFormat, ...)
{...}
 
extern void uartSendChar(uart * pUART, const char Char)
{
oosmos_QueuePush(&pUART->m_SendDataQueue, &Char, sizeof(Char));
}
 
extern void uartSendString(uart * pUART, const char * pString)
{...}
 
extern void uartStart(uart * pUART)
{...}
 
//
// 'UartModule' is 1, 2, etc.
//
extern uart * uartNew(const int UartModule, const int BaudRate)
{
[...]
 
oosmos_QueueConstruct(&pUART->m_SendDataQueue, pUART->m_SendDataQueueData);
 
oosmos_QueueConstruct(&pUART->m_ReceiveDataQueue, pUART->m_ReceiveDataQueueData);
oosmos_SubscriberListInit(pUART->m_ReceivedByteEventSubscribers);
 
oosmos_RegisterActiveObject(pUART, RunStateMachine, &pUART->m_ActiveObject);
 
return pUART;
}
 
extern void uartSubscribe(uart * pUART, oosmos_sQueue * pQueue, const int EventCode, void * pContext)
{
oosmos_SubscriberListAdd(pUART->m_ReceivedByteEventSubscribers, pQueue, EventCode, pContext);
}
 
[Vectors]
uart Send and Receive Work Queues

6.3 Asynchronous Calls

The following code shows how to create an asynchronous call. Note that this example shows a state-aware call in that if pumpOn is called when the pump is in the Pumping state, the call will have no effect.
Alternatively, you could use oosmos_QueuePush to push work to a queue that is later consumed by an oosmos_INSTATE handler in an appropriate state.
1
22
23
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
129
130
131
132
133
134
135
136
137
138
[GPLv2]
 
[...]
 
static bool Idle_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
pump * pPump = (pump *) pObject;
 
switch (pEvent->Code) {
case StartEvent:
return oosmos_Transition(pRegion, &pPump->Pumping_State);
}
 
return false;
}
 
static bool Pumping_State_Code(void * pObject, oosmos_sRegion * pRegion, const oosmos_sEvent * pEvent)
{
pump * pPump = (pump *) pObject;
 
switch (pEvent->Code) {
case StopEvent:
return oosmos_Transition(pRegion, &pPump->Idle_State);
 
case UpPressedEvent:
pPump->PumpSpeed = oosmos_Min(10, pPump->PumpSpeed+1);
printf("pump: PUMPING SPEED %d...\n", pPump->PumpSpeed);
return true;
 
case DownPressedEvent:
pPump->PumpSpeed = oosmos_Max(1, pPump->PumpSpeed-1);
printf("pump: PUMPING SPEED %d...\n", pPump->PumpSpeed);
return true;
 
case oosmos_INSTATE:
oosmos_AyncBegin(pRegion);
while (true) {
printf("pump: PUMPING...\n");
oosmos_AyncDelayMS(pRegion, (10-pPump->PumpSpeed) * 200);
}
oosmos_AyncEnd(pRegion);
return true;
}
 
return false;
}
 
extern pump * pumpNew(key * pUpKey, key * pDownKey)
{...}
 
extern void pumpOn(pump * pMotor)
{
oosmos_SendEvent(pMotor, StartEvent);
}
 
extern void pumpOff(pump * pMotor)
{
oosmos_SendEvent(pMotor, StopEvent);
}
Snippet 8. Asynchronous Call Pattern
Figure 6. Publish/Subscribe Event Queue Pattern

7 The bool Data Type

We like to use the bool data type even in "old" C, so we use the following preprocessor logic in oosmos.h to define it. If you are using C99, then you'll want to specify the oosmos_HAS_BOOL preprocessor constant on your compilation command line.
1
2
3
4
5
6
7
8
9
10
11
12
 
#if !defined(__cplusplus)
#if !defined(oosmos_HAS_BOOL)
#undef bool
#undef false
#undef true
#define bool char
#define false 0
#define true 1
#endif
#endif
 
bool datatype definition logic for C

8 Object Allocation

Commonly, dynamic memory allocation is done using malloc and free in C programs and new and delete in C++ programs. There are a number of problems with using these standard library functions, namely:
  1. Sudden and unpredictable memory exhaustion.
  2. Unpredictable allocation and deallocation times due to memory fragmentation.
  3. Reuse of memory blocks could lead to security concerns unless freed memory is cleared.
  4. Careless coding can lead to memory leaks or references to freed memory.
  5. In memory constrained environments:
    1. Non-trivial functions like malloc and free take up precious code space.
    2. Managing variable-sized heap space requires additional housekeeping memory.
    3. To avoid sudden memory exhaustion, typically much more memory is allocated to the heap than is actually required, which is wasteful.

8.1 OOSMOS Object Allocation

OOSMOS uses a different dynamic memory allocation scheme which is ideal for memory constrained environments. It has the following attributes and benefits:
  1. All allocation is done at the beginning of the program. There are no calls to "free" memory.
  2. Allows you to allocate exactly the number of objects required. No wasted space.
  3. Allocation is done in a one-line macro.
  4. Hides the space allocation within the scope of the object's special "New" function.
  5. Extremely fast, constant-time allocation.

8.1.1 Example

The following code snippet contains a typical OOSMOS object allocation. In this example, we implement the "New" function for the toggle object. It behaves very much like the new function in C++, except that we will orchestrate the allocation of memory; in C++ that is handled automatically by the language and run-time.
Per our naming conventions, the name of this external function is toggleNew. Its job is to allocate an object of type toggle, then initialize the members of the object.
The allocation macro is called oosmos_Allocate, which takes four arguments:
  1. pToggle is the name of pointer variable that will be set to point to the allocated object.
  2. toggle is the type of the object to be allocated.
  3. toggleMAX specifies the number of objects to pre-allocate.
  4. OutOfMemory which is set to NULL in this example, is the address of a subroutine to call if memory is exhausted. NULL specifies that oosmos_Allocate should loop forever when memory is exhausted.
1
2
3
4
5
6
7
8
9
10
15
16
19
20
21
22
23
 
#ifndef toggleMAX
#define toggleMAX 4
#endif
 
extern toggle * toggleNew(pin * pPin, const int TimeOnMS, const int TimeOffMS)
{
oosmos_Allocate(pToggle, toggle, toggleMAX, NULL);
 
[State Machine Initialization]...
 
[Member Initialization]...
 
return pToggle;
}
 
 
Object Allocation and Initialization

8.1.2 Alternative Allocation

If you are not in a memory-constrained environment where every byte of RAM matters, then you can replace the oosmos_Allocate with a call to malloc instead. Then, for symmetry, you'd also want to create a toggleDelete function, which accepts the object pointer as the only argument, and then calls free.

9 The OOSMOS Name Space

OOSMOS is a library; possibly one of many libraries in an application. Therefore it is important that OOSMOS not pollute the global namespace.
Accordingly, we prefix each externally visible name with either oosmos_ or OOSMOS_. Names that start with oosmos_ are part of the formal user-facing API. Names that begin with OOSMOS_ are names that are part of the internal private API and should not be used in your applications.
Copyright © 2014-2016  OOSMOS, LLC