For this project we are assuming, that you have a Delphi development environment set up and that you have a rudimentary understanding of the Delphi language.
If you are totally new to Delphi itself you should start here. If you are new to the Tinkerforge API, you should start here.
We are also assuming that you have a smoke detector connected to an Industrial Digital In 4 Bricklet as described here.
We are setting the following goal for this project:
Since this project will likely run 24/7, we will also make sure that the application is as robust towards external influences as possible. The application should still work when
In the following we will show step-by-step how this can be achieved.
To start off, we need to define where our program should connect to:
const
HOST = 'localhost';
PORT = 4223;
If the WIFI Extension is used or if the Brick Daemon is running on a different PC, you have to exchange "localhost" with the IP address or hostname of the WIFI Extension or PC.
When the program is started, we need to register the OnEnumerate
callback and the OnConnected
callback and trigger a first
enumerate:
procedure TSmokeDetector.Execute;
begin
ipcon := TIPConnection.Create;
ipcon.Connect(HOST, PORT);
ipcon.OnEnumerate := {$ifdef FPC}@{$endif}EnumerateCB;
ipcon.OnConnected := {$ifdef FPC}@{$endif}ConnectedCB;
ipcon.Enumerate;
end;
The enumerate callback is triggered if a Brick gets connected over USB or if
the Enumerate
function is called. This allows to discover the Bricks and
Bricklets in a stack without knowing their types or UIDs beforehand.
The connected callback is triggered if the connection to the WIFI Extension or to the Brick Daemon got established. In this callback we need to trigger the enumerate again, if the reason is an auto reconnect:
procedure TSmokeDetector.ConnectedCB(sender: TIPConnection; const connectedReason: byte);
begin
if (connectedReason = IPCON_CONNECT_REASON_AUTO_RECONNECT) then begin
ipcon.Enumerate;
end;
end;
An auto reconnect means, that the connection to the WIFI Extension or to the Brick Daemon was lost and could subsequently be established again. In this case the Bricklets may have lost their configurations and we have to reconfigure them. Since the configuration is done during the enumeration process (see below), we have to trigger another enumeration.
Step 1 put together:
const
HOST = 'localhost';
PORT = 4223;
type
TSmokeDetector = class
private
ipcon: TIPConnection;
public
procedure ConnectedCB(sender: TIPConnection; const connectedReason: byte);
procedure Execute;
end;
procedure TSmokeDetector.ConnectedCB(sender: TIPConnection; const connectedReason: byte);
begin
if (connectedReason = IPCON_CONNECT_REASON_AUTO_RECONNECT) then begin
ipcon.Enumerate;
end;
end;
procedure TSmokeDetector.Execute;
begin
ipcon := TIPConnection.Create;
ipcon.Connect(HOST, PORT);
ipcon.OnEnumerate := {$ifdef FPC}@{$endif}EnumerateCB;
ipcon.OnConnected := {$ifdef FPC}@{$endif}ConnectedCB;
ipcon.Enumerate;
end;
During the enumeration we want to configure the Industrial Digital In 4 Bricklet. Doing this during the enumeration ensures that the Bricklet gets reconfigured if the Brick was disconnected or there was a power loss.
The configurations should be performed on first startup
(IPCON_ENUMERATION_TYPE_CONNECTED
) as well as whenever the enumeration is
triggered externally by us (IPCON_ENUMERATION_TYPE_AVAILABLE
):
procedure TSmokeDetector.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
We configure the Industrial Digital In 4 Bricklet to call the InterruptCB
callback if a change of the voltage level on any input pin is detected. The
debounce period is set to 10s (10000ms) to avoid being spammed with callbacks.
Interrupt detection is enabled for all inputs (15 = 0b1111).
if (deviceIdentifier = BRICKLET_INDUSTRIAL_DIGITAL_IN_4_DEVICE_IDENTIFIER) then begin
brickletIndustrialDigitalIn4 := TBrickletIndustrialDigitalIn4.Create(uid, ipcon);
brickletIndustrialDigitalIn4.SetDebouncePeriod(10000);
brickletIndustrialDigitalIn4.SetInterrupt(15);
brickletIndustrialDigitalIn4.OnInterrupt := {$ifdef FPC}@{$endif}InterruptCB;
end;
Step 2 put together:
procedure TSmokeDetector.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_INDUSTRIAL_DIGITAL_IN_4_DEVICE_IDENTIFIER) then begin
brickletIndustrialDigitalIn4 := TBrickletIndustrialDigitalIn4.Create(uid, ipcon);
brickletIndustrialDigitalIn4.SetDebouncePeriod(10000);
brickletIndustrialDigitalIn4.SetInterrupt(15);
brickletIndustrialDigitalIn4.OnInterrupt := {$ifdef FPC}@{$endif}InterruptCB;
end;
end;
end;
Now we need to react on the alarm signal of the smoke detector. But we want to
react only if the LED is turned on, not if it is turn off. This is done by
checking valueMask
for being > 0
. In that case there is a voltage
applied to at least one input, therefore, the LED is on.
procedure TSmokeDetector.InterruptCB(sender: TBrickletIndustrialDigitalIn4; const interruptMask: word; const valueMask: word);
begin
if (valueMask > 0) then begin
WriteLn('Fire! Fire!');
end;
end;
That's it. If we would copy these three steps together in one file and execute it, we would have a working program that reads the alarm status of a hacked smoke detector and reacts on its alarm signal!
Currently the program just outputs a warning. There are several ways to extend this. For example, the program could send an email or a text message to notify someone about the alarm.
However, we do not meet all of our goals yet. The program is not yet robust enough. What happens if it can't connect on startup? What happens if the enumerate after an auto reconnect doesn't work?
What we need is error handling!
On startup, we need to try to connect until the connection works:
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;
and we need to try enumerating until the message goes through:
while (true) do begin
try
ipcon.Enumerate;
break;
except
on e: Exception do begin
WriteLn('Enumeration Error: ' + e.Message);
Sleep(1000);
end;
end;
end;
With these changes it is now possible to first start the program and connect the Master Brick afterwards.
We also have to deal with errors during the initialization:
if (deviceIdentifier = BRICKLET_INDUSTRIAL_DIGITAL_IN_4_DEVICE_IDENTIFIER) then begin
try
brickletIndustrialDigitalIn4 := TBrickletIndustrialDigitalIn4.Create(uid, ipcon);
brickletIndustrialDigitalIn4.SetDebouncePeriod(10000);
brickletIndustrialDigitalIn4.SetInterrupt(15);
brickletIndustrialDigitalIn4.OnInterrupt := {$ifdef FPC}@{$endif}InterruptCB;
WriteLn('Industrial Digital In 4 initialized');
except
on e: Exception do begin
WriteLn('Industrial Digital In 4 init failed: ' + e.Message);
brickletIndustrialDigitalIn4 := nil;
end;
end;
end;
Additionally we added some logging. With the logging we can later find out what exactly caused a potential problem.
For example, if we connect to the Master Brick via Wi-Fi and we have regular auto reconnects, it likely means that the Wi-Fi connection is not very stable.
That's it! We are already done with our hacked smoke detector and all of the goals should be met.
Now all of the above put together (download):
program SmokeDetector;
{$ifdef MSWINDOWS}{$apptype CONSOLE}{$endif}
{$ifdef FPC}{$mode OBJFPC}{$H+}{$endif}
uses
SysUtils, IPConnection, Device, BrickletIndustrialDigitalIn4;
const
HOST = 'localhost';
PORT = 4223;
type
TSmokeDetector = class
private
ipcon: TIPConnection;
brickletIndustrialDigitalIn4: TBrickletIndustrialDigitalIn4;
public
constructor Create;
destructor Destroy; override;
procedure ConnectedCB(sender: TIPConnection; const connectedReason: byte);
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 InterruptCB(sender: TBrickletIndustrialDigitalIn4; const interruptMask: word; const valueMask: word);
procedure Execute;
end;
var
sd: TSmokeDetector;
constructor TSmokeDetector.Create;
begin
ipcon := nil;
brickletIndustrialDigitalIn4 := nil;
end;
destructor TSmokeDetector.Destroy;
begin
if (brickletIndustrialDigitalIn4 <> nil) then brickletIndustrialDigitalIn4.Destroy;
if (ipcon <> nil) then ipcon.Destroy;
inherited Destroy;
end;
procedure TSmokeDetector.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 TSmokeDetector.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_INDUSTRIAL_DIGITAL_IN_4_DEVICE_IDENTIFIER) then begin
try
brickletIndustrialDigitalIn4 := TBrickletIndustrialDigitalIn4.Create(uid, ipcon);
brickletIndustrialDigitalIn4.SetDebouncePeriod(10000);
brickletIndustrialDigitalIn4.SetInterrupt(15);
brickletIndustrialDigitalIn4.OnInterrupt := {$ifdef FPC}@{$endif}InterruptCB;
WriteLn('Industrial Digital In 4 initialized');
except
on e: Exception do begin
WriteLn('Industrial Digital In 4 init failed: ' + e.Message);
brickletIndustrialDigitalIn4 := nil;
end;
end;
end;
end;
end;
procedure TSmokeDetector.InterruptCB(sender: TBrickletIndustrialDigitalIn4; const interruptMask: word; const valueMask: word);
begin
if (valueMask > 0) then begin
WriteLn('Fire! Fire!');
end;
end;
procedure TSmokeDetector.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
sd := TSmokeDetector.Create;
sd.Execute;
sd.Destroy;
end.