dwhall / farc

Framework for state machines with run-to-completion concurrency using asyncio. Python 3.4 or later
MIT License
28 stars 6 forks source link

Reimplement HSM algorithm and include hsm_test #1

Closed Picarro-SzeMengTan closed 5 years ago

Picarro-SzeMengTan commented 5 years ago

I have added the comprehensive example given in section 2.3.15 of "Practical UML statecharts in C/C++" 2nd Edition and modified the code in the Hsm class so that it gives the same results as the QHSMTST utility. The outputs of hsm_test.py and of qhsmtst.exe are included as text files for two specific input sequences, including the one leading to Figure 2.12 the book.

The hsm_test.py file is set up to run in interactive mode, so that the user can enter signal names ("a" through "i" and "t" to terminate) and examine at the results. This calls "dispatch" directly bypassing the FIFO. A SimpleSpy module has been written which responds to a new "on_state_handler_called" message in order to display these results. I have also modified the VcdSpy module so that it ignores messages that it does not understand.

Sze Tan

dwhall commented 5 years ago

First, thanks for this! The HSM algorithm is something I stumbled through during creation. I never fully grokked the C version from the QPC source. I just kinda winged it (if that wasn't obvious) based on reading PSiCC and making my own state machines. So I really appreciate you adding the example that (from memory) exercises all the different types of transitions. I did a quick read-through and the code looks clean and nice. My preliminary thoughts:

Again, thanks for the submission,

!!Dean

Picarro-SzeMengTan commented 5 years ago

Hello Dean,

You are most welcome. The only use of the meta-class is to make it such that any undefined attributes of the class (such as static methods) do not raise errors if they are called. One can then choose to implement only a subset of the Spy handlers in classes such as VcdSpy and SimpleSpy. Deleting the meta-class completely would be fine if one always writes all of the handlers.

The _perform_transition static method is perhaps the most complicated part of the algorithm because it goes through all of the special cases enumerated in PSiCC. If one is willing to sacrifice some efficiency, it can be replaced by the following code:

@staticmethod
def _perform_transition(me, source, target):
    # Handle the state transition from source to target in the HSM.
    s, t = source, target
    path = [t]
    lca_found = False
    # Find and save ancestors of target into path
    #  until we find the source or hit the top
    me.state = t
    while me.state != Hsm.top:
        Hsm.trig(me, me.state, Signal.EMPTY)
        path.append(me.state)
        if me.state == s:
            lca_found = True
            break 
    if lca_found:  # The source is an ancestor, just follow reversed path to get to target
        for st in reversed(path[:-1]):
            Hsm.enter(me, st)
    else:  # All other cases require us to exit the source
        Hsm.exit(me, s)
        while me.state not in path:
            # Keep exiting until we reach the LCA. Note that exit
            #  modifies me.state (so we do not use Signal.EMPTY)
            Hsm.exit(me, me.state)
        t = me.state
        # Step into decendents until we enter the target
        for st in reversed(path[:path.index(t)]):
            Hsm.enter(me, st)

Best wishes, Sze

Picarro-SzeMengTan commented 5 years ago

I've recently discovered a problem with the _perform_transition function which occurs if states handle the EXIT signal. The latest change fixes the problem and the hsm_test.py file has been modified to trigger the problem in the old code.