Finite State Machine Model for a Persisted Traffic Light in Frame
(Frame is a new Domain Specific Language (DSL) focused on enabling developers to easily create programs using automata. See the Frame documentation as well as Getting Started With Frame to find tools and other resources to explore the Frame language.)
In a previous article on software modeling I explored how to create Finite State Machines (automata technically) in a new programming language called Frame.
If you are completely new to Frame, you might start with that article first.
In this article we will increase the complexity significantly and leap from a two state lamp system to a three state system — a traffic light. In addition we will also explore how to persist a Frame system and restore it. This is a key capability for implementing workflow systems.
Creating a Stateful Model
To start we will sketch out the states that a traffic light can normally be in:
#TrafficLight
-machine-
$Green
$Yellow
$Red
##
With simple, well known systems like traffic lights quickly itemizing the full state inventory is straightforward. Next we will add the transitions that make them into a real state machine.
#TrafficLight
-machine-
$Green
|tick|
-> $Yellow ^
$Yellow
|tick|
-> $Red ^
$Red
|tick|
-> $Green ^
##
Above we see that the traffic light will now receive a tick message and cycle between each of the states indefinitely.
The Interface
Next we need to provide some way to drive the system. To do so we will create an interface block and add a tick interface method to send tick events to the system.
#TrafficLight
-interface-
tick
-machine-
$Red
|tick|
-> $Yellow ^
$Yellow
|tick|
-> $Green ^
$Green
|tick|
-> $Red ^
##
We now have a working state machine but it doesn’t really do anything other than transition. Let’s add some output next.
Adding Enter Event Handlers
Frame’s enter event handlers enable triggering behavior upon entry to a new state. To do so, the Frame system runtime that is generated inside each system controller (the actual Python class that is generated for the system) sends a special event with the reserved message of just a single “>” character.
#TrafficLight
-interface-
tick
-machine-
$Red
|>|
print("Red") ^
|tick|
-> $Yellow ^
$Yellow
|>|
print("Yellow") ^
|tick|
-> $Green ^
$Green
|>|
print("Green") ^
|tick|
-> $Red ^
##
Above we add an enter event handler for each state that prints out the name of the state that has just been entered. We just need a way to “make it go”.
Main Function
We now are ready to add a main() function and test out our traffic light system. Below we see a program that instantiates a TrafficLight controller and loops sending a tick messages into the machine and then sleeping after each tick:
`import time`
fn main {
var tl:# = #TrafficLight()
loop var x = 0; x < 9; x = x + 1 {
tl.tick()
time.sleep(.5)
}
}
Running the program results in the following output:
Red
Yellow
Green
Red
Yellow
Green
Red
Yellow
Green
Red
We have coded the basic functionality for the traffic light. Next we need to enable it to be persisted and restored.
Persisting Frame Controllers
Workflows are basically state machines that can be persisted to disk or some datastore and then revived when a new event comes in for it to handle. Frame supports this in Python using the “jsonpickle” module.
The first thing we need to do is import said module. Frame syntax is “porus” in order to support arbitrary native code insertions. Therefore to directly inject the Python import statements, we use the “superstring” syntax shown here:
`import sys`
`import time`
`import jsonpickle`
Next we add a couple of operations to “pickle” (marshal) and “unpickle” (unmarshal) the controller:
-operations-
#[static]
unmarshal [data] : #TrafficLight {
^(jsonpickle.decode(data))
}
marshal : JSON {
^(jsonpickle.encode(self))
}
Operations are privileged methods that can access the internals of a controller without going through the machine. Frame supports static methods by use of the #[static] attribute. This enables having a system scoped method to perform this operation, but could be done just using jsonpickle by itself as well.
Now we can update our main function to take advantage of these operations.
fn main {
// Instantiate System Controller
var tl:# = #TrafficLight()
// Save off internal state
var data = tl.marshal()
// Set controller variable to nil
tl = nil
// Sleep a bit
time.sleep(.5)
loop var x = 0; x < 9; x = x + 1 {
// Now reinstantiate controller from
// persisted data using the unmarshal operation
tl = #TrafficLight.unmarshal(data)
// Send tick event to drive state machine
tl.tick()
// Sleep some more
time.sleep(.5)
// Repeat saving off state and removing
// reference to system controller
data = tl.marshal()
tl = nil
}
In the code above, the controller data is never saved to disk, but instead resides in a “data” variable. However these is exactly the operations that would be executed regardless of where the data was being stored. In this case it is just to memory.
With this capability, Frame starts to line up as a workable alterantive to “low-code” programming solutions. However the Frame approach fits better with standard source control and devops processes that developers are familiar with.
The Full Monty
Pulling all the various bits and bobs together, here is the final listing.
`import sys`
`import time`
`import jsonpickle`
fn main {
// Instantiate System Controller
var tl:# = #TrafficLight()
// Save off internal state
var data = tl.marshal()
// Set controller variable to nil
tl = nil
// Sleep a bit
time.sleep(.5)
loop var x = 0; x < 9; x = x + 1 {
// Now reinstantiate controller from
// persisted data using the unmarshal operation
tl = #TrafficLight.unmarshal(data)
// Send tick event to drive state machine
tl.tick()
// Sleep some more
time.sleep(.5)
// Repeat saving off state and removing
// reference to system controller
data = tl.marshal()
tl = nil
}
}
#TrafficLight
-operations-
#[static]
unmarshal [data] : #TrafficLight {
^(jsonpickle.decode(data))
}
marshal : JSON {
^(jsonpickle.encode(self))
}
-interface-
tick
-machine-
$Green
|>|
print("Green") ^
|tick|
-> $Yellow ^
$Yellow
|>|
print("Yellow") ^
|tick|
-> $Red ^
$Red
|>|
print("Red") ^
|tick|
-> $Green ^
##
Conclusion
This quick introduction demonstrates how a persisted workflow can be created using Frame. Using the jsonpickle
library it is straightforward to serialize a Frame system controller and then restore it to good working order.