Mit Delphi auf das LCD 20x4 Bricklet schreiben

Für diese Projekt setzen wir voraus, dass eine Delphi Entwicklungsumgebung eingerichtet ist und ein grundsätzliches Verständnis der Delphi Programmiersprache vorhanden ist.

Falls dies nicht der Fall ist sollte hier begonnen werden. Informationen über die Tinkerforge API sind dann hier zu finden.

Ziele

Wir setzen uns folgende Ziele für dieses Projekt:

  • Temperatur, Helligkeit, Luftfeuchte und Luftdruck sollen auf dem LCD 20x4 Bricklet angezeigt werden,
  • die gemessenen Werte sollen automatisch aktualisiert werden sobald sie sich verändern und
  • die gemessenen Werte sollen in einem verständlichen Format angezeigt werden.

Da dieses Projekt wahrscheinlich 24/7 laufen wird, wollen wir sicherstellen, dass das Programm möglichst robust gegen externe Einflüsse ist. Das Programm sollte weiterhin funktionieren falls

  • Bricklets ausgetauscht werden (z.B. verwenden wir keine fixen UIDs),
  • Brick Daemon läuft nicht oder wird neu gestartet,
  • WIFI Extension ist außer Reichweite oder
  • Wetterstation wurde neu gestartet (Stromausfall oder USB getrennt).

Im Folgenden werden wir Schritt für Schritt zeigen wie diese Ziele erreicht werden können.

Schritt 1: Bricks und Bricklets dynamisch erkennen

Als Erstes legen wir fest wohin unser Programm sich verbinden soll:

const
  HOST = 'localhost';
  PORT = 4223;

Falls eine WIFI Extension verwendet wird, oder der Brick Daemon auf einem anderen PC läuft, dann muss "localhost" durch die IP Adresse oder den Hostnamen der WIFI Extension oder des anderen PCs ersetzt werden.

Nach dem Start des Programms müssen der OnEnumerate Callback und der OnConnected Callback registriert und ein erstes Enumerate ausgelöst werden:

procedure TWeatherStation.Execute;
begin
  ipcon := TIPConnection.Create;
  ipcon.Connect(HOST, PORT);
  ipcon.OnEnumerate := {$ifdef FPC}@{$endif}EnumerateCB;
  ipcon.OnConnected := {$ifdef FPC}@{$endif}ConnectedCB;
  ipcon.Enumerate;
end;

Der Enumerate Callback wird ausgelöst wenn ein Brick per USB angeschlossen wird oder wenn die Enumerate Funktion aufgerufen wird. Dies ermöglicht es die Bricks und Bricklets im Stapel zu erkennen ohne im Voraus ihre UIDs kennen zu müssen.

Der Connected Callback wird ausgelöst wenn die Verbindung zur WIFI Extension oder zum Brick Daemon hergestellt wurde. In diesem Callback muss wiederum ein Enumerate angestoßen werden, wenn es sich um ein Auto-Reconnect handelt:

procedure TWeatherStation.ConnectedCB(sender: TIPConnection; const connectedReason: byte);
begin
  if (connectedReason = IPCON_CONNECT_REASON_AUTO_RECONNECT) then begin
    ipcon.Enumerate;
  end;
end;

Ein Auto-Reconnect bedeutet, dass die Verbindung zur WIFI Extension oder zum Brick Daemon verloren gegangen ist und automatisch wiederhergestellt werden konnte. In diesem Fall kann es sein, dass die Bricklets ihre Konfiguration verloren haben und wir sie neu konfigurieren müssen. Da die Konfiguration beim Enumerate (siehe unten) durchgeführt wird, lösen wir einfach noch ein Enumerate aus.

Schritt 1 zusammengefügt:

const
  HOST = 'localhost';
  PORT = 4223;

type
  TWeatherStation = class
  private
    ipcon: TIPConnection;
  public
    procedure ConnectedCB(sender: TIPConnection; const connectedReason: byte);
    procedure Execute;
  end;

procedure TWeatherStation.ConnectedCB(sender: TIPConnection; const connectedReason: byte);
begin
  if (connectedReason = IPCON_CONNECT_REASON_AUTO_RECONNECT) then begin
    ipcon.Enumerate;
  end;
end;

procedure TWeatherStation.Execute;
begin
  ipcon := TIPConnection.Create;
  ipcon.Connect(HOST, PORT);
  ipcon.OnEnumerate := {$ifdef FPC}@{$endif}EnumerateCB;
  ipcon.OnConnected := {$ifdef FPC}@{$endif}ConnectedCB;
  ipcon.Enumerate;
end;

Schritt 2: Bricklets beim Enumerate initialisieren

Während des Enumerierungsprozesse sollen alle messenden Bricklets konfiguriert werden. Dadurch ist sichergestellt, dass sie neu konfiguriert werden nach einem Verbindungsabbruch oder einer Unterbrechung der Stromversorgung.

Die Konfiguration soll beim ersten Start (IPCON_ENUMERATION_TYPE_CONNECTED) durchgeführt werden und auch bei jedem extern ausgelösten Enumerate (IPCON_ENUMERATION_TYPE_AVAILABLE):

procedure TWeatherStation.EnumerateCB(sender: TIPConnection; const uid: string;
                                      const connectedUid: string; const position: char;
                                      const hardwareVersion: TVersionNumber;
                                      const firmwareVersion: TVersionNumber;
                                      const deviceIdentifier: word; const enumerationType: byte);
begin
  if ((enumerationType = IPCON_ENUMERATION_TYPE_CONNECTED) or
      (enumerationType = IPCON_ENUMERATION_TYPE_AVAILABLE)) then begin

Die Konfiguration des LCD 20x4 ist einfach, wir löschen den aktuellen Inhalt des Displays und schalten das Backlight ein:

if (deviceIdentifier = BRICKLET_LCD_20X4_DEVICE_IDENTIFIER) then begin
  brickletLCD := TBrickletLCD20x4.Create(UID, ipcon);
  brickletLCD.ClearDisplay();
  brickletLCD.BacklightOn();
end;

Das Ambient Light, Humidity und Barometer Bricklet werden so eingestellt, dass sie uns ihre jeweiligen Messwerte höchsten mit einer Periode von 1000ms (1s) mitteilen:

else if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_DEVICE_IDENTIFIER) then begin
  brickletAmbientLight := TBrickletAmbientLight.Create(uid, ipcon);
  brickletAmbientLight.SetIlluminanceCallbackPeriod(1000);
  brickletAmbientLight.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceCB;
end
else if (deviceIdentifier = BRICKLET_HUMIDITY_DEVICE_IDENTIFIER) then begin
  brickletHumidity := TBrickletHumidity.Create(uid, ipcon);
  brickletHumidity.SetHumidityCallbackPeriod(1000);
  brickletHumidity.OnHumidity := {$ifdef FPC}@{$endif}HumidityCB;
end
else if (deviceIdentifier = BRICKLET_BAROMETER_DEVICE_IDENTIFIER) then begin
  brickletBarometer := TBrickletBarometer.Create(uid, ipcon);
  brickletBarometer.SetAirPressureCallbackPeriod(1000);
  brickletBarometer.OnAirPressure := {$ifdef FPC}@{$endif}AirPressureCB;
end;

Dies bedeutet, dass die Bricklets die IlluminanceCB, HumidityCB und AirPressureCB Callback-Funktionen immer dann aufrufen wenn sich der Messwert verändert hat, aber höchsten alle 1000ms.

Schritt 2 zusammengefügt:

procedure TWeatherStation.EnumerateCB(sender: TIPConnection; const uid: string;
                                      const connectedUid: string; const position: char;
                                      const hardwareVersion: TVersionNumber;
                                      const firmwareVersion: TVersionNumber;
                                      const deviceIdentifier: word; const enumerationType: byte);
begin
  if ((enumerationType = IPCON_ENUMERATION_TYPE_CONNECTED) or
      (enumerationType = IPCON_ENUMERATION_TYPE_AVAILABLE)) then begin
    if (deviceIdentifier = BRICKLET_LCD_20X4_DEVICE_IDENTIFIER) then begin
      brickletLCD := TBrickletLCD20x4.Create(UID, ipcon);
      brickletLCD.ClearDisplay();
      brickletLCD.BacklightOn();
    end
    else if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_DEVICE_IDENTIFIER) then begin
      brickletAmbientLight := TBrickletAmbientLight.Create(uid, ipcon);
      brickletAmbientLight.SetIlluminanceCallbackPeriod(1000);
      brickletAmbientLight.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceCB;
    end
    else if (deviceIdentifier = BRICKLET_HUMIDITY_DEVICE_IDENTIFIER) then begin
      brickletHumidity := TBrickletHumidity.Create(uid, ipcon);
      brickletHumidity.SetHumidityCallbackPeriod(1000);
      brickletHumidity.OnHumidity := {$ifdef FPC}@{$endif}HumidityCB;
    end
    else if (deviceIdentifier = BRICKLET_BAROMETER_DEVICE_IDENTIFIER) then begin
      brickletBarometer := TBrickletBarometer.Create(uid, ipcon);
      brickletBarometer.SetAirPressureCallbackPeriod(1000);
      brickletBarometer.OnAirPressure := {$ifdef FPC}@{$endif}AirPressureCB;
    end;
  end;
end;

Schritt 3: Messwerte auf dem Display anzeigen

Wir wollen eine hübsche Darstellung der Messwerte auf dem Display. Zum Beispiel:

Illuminanc 137.39 lx
Humidity    34.10 %
Air Press  987.70 mb
Temperature 22.64 °C

Die Dezimaltrennzeichen und die Einheiten sollen in jeweils einer Spalte übereinander stehen. Daher verwenden wird zwei Zeichen für jede Einheit, zwei Nachkommastellen und kürzen die Namen so, dass sie in den restlichen Platz der jeweiligen Zeile passen. Das ist auch der Grund, warum dem "Illuminanc" das letzte "e" fehlt.

text := Format('%6.2f', [value])

Der obige Ausdruck wandelt eine Fließkommazahl in eine Zeichenkette um, gemäß der gegebenen Formatspezifikation. Das Ergebnis ist dann mindestens 6 Zeichen lang mit 2 Nachkommastellen. Fall es weniger als 6 Zeichen sind wird von Links mit Leerzeichen aufgefüllt.

procedure TWeatherStation.IlluminanceCB(sender: TBrickletAmbientLight; const illuminance: word);
var text: string;
begin
  text := Format('Illuminanc %6.2f lx', [illuminance/10.0]);
  brickletLCD.WriteLine(0, 0, text);
end;

procedure TWeatherStation.HumidityCB(sender: TBrickletHumidity; const humidity: word);
var text: string;
begin
  text := Format('Humidity   %6.2f %%', [humidity/10.0]);
  brickletLCD.WriteLine(1, 0, text);
end;

procedure TWeatherStation.AirPressureCB(sender: TBrickletBarometer; const airPressure: longint);
var text: string;
begin
  text := Format('Air Press %7.2f mb', [airPressure/1000.0]);
  brickletLCD.WriteLine(2, 0, text);
end;

Es fehlt noch die Temperatur. Das Barometer Bricklet kann auch die Temperatur messen, aber es hat dafür keinen Callback. Als einfacher Workaround können wir die Temperatur in der AirPressureCB Callback-Funktion abfragen:

procedure TWeatherStation.AirPressureCB(sender: TBrickletBarometer; const airPressure: longint);
var text: string; temperature: smallint;
begin
  text := Format('Air Press %7.2f mb', [airPressure/1000.0]);
  brickletLCD.WriteLine(2, 0, text);

  temperature := brickletBarometer.GetChipTemperature;
  text := Format('Temperature %5.2f %sC', [temperature/100.0, '' + char($DF)]);
  brickletLCD.WriteLine(3, 0, text);
end;

Schritt 3 zusammengefügt:

procedure TWeatherStation.IlluminanceCB(sender: TBrickletAmbientLight; const illuminance: word);
var text: string;
begin
  text := Format('Illuminanc %6.2f lx', [illuminance/10.0]);
  brickletLCD.WriteLine(0, 0, text);
end;

procedure TWeatherStation.HumidityCB(sender: TBrickletHumidity; const humidity: word);
var text: string;
begin
  text := Format('Humidity   %6.2f %%', [humidity/10.0]);
  brickletLCD.WriteLine(1, 0, text);
end;

procedure TWeatherStation.AirPressureCB(sender: TBrickletBarometer; const airPressure: longint);
var text: string; temperature: smallint;
begin
  text := Format('Air Press %7.2f mb', [airPressure/1000.0]);
  brickletLCD.WriteLine(2, 0, text);

  temperature := brickletBarometer.GetChipTemperature;
  { 0xDF == ° on LCD 20x4 charset }
  text := Format('Temperature %5.2f %sC', [temperature/100.0, '' + char($DF)]);
  brickletLCD.WriteLine(3, 0, text);
end;

Das ist es. Wenn wir diese drei Schritte zusammen in eine Datei kopieren und ausführen, dann hätten wir jetzt eine funktionierenden Wetterstation.

Es gibt einige offensichtliche Möglichkeiten die Ausgabe der Messdaten noch zu verbessern. Die Namen könnten dynamisch exakt gekürzt werden, abhängig vom aktuell freien Raum der jeweiligen Zeile. Auch könnten die Namen können noch ins Deutsche übersetzt werden. Ein anderes Problem ist die Abfrage der Temperatur in der AirPressureCB Callback-Funktion. Wenn sich der Luftdruck nicht ändert dann wird auch die Anzeige der Temperatur nicht aktualisiert, auch wenn sich diese eigentlich geändert hat. Es wäre besser die Temperatur jede Sekunde in einem eigenen Thread anzufragen. Aber wir wollen das Programm für den Anfang einfach halten.

Wie dem auch sei, wir haben noch nicht alle Ziele erreicht. Das Programm ist noch nicht robust genug. Was passiert wenn die Verbindung beim Start des Programms nicht hergestellt werden kann, oder wenn das Enumerate nach einem Auto-Reconnect nicht funktioniert?

Wir brauchen noch Fehlerbehandlung!

Schritt 4: Fehlerbehandlung und Logging

Beim Start des Programms versuchen wir solange die Verbindung herzustellen, bis es klappt:

while (true) do begin
  try
    ipcon.Connect(HOST, PORT);
    break;
  except
    on e: Exception do begin
      WriteLn('Connection Error: ' + e.Message);
      Sleep(1000);
    end;
  end;
end;

und es wird solange versucht ein Enumerate zu starten bis auch dis geklappt hat:

while (true) do begin
  try
    ipcon.Enumerate;
    break;
  except
    on e: Exception do begin
      WriteLn('Enumeration Error: ' + e.Message);
      Sleep(1000);
    end;
  end;
end;

Mit diesen Änderungen kann das Programm schon gestartet werden bevor die Wetterstation angeschlossen ist.

Es muss auch sichergestellt werden, dass wir nur auf das LCD schreiben nachdem es initialisiert wurde:

procedure TWeatherStation.IlluminanceCB(sender: TBrickletAmbientLight; const illuminance: word);
var text: string;
begin
  if (brickletLCD <> nil) then begin
    text := Format('Illuminanc %6.2f lx', [illuminance/10.0]);
    brickletLCD.WriteLine(0, 0, text);
    WriteLn('Write to line 0: ' + text);
  end;
end;

und es müssen mögliche Fehler während des Enumerierungsprozesses behandelt werden:

if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_DEVICE_IDENTIFIER) then begin
  try
    brickletAmbientLight := TBrickletAmbientLight.Create(uid, ipcon);
    brickletAmbientLight.SetIlluminanceCallbackPeriod(1000);
    brickletAmbientLight.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceCB;
    WriteLn('Ambient Light initialized');
  except
    on e: Exception do begin
      WriteLn('Ambient Light init failed: ' + e.Message);
      brickletAmbientLight := nil;
    end;
  end;
end;

Zusätzlich wollen wir noch ein paar Logausgaben einfügen. Diese ermöglichen es später herauszufinden was ein Problem ausgelöst hat, wenn die Wetterstation nach einer Weile möglicherweise nicht mehr funktioniert wie erwartet.

Zum Beispiel, wenn die Wetterstation über WLAN angebunden ist und häufig Auto-Reconnects auftreten, dann ist wahrscheinlich die WLAN Verbindung nicht sehr stabil.

Schritt 5: Alles zusammen

Jetzt sind alle für diese Projekt gesteckten Ziele erreicht.

Das gesamte Programm für die Wetterstation (download):

program WeatherStation;

{$ifdef MSWINDOWS}{$apptype CONSOLE}{$endif}
{$ifdef FPC}{$mode OBJFPC}{$H+}{$endif}

uses
  Device,
  SysUtils,
  IPConnection,
  BrickletLCD20x4,
  BrickletHumidity,
  BrickletBarometer,
  BrickletHumidityV2,
  BrickletBarometerV2,
  BrickletAmbientLight,
  BrickletAmbientLightV2,
  BrickletAmbientLightV3;

const
  HOST = 'localhost';
  PORT = 4223;

type
  TWeatherStation = class

  private
    ipcon: TIPConnection;
    brickletLCD: TBrickletLCD20x4;
    brickletHumidity: TBrickletHumidity;
    brickletBarometer: TBrickletBarometer;
    brickletHumidityV2: TBrickletHumidityV2;
    brickletBarometerV2: TBrickletBarometerV2;
    brickletAmbientLight: TBrickletAmbientLight;
    brickletAmbientLightV2: TBrickletAmbientLightV2;
    brickletAmbientLightV3: TBrickletAmbientLightV3;

  public
    procedure Execute;
    constructor Create;
    destructor Destroy; override;
    procedure EnumerateCB(sender: TIPConnection;
                          const uid: string;
                          const connectedUid: string;
                          const position: char;
                          const hardwareVersion: TVersionNumber;
                          const firmwareVersion: TVersionNumber;
                          const deviceIdentifier: word;
                          const enumerationType: byte);
    procedure HumidityCB(sender: TBrickletHumidity; const humidity: word);
    procedure HumidityV2CB(sender: TBrickletHumidityV2; const humidity: word);
    procedure ConnectedCB(sender: TIPConnection; const connectedReason: byte);
    procedure AirPressureCB(sender: TBrickletBarometer; const airPressure: longint);
    procedure IlluminanceCB(sender: TBrickletAmbientLight; const illuminance: word);
    procedure AirPressureV2CB(sender: TBrickletBarometerV2; const airPressure: longint);
    procedure IlluminanceV2CB(sender: TBrickletAmbientLightV2; const illuminance: longword);
    procedure IlluminanceV3CB(sender: TBrickletAmbientLightV3; const illuminance: longword);
  end;

var
  ws: TWeatherStation;

constructor TWeatherStation.Create;
begin
  ipcon := nil;
  brickletLCD := nil;
  brickletHumidity := nil;
  brickletBarometer := nil;
  brickletHumidityV2 := nil;
  brickletBarometerV2 := nil;
  brickletAmbientLight := nil;
  brickletAmbientLightV2 := nil;
  brickletAmbientLightV3 := nil;
end;

destructor TWeatherStation.Destroy;
begin
  if (ipcon <> nil) then ipcon.Destroy;
  if (brickletLCD <> nil) then brickletLCD.Destroy;
  if (brickletHumidity <> nil) then brickletHumidity.Destroy;
  if (brickletBarometer <> nil) then brickletBarometer.Destroy;
  if (brickletHumidityV2 <> nil) then brickletHumidityV2.Destroy;
  if (brickletBarometerV2 <> nil) then brickletBarometerV2.Destroy;
  if (brickletAmbientLight <> nil) then brickletAmbientLight.Destroy;
  if (brickletAmbientLightV2 <> nil) then brickletAmbientLightV2.Destroy;
  if (brickletAmbientLightV3 <> nil) then brickletAmbientLightV3.Destroy;

  inherited Destroy;
end;

procedure TWeatherStation.ConnectedCB(sender: TIPConnection; const connectedReason: byte);
begin
  if (connectedReason = IPCON_CONNECT_REASON_AUTO_RECONNECT) then
  begin
    WriteLn('Auto Reconnect');

    while (true) do begin
      try
        ipcon.Enumerate;

        break;
      except
        on e: Exception do
        begin
          WriteLn('Enumeration Error: ' + e.Message);
          Sleep(1000);
        end;
      end;
    end;
  end;
end;

procedure TWeatherStation.EnumerateCB(sender: TIPConnection; const uid: string;
                                      const connectedUid: string; const position: char;
                                      const hardwareVersion: TVersionNumber;
                                      const firmwareVersion: TVersionNumber;
                                      const deviceIdentifier: word; const enumerationType: byte);
begin
  if ((enumerationType = IPCON_ENUMERATION_TYPE_CONNECTED) or
      (enumerationType = IPCON_ENUMERATION_TYPE_AVAILABLE)) then
    begin
      if (deviceIdentifier = BRICKLET_LCD_20X4_DEVICE_IDENTIFIER) then
      begin
        try
          brickletLCD := TBrickletLCD20x4.Create(UID, ipcon);
          brickletLCD.ClearDisplay;
          brickletLCD.BacklightOn;
          WriteLn('LCD 20x4 initialized');
        except
          on e: Exception do
          begin
            WriteLn('LCD 20x4 init failed: ' + e.Message);
            brickletLCD := nil;
          end;
        end;
      end
    else if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_DEVICE_IDENTIFIER) then
    begin
      try
        brickletAmbientLight := TBrickletAmbientLight.Create(uid, ipcon);
        brickletAmbientLight.SetIlluminanceCallbackPeriod(1000);
        brickletAmbientLight.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceCB;
        WriteLn('Ambient Light initialized');
      except
        on e: Exception do
        begin
          WriteLn('Ambient Light init failed: ' + e.Message);
          brickletAmbientLight := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_V2_DEVICE_IDENTIFIER) then
    begin
      try
        brickletAmbientLightV2 := TBrickletAmbientLightV2.Create(uid, ipcon);
        brickletAmbientLightV2.SetConfiguration(BRICKLET_AMBIENT_LIGHT_V2_ILLUMINANCE_RANGE_64000LUX,
                                                BRICKLET_AMBIENT_LIGHT_V2_INTEGRATION_TIME_200MS);
        brickletAmbientLightV2.SetIlluminanceCallbackPeriod(1000);
        brickletAmbientLightV2.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceV2CB;
        WriteLn('Ambient Light 2.0 initialized');
      except
        on e: Exception do
        begin
          WriteLn('Ambient Light 2.0 init failed: ' + e.Message);
          brickletAmbientLightV2 := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_AMBIENT_LIGHT_V3_DEVICE_IDENTIFIER) then
    begin
      try
        brickletAmbientLightV3 := TBrickletAmbientLightV3.Create(uid, ipcon);
        brickletAmbientLightV3.SetConfiguration(BRICKLET_AMBIENT_LIGHT_V2_ILLUMINANCE_RANGE_64000LUX,
                                                BRICKLET_AMBIENT_LIGHT_V2_INTEGRATION_TIME_200MS);
        brickletAmbientLightV3.SetIlluminanceCallbackConfiguration(1000, False, 'x', 0, 0);
        brickletAmbientLightV3.OnIlluminance := {$ifdef FPC}@{$endif}IlluminanceV3CB;
        WriteLn('Ambient Light 3.0 initialized');
      except
        on e: Exception do
        begin
          WriteLn('Ambient Light 3.0 init failed: ' + e.Message);
          brickletAmbientLightV3 := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_HUMIDITY_DEVICE_IDENTIFIER) then
    begin
      try
        brickletHumidity := TBrickletHumidity.Create(uid, ipcon);
        brickletHumidity.SetHumidityCallbackPeriod(1000);
        brickletHumidity.OnHumidity := {$ifdef FPC}@{$endif}HumidityCB;
        WriteLn('Humidity initialized');
      except
        on e: Exception do
        begin
          WriteLn('Humidity init failed: ' + e.Message);
          brickletHumidity := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_HUMIDITY_V2_DEVICE_IDENTIFIER) then
    begin
      try
        brickletHumidityV2 := TBrickletHumidityV2.Create(uid, ipcon);
        brickletHumidityV2.SetHumidityCallbackConfiguration(1000, true, 'x', 0, 0);
        brickletHumidityV2.OnHumidity := {$ifdef FPC}@{$endif}HumidityV2CB;
        WriteLn('Humidity 2.0 initialized');
      except
        on e: Exception do
        begin
          WriteLn('Humidity 2.0 init failed: ' + e.Message);
          brickletHumidityV2 := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_BAROMETER_DEVICE_IDENTIFIER) then
    begin
      try
        brickletBarometer := TBrickletBarometer.Create(uid, ipcon);
        brickletBarometer.SetAirPressureCallbackPeriod(1000);
        brickletBarometer.OnAirPressure := {$ifdef FPC}@{$endif}AirPressureCB;
        WriteLn('Barometer initialized');
      except
        on e: Exception do
        begin
          WriteLn('Barometer init failed: ' + e.Message);
          brickletBarometer := nil;
        end;
      end;
    end
    else if (deviceIdentifier = BRICKLET_BAROMETER_V2_DEVICE_IDENTIFIER) then
    begin
      try
        brickletBarometerV2 := TBrickletBarometerV2.Create(uid, ipcon);
        brickletBarometerV2.SetAirPressureCallbackConfiguration(1000, False, 'x', 0, 0);
        brickletBarometerV2.OnAirPressure := {$ifdef FPC}@{$endif}AirPressureV2CB;
        WriteLn('Barometer 2.0 initialized');
      except
        on e: Exception do
        begin
          WriteLn('Barometer 2.0 init failed: ' + e.Message);
          brickletBarometerV2 := nil;
        end;
      end;
    end;
  end;
end;

procedure TWeatherStation.IlluminanceCB(sender: TBrickletAmbientLight; const illuminance: word);

var text: string;

begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Illuminanc %6.2f lx', [illuminance/10.0]);
    brickletLCD.WriteLine(0, 0, text);
    WriteLn('Write to line 0: ' + text);
  end;
end;

procedure TWeatherStation.IlluminanceV2CB(sender: TBrickletAmbientLightV2;
                                          const illuminance: longword);

var text: string;

begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Illumina %8.2f lx', [illuminance/100.0]);
    brickletLCD.WriteLine(0, 0, text);
    WriteLn('Write to line 0: ' + text);
  end;
end;

procedure TWeatherStation.IlluminanceV3CB(sender: TBrickletAmbientLightV3;
                                          const illuminance: longword);
var text: string;
begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Illumina %8.2f lx', [illuminance / 100.0]);
    brickletLCD.WriteLine(0, 0, text);
    WriteLn('Write to line 0: ' + text);
  end;
end;

procedure TWeatherStation.HumidityCB(sender: TBrickletHumidity;
                                     const humidity: word);
var text: string;
begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Humidity   %6.2f %%', [humidity/10.0]);
    brickletLCD.WriteLine(1, 0, text);
    WriteLn('Write to line 1: ' + text);
  end;
end;

procedure TWeatherStation.HumidityV2CB(sender: TBrickletHumidityV2;
                                       const humidity: word);
var text: string;
begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Humidity   %6.2f %%', [humidity/100.0]);
    brickletLCD.WriteLine(1, 0, text);
    WriteLn('Write to line 1: ' + text);
  end;
end;

procedure TWeatherStation.AirPressureCB(sender: TBrickletBarometer;
                                        const airPressure: longint);
var text: string; temperature: smallint;
begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Air Press %7.2f mb', [airPressure/1000.0]);
    brickletLCD.WriteLine(2, 0, text);
    WriteLn('Write to line 2: ' + text);

    try
      temperature := brickletBarometer.GetChipTemperature;
    except
      on e: Exception do
      begin
        WriteLn('Could not get temperature: ' + e.Message);
        exit;
      end;
    end;

    { $DF == ° on LCD 20x4 charset. }
    text := Format('Temperature %5.2f %sC', [temperature/100.0, '' + char($DF)]);
    brickletLCD.WriteLine(3, 0, text);
    text := StringReplace(text, char($DF), '°', [rfReplaceAll]);
    WriteLn('Write to line 3: ' + text);
  end;
end;

procedure TWeatherStation.AirPressureV2CB(sender: TBrickletBarometerV2;
                                          const airPressure: longint);
var text: string;
temperature: smallint;
begin
  if (brickletLCD <> nil) then
  begin
    text := Format('Air Press %7.2f mb', [airPressure/1000.0]);
    brickletLCD.WriteLine(2, 0, text);
    WriteLn('Write to line 2: ' + text);

    try
      temperature := brickletBarometerV2.GetTemperature;
    except
      on e: Exception do
      begin
        WriteLn('Could not get temperature: ' + e.Message);
        exit;
      end;
    end;

    { $DF == ° on LCD 20x4 charset. }
    text := Format('Temperature %5.2f %sC', [temperature/100.0, '' + char($DF)]);
    brickletLCD.WriteLine(3, 0, text);
    text := StringReplace(text, char($DF), '°', [rfReplaceAll]);
    WriteLn('Write to line 3: ' + text);
  end;
end;

procedure TWeatherStation.Execute;
begin
  ipcon := TIPConnection.Create;
  while (true) do
  begin
    try
      ipcon.Connect(HOST, PORT);
      break;
    except
      on e: Exception do
      begin
        WriteLn('Connection Error: ' + e.Message);
        Sleep(1000);
      end;
    end;
  end;
  ipcon.OnEnumerate := {$ifdef FPC}@{$endif}EnumerateCB;
  ipcon.OnConnected := {$ifdef FPC}@{$endif}ConnectedCB;
  while (true) do
  begin
    try
      ipcon.Enumerate;
      break;
    except
      on e: Exception do
      begin
        WriteLn('Enumeration Error: ' + e.Message);
        Sleep(1000);
      end;
    end;
  end;
  WriteLn('Press key to exit');
  ReadLn;
end;

begin
  ws := TWeatherStation.Create;
  ws.Execute;
  ws.Destroy;
end.