{"id":21274152,"url":"https://github.com/endurodave/c_statemachinewiththreads","last_synced_at":"2025-07-11T06:33:45.376Z","repository":{"id":113549595,"uuid":"173483710","full_name":"endurodave/C_StateMachineWithThreads","owner":"endurodave","description":"C Language State Machine with Threads","archived":false,"fork":false,"pushed_at":"2024-11-13T14:23:31.000Z","size":234,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-11-13T15:27:51.123Z","etag":null,"topics":["asynchronous-callbacks","c","c-language","cross-platform","multithreading","state-machine","threads"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endurodave.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-02T18:24:56.000Z","updated_at":"2024-11-13T14:23:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"dcbbd418-464d-4b55-8f41-5469c25f0c70","html_url":"https://github.com/endurodave/C_StateMachineWithThreads","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FC_StateMachineWithThreads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FC_StateMachineWithThreads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FC_StateMachineWithThreads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endurodave%2FC_StateMachineWithThreads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endurodave","download_url":"https://codeload.github.com/endurodave/C_StateMachineWithThreads/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225701487,"owners_count":17510542,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous-callbacks","c","c-language","cross-platform","multithreading","state-machine","threads"],"created_at":"2024-11-21T09:19:10.150Z","updated_at":"2025-07-11T06:33:45.370Z","avatar_url":"https://github.com/endurodave.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"![License MIT](https://img.shields.io/github/license/BehaviorTree/BehaviorTree.CPP?color=blue)\n[![conan Ubuntu](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_ubuntu.yml/badge.svg)](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_ubuntu.yml)\n[![conan Ubuntu](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_clang.yml/badge.svg)](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_clang.yml)\n[![conan Windows](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/endurodave/C_StateMachineWithThreads/actions/workflows/cmake_windows.yml)\n\n# C Language State Machine with Threads\n\nA framework combining C language state machines and multicast asynchronous callbacks.\n\n# Table of Contents\n\n- [C Language State Machine with Threads](#c-language-state-machine-with-threads)\n- [Table of Contents](#table-of-contents)\n- [Introduction](#introduction)\n  - [Related repositories](#related-repositories)\n- [Getting Started](#getting-started)\n- [SelfTestEngine](#selftestengine)\n- [CentrifugeTest](#centrifugetest)\n- [Timer](#timer)\n- [Run-Time](#run-time)\n- [Conclusion](#conclusion)\n\n# Introduction\n\n\u003cp\u003eA software-based Finite State Machines (FSM) is an implementation method used to decompose a design into states and events. Simple embedded devices with no operating system employ single threading such that the state machines run on a single \u0026ldquo;thread\u0026rdquo;. More complex systems use multithreading to divvy up the processing.\u003c/p\u003e\n\n\u003cp\u003eMany FSM implementations exist including one I wrote about here entitled \u0026ldquo;\u003cstrong\u003e\u003ca href=\"https://github.com/endurodave/C_StateMachine\"\u003eState Machine Design in C\u003c/a\u003e\u003c/strong\u003e\u0026rdquo;. The article covers how to create C state machines. What is missing, however, is how to integrate multiple state machines into the context of a multithreaded environment.\u003c/p\u003e\n\n\u003cp\u003e\u0026ldquo;\u003cstrong\u003e\u003ca href=\"https://github.com/endurodave/C_AsyncCallback\"\u003eAsynchronous Callbacks in C\u003c/a\u003e\u003c/strong\u003e\u0026rdquo; is another article I wrote. This design provides a simple, portable callback mechanism that handles the low-level details of asynchronously invoking a callback with event data on a client-specified thread of control.\u003c/p\u003e\n\n\u003cp\u003eThis article combines the two previously described techniques, state machines and asynchronous callbacks, into a single project. In the previous articles, it may not be readily apparent using simple examples how multiple state machines coordinate activities and dispatch events to each other. The goal for the article is to provide a complete working project with threads, timers, events, and state machines all working together. To illustrate the concept, the example project implements a state-based self-test engine utilizing asynchronous communication between threads.\u003c/p\u003e\n\n\u003cp\u003eThe primary focus is on how to combine the state machine and asynchronous callbacks into a single framework.\u003c/p\u003e\n\n## Related repositories\n\n\u003cul\u003e\n\t\u003cli\u003e\u003ca href=\"https://github.com/endurodave/C_StateMachine\"\u003eState Machine Design in C\u003c/a\u003e - A compact C language finite state machine (FSM) implementation.\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/C_AsyncCallback\"\u003eAsynchronous Callbacks in C\u003c/a\u003e - Asynchronous callbacks library in C.\u003c/li\u003e\n\t\u003cli\u003e\u003ca href=\"https://github.com/endurodave/C_Allocator\"\u003eA Fixed Block Allocator in C\u003c/a\u003e - A C language fixed block memory allocator.\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/StateMachine\"\u003eState Machine Design in C++\u003c/a\u003e A C++ finite state machine (FSM) implementation.\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/endurodave/StateMachineWithThreads\"\u003eC++ State Machine with Threads\u003c/a\u003e - A C++ state machine integrated with an asynchronous callback library.\u003c/li\u003e\n\u003c/ul\u003e\n\n# Getting Started\n\n[CMake](https://cmake.org/) is used to create the project build files on any Windows or Linux machine. \n\n1. Clone the repository.\n2. From the repository root, run the following CMake command:   \n   `cmake -B Build .`\n3. Build and run the project within the `Build` directory. \n\n# SelfTestEngine\n\n\u003cp\u003e\u003ccode\u003eSelfTestEngine\u003c/code\u003e is thread-safe and the main point of contact for client\u0026rsquo;s utilizing the self-test subsystem. \u003ccode\u003eCentrifugeTest\u003c/code\u003e and \u003ccode\u003ePressureTest\u003c/code\u003e are members of \u003ccode\u003eSelfTestEngine\u003c/code\u003e. \u003ccode\u003eSelfTestEngine\u003c/code\u003e is responsible for sequencing the individual self-tests in the correct order as shown in the state diagram below. \u0026nbsp;\u003c/p\u003e\n\n\u003cp style=\"text-align: center\"\u003e\u003cimg height=\"370px\" src=\"Figure_2_1.png\" width=\"530px\" /\u003e\u003c/p\u003e\n\n\u003cp style=\"text-align: center\"\u003e\u003cstrong\u003eFigure 2: SelfTestEngine State Machine\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eThe \u003ccode\u003eSTE_Start\u003c/code\u003e event initiates the self-test engine.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\n// Start external event\nEVENT_DEFINE(STE_Start, NoEventData)\n{\n    // Given the Start event, transition to a new state based upon \n    // the current state of the state machine\n    BEGIN_TRANSITION_MAP                                // - Current State -\n        TRANSITION_MAP_ENTRY(ST_START_CENTRIFUGE_TEST)  // ST_Idle\n        TRANSITION_MAP_ENTRY(CANNOT_HAPPEN)             // ST_Completed\n        TRANSITION_MAP_ENTRY(CANNOT_HAPPEN)             // ST_Failed\n        TRANSITION_MAP_ENTRY(EVENT_IGNORED)             // ST_StartCentrifugeTest\n        TRANSITION_MAP_ENTRY(EVENT_IGNORED)             // ST_StartPressureTest\n    END_TRANSITION_MAP(SelfTestEngine, pEventData)\n}\n\u003c/pre\u003e\n\n\u003cp\u003e\u003ccode\u003eSelfTestEngine\u003c/code\u003e has one public event function, \u003ccode\u003eSTE_Start()\u003c/code\u003e, that starts the self-tests. \u003ccode\u003eSTE_StatusCb\u003c/code\u003e is an asynchronous callback allowing client\u0026rsquo;s to register for status updates during testing.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\n#include \"DataTypes.h\"\n#include \"StateMachine.h\"\n#include \"callback.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct\n{\n    BOOL testActive;\n} SelfTestStatus;\n\n// Declare public callback interfaces\nCB_DECLARE(STE_StatusCb, const SelfTestStatus*)\nCB_DECLARE(STE_CompletedCb, void*)\nCB_DECLARE(STE_FailedCb, void*)\n\n// Declare the private instance of SelfTestEngine state machine\nSM_DECLARE(SelfTestEngineSM)\n\n// State machine event functions\nEVENT_DECLARE(STE_Start, NoEventData)\nEVENT_DECLARE(STE_Cancel, NoEventData)\n\nvoid STE_Init();\u003c/pre\u003e\n\n\u003cp\u003eAs mentioned previously, the \u003ccode\u003eSelfTestEngine\u003c/code\u003e registers for asynchronous callbacks from each sub self-tests as shown below.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid STE_Init()\n{\n    // Register with CentrifugeTest and PressureTest state machines\n    CB_Register(CFG_CompletedCb, STE_CompletedCallback, DispatchCallbackThread1, NULL);\n    CB_Register(CFG_FailedCb, STE_FailedCallback, DispatchCallbackThread1, NULL);\n    CB_Register(PRE_CompletedCb, STE_CompletedCallback, DispatchCallbackThread1, NULL);\n    CB_Register(PRE_FailedCb, STE_FailedCallback, DispatchCallbackThread1, NULL);\n}\u003c/pre\u003e\n\n\u003cp\u003eOne might ask why the state machines use asynchronous callbacks. If the state machines are on the same thread, why not use a normal, synchronous callback instead? The problem to prevent is a callback into a currently executing state machine, that is, the call stack wrapping back around into the same class instance. For example, the following call sequence should be prevented: \u003ccode\u003eSelfTestEngine\u003c/code\u003e calls \u003ccode\u003eCentrifugeTest\u003c/code\u003e calls back \u003ccode\u003eSelfTestEngine\u003c/code\u003e. An asynchronous callback allows the stack to unwind and prevents this unwanted behavior.\u003c/p\u003e\n\n# CentrifugeTest\n\n\u003cp\u003eThe \u003ccode\u003eCentrifugeTest\u003c/code\u003e state machine diagram show below implements the centrifuge self-test described in \u0026quot;\u003ca href=\"https://github.com/endurodave/C_StateMachine\"\u003e\u003cb\u003eState Machine Design in C\u003c/b\u003e\u003c/a\u003e\u0026quot;. The difference here is that the \u003ccode\u003eTimer\u003c/code\u003e class is used to provide \u003ccode\u003ePoll\u003c/code\u003e events via asynchronous callbacks.\u003c/p\u003e\n\n\u003cp style=\"text-align: center\"\u003e\u003cimg height=\"769px\" src=\"Figure_3_1.png\" width=\"524px\" /\u003e\u003c/p\u003e\n\n\u003cp style=\"text-align: center\"\u003e\u003cstrong\u003eFigure 3: CentrifugeTest State Machine\u003c/strong\u003e\u003c/p\u003e\n\n# Timer\n\n\u003cp\u003eThe \u003ccode\u003eTimer\u003c/code\u003e class provides a common mechanism to receive function callbacks by registering with \u003ccode\u003eExpired\u003c/code\u003e. \u003ccode\u003eTMR_Start()\u003c/code\u003e starts the callbacks at a particular interval. \u003ccode\u003eTMR_Stop()\u003c/code\u003e stops the callbacks.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nvoid TMR_Init();\nvoid TMR_Term();\nBOOL TMR_Start(CB_CallbackFuncType cbFunc, CB_DispatchCallbackFuncType cbDispatchFunc, DWORD timeout);\nvoid TMR_Stop(CB_CallbackFuncType cbFunc, CB_DispatchCallbackFuncType cbDispatchFunc);\n\n// Called periodically by a thread to process all timers\nvoid TMR_ProcessTimers();\u003c/pre\u003e\n\n# Run-Time\n\n\u003cp\u003eThe program\u0026rsquo;s \u003ccode\u003emain()\u003c/code\u003e function is shown below. It creates the threads, registers for callbacks from \u003ccode\u003eSelfTestEngine\u003c/code\u003e, then calls \u003ccode\u003eSTE_Start()\u003c/code\u003e to start the self-tests.\u003c/p\u003e\n\n\u003cpre lang=\"c++\"\u003e\nint main()\n{\n    // Initialize modules\n    ALLOC_Init();\n    TMR_Init();\n\tCB_Init();\n    STE_Init();\n    CreateThreads();\n\n    // Register for SelfTestEngine callbacks on DispatchCallbackThread2\n    CB_Register(STE_StatusCb, STE_StatusCallback, DispatchCallbackThread2, NULL);\n    CB_Register(STE_CompletedCb, STE_CompletedCallback, DispatchCallbackThread2, NULL);\n    CB_Register(STE_FailedCb, STE_FailedCallback, DispatchCallbackThread2, NULL);\n\n    // Start SelfTestEngine\n    SM_Event(SelfTestEngineSM, STE_Start, NULL);\n\n    // Wait for SelfTestEngine to complete \n    while (!selfTestEngineCompleted)\n        this_thread::sleep_for(1s);\n\n    // Cleanup before exit\n    ExitThreads();\n    CB_Term();\n    TMR_Term();\n    ALLOC_Term();\n\n    this_thread::sleep_for(1s);\n\n    return 0;\n}\u003c/pre\u003e\n\n# Conclusion\n\n\u003cp\u003eThe C state machine and asynchronous callbacks implementations can be used separately. Each is useful unto itself. However, combining the two offers a novel framework for multithreaded state-driven application development. The article has shown how to coordinate the behavior of state machines when multiple threads are used,\u0026nbsp;which may not be entirely obvious when looking at simplistic, single threaded examples.\u003c/p\u003e\n\n\u003cp\u003eI\u0026rsquo;ve successfully used ideas similar to this on many different PC and embedded projects. The code is portable to any platform with a small amount of effort. I particularly like idea of asynchronous callbacks because it effectively hides inter-thread communication and the organization of the state machines makes creating and maintaining self-tests easy.\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fc_statemachinewiththreads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendurodave%2Fc_statemachinewiththreads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendurodave%2Fc_statemachinewiththreads/lists"}