Hi ..
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
···
vruz <horacio.lopez@gmail.com> wrote:
#
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.