Quando si sviluppa un'applicazione destinata a dialogare con hardware reale — un sensore collegato a un Raspberry Pi, un modulo GPIO, una scheda di acquisizione analogica — è utile poter verificare la logica applicativa su un normale PC, prima ancora di avere il dispositivo sotto mano. Lazarus, con la sua libreria di componenti LCL (Lazarus Component Library), si presta bene a questo: in pochi minuti si costruisce un pannello di controllo multipiattaforma che simula il comportamento dei sensori, riutilizzabile poi con il driver vero.
Bottoni, timer e memo: lo scheletro dell'interfaccia
Per un banco di test tipico bastano pochi componenti, tutti dichiarati a design time nel file .lfm della form e collegati ai rispettivi gestori di evento: alcuni TButton per avviare la simulazione dei diversi sensori, un TMemo per il log testuale e un TTimer che scandisce il ciclo di lettura periodica.
type
{ TMainForm }
TMainForm = class(TForm)
btnRainSensor: TButton;
btnFireDetector: TButton;
memoOutput: TMemo;
tmrSensor: TTimer;
procedure btnRainSensorClick(Sender: TObject);
procedure tmrSensorTimer(Sender: TObject);
private
FCurrentSensor: string;
procedure Log(const AMessage: string);
function SimulateADCRead: Byte;
end;
procedure TMainForm.btnRainSensorClick(Sender: TObject);
begin
FCurrentSensor := 'Rain';
Log('Avvio sensore pioggia...');
tmrSensor.Enabled := True;
end;Un livello di astrazione per l'hardware
La parte interessante non è l'interfaccia in sé, ma il modo in cui si isola l'accesso all'hardware dietro classi di utilità come TADC e TGPIO. Durante lo sviluppo desktop questi metodi si limitano a scrivere nel log; una volta portato il progetto su Raspberry Pi, la stessa interfaccia pubblica viene implementata appoggiandosi a una libreria GPIO reale, senza toccare il resto del codice.
type
TGPIO = class
class procedure Setup(pin: integer; mode: integer);
class function Input(pin: integer): integer;
end;
class procedure TGPIO.Setup(pin: integer; mode: integer);
begin
// In produzione: chiamata alla libreria GPIO reale
MainForm.Log('GPIO pin ' + IntToStr(pin) + ' impostato a modo: ' + IntToStr(mode));
end;
class function TGPIO.Input(pin: integer): integer;
begin
// Simulazione: valore casuale al posto della lettura fisica
Result := Random(2);
end;Il timer come ciclo di polling
Il TTimer sostituisce il classico ciclo while True che si trova nei programmi console per Raspberry Pi: mantiene l'interfaccia reattiva perché ogni iterazione viene eseguita nel thread principale della LCL senza bloccare la form. Un semplice case sulla stringa che identifica il sensore attivo decide quale logica eseguire a ogni tick.
procedure TMainForm.tmrSensorTimer(Sender: TObject);
var
value: Integer;
begin
case FCurrentSensor of
'Rain':
begin
value := SimulateADCRead;
Log('Valore ADC: ' + IntToStr(value));
end;
end;
end;LCL contro VCL: cosa cambia davvero
Chi arriva da Delphi ritrova quasi tutto: stessa logica a eventi, stesso concetto di form e componenti. Alcune differenze pratiche però contano. Il file di progetto include sempre Interfaces prima delle altre unit: è quella unit a selezionare il widgetset del sistema operativo (GTK, Qt, Win32, Cocoa...), motivo per cui lo stesso eseguibile — ricompilato — gira senza modifiche su Linux, Windows o macOS. Su sistemi Unix, se si usano thread, va aggiunta la unit CThreads nella clausola uses del programma principale, altrimenti il runtime FPC non li gestisce correttamente. Il file .lfm riporta anche la versione della LCL con cui è stato salvato (LCLVersion), utile per capire con quale IDE è stata generata la form quando si riprende un progetto dopo tempo. Infine, per interfacce leggibili anche su schermi ad alta densità, vale la pena impostare Application.Scaled := True nel file .lpr, equivalente concettuale dello scaling DPI che in VCL si gestisce diversamente.