Framework Details
The Lewis framework is built around a cycle-driven core which in turn drives the device simulation, including an optional StateMachine, and shared protocol adapters that separate the communication layer from the simulated device.
Cycle-driven
All processing in the framework occurs during “heartbeat” simulation ticks
which propagate calls to process
methods throughout the simulation,
along with a Δt parameter that contains the time that has
passed since the last tick. The device simulation is then responsible for
updating its state based on how much time has passed and what input has
been received during that time.
The benefits of this approach include:
This closely models real device behaviour, since processing in electronic devices naturally occurs on a cycle basis.
As a side-effect of the above, certain quirks of real devices are often captured by the simulated device naturally, without additional effort.
The simulation becomes deterministic: The same amount of process cycles, with the same Δt parameters along the way, and the same input via the device protocol, will always result in exactly the same device state.
Simulation speed can be controlled by increasing (fast-forward) or decreasing (slow-motion) the Δt parameter by a given factor.
Simulation fidelity can be controlled independently from speed by increasing or decreasing the number of cycles per second while adjusting the Δt parameter to compensate.
The above traits are very desirable both for running automated tests against the simulation, and for debugging any issues that are identified.
Statemachine
A class designed for a cycle-driven approach is provided to allow modeling complex device behaviour in an event-driven fashion.
A device may initialize a statemachine on construction, telling it what states the device can be in and what conditions should cause it to transition between them. The statemachine will automatically check eligible (exiting current state) transition conditions every cycle and perform transitions as necessary, triggering callbacks for any event that occurs. The following events are available for every state:
on_exit
is triggered once just before exiting the stateon_entry
is triggered once when entering the statein_state
is triggered every cycle that ends in the state
Every cycle will trigger exactly one in_state
event. This will
always be the last event of the cycle. When no transition occurs, this
is the only event. On the very first cycle of a simulation run,
on_entry
is raised against the initial state before raising an
in_state
against it. Any other cycles that involve a transition
first raise on_exit
against the current state, and then raise
on_entry
and in_state
against the new state. Only one transition
may occur per cycle.
There are three ways to specify event handlers when initializing the statemachine:
Object-Oriented: Implement one class per state, derived from
lewis.core.statemachine.State
, which optionally contains up to one of each event handlerFunction-Driven: Bind individual functions to individual events that need handling
Implicit: Implement handlers in the device class, with standard names like
on_entry_init
for a state called “init”, and callbindHandlersByName()