fsmgen 0.1

Discussion in 'Ruby' started by Mark Probert, Nov 2, 2004.

  1. Mark Probert

    Mark Probert Guest

    Hi, all.

    I am pleased to announce the release of fsmgen. This is a class library
    and generator for creating simple finite state machine. The application
    is to make simple TCP / UDP servers easy to write. This is done using a
    YAML file to define the server and then using a generator to create a
    default server and client template. These templates are then modified to
    add "real" responses, rather than the default of sending strings back.

    An example of a simple config file is:

    Server:
    name: SimpleSvr # we need a name
    type: TCP # default server type [TCP|UDP]
    port: 8045 # mandatory field

    Events:
    HELO: A client wants to connect
    CMD: The client sends in a command

    Actions:
    INIT: [ init, "Initializing the system." ]
    ACK: [ acknowledge, "HELO" ]
    LIST: [ list, "Show available commands" ]
    RUN: [ run, "Run a command" ]

    States:
    Start: [ INIT, Idle ]
    Idle:
    HELO: [ ACK, Ready ]
    CMD:
    Ready:
    CMD: [ RUN, Idle ]
    LIST: [ LIST, Ready ]
    HELO:


    I don't have a website up at the moment, so, if you are interested,
    please contact me for the code.

    Regards,
    -mark. (probertm at acm dot org)
     
    Mark Probert, Nov 2, 2004
    #1
    1. Advertising

  2. Mark Probert

    Guest

    On Tue, 2 Nov 2004, Mark Probert wrote:

    > Hi, all.
    >
    > I am pleased to announce the release of fsmgen. This is a class library
    > and generator for creating simple finite state machine. The application
    > is to make simple TCP / UDP servers easy to write. This is done using a
    > YAML file to define the server and then using a generator to create a
    > default server and client template. These templates are then modified to
    > add "real" responses, rather than the default of sending strings back.
    >
    > An example of a simple config file is:
    >
    > Server:
    > name: SimpleSvr # we need a name
    > type: TCP # default server type [TCP|UDP]
    > port: 8045 # mandatory field
    >
    > Events:
    > HELO: A client wants to connect
    > CMD: The client sends in a command
    >
    > Actions:
    > INIT: [ init, "Initializing the system." ]
    > ACK: [ acknowledge, "HELO" ]
    > LIST: [ list, "Show available commands" ]
    > RUN: [ run, "Run a command" ]
    >
    > States:
    > Start: [ INIT, Idle ]
    > Idle:
    > HELO: [ ACK, Ready ]
    > CMD:
    > Ready:
    > CMD: [ RUN, Idle ]
    > LIST: [ LIST, Ready ]
    > HELO:
    >
    >
    > I don't have a website up at the moment, so, if you are interested,
    > please contact me for the code.
    >
    > Regards,
    > -mark. (probertm at acm dot org)


    you can put it up on my site if you with - contact me offline.

    same goes for anyone else with code to share.

    regards.

    -a
    --
    ===============================================================================
    | EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
    | PHONE :: 303.497.6469
    | When you do something, you should burn yourself completely, like a good
    | bonfire, leaving no trace of yourself. --Shunryu Suzuki
    ===============================================================================
     
    , Nov 2, 2004
    #2
    1. Advertising

  3. Why don't you post it on rubyforge? Plenty of free space there
    (thanks to those who are paying for it). This sounds like a great
    tool!

    On Wed, 3 Nov 2004 05:53:48 +0900, Mark Probert <> wrote:
    > Hi, all.
    >
    > I am pleased to announce the release of fsmgen. This is a class library
    > and generator for creating simple finite state machine. The application
    > is to make simple TCP / UDP servers easy to write. This is done using a
    > YAML file to define the server and then using a generator to create a
    > default server and client template. These templates are then modified to
    > add "real" responses, rather than the default of sending strings back.
     
    Carl Youngblood, Nov 3, 2004
    #3
  4. Mark Probert

    Mark Probert Guest

    Hi ..

    Carl Youngblood <> wrote:
    >
    > Why don't you post it on rubyforge? Plenty of free space there
    > (thanks to those who are paying for it). This sounds like a great
    > tool!
    >

    Thanks, Carl. I forgot about rubyforge (doh!). I am setting up the
    account now and will post when it is done.

    -mark.
     
    Mark Probert, Nov 3, 2004
    #4
  5. Mark Probert

    vruz Guest

    > I am pleased to announce the release of fsmgen. This is a class library
    > and generator for creating simple finite state machine. The application
    > is to make simple TCP / UDP servers easy to write. This is done using a
    > YAML file to define the server and then using a generator to create a
    > default server and client template. These templates are then modified to
    > add "real" responses, rather than the default of sending strings back.



    Great work, this is a nice addition that could well be (after polished
    and debugged to death, of course) part of the stdlib.

    That YAML code is the FSM definition, now...
    how do you jump from one state to another ?
    all in Ruby code ?

    Taking as an example this vending machine (sorry, in C# + XML)
    http://www.codeproject.com/csharp/xmlfsm.asp

    What would be the corresponding code in Ruby + YAML ?
    Thanks for your work,

    best,

    vruz
     
    vruz, Nov 3, 2004
    #5
  6. Mark Probert

    Mark Probert Guest

    Re: fsmgen 0.1 (longish)

    Hi ..

    vruz <> wrote:
    >
    > Great work, this is a nice addition that could well be (after polished
    > and debugged to death, of course) part of the stdlib.
    >

    Thank you. It needs polishing and debugging, that is for sure. It does
    work, though I am not 100% on all of the failure paths.

    > That YAML code is the FSM definition, now...
    > how do you jump from one state to another ?
    > all in Ruby code ?
    >

    Fairly easily (Ruby is great for this kind of thing). I take states,
    actions and events and create hashes. The actions are keyed off an MD5
    hash of state and action (this is really a DFA machine -- for an action,
    each of the events must be unique). When an incoming message (event)
    matches for a given action, the action callback is fired. By default, this
    returns a message on the socket.

    > Taking as an example this vending machine (sorry, in C# + XML)
    > http://www.codeproject.com/csharp/xmlfsm.asp
    > What would be the corresponding code in Ruby + YAML ?
    >

    This is in fact the example I have included :), though I took the example
    courtesy of Robert C Martin's article 'UML Tutorial: Finite State
    Machines' from C++ Report

    http://www.objectmentor.com/publications/UMLFSM.PDF

    The example, without comments, is as follows (if you want the comments,
    then I can send the package):

    # --------------------( turnstile.yaml )
    Server:
    name: Turnstile # we need a name
    type: TCP # default server type [TCP|UDP]
    port: 13345 # mandatory field

    Events:
    COIN: Coin is placed in the turnstile
    PASS: The turnstile has been passed
    RESET: Turn off the alarm
    READY: Ready for normal operation

    Actions:
    LOCK: [ lock, "The turnstile is now locked." ]
    UNLOCK: [ unlock, "The turnstile is now unlocked." ]
    THANKS: [ donation, "Thank you for your donation." ]
    ALARM_ON: [ alarm_on, "Woop! Woop!" ]
    ALARM_OFF: [ alarm_off, "The alarm is now turned off." ]
    READY: [ ready, "The system is now ready." ]

    States:
    Start: [ LOCK, Locked ]
    Locked:
    COIN: [ UNLOCK, Unlocked ]
    PASS: [ ALARM_ON, Violation ]
    Unlocked:
    COIN: [ THANKS, Unlocked ]
    PASS: [ LOCK, Locked ]
    Violation:
    RESET: [ ALARM_OFF, Violation ]
    READY: [ [ ALARM_OFF, LOCK ], Locked ]
    PASS:
    COIN:

    You run 'fsmgen' on the YAML file to produce the server and a simple test
    client. The basic server looks like:

    # -----------------( Turnstile.rb )
    require 'FSM'
    class Turnstile < FSM::DFA

    # -----( alarm_off )
    # Action for event: ALARM_OFF
    #
    def alarm_off ( params )
    @sess.puts "The alarm is now turned off."
    end

    # -----( unlock )
    # Action for event: UNLOCK
    #
    def unlock ( params )
    val = params.to_i
    if val < 25
    @sess.puts "Not enough bud! (#{val}c) You need 25c."
    @change_state = false
    else
    @sess.puts "The turnstile is now unlocked."
    end
    end
    # ... etc
    end
    # -----( done )

    class TurnstileSvr < FSM::SimpleFSMServer
    # -----
    # Basic startup
    #
    def initialize(cfg, verbose=false)
    super(cfg, verbose)
    end

    # -----
    # Basic server startup
    #
    def start
    srv = TCPServer.new(@port)
    puts "server started"
    while (session = srv.accept)
    Thread.new(session) do |s|
    domian, port, ipname, ipaddr = s.peeraddr
    str = "connection from #{ipname}"
    puts str; @log.info str
    @fsm = Turnstile.new(@config, @log)
    @fsm.verbose = @verbose
    while true do
    event = s.gets.chomp
    @log.info "client(#{ipname}) event(#{event})"
    @fsm.handle_response(s, event)
    end
    end
    end
    end
    end # ----------( server )

    # ---------------------------------- #
    # MAIN -- Server Start #
    # ---------------------------------- #

    def end_program()
    puts " *** Terminating *** "
    exit
    end

    trap("SIGINT") { end_program }
    trap("SIGKILL") { end_program }

    svr = TurnstileSvr.new("turnstile.yaml", true)
    svr.start

    # --------------------( done )

    And a basic test client like:

    # -----------------( Turnstile_client.rb )
    require 'socket'

    PORT = 13345
    HOST = ARGV[0] || "localhost"

    def test_events(sess, evts)
    evts.each do |evt|
    sess.puts evt
    s = sess.gets
    puts "sent(#{evt}) response --> #{s}"
    end
    end

    puts "Testing:"
    session = TCPSocket.new(HOST, PORT)

    puts "Correct sequence .."
    evts = ["PASS", "RESET", "COIN 25", "PASS", "READY"]
    test_events(session, evts)

    session.close
    puts "...done"

    I hope that this helps a little in explaining what I have done.

    Regards,
    -mark.
     
    Mark Probert, Nov 3, 2004
    #6
  7. Mark Probert

    vruz Guest

    Re: fsmgen 0.1 (longish)

    [snip]
    > This is in fact the example I have included :), though I took the example
    > courtesy of Robert C Martin's article 'UML Tutorial: Finite State
    > Machines' from C++ Report
    > The example, without comments, is as follows


    Now it's a lot more clear, thank you

    >(if you want the comments,
    > then I can send the package):


    No hurries, I can wait until it's finally publicly published

    > I hope that this helps a little in explaining what I have done.
    > Regards,
    > -mark.


    best,
    vruz
     
    vruz, Nov 3, 2004
    #7
  8. On Wed, 3 Nov 2004 05:53:48 +0900, Mark Probert <> wrote:
    > Hi, all.
    >
    > I am pleased to announce the release of fsmgen. This is a class library
    > and generator for creating simple finite state machine. The application
    > is to make simple TCP / UDP servers easy to write. This is done using a
    > YAML file to define the server and then using a generator to create a
    > default server and client template. These templates are then modified to
    > add "real" responses, rather than the default of sending strings back.
    >
    > An example of a simple config file is:


    Wouldn't this be better implemented as meta-programming in Ruby, so
    you don't generate Ruby code from it once - that's just
    meta-programmed every time you execute the program? It would seem to
    be easier to modify things that way (for the case where the initial
    state machine wasn't quite right, something I find to happen about 9
    times out of 10).

    Apart from that, I like that you're helping abstract state machines -
    too often, they end up non-normalized and buried.

    Eivind.
    --
    Hazzle free packages for Ruby?
    RPA is available from http://www.rubyarchive.org/
     
    Eivind Eklund, Nov 3, 2004
    #8
  9. Mark Probert

    Mark Probert Guest

    Hi ..

    Eivind Eklund <> wrote:
    >
    > Wouldn't this be better implemented as meta-programming in Ruby, so
    > you don't generate Ruby code from it once - that's just
    > meta-programmed every time you execute the program?
    >

    This is a very good point and one I am not sure of the correct design
    approach. The problem with the current generator style is that if you do
    work on the state machine action specifics, then change the machine, you
    have to merge back all the changes. That is a pain.

    On the plus side, using a generator gives you a lot more freedom. Don't
    like the specifics of the server? Change it. Need to modify some
    behaviour on the fly? Not a problem. The only thing hidden is the
    event-action selection mechanism.

    For me, I tend to have simple state machines, so the config files doesn't
    change too much once it settles down.

    I am going to be puting the source up on Ruby Forge today under a PD
    licence, so you are more than welcome to have a look and modify away. :)

    >
    > Apart from that, I like that you're helping abstract state machines -
    > too often, they end up non-normalized and buried.
    >

    Thank you.

    Regards,
    -mark.
     
    Mark Probert, Nov 3, 2004
    #9
  10. Mark Probert

    Mark Probert Guest

    Mark Probert, Nov 3, 2004
    #10
  11. Mark Probert

    Jim Weirich Guest

    On Wednesday 03 November 2004 11:34 am, Eivind Eklund wrote:
    > Wouldn't this be better implemented as meta-programming in Ruby,


    Funny you should mention this ... I was playing with ruby state machines about
    two weeks ago (even using the turnstile example in my unit tests). This is
    what I came up with ....

    class TurnStileFSM < StateMachine
    state_machine do
    state :locked do
    start_state
    event :coin, :unlocked, :unlock
    event :pass, :violation, :alarm_on
    end
    state :unlocked do
    event :coin, :unlocked, :thank_you
    event :pass, :locked, :lock
    end
    state :violation do
    event :reset, :violation
    event :ready, :locked, :alarm_off, :lock
    event :pass
    event :coin
    end
    end
    end

    To use, you just inherit from the FSM and implement the actions (e.g. unlock,
    lock, alarm_on). (Or I suppose you could implement the actions directly in
    the FSM class).

    --
    -- Jim Weirich http://onestepback.org
    -----------------------------------------------------------------
    "Beware of bugs in the above code; I have only proved it correct,
    not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
     
    Jim Weirich, Nov 4, 2004
    #11
  12. Mark Probert

    Phil Tomson Guest

    In article <>,
    Jim Weirich <> wrote:
    >On Wednesday 03 November 2004 11:34 am, Eivind Eklund wrote:
    >> Wouldn't this be better implemented as meta-programming in Ruby,

    >
    >Funny you should mention this ... I was playing with ruby state machines about
    >two weeks ago (even using the turnstile example in my unit tests). This is
    >what I came up with ....
    >
    > class TurnStileFSM < StateMachine
    > state_machine do
    > state :locked do
    > start_state
    > event :coin, :unlocked, :unlock
    > event :pass, :violation, :alarm_on
    > end
    > state :unlocked do
    > event :coin, :unlocked, :thank_you
    > event :pass, :locked, :lock
    > end
    > state :violation do
    > event :reset, :violation
    > event :ready, :locked, :alarm_off, :lock
    > event :pass
    > event :coin
    > end
    > end
    > end
    >
    >To use, you just inherit from the FSM and implement the actions (e.g. unlock,
    >lock, alarm_on). (Or I suppose you could implement the actions directly in
    >the FSM class).
    >


    Where would one find the StateMachine class/module that you're inheriting
    from in the code above?


    Phil
     
    Phil Tomson, Nov 4, 2004
    #12
  13. Mark Probert

    Mark Probert Guest

    Hi ..

    Jim Weirich <> wrote:
    > On Wednesday 03 November 2004 11:34 am, Eivind Eklund wrote:
    >> Wouldn't this be better implemented as meta-programming in Ruby,

    >
    > Funny you should mention this ... I was playing with ruby state
    > machines about two weeks ago (even using the turnstile example in my
    > unit tests). This is what I came up with ....
    >

    Funny how this happens .. :) How do you access the state machine, Jim?

    I also came up with a similar scheme before I went to the generator
    approach. One of the advantages that I saw to using a YAML file is that
    the schema is pretty clean (events, actions, state transitions) and you
    only have to implement the actions. Then again, that is based on a server
    receiving client messages, which may be a different model to the one you
    are using.

    Are you going to publish your StateMachine class?

    Regards,
    -mark.
     
    Mark Probert, Nov 4, 2004
    #13
  14. Mark Probert

    Jim Weirich Guest

    On Wednesday 03 November 2004 09:03 pm, Mark Probert wrote:

    > Funny how this happens .. :) How do you access the state machine, Jim?


    Several ways. The simplest is to just inherit the FSM and implement the
    actions as methods ...

    class TurnStile < TurnStileFSM
    def alarm_on() ... end
    def alarm_off() ... end
    def lock() ... end
    def unlock() ... end
    # etc.
    end

    ts = TurnStile.new
    ts.coin # ts.unlock is called
    ts.pass # ts.lock is called
    ts.pass # ts.alarm_on is called

    You can also setup the FSM as a separate object that interacts with your
    domain object ...

    class TurnStile
    def initialize
    @fsm = TurnStileFSM.new(self)
    end
    # Define unlock, lock, alarm_xx as before

    def person_attempts_to_pass
    @fsm.pass
    end
    end

    This second way might be better if the FSM events do not map cleanly to your
    domain object. I'm still playing around with the ideas.

    > I also came up with a similar scheme before I went to the generator
    > approach. One of the advantages that I saw to using a YAML file is that
    > the schema is pretty clean (events, actions, state transitions) and you
    > only have to implement the actions.


    Same here, the user needs only implement the actions, the event methods and
    all the book-keeping is handled automatically. The Ruby syntax is even
    vaguely reminiscent of Bob Martin's SMC (State Machine Compiler), but with
    do/end instead of curly braces and colons on the state/event/action names.

    One of the big advantages of the rubyesque syntax is the ability to specify
    nested states. For example, perhaps we would like the turn stile to go into
    repair mode from any of the running states. We could specify that by placing
    an explicit event line in every running state, or we could do something like
    this ...

    state :running do
    # unless overridden, this event definition applies to
    # all the nested states.
    event :service, :repair_mode, :unlock

    state :unlocked do
    # as before
    end

    state :locked do
    # as before
    end
    end

    state :violoation do
    # as before
    end

    Now a :service event in either :locked or :unlocked will put the turnstile in
    repair mode. (Note: the prototype doesn't support nested states at the
    moment. I'm just speaking hypothetically).

    Currently the prototype just builds a simple state transition table in the FSM
    class. But once you have the table built, you can generate any number of
    implementations (e.g. table-base, case statement based, GOF state pattern).
    You are not even limited to Ruby, but could generate the FSM in any language
    (again, similar to SMC).

    > Then again, that is based on a server
    > receiving client messages, which may be a different model to the one you
    > are using.


    I am not assuming any kind of server implementation. Events are methods on
    the FSM object and actions are methods on the domain object (which might be
    the same object!). You would have to add server specific code to my
    prototype if that's how you were going to use it.

    > Are you going to publish your StateMachine class?


    The prototype is research for something I'm writing on state machines.
    Eventually I plan publish it, but for now it is just an experiment. If
    anyone is interested, I'm can email them a copy of the prototype. (if you
    ask by email, use the address. My normal email server is a
    bit flakey over the past day or so)

    --
    -- Jim Weirich http://onestepback.org
    -----------------------------------------------------------------
    "Beware of bugs in the above code; I have only proved it correct,
    not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
     
    Jim Weirich, Nov 4, 2004
    #14
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.

Share This Page