Skip to content

Instantly share code, notes, and snippets.

@hlord2000
Created January 21, 2025 17:39
Show Gist options
  • Save hlord2000/74b6975bbed9a6558b8e514dce5aead3 to your computer and use it in GitHub Desktop.
Save hlord2000/74b6975bbed9a6558b8e514dce5aead3 to your computer and use it in GitHub Desktop.
import re
import sys
import argparse
from dataclasses import dataclass
from typing import List, Dict, Set, Optional
@dataclass
class State:
name: str
parent: Optional[str] = None
entry_action: bool = True # Changed to default True
run_action: bool = True # Most states need a run action for transitions
exit_action: bool = True # Changed to default True
transitions: List[str] = None
initial_child: Optional[str] = None
def __post_init__(self):
if self.transitions is None:
self.transitions = []
class UMLtoSMFConverter:
def __init__(self):
self.states: Dict[str, State] = {}
self.state_enum_values: Set[str] = set()
def parse_uml_file(self, filepath: str):
"""Parse UML text file and build state machine structure."""
try:
with open(filepath, 'r') as f:
lines = f.readlines()
except FileNotFoundError:
print(f"Error: File '{filepath}' not found")
sys.exit(1)
except Exception as e:
print(f"Error reading file: {str(e)}")
sys.exit(1)
current_parent = None
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
# Parse state definitions
if line.startswith('state'):
state_match = re.match(r'state\s+(\w+)(?:\s+parent\s+(\w+))?', line)
if state_match:
state_name = state_match.group(1)
parent_name = state_match.group(2)
self.states[state_name] = State(name=state_name, parent=parent_name)
self.state_enum_values.add(state_name)
# Parse transitions
elif '->' in line:
trans_match = re.match(r'(\w+)\s*->\s*(\w+)', line)
if trans_match:
from_state = trans_match.group(1)
to_state = trans_match.group(2)
if from_state in self.states:
self.states[from_state].transitions.append(to_state)
# Parse actions (now mainly used to disable actions if specified)
elif line.startswith('actions'):
action_match = re.match(r'actions\s+(\w+)\s*:\s*no_(entry|run|exit)', line)
if action_match:
state_name = action_match.group(1)
action_type = action_match.group(2)
if state_name in self.states:
if action_type == 'entry':
self.states[state_name].entry_action = False
elif action_type == 'run':
self.states[state_name].run_action = False
elif action_type == 'exit':
self.states[state_name].exit_action = False
# Parse initial transitions
elif line.startswith('initial'):
init_match = re.match(r'initial\s+(\w+)\s*->\s*(\w+)', line)
if init_match:
parent_state = init_match.group(1)
child_state = init_match.group(2)
if parent_state in self.states:
self.states[parent_state].initial_child = child_state
def generate_smf_code(self) -> str:
"""Generate Zephyr SMF C code from parsed state machine."""
code = []
# Add includes
code.append("#include <zephyr/smf.h>")
code.append("")
# Forward declaration
code.append("/* Forward declaration of state table */")
code.append("static const struct smf_state demo_states[];")
code.append("")
# State enum
code.append("/* List of demo states */")
enum_values = sorted(self.state_enum_values)
code.append("enum demo_state {")
code.append(" " + ",\n ".join(enum_values))
code.append("};")
code.append("")
# Context structure
code.append("/* User defined object */")
code.append("struct s_object {")
code.append(" /* This must be first */")
code.append(" struct smf_ctx ctx;")
code.append(" /* Other state specific data add here */")
code.append("} s_obj;")
code.append("")
# Generate state functions
for state_name, state in self.states.items():
if state.entry_action:
code.append(f"/* State {state_name} entry */")
code.append(f"static void {state_name.lower()}_entry(void *o)")
code.append("{")
code.append(" /* TODO: Implement entry action */")
code.append(" printk(\"Entering state %s\\n\", \"" + state_name + "\");")
code.append("}")
code.append("")
if state.run_action:
code.append(f"/* State {state_name} run */")
code.append(f"static void {state_name.lower()}_run(void *o)")
code.append("{")
if state.transitions:
trans = state.transitions[0] # For simplicity, using first transition
code.append(f" /* TODO: Add conditions for state transition */")
code.append(f" smf_set_state(SMF_CTX(&s_obj), &demo_states[{trans}]);")
code.append("}")
code.append("")
if state.exit_action:
code.append(f"/* State {state_name} exit */")
code.append(f"static void {state_name.lower()}_exit(void *o)")
code.append("{")
code.append(" /* TODO: Implement exit action */")
code.append(" printk(\"Exiting state %s\\n\", \"" + state_name + "\");")
code.append("}")
code.append("")
# Generate state table
code.append("/* Populate state table */")
code.append("static const struct smf_state demo_states[] = {")
for state_name in enum_values:
state = self.states[state_name]
entry_fn = f"{state_name.lower()}_entry" if state.entry_action else "NULL"
run_fn = f"{state_name.lower()}_run" if state.run_action else "NULL"
exit_fn = f"{state_name.lower()}_exit" if state.exit_action else "NULL"
parent = f"&demo_states[{state.parent}]" if state.parent else "NULL"
initial = f"&demo_states[{state.initial_child}]" if state.initial_child else "NULL"
code.append(f" [{state_name}] = SMF_CREATE_STATE({entry_fn}, {run_fn}, {exit_fn}, {parent}, {initial}),")
code.append("};")
code.append("")
# Main function
code.append("int main(void)")
code.append("{")
code.append(" int32_t ret;")
code.append("")
code.append(" /* Set initial state */")
initial_state = enum_values[0] # Using first state as initial
code.append(f" smf_set_initial(SMF_CTX(&s_obj), &demo_states[{initial_state}]);")
code.append("")
code.append(" /* Run the state machine */")
code.append(" while(1) {")
code.append(" /* State machine terminates if a non-zero value is returned */")
code.append(" ret = smf_run_state(SMF_CTX(&s_obj));")
code.append(" if (ret) {")
code.append(" /* handle return code and terminate state machine */")
code.append(" break;")
code.append(" }")
code.append(" k_msleep(1000);")
code.append(" }")
code.append(" return ret;")
code.append("}")
return "\n".join(code)
def main():
"""Main function to process command line arguments and convert UML to SMF."""
parser = argparse.ArgumentParser(
description='Convert UML state machine description to Zephyr SMF code',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
UML file format:
state <name> [parent <parent_name>] # Define a state
<state_name> -> <state_name> # Define a transition
initial <parent_state> -> <child_state> # Define initial transition
actions <state_name>: no_entry # Disable entry action
actions <state_name>: no_run # Disable run action
actions <state_name>: no_exit # Disable exit action
"""
)
parser.add_argument('input_file', help='Input UML description file')
parser.add_argument('-o', '--output', help='Output C file (default: generated_smf.c)',
default='generated_smf.c')
args = parser.parse_args()
converter = UMLtoSMFConverter()
try:
converter.parse_uml_file(args.input_file)
smf_code = converter.generate_smf_code()
with open(args.output, 'w') as f:
f.write(smf_code)
print(f"Generated SMF code has been written to {args.output}")
except Exception as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment