The Generino language is an explicit implementation of message-based finite-state machines (FSM).
Complete and formal definitions of FSMs can be find on the internet (see for example Wikipedia, so we shall give a short and down-to-earth definition of it.
Finite-State Machines are a programming paradigm where the involved objects simply sit there waiting for something to happen. When something interesting happens, they receive a notification called event. When an object receives an event, it is allowed to execute commands but using a negligible amount of time.
When such objects receive an event, they can generate events directed to other objects, change bits in the processor registry and doing any other thing. This sequence of actions is called transition.
When they are done processing, they move into a state which can be the same as before or a new one.
Every state has a list of events it reacts to with their related transition code: in this way, the same object can react to the same event in different way according to its state. For example, a simple light controller could have the state ON and OFF: when it receives the "toggle" event, could react turning on the light if in state OFF or turning it on if in the ON state.
In the Generino implementation, only one transaction is executed at a given time: events sent to other objects will be queued until the target object reaches its execution slot.
The Generino implementation is very simple: all objects are called in turn continuosly. At each call, every object checks its own queue: if it has some events waiting, it will process them; then, it will return control.
The entire Generino FSM implementation is contained by classes; a class is a logical container that lists the valid states the FSM, what events it expects and sends and how it should react, state by state, when an event is received.
In order to be operative, a class is to be instanced into an object. This allow reusing the same concepts for multiple similar instances. For example, we can have a class LedBlinker describing a blinking led: then we can create many objects, instances of LedBlinker, each one controlling a different led.
If a class is designed to handle a single problem that can not be replicated, it will have one class and one object instanced.
Generino creates a .ino file that can be compiled with the normal Arduino IDE.
Name and location of the generated file must declared in the Generino source file using the FILE keyword:
In the example above the generated file will be prog1.ino, that will be created under a directory named prog1.
Since the path specified is relative, the directory will be created below the directory where the .fct target project has been saved.
Note the double backslash (\\): this is required because quoted strings supports the same escape characters of C and C++. Note also that you can use as well the slash (/) symbol: both work automatically on Windows and Unix environments.
No matter how manu .gino files your project includes, there must be one and only one FILE declaration.
Important: the Arduino compiler requires the main .ino file to be in a directory with the same name. So, if you create abc.ino, this file must be in a directory named abc.
Pin declaration is used to define which Arduino pins are being used, how (digital input, digital output or analog input) and their name. The Generino code generator will automatically generate the appropriate pinMode function.
The pin declaration can be done using the given form:
DECLARE type name = value
The supported types are:
Arduino digital input pin; value must be a valid digital pin number. For example:
DECLARE DIGITAL INPUT PIN_ENC_A = 8
Arduino digital output pin; value must be a valid digital pin number. For example:
DECLARE DIGITAL OUTPUT PIN_LED_YELLOW = 13
Arduino digital output pin; value must be a valid analog pin number; strings like A0 are admitted. For example:
DECLARE ANALOG INPUT PIN_SENSOR = A0
this is a special case where a generic number can be defined; it can be used wherever a number is required, like for example when setting a timer timeout. It supports expressions:
DECLARE NUMBER Period1 = 100+Period2 DECLARE NUMBER Period2 = 320
Generino supports some predefined values that can be used without any definition:
|nowMs||NUMBER||Current reference time in milliseconds, used by timers. Set once at the beginning of every cycle.|
|HIGH||NUMBER||Same as Arduino's HIGH macro, used for digital pins.|
|LOW||NUMBER||Same as Arduino's LOW macro, used for digital pins.|
The Generino language supports expressions formed by the usual C operators. The Generino expressions accept and return values of the NUMBER type. For example:
DECLARE NUMBER Period1 = 2*(100+Period2)+10*HIGH DECLARE NUMBER Period2 = 320
In Generino a class is defined with the CLASS/END CLASS statements. This is a minimal class definition:
CLASS BlinkingLed STATE ledOff END STATE STATE ledOn END STATE END CLASS
The class above has two states and reacts to no events at all.
Ports define the channels that allow an object to receive or send events:
CLASS BlinkingLed PORT IN commandsToMe RECEIVES enable, disable, faster, slower PORT OUT notifications SENDS ledIsOn, ledIsOff STATE ledOff END STATE STATE ledOn END STATE END CLASS
The code above declares two ports. The port named commandsToMe is an incoming port and it is enabled to receive messages. The port The port named notifications is an outgoing port and it is enabled to send messages.
Each port contains a list of events that can be sent or received by the port.
Transitions are sequences of instructions that are triggered by the reception of an event in a given state.
CLASS BlinkingLed PORT IN commandsToMe RECEIVES enable, disable, faster, slower PORT OUT notifications SENDS ledIsOn, ledIsOff STATE ledOff ON EVENT enable transition 1... END ON EVENT disable transition 2... END END STATE STATE ledOn END STATE END CLASS
The above code contains two transitions: transition 1 is the code executed when, in the ledOff state, the object receives an enable event. Transition transition 2, instead, is executed when in the same state it receives a disable event.
In the following chapters we shall see the instructions supported by the transition code.
SET STATE newState
Sets the next state to newState. The next transition will start from state newState.
If multiple SET STATE are specified, only the last one will be taken in accound. If no SET STATE are specified, the next state will be the same as the previous one.
The state specified in SET STATE must be a valid state declared with STATE x in the same class.
CLASS BlinkingLed PORT IN commandsToMe RECEIVES enable, disable, faster, slower PORT OUT notifications SENDS ledIsOn, ledIsOff STATE ledOff ON EVENT enable SET STATE ledOn END ON EVENT disable END END STATE STATE ledOn END STATE END CLASS
DIGITAL WRITE value TO PIN digOut
Sets the digital output
DIGITAL WRITE HIGH TO PIN LED_PIN
Parameter value must be of type DIGITAL OUTPUT.
The SEND statement is used within a transition to send an event to another object. The event is to be routed through one of the output ports that support the event being sent:
SEND event TO PORT portName
Parameter event is one of the events listed after the PORT OUT declaration whose name matches portName.
The event being sent will be queued in all objects connected to the given output port.
The "TO PORT portName" part can be omitted if this class has only one output port defined.
TIMER timerName SET TIMER timerName TO ms CLEAR TIMER timerName
A timer is an internal reasource that sends an event after a given number of milliseconds to the object that activated it.
Parameter ms must be of type NUMBER.
The timer event appears like normal event but it does not come from a port. For this reason, the timer can not have a
Command SET TIMER sets the timer timerName to expire after ms milliseconds from now. For example:
SET TIMER tim TO 100
If the timer was already set, it will be set to the new timeout value. The timer
Command CLEAR TIMER clears the timer timerName so it does not expire anymore. If the timer was already off, nothing happens. For example:
CLEAR TIMER tim
This is a timer example: the led is turned off after 3s from start unless a stopOperations event is received:
// Turn on a led after 3s unless the "stopOperations" event is received CLASS Led PORT IN inChannel RECEIVES stopOperations TIMER blinkPeriod START SET TIMER blinkPeriod TO 3000 END START STATE blinkedOff ON EVENT blinkPeriod DIGITAL WRITE HIGH TO PIN ledPin END ON EVENT stopOperations CLEAR TIMER blinkPeriod END END STATE END CLASS
The start transition is a transition that is automatically executed when the object is instanced:
CLASS myClass START start transition code END START ... END CLASS
The start transition can contain any code that can normally go within a transition. It can be used, for example, to set an initial state, to set initial timers, set pin outputs and so on.
In order to use the same implementation for multiple objects, classes support object parameters. Object parameters are some configuration data that must be specified every time an object of that class is being created. For example, a BlinkingLed class could have two object parameters, pinNumber and blinkMillisecs: in this way, multiple BlikingLed objects could be instanced on different leds and different blinking ratios.
Object parameters are declared within the class with the REQUIRES keyword:
REQUIRES type name
The available parameter types are the same as those alreay exposed in the declaration types section.
CLASS BlinkingLed REQUIRES DIGITAL OUTPUT ledPin REQUIRES NUMBER blinkMs ... END CLASS
Once declared, these parameters can be used inside a class in their appropriate position. For example, a DIGITAL OUTPUT parameter can be used in a DIGITAL WRITE statement:
DIGITAL WRITE HIGH TO PIN ledPin
Class attributes are values that maintained by each object of the class for internal use. They are declared inside a class with the following syntax:
ATTRIBUTE type name
Although type can be any valid type, the most used one will be NUMBER.
Attributes can be used within expressions and assigned using the = symbol inside the transition code:
ATTRIBUTE NUMBER blinkDelay ON EVENT myEvent blinkDelay = blinkDelay + 100 SET TIMER tim TO blinkDelay END
OBJECT className objectName par1=value1 par2=value2 ... parN=valueN
OBJECT BlinkingLed led2 ledPin=PIN_LED_YELLOW blinkMs=100
The IF statement allows to control the execution flow within a transition.
Its general form is:
IF expr1 THEN statements1 ELSIF expr2 THEN statements2 ... ELSIF exprN THEN statements2 ELSE statementsElse END IF
A continuous event consists in a repeated monitoring of a resource looking for a change. For example, when waiting for an hardware button to be pressed, the system has to continously read its pin waiting for its value to go high.
In Generino, this is obtained by the WHEN keyword:
WHEN expression statements END WHEN
The NUMBER expression is evaluated at every cycle. If it returns other than zero, it will execute the statements.
In the example below, the machine state changes its state when a button is pressed or released. Every time a button is pressed, a trigger event is emitted:
CLASS SimpleButton REQUIRES DIGITAL INPUT buttonPin PORT OUT action SENDS trigger STATE depressed WHEN digitalRead(buttonPin) SET STATE pressed SEND trigger TO PORT action END WHEN END STATE STATE pressed WHEN !digitalRead(buttonPin) SET STATE depressed END WHEN END STATE END CLASS
Connections are to be used to connect input and output ports of various objects:
CONNECT portOut@objectOut TO portIn1@objectIn1, portIn2@objectIn2, ..., portInN@objectInN
CONNECT out1@logic TO commands@led1 CONNECT out2@logic TO commands@led2 CONNECT action@btn1 TO inCommand1@logic, commands@bouncer CONNECT action@btn2 TO inCommand1@logic CONNECT action@btn2 TO clear@displ CONNECT action@rot TO rotary@displ
The connected input and output ports must hold at least one event in common. Otherwise an error will occur because communication among those ports would be impossibile.
Events sent from one object to another are queued up until the receiving object is able to process them. In theory, an object could receive any number of events before being able to process them. However, maintaining a long queue is expensive in terms of memory: for this reason, the length of the queue can be finely tuned.
The default queue length is 4 items but it can be programmed using the QUEUE LENGTH keyword:
CLASS MyClass QUEUE LENGTH 1 ... END CLASS
Classes without input port have always queue length of 0. For all the other classes, the queue length can range from 1 to 255.