fsmgen 0.1

M

Mark Probert

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)
 
A

Ara.T.Howard

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
===============================================================================
 
C

Carl Youngblood

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!
 
M

Mark Probert

Hi ..

Carl Youngblood said:
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.
 
V

vruz

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
 
M

Mark Probert

Hi ..

vruz said:
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.
 
V

vruz

[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
 
E

Eivind Eklund

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.
 
M

Mark Probert

Hi ..

Eivind Eklund said:
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.
 
J

Jim Weirich

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).
 
P

Phil Tomson

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
 
M

Mark Probert

Hi ..

Jim Weirich said:
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.
 
J

Jim Weirich

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 (e-mail address removed) address. My normal email server is a
bit flakey over the past day or so)
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top