system/smf: Port SMF .c/.h files to NuttX

This commit add state machine framework lib to the NuttX project. Also an example is added to help users understand and use this feature.

Changes: Added some files. No impact in other features are expected.

Adjust SMF macro names

Fix issues requested during MR review

update kconfig

fix ci-cd issue

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
This commit is contained in:
Felipe MdeO 2026-01-01 16:46:27 -03:00 committed by Matteo Golin
parent 4f93ec0a43
commit ca11a7e093
12 changed files with 1913 additions and 0 deletions

View file

@ -0,0 +1,18 @@
# ##############################################################################
# apps/examples/hsm_psicc2/CMakeLists.txt
#
# SPDX-License-Identifier: Apache-2.0
# ##############################################################################
if(CONFIG_EXAMPLES_SMF)
nuttx_add_application(
NAME
${CONFIG_EXAMPLES_SMF_PROGNAME}
SRCS
smf_main.c
hsm_psicc2_thread.c
STACKSIZE
${CONFIG_EXAMPLES_SMF_STACKSIZE}
PRIORITY
${CONFIG_EXAMPLES_SMF_PRIORITY})
endif()

37
examples/smf/Kconfig Normal file
View file

@ -0,0 +1,37 @@
#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#
config EXAMPLES_SMF
tristate "State Machine Framework PSICC2 demo (HSM)"
default n
depends on NSH_BUILTIN_APPS
depends on SYSTEM_SMF
depends on SYSTEM_SMF_ANCESTOR_SUPPORT
depends on SYSTEM_SMF_INITIAL_TRANSITION
if EXAMPLES_SMF
config EXAMPLES_SMF_PROGNAME
string "Program name"
default "hsm_psicc2"
config EXAMPLES_SMF_PRIORITY
int "Priority"
default 100
config EXAMPLES_SMF_STACKSIZE
int "Stack size"
default 2048
config EXAMPLES_SMF_EVENT_QUEUE_SIZE
int "Event queue size"
default 10
config EXAMPLES_SMF_MQ_NAME
string "Message queue name"
default "/hsm_psicc2_mq"
endif

25
examples/smf/Make.defs Normal file
View file

@ -0,0 +1,25 @@
############################################################################
# apps/examples/hello/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifneq ($(CONFIG_EXAMPLES_SMF),)
CONFIGURED_APPS += $(APPDIR)/examples/smf
endif

37
examples/smf/Makefile Normal file
View file

@ -0,0 +1,37 @@
############################################################################
# apps/examples/hsm_psicc2/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
# HSM PSICC2 (SMF demo) built-in application info
PROGNAME = $(CONFIG_EXAMPLES_SMF_PROGNAME)
PRIORITY = $(CONFIG_EXAMPLES_SMF_PRIORITY)
STACKSIZE = $(CONFIG_EXAMPLES_SMF_STACKSIZE)
MODULE = $(CONFIG_EXAMPLES_SMF)
# HSM PSICC2 Example
MAINSRC = smf_main.c
CSRCS += hsm_psicc2_thread.c
include $(APPDIR)/Application.mk

View file

@ -0,0 +1,626 @@
/****************************************************************************
* apps/examples/smf/hsm_psicc2_thread.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2024 Glenn Andrews
* State Machine example copyright (c) Miro Samek
*
* Implementation of the statechart in Figure 2.11 of
* Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek
* https://www.state-machine.com/psicc2
* Used with permission of the author.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <errno.h>
#include <fcntl.h>
#include <mqueue.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <system/smf.h>
#include "hsm_psicc2_thread.h"
/****************************************************************************
* Private Types
****************************************************************************/
struct s_object
{
struct smf_ctx ctx; /* MUST be first (matches SMF_CTX(obj)) */
struct hsm_psicc2_event event;
int foo;
};
enum demo_states
{
STATE_INITIAL,
STATE_S,
STATE_S1,
STATE_S2,
STATE_S11,
STATE_S21,
STATE_S211
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct s_object s_obj;
static bool g_started;
static const struct smf_state demo_states[];
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* STATE_INITIAL
****************************************************************************/
static void initial_entry(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
obj->foo = false;
}
static enum smf_state_result initial_run(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
return SMF_EVENT_PROPAGATE;
}
static void initial_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* STATE_S
****************************************************************************/
static void s_entry(void *o)
{
(void)o;
printf("[psicc2] %s", __func__);
}
static enum smf_state_result s_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_E:
printf("[psicc2] %s received EVENT_E", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]);
break;
case EVENT_I:
if (obj->foo)
{
printf("[psicc2] %s received EVENT_I and set foo false\n",
__func__);
obj->foo = false;
}
else
{
printf("[psicc2] %s received EVENT_I and did nothing\n",
__func__);
}
return SMF_EVENT_HANDLED;
case EVENT_TERMINATE:
printf("[psicc2] %s received EVENT_TERMINATE. Terminating\n",
__func__);
smf_set_terminate(SMF_CTX(obj), -1);
break;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s_exit(void *o)
{
(void)o;
printf("%s", __func__);
}
/****************************************************************************
* STATE_S1
****************************************************************************/
static void s1_entry(void *o)
{
(void)o;
printf("%s", __func__);
}
static enum smf_state_result s1_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_A:
printf("[psicc2] %s received EVENT_A\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]);
break;
case EVENT_B:
printf("[psicc2] %s received EVENT_B\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]);
break;
case EVENT_C:
printf("[psicc2] %s received EVENT_C\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S2]);
break;
case EVENT_D:
if (!obj->foo)
{
printf("[psicc2] %s received EVENT_D and acted on it\n",
__func__);
obj->foo = true;
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]);
}
else
{
printf("[psicc2] %s received EVENT_D and ignored it\n",
__func__);
}
break;
case EVENT_F:
printf("[psicc2] %s received EVENT_F\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S211]);
break;
case EVENT_I:
printf("[psicc2] %s received EVENT_I\n", __func__);
return SMF_EVENT_HANDLED;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s1_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* STATE_S11
****************************************************************************/
static void s11_entry(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
static enum smf_state_result s11_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_D:
if (obj->foo)
{
printf("[psicc2] %s received EVENT_D and acted upon it\n",
__func__);
obj->foo = false;
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]);
}
else
{
printf("[psicc2] %s received EVENT_D and ignored it\n",
__func__);
}
break;
case EVENT_G:
printf("[psicc2] %s received EVENT_G\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]);
break;
case EVENT_H:
printf("[psicc2] %s received EVENT_H\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]);
break;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s11_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* STATE_S2
****************************************************************************/
static void s2_entry(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
static enum smf_state_result s2_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_C:
printf("[psicc2] %s received EVENT_C\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]);
break;
case EVENT_F:
printf("[psicc2] %s received EVENT_F\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S11]);
break;
case EVENT_I:
if (!obj->foo)
{
printf("[psicc2] %s received EVENT_I and set foo true\n",
__func__);
obj->foo = true;
return SMF_EVENT_HANDLED;
}
else
{
printf("[psicc2] %s received EVENT_I and did nothing\n",
__func__);
}
break;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s2_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* STATE_S21
****************************************************************************/
static void s21_entry(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
static enum smf_state_result s21_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_A:
printf("[psicc2] %s received EVENT_A\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]);
break;
case EVENT_B:
printf("[psicc2] %s received EVENT_B\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S211]);
break;
case EVENT_G:
printf("[psicc2] %s received EVENT_G\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S1]);
break;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s21_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* STATE_S211
****************************************************************************/
static void s211_entry(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
static enum smf_state_result s211_run(void *o)
{
struct s_object *obj = (struct s_object *)o;
printf("[psicc2] %s\n", __func__);
switch (obj->event.event_id)
{
case EVENT_D:
printf("[psicc2] %s received EVENT_D\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S21]);
break;
case EVENT_H:
printf("[psicc2] %s received EVENT_H\n", __func__);
smf_set_state(SMF_CTX(obj), &demo_states[STATE_S]);
break;
default:
break;
}
return SMF_EVENT_PROPAGATE;
}
static void s211_exit(void *o)
{
(void)o;
printf("[psicc2] %s\n", __func__);
}
/****************************************************************************
* State Table
****************************************************************************/
static const struct smf_state demo_states[] =
{
[STATE_INITIAL] = SMF_CREATE_STATE(
initial_entry,
initial_run,
initial_exit,
NULL,
&demo_states[STATE_S2]
),
[STATE_S] = SMF_CREATE_STATE(
s_entry,
s_run,
s_exit,
&demo_states[STATE_INITIAL],
&demo_states[STATE_S11]
),
[STATE_S1] = SMF_CREATE_STATE(
s1_entry,
s1_run,
s1_exit,
&demo_states[STATE_S],
&demo_states[STATE_S11]
),
[STATE_S2] = SMF_CREATE_STATE(
s2_entry,
s2_run,
s2_exit,
&demo_states[STATE_S],
&demo_states[STATE_S211]
),
[STATE_S11] = SMF_CREATE_STATE(
s11_entry,
s11_run,
s11_exit,
&demo_states[STATE_S1],
NULL
),
[STATE_S21] = SMF_CREATE_STATE(
s21_entry,
s21_run,
s21_exit,
&demo_states[STATE_S2],
&demo_states[STATE_S211]
),
[STATE_S211] = SMF_CREATE_STATE(
s211_entry,
s211_run,
s211_exit,
&demo_states[STATE_S21],
NULL
)
};
/****************************************************************************
* Name: hsm_psicc2_thread_main
****************************************************************************/
static int hsm_psicc2_thread_main(int argc, char **argv)
{
struct mq_attr attr;
mqd_t mq;
ssize_t n;
int rc;
(void)argc;
(void)argv;
memset(&attr, 0, sizeof(attr));
attr.mq_maxmsg = CONFIG_EXAMPLES_SMF_EVENT_QUEUE_SIZE;
attr.mq_msgsize = sizeof(struct hsm_psicc2_event);
mq = mq_open(CONFIG_EXAMPLES_SMF_MQ_NAME, O_CREAT | O_RDONLY, 0666, &attr);
if (mq == (mqd_t)-1)
{
printf("psicc2][ERR] mq_open(%s) failed: %d\n",
CONFIG_EXAMPLES_SMF_MQ_NAME, errno);
return -1;
}
printf("[psicc2] State Machine thread started\n");
memset(&s_obj, 0, sizeof(s_obj));
smf_set_initial(SMF_CTX(&s_obj), &demo_states[STATE_INITIAL]);
while (true)
{
n = mq_receive(mq, (char *)&s_obj.event, sizeof(s_obj.event), NULL);
if (n < 0)
{
printf("psicc2][ERR] mq_receive failed: %d\n", errno);
continue;
}
rc = smf_run_state(SMF_CTX(&s_obj));
if (rc != 0)
{
printf("[psicc2] SMF terminated (rc=%d). Exiting thread\n", rc);
break;
}
}
mq_close(mq);
mq_unlink(CONFIG_EXAMPLES_SMF_MQ_NAME);
g_started = false;
memset(&s_obj, 0, sizeof(s_obj));
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: hsm_psicc2_thread_start
****************************************************************************/
int hsm_psicc2_thread_start(void)
{
int pid;
if (g_started)
{
return 0;
}
pid = task_create("psicc2_thread",
CONFIG_EXAMPLES_SMF_PRIORITY,
CONFIG_EXAMPLES_SMF_STACKSIZE,
hsm_psicc2_thread_main,
NULL);
if (pid < 0)
{
printf("psicc2][ERR] task_create failed: %d", errno);
return -1;
}
g_started = true;
return 0;
}
/****************************************************************************
* Name: hsm_psicc2_post_event
****************************************************************************/
int hsm_psicc2_post_event(uint32_t event_id)
{
struct hsm_psicc2_event ev;
mqd_t mq;
int rc;
ev.event_id = event_id;
mq = mq_open(CONFIG_EXAMPLES_SMF_MQ_NAME, O_WRONLY | O_NONBLOCK);
if (mq == (mqd_t)-1)
{
printf("psicc2][ERR] mq_open(O_WRONLY) failed: %d "
"(did you run 'hsm_psicc2 start'?)\n",
errno);
return -1;
}
rc = mq_send(mq, (const char *)&ev, sizeof(ev), 0);
if (rc < 0)
{
printf("psicc2][ERR] mq_send failed: %d\n", errno);
mq_close(mq);
return -1;
}
mq_close(mq);
return 0;
}

View file

@ -0,0 +1,89 @@
/****************************************************************************
* apps/examples/smf/hsm_psicc2_thread.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2024 Glenn Andrews
* State Machine example copyright (c) Miro Samek
*
* Implementation of the statechart in Figure 2.11 of
* Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek
* https://www.state-machine.com/psicc2
* Used with permission of the author.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H
#define __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Public Types
****************************************************************************/
struct hsm_psicc2_event
{
uint32_t event_id;
};
enum demo_events
{
EVENT_A,
EVENT_B,
EVENT_C,
EVENT_D,
EVENT_E,
EVENT_F,
EVENT_G,
EVENT_H,
EVENT_I,
EVENT_TERMINATE
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
int hsm_psicc2_thread_start(void);
int hsm_psicc2_post_event(uint32_t event_id);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __APPS_EXAMPLES_SMF_HSM_PSICC2_THREAD_H */

127
examples/smf/smf_main.c Normal file
View file

@ -0,0 +1,127 @@
/****************************************************************************
* apps/examples/smf/smf_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2024 Glenn Andrews
* State Machine example copyright (c) Miro Samek
*
* Implementation of the statechart in Figure 2.11 of
* Practical UML Statecharts in C/C++, 2nd Edition by Miro Samek
* https://www.state-machine.com/psicc2
* Used with permission of the author.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "hsm_psicc2_thread.h"
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: usage
****************************************************************************/
static void usage(void)
{
printf("Usage:\n");
printf(" hsm_psicc2 start\n");
printf(" hsm_psicc2 event <A..I>\n");
printf(" hsm_psicc2 terminate\n");
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main
****************************************************************************/
int main(int argc, char *argv[])
{
if (argc < 2)
{
usage();
return 1;
}
if (strcmp(argv[1], "start") == 0)
{
printf("State Machine Framework Demo\n");
printf("See PSiCC2 Fig 2.11 for the statechart\n");
printf("https://www.state-machine.com/psicc2\n\n");
return hsm_psicc2_thread_start();
}
if (strcmp(argv[1], "terminate") == 0)
{
return hsm_psicc2_post_event(EVENT_TERMINATE);
}
if (strcmp(argv[1], "event") == 0)
{
int c;
if (argc < 3 || argv[2] == NULL || argv[2][0] == '\0')
{
usage();
return 1;
}
c = toupper((unsigned char)argv[2][0]);
switch (c)
{
case 'A':
return hsm_psicc2_post_event(EVENT_A);
case 'B':
return hsm_psicc2_post_event(EVENT_B);
case 'C':
return hsm_psicc2_post_event(EVENT_C);
case 'D':
return hsm_psicc2_post_event(EVENT_D);
case 'E':
return hsm_psicc2_post_event(EVENT_E);
case 'F':
return hsm_psicc2_post_event(EVENT_F);
case 'G':
return hsm_psicc2_post_event(EVENT_G);
case 'H':
return hsm_psicc2_post_event(EVENT_H);
case 'I':
return hsm_psicc2_post_event(EVENT_I);
default:
printf("Invalid event '%c'\n", c);
return 1;
}
}
usage();
return 1;
}

267
include/system/smf.h Normal file
View file

@ -0,0 +1,267 @@
/****************************************************************************
* apps/include/system/smf.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __APPS_INCLUDE_SYSTEM_SMF_H
#define __APPS_INCLUDE_SYSTEM_SMF_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <stdbool.h>
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
# ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION
# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
{ \
.entry = (_entry), \
.run = (_run), \
.exit = (_exit), \
.parent = (_parent), \
.initial = (_initial), \
}
# else
# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
{ \
.entry = (_entry), \
.run = (_run), \
.exit = (_exit), \
.parent = (_parent), \
}
# endif
#else
# define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
{ \
.entry = (_entry), \
.run = (_run), \
.exit = (_exit), \
}
#endif
#define SMF_CTX(o) ((struct smf_ctx *)(o))
/****************************************************************************
* Public Types
****************************************************************************/
enum smf_state_result
{
SMF_EVENT_HANDLED,
SMF_EVENT_PROPAGATE
};
typedef void (*state_method)(void *obj);
typedef enum smf_state_result (*state_execution)(void *obj);
struct smf_state
{
/* Optional method that will be run when this state is entered. */
state_method entry;
/* Optional method that will be run repeatedly during the state machine
* loop.
*/
state_execution run;
/* Optional method that will be run when this state exits. */
state_method exit;
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
/* Optional parent state that contains common entry/run/exit
* implementation among various child states.
* entry: Parent function executes BEFORE child function.
* run: Parent function executes AFTER child function.
* exit: Parent function executes AFTER child function.
* Note: When transitioning between two child states with a shared
* parent, that parent's exit and entry functions do not execute.
*/
const struct smf_state *parent;
# ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION
/* Optional initial transition state. NULL for leaf states. */
const struct smf_state *initial;
# endif /* CONFIG_SYSTEM_SMF_INITIAL_TRANSITION */
#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */
};
struct smf_ctx
{
/* Current state the state machine is executing. */
const struct smf_state *current;
/* Previous state the state machine executed. */
const struct smf_state *previous;
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
/* Currently executing state (which may be a parent). */
const struct smf_state *executing;
#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */
/* This value is set by the set_terminate function and should
* terminate the state machine when its set to a value other than
* zero when it's returned by the run_state function.
*/
int32_t terminate_val;
/* The state machine casts this to a "struct internal_ctx" and it's
* used to track state machine context.
*/
uint32_t internal;
};
/****************************************************************************
* Public Data
****************************************************************************/
#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: smf_set_initial
*
* Description:
* Initialize the state machine and set its initial state.
*
* Input Parameters:
* ctx - State machine context
* init_state - Initial state the state machine starts in
*
****************************************************************************/
void smf_set_initial(struct smf_ctx *ctx,
const struct smf_state *init_state);
/****************************************************************************
* Name: smf_set_state
*
* Description:
* Change the state machine state. This handles exiting the previous state
* and entering the target state. For HSMs the entry and exit actions of
* the Least Common Ancestor are not run.
*
* Input Parameters:
* ctx - State machine context
* new_state - State to transition to
*
****************************************************************************/
void smf_set_state(struct smf_ctx *ctx, const struct smf_state *new_state);
/****************************************************************************
* Name: smf_set_terminate
*
* Description:
* Terminate a state machine.
*
* Input Parameters:
* ctx - State machine context
* val - Non-zero termination value returned by smf_run_state
*
****************************************************************************/
void smf_set_terminate(struct smf_ctx *ctx, int32_t val);
/****************************************************************************
* Name: smf_run_state
*
* Description:
* Run one iteration of a state machine (including any parent states).
*
* Input Parameters:
* ctx - State machine context
*
* Returned Value:
* A non-zero value terminates the state machine. This non-zero value may
* represent a terminal state being reached or detection of an error.
*
****************************************************************************/
int32_t smf_run_state(struct smf_ctx *ctx);
/****************************************************************************
* Name: smf_get_current_leaf_state
*
* Description:
* Get the current leaf state. This may be a parent state if the HSM is
* malformed (initial transitions are not set up correctly).
*
****************************************************************************/
static inline const struct smf_state *
smf_get_current_leaf_state(const struct smf_ctx *ctx)
{
return ctx->current;
}
/****************************************************************************
* Name: smf_get_current_executing_state
*
* Description:
* Get the state that is currently executing. This may be a parent state.
*
****************************************************************************/
static inline const struct smf_state *
smf_get_current_executing_state(const struct smf_ctx *ctx)
{
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
return ctx->executing;
#else
return ctx->current;
#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */
}
#undef EXTERN
#if defined(__cplusplus)
}
#endif
#endif /* __APPS_INCLUDE_SYSTEM_SMF_H */

28
system/smf/Kconfig Normal file
View file

@ -0,0 +1,28 @@
# SMF (State Machine Framework) configuration options
#
# For syntax reference, see kconfig-language.txt in the NuttX tools repository.
config SYSTEM_SMF
bool "(SMF) support"
default n
---help---
Enables the State Machine Framework (SMF) for implementing state machines
if SYSTEM_SMF
config SYSTEM_SMF_ANCESTOR_SUPPORT
bool "Enable ancestor/parent state support"
default n
---help---
Enables support for parent/ancestor relationships between SMF states.
Required for hierarchical state machines.
config SYSTEM_SMF_INITIAL_TRANSITION
bool "Enable initial transition support"
default n
depends on SYSTEM_SMF_ANCESTOR_SUPPORT
---help---
Enables support for initial transitions in hierarchical states.
Depends on ancestor/parent state support.
endif # SYSTEM_SMF

25
system/smf/Make.defs Normal file
View file

@ -0,0 +1,25 @@
############################################################################
# apps/smf/Make.defs
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
ifeq ($(CONFIG_SYSTEM_SMF),y)
CONFIGURED_APPS += $(APPDIR)/system/smf
endif

29
system/smf/Makefile Normal file
View file

@ -0,0 +1,29 @@
############################################################################
# apps/smf/Makefile
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
include $(APPDIR)/Make.defs
# State Machine Framework library
CSRCS = smf.c
include $(APPDIR)/Application.mk

605
system/smf/smf.c Normal file
View file

@ -0,0 +1,605 @@
/****************************************************************************
* apps/system/smf/smf.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2021 The Chromium OS Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <system/smf.h>
/****************************************************************************
* Private Types
****************************************************************************/
struct internal_ctx
{
bool new_state : 1;
bool terminate : 1;
bool is_exit : 1;
bool handled : 1;
};
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
/****************************************************************************
* Name: is_descendant_of
****************************************************************************/
static bool is_descendant_of(const struct smf_state *test_state,
const struct smf_state *target_state)
{
const struct smf_state *state;
for (state = test_state; state != NULL; state = state->parent)
{
if (target_state == state)
{
return true;
}
}
return false;
}
/****************************************************************************
* Name: get_child_of
****************************************************************************/
static const struct smf_state *
get_child_of(const struct smf_state *states, const struct smf_state *parent)
{
const struct smf_state *state = states;
while (state != NULL)
{
if (state->parent == parent)
{
return state;
}
state = state->parent;
}
return NULL;
}
/****************************************************************************
* Name: get_lca_of
*
* Description:
* Find the Least Common Ancestor (LCA) of two states that are not
* ancestors of one another.
*
****************************************************************************/
static const struct smf_state *
get_lca_of(const struct smf_state *source, const struct smf_state *dest)
{
const struct smf_state *ancestor;
for (ancestor = source->parent; ancestor != NULL;
ancestor = ancestor->parent)
{
/* First common ancestor. */
if (is_descendant_of(dest, ancestor))
{
return ancestor;
}
}
return NULL;
}
/****************************************************************************
* Name: smf_execute_all_entry_actions
****************************************************************************/
static bool smf_execute_all_entry_actions(struct smf_ctx *const ctx,
const struct smf_state *new_state,
const struct smf_state *topmost)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
const struct smf_state *to_execute;
if (new_state == topmost)
{
/* There are no child states, so do nothing. */
return false;
}
for (to_execute = get_child_of(new_state, topmost);
to_execute != NULL && to_execute != new_state;
to_execute = get_child_of(new_state, to_execute))
{
/* Keep track of the executing entry action in case it calls
* smf_set_state().
*/
ctx->executing = to_execute;
/* Execute every entry action EXCEPT that of the topmost state. */
if (to_execute->entry != NULL)
{
to_execute->entry(ctx);
/* No need to continue if terminate was set. */
if (internal->terminate)
{
ctx->executing = ctx->current;
return true;
}
}
}
/* Execute the new state entry action. */
ctx->executing = new_state;
if (new_state->entry != NULL)
{
new_state->entry(ctx);
/* No need to continue if terminate was set. */
if (internal->terminate)
{
ctx->executing = ctx->current;
return true;
}
}
ctx->executing = ctx->current;
return false;
}
/****************************************************************************
* Name: smf_execute_ancestor_run_actions
****************************************************************************/
static bool smf_execute_ancestor_run_actions(struct smf_ctx *const ctx)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
const struct smf_state *tmp_state;
/* Execute all run actions in reverse order. */
if (internal->terminate)
{
/* Return if the current state terminated. */
return true;
}
if (internal->new_state || internal->handled)
{
/* The child state transitioned or handled it. Stop propagating. */
return false;
}
/* Try to run parent run actions. */
for (tmp_state = ctx->current->parent; tmp_state != NULL;
tmp_state = tmp_state->parent)
{
enum smf_state_result rc;
/* Keep track of where we are in case an ancestor calls
* smf_set_state().
*/
ctx->executing = tmp_state;
/* Execute parent run action. */
if (tmp_state->run != NULL)
{
rc = tmp_state->run(ctx);
if (rc == SMF_EVENT_HANDLED)
{
internal->handled = true;
}
/* No need to continue if terminate was set. */
if (internal->terminate)
{
ctx->executing = ctx->current;
return true;
}
/* This state dealt with it. Stop propagating. */
if (internal->new_state || internal->handled)
{
break;
}
}
}
/* All done executing the run actions. */
ctx->executing = ctx->current;
return false;
}
/****************************************************************************
* Name: smf_execute_all_exit_actions
****************************************************************************/
static bool smf_execute_all_exit_actions(struct smf_ctx *const ctx,
const struct smf_state *topmost)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
const struct smf_state *tmp_state;
const struct smf_state *to_execute;
tmp_state = ctx->executing;
for (to_execute = ctx->current;
to_execute != NULL && to_execute != topmost;
to_execute = to_execute->parent)
{
if (to_execute->exit != NULL)
{
ctx->executing = to_execute;
to_execute->exit(ctx);
/* No need to continue if terminate was set in the exit action. */
if (internal->terminate)
{
ctx->executing = tmp_state;
return true;
}
}
}
ctx->executing = tmp_state;
return false;
}
#endif /* CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT */
/****************************************************************************
* Name: smf_clear_internal_state
****************************************************************************/
static void smf_clear_internal_state(struct smf_ctx *const ctx)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
internal->is_exit = false;
internal->terminate = false;
internal->handled = false;
internal->new_state = false;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: smf_set_initial
****************************************************************************/
void smf_set_initial(struct smf_ctx *const ctx,
const struct smf_state *init_state)
{
#ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION
/* The final target will be the deepest leaf state that the target
* contains. Set that as the real target.
*/
while (init_state->initial != NULL)
{
init_state = init_state->initial;
}
#endif
smf_clear_internal_state(ctx);
ctx->current = init_state;
ctx->previous = NULL;
ctx->terminate_val = 0;
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
struct internal_ctx *const internal = (void *)&ctx->internal;
const struct smf_state *topmost;
ctx->executing = init_state;
/* topmost is the root ancestor of init_state, its parent == NULL. */
topmost = get_child_of(init_state, NULL);
/* Execute topmost state entry action,
* since smf_execute_all_entry_actions doesn't.
*/
if (topmost->entry != NULL)
{
ctx->executing = topmost;
topmost->entry(ctx);
ctx->executing = init_state;
if (internal->terminate)
{
/* No need to continue if terminate was set. */
return;
}
}
if (smf_execute_all_entry_actions(ctx, init_state, topmost))
{
/* No need to continue if terminate was set. */
return;
}
#else
/* Execute entry action if it exists. */
if (init_state->entry != NULL)
{
init_state->entry(ctx);
}
#endif
}
/****************************************************************************
* Name: smf_set_state
****************************************************************************/
void smf_set_state(struct smf_ctx *const ctx,
const struct smf_state *new_state)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
if (new_state == NULL)
{
printf("SMF ERR: new_state cannot be NULL\n");
return;
}
/* It does not make sense to call smf_set_state in an exit phase of a
* state since we are already in a transition; we would always ignore
* the intended state to transition into.
*/
if (internal->is_exit)
{
printf("SMF ERR: Calling %s from exit action\n", __func__);
return;
}
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
const struct smf_state *topmost;
if (ctx->executing != new_state &&
ctx->executing->parent == new_state->parent)
{
/* Optimize sibling transitions (different states under same parent). */
topmost = ctx->executing->parent;
}
else if (is_descendant_of(ctx->executing, new_state))
{
/* New state is a parent of where we are now. */
topmost = new_state;
}
else if (is_descendant_of(new_state, ctx->executing))
{
/* We are a parent of the new state. */
topmost = ctx->executing;
}
else
{
/* Not directly related, find LCA. */
topmost = get_lca_of(ctx->executing, new_state);
}
internal->is_exit = true;
internal->new_state = true;
/* Call all exit actions up to (but not including) the topmost. */
if (smf_execute_all_exit_actions(ctx, topmost))
{
/* No need to continue if terminate was set in the exit action. */
return;
}
/* If self-transition, call the exit action. */
if (ctx->executing == new_state && new_state->exit != NULL)
{
new_state->exit(ctx);
/* No need to continue if terminate was set in the exit action. */
if (internal->terminate)
{
return;
}
}
internal->is_exit = false;
/* If self transition, call the entry action. */
if (ctx->executing == new_state && new_state->entry != NULL)
{
new_state->entry(ctx);
/* No need to continue if terminate was set in the entry action. */
if (internal->terminate)
{
return;
}
}
#ifdef CONFIG_SYSTEM_SMF_INITIAL_TRANSITION
/* The final target will be the deepest leaf state that the target
* contains. Set that as the real target.
*/
while (new_state->initial != NULL)
{
new_state = new_state->initial;
}
#endif
/* Update the state variables. */
ctx->previous = ctx->current;
ctx->current = new_state;
ctx->executing = new_state;
/* Call all entry actions (except those of topmost). */
if (smf_execute_all_entry_actions(ctx, new_state, topmost))
{
/* No need to continue if terminate was set in the entry action. */
return;
}
#else
/* Flat state machines have a very simple transition. */
if (ctx->current->exit != NULL)
{
internal->is_exit = true;
ctx->current->exit(ctx);
/* No need to continue if terminate was set in the exit action. */
if (internal->terminate)
{
return;
}
internal->is_exit = false;
}
/* Update the state variables. */
ctx->previous = ctx->current;
ctx->current = new_state;
if (ctx->current->entry != NULL)
{
ctx->current->entry(ctx);
/* No need to continue if terminate was set in the entry action. */
if (internal->terminate)
{
return;
}
}
#endif
}
/****************************************************************************
* Name: smf_set_terminate
****************************************************************************/
void smf_set_terminate(struct smf_ctx *const ctx, int32_t val)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
internal->terminate = true;
ctx->terminate_val = val;
}
/****************************************************************************
* Name: smf_run_state
****************************************************************************/
int32_t smf_run_state(struct smf_ctx *const ctx)
{
struct internal_ctx *const internal = (void *)&ctx->internal;
/* No need to continue if terminate was set. */
if (internal->terminate)
{
return ctx->terminate_val;
}
/* Executing a state's run function could cause a transition, so clear
* the internal state to ensure that the transition is handled correctly.
*/
smf_clear_internal_state(ctx);
#ifdef CONFIG_SYSTEM_SMF_ANCESTOR_SUPPORT
ctx->executing = ctx->current;
if (ctx->current->run != NULL)
{
enum smf_state_result rc;
rc = ctx->current->run(ctx);
if (rc == SMF_EVENT_HANDLED)
{
internal->handled = true;
}
}
if (smf_execute_ancestor_run_actions(ctx))
{
return ctx->terminate_val;
}
#else
if (ctx->current->run != NULL)
{
ctx->current->run(ctx);
}
#endif
return 0;
}