px4-firmware/Tools/fsm_visualisation.py

202 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""fsm_visualisation.py: Create dot code and dokuwiki table from a state transition table
convert dot code to png using graphviz:
dot fsm.dot -Tpng -o fsm.png
"""
import argparse
import re
__author__ = "Julian Oes"
def get_dot_header():
return """digraph finite_state_machine {
graph [ dpi = 300 ];
ratio = 1.5
node [shape = circle];"""
def get_dot_footer():
return """}\n"""
def main():
# parse input arguments
parser = argparse.ArgumentParser(description='Create dot code and dokuwiki table from a state transition table.')
parser.add_argument("-i", "--input-file", default=None, help="choose file to parse")
parser.add_argument("-d", "--dot-file", default=None, help="choose file for output dot file")
parser.add_argument("-t", "--table-file", default=None, help="choose file for output of table")
args = parser.parse_args()
# open source file
if args.input_file == None:
exit('please specify file')
f = open(args.input_file,'r')
source = f.read()
# search for state transition table and extract the table itself
# first look for StateTable::Tran
# then accept anything including newline until {
# but don't accept the definition (without ;)
# then extract anything inside the brackets until };
match = re.search(r'StateTable::Tran(?:.|\n!;)*\{((?:.|\n)*?)\};', source)
if not match:
exit('no state transition table found')
table_source = match.group(1)
# bookkeeping for error checking
num_errors_found = 0
states = []
events = []
# first get all states and events
for table_line in table_source.split('\n'):
match = re.search(r'/\*\s+\w+_STATE_(\w+)\s+\*/', table_line)
if match:
states.append(str(match.group(1)))
# go to next line
continue
if len(states) == 1:
match = re.search(r'/\*\s+EVENT_(\w+)\s+\*/', table_line)
if match:
events.append(str(match.group(1)))
print('Found %d states and %d events' % (len(states), len(events)))
# keep track of origin state
state = None
# fill dot code in here
dot_code = ''
# create table len(states)xlen(events)
transition_table = [[[] for x in range(len(states))] for y in range(len(events))]
# now fill the transition table and write the dot code
for table_line in table_source.split('\n'):
# get states
# from: /* NAV_STATE_NONE */
# extract only "NONE"
match = re.search(r'/\*\s+\w+_STATE_(\w+)\s+\*/', table_line)
if match:
state = match.group(1)
state_index = states.index(state)
# go to next line
continue
# can't advance without proper state
if state == None:
continue
# get event and next state
# from /* EVENT_READY_REQUESTED */ {ACTION(&Navigator::start_ready), NAV_STATE_READY}
# extract "READY_REQUESTED" and "READY" if there is ACTION
match_action = re.search(r'/\*\s+EVENT_(\w+)\s+\*/\s+\{ACTION\((?:.|\n)*\w+_STATE_(\w+)', table_line)
# get event and next state
# from /* EVENT_NONE_REQUESTED */ {NO_ACTION, NAV_STATE_NONE},
# extract "NONE_REQUESTED" and "NAV_STATE_NONE" if there is NO_ACTION
match_no_action = re.search(r'/\*\s+EVENT_(\w+)\s+\*/\s+\{NO_ACTION(?:.|\n)*\w+_STATE_(\w+)', table_line)
# ignore lines with brackets only
if match_action or match_no_action:
# only write arrows for actions
if match_action:
event = match_action.group(1)
new_state = match_action.group(2)
dot_code += ' ' + state + ' -> ' + new_state + '[ label = "' + event + '"];\n'
elif match_no_action:
event = match_no_action.group(1)
new_state = match_no_action.group(2)
# check for state changes without action
if state != new_state:
print('Error: no action but state change:')
print('State: ' + state + ' changed to: ' + new_state)
print(table_line)
num_errors_found += 1
# check for wrong events
if event not in events:
print('Error: unknown event: ' + event)
print(table_line)
num_errors_found += 1
# check for wrong new states
if new_state not in states:
print('Error: unknown new state: ' + new_state)
print(table_line)
num_errors_found += 1
# save new state in transition table
event_index = events.index(event)
# bold for action
if match_action:
transition_table[event_index][state_index] = '**' + new_state + '**'
else:
transition_table[event_index][state_index] = new_state
# assemble dot code
dot_code = get_dot_header() + dot_code + get_dot_footer()
# write or print dot file
if args.dot_file:
f = open(args.dot_file,'w')
f.write(dot_code)
print('Wrote dot file')
else:
print('##########Dot-start##########')
print(dot_code)
print('##########Dot-end############')
# assemble doku wiki table
table_code = '| ^ '
# start with header of all states
for state in states:
table_code += state + ' ^ '
table_code += '\n'
# add events and new states
for event, row in zip(events, transition_table):
table_code += '^ ' + event + ' | '
for new_state in row:
table_code += new_state + ' | '
table_code += '\n'
# write or print wiki table
if args.table_file:
f = open(args.table_file,'w')
f.write(table_code)
print('Wrote table file')
else:
print('##########Table-start########')
print(table_code)
print('##########Table-end##########')
# report obvous errors
if num_errors_found:
print('Obvious errors found: %d' % num_errors_found)
else:
print('No obvious errors found')
if __name__ == '__main__':
main()