Created
January 21, 2025 17:39
-
-
Save hlord2000/74b6975bbed9a6558b8e514dce5aead3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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