Per la realizzazione, mi sono avvalso del linguaggio C#, che per me aveva due vantaggi:
La macchina a stati:
public class CrossingSm : EuSm<CrossingSm>
{
// Variabili di stato extra.
public EuTimer Timer { get; private set; }
public int PedestrianCounter { get; private set; } = 0;
// Il client MQTT.
public readonly MqttClient _MqttClient;
// Costruzione della macchina a stati.
public CrossingSm() : base("CrossingControl")
{
// Includi tutti gli stati, e lo pseudo-stato.
WithStates(new EuState<CrossingSm>[]
{
new GreenState(),
new LongGreenState(),
new PendingState(),
new YellowState(),
new RedState(),
new UrgingState(),
new InactiveState()
});
WithAnyState(new AnyState());
// Inizializza il timer e il client MQTT.
Timer = new EuTimer("CrossingTimer").WithEventQueue(EventQueue);
_MqttClient = new MqttClient("CrossingControlClient", EventQueue)
.WithBrokerHost("myhost")
.WithCredentials(new Tuple<string, string>("myuser", "mypass"));
}
// Inizializzazione della macchina a stati.
protected override void OnEntry()
{
// Avvia il client MQTT.
_MqttClient.StartTask();
}
// Deinizializzazione della macchina a stati.
protected override void OnExit()
{
// Ferma il timer e il client MQTT.
Timer.StopTimer();
_MqttClient.StopTask();
_MqttClient.Dispose();
}
// Funzione delegata per il riallineamento.
public void Refresh()
{
// ... altro codice, per pubblicare tutte le info rilevanti.
}
// Funzione delegata per eseguire la (dis)attivazione del sistema.
public void SetSystemActive(bool isActive)
{
_MqttClient.PublishSystemActive(isActive);
}
// Funzione delegata per il cambio di colore del semaforo stradale.
public void SetTrafficColor(LightColor color, bool isFlashing = false)
{
_MqttClient.PublishTrafficColor(color, isFlashing);
}
// ... altro codice
// Funzione delegata per l'impostazione del conteggio sul display pedonale.
public void SetPedestrianCounter(int seconds)
{
PedestrianCounter = seconds;
_MqttClient.PublishPedestrianCounter(PedestrianCounter);
}
// Funzione delegata per il decremento del conteggio sul display pedonale.
public void DecrementPedestrianCounter()
{
if (PedestrianCounter > 0)
PedestrianCounter--;
_MqttClient.PublishPedestrianCounter(PedestrianCounter);
}
}
Il codice è auto-esplicativo; le variabili Timer e PedestrianCounter, nonché la serie di funzioni da Refresh a DecrementPedestrianCounter, sono quelle per le quali la macchina a stati funge da delegato per gli stati.
La macchina a stati, se è impostato uno stato di tipo AnyState, lo gestisce prima di gestire lo stato corrente, a prescindere da quale sia lo stato corrente.
Gli pseudo-stati Any state e Any state except Inactive, con un semplice artificio, vengono gestiti entrambi dalla medesima classe AnyState:
public class AnyState : EuState<CrossingSm>
{
// Gestione degli eventi di riallineamento e di riattivazione del sistema.
public override void OnEvent(EuEvent smEvent)
{
if (smEvent is RefreshEvent)
_sm.Refresh();
else if (_sm.CurrentNode is not InactiveState &&
smEvent is SystemActiveEvent activeEvent && !activeEvent.IsActive)
ToState<InactiveState>();
base.OnEvent(smEvent);
}
}
In generale, per implementare gli stati, è sufficiente seguire pedissequamente il diagramma a stati. È buona norma, se si deve fare una modifica, aggiornare prima il diagramma, e solo in seguito il codice, al fine di averli sempre ben allineati.
Lo stato GreenState implementa in modo specifico sia la funzione OnEntry che la OnEvent, nel modo seguente:
public class GreenState : EuState<CrossingSm>
{
// Esecuzione delle azioni all'ingresso dello stato.
public override void OnEntry()
{
_sm.SetTrafficColor(LightColor.Green);
_sm.SetPedestrianColor(LightColor.Red);
_sm._Timer.StartTimer(CrossingSm.GreenTime);
}
// Gestione degli eventi, in particolare lo scadere
// del timer e la richiesta attraversamento.
public override void OnEvent(EuEvent smEvent)
{
if (smEvent is EuTimer.ElapsedEvent)
ToState<LongGreenState>();
else if (smEvent is PedestrianRequestEvent)
ToState<PendingState>();
}
}
Come si vede, lo stato fa riferimento alla macchina a stati sia per le variabili extra che per eseguire le azioni.
Lo stato LongGreenState implementa in modo specifico solo la funzione OnEvent, poiché non prevede azioni all'ingresso dello stato:
public class LongGreenState : EuState<CrossingSm>
{
public override void OnEvent(EuEvent smEvent)
{
if (smEvent is PedestrianRequestEvent)
{
int pedestrianCounter = (int)CrossingSm.YellowTime.TotalSeconds;
_sm.SetPedestrianCounter(pedestrianCounter);
ToState<YellowState>();
}
}
}
Le transizioni di stato, ove previste, vengono eseguite utilizzando la funzione ToState, che è parametrizzata con la classe dello stato, non la sua istanza; si tratta di una decisione di progetto a livello della libreria, per garantire la type safety.
Giorgio Barchiesi
Albo degli Ingegneri Sez. A, N. 4027 della Prov. di Trento
P.IVA 02370260222, C.F. BRC GRG 58L26 C794R
Copyright © 2015-2024 Giorgio Barchiesi - Tutti i diritti riservati