Delphi REST client that talks to a REST Web API on a Raspberry PI in JavaScript

This article presents an approach for developing a mobile application to monitor a Raspberry Pi cluster using a simple Web API built in JavaScript. If you haven't followed my progress regarding the Raspberry Pi cluster you can read my article here. I would also encourage you to read the article regarding node.js and how I created a simple web monitor.

The main idea is to be able to check the status of my grid from anywhere and the best place for it is on my mobile device. In this case I extrapolated the idea from my initial article so I could reuse some of the concepts and leverage the User Interface with Delphi 10.1 Berlin. As I'm still waiting for the Delphi version that offers Linux Server support I will have to use a RESTful approach to communicate with my devices with ease.

In this case, the Android application will talk to the REST Web API that sits in one of the Raspberry PIs. This Web API is built using node.js. Here is the architecture of the example to build:

I have a cluster of 4 Raspberry Pi and I want to check if they are alive without having to go physically to them (I saw few times where those little ones were not responding properly after a reboot). Thanks to the RAD that Delphi 10.1 Berlin offers I can have my Android application up and running in less than one hour as the components are already in-built and I only have to worry about the design and the responsiveness of the final application.

On the back-end, I have a really simple JavaScript Web API built with node.js. This simple API awaits external requests through a controller named status which expects machine names (e.g. pi02:3000/status/pi01) or IP addresses (e.g. pi02:3000/status/192.168.1.1) to test the connectivity to them. The result is in json format. The source code can be seen below:
/**
* webapi.js
*
* @version 1.0 : August 2016
*
* DESCRIPTION:
* A simple server-side application to demonstrate running a node
* API Application Server on a Raspberry Pi to ping other nodes
* Uses the Express, http and ping node packages.
*
*
* @throws none
* @see nodejs.org
* @see express.org
*
* @author Jordi Corbilla
* (C) 2016
* MIT Licensed
*/
'use strict'
var http = require('http');
var express = require('express');
var ping = require('ping');
var app = express();
var DEFAULT_PORT = 3000
// configure Express to deliver index.html and any other static pages stored in the home directory
app.use(express.static(__dirname));
// Express route for incoming requests with method status/xxx where xxx is the machineName
app.get('/status/:machinename', function (req, res) {
console.log('id = ' + req.params.machinename);
ping.sys.probe(req.params.machinename, function (isAlive) {
var msg = isAlive ? 'host ' + req.params.machinename + ' is alive' : 'host ' + req.params.machinename + ' is dead';
console.log(msg);
var s = [{host: isAlive ? 'alive' : 'dead'}];
res.status(200).send(s);
});
});
view raw webapi.js hosted with ❤ by GitHub

Alternatively, the source code of the entire project can be found in Github at the link below:
In here you will find the Web API and the Delphi Application (Win64 and Android). You could also run it in iOS but this is up to you to test.

Here the Web API in action:
As you can see it's very simple. The API just pings the device you ask for and returns dead/alive in json format. This is then parsed by the client and represented in different statuses. To ping the devices I'm using the ping npm package.

The layout of my Android application using Delphi is as follows:

As you can see I'm using the components TRestClient, TRestRequest and TRestResponse. Now I need to be able to send REST requests to my WebAPI. To do this I need to configure these three components in the following way:

Place the three components in your form and they will automatically reference each other, then configure the TRestClient using the following parameters:

The important ones are Accept, AcceptCharset and BaseUrl (this last one will contain the url you want to request). Notice that this is just the base URL as the resource will be specified in the RestRequest component.
Now you only need to edit this "Resource" property and use the correct argument to check the machine you want to check.

To make sure that the application is fully responsive I'm using a different thread to check the communications and also show a nice TAniIndicator while the operation occurs. This will make the application fully responsive without freezing the app.

Here is a summary of the code for your review:
// Copyright (c) 2016, Jordi Corbilla
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// - Neither the name of this library nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//Example - create list of nodes to scan:
listNodes := TList<TNode>.create();
listNodes.Add(TNode.Create('status/picontroller'));
listNodes.Add(TNode.Create('status/pi01'));
listNodes.Add(TNode.Create('status/pi02'));
listNodes.Add(TNode.Create('status/pi03'));
//Use this list to loop through and request the correct node:
procedure TNodeThread.Execute;
var
node : TNode;
jValue: TJSONValue;
LJsonArr : TJSONArray;
LJsonValue : TJSONValue;
LItem : TJSONValue;
status : string;
begin
Synchronize(
procedure ()
begin
FAniIndicator.Visible := true;
FAniIndicator.Enabled := true;
end
);
for node in FListNodes do
begin
FRequest.Resource := node.Action;
try
FRequest.Execute;
jValue := FResponse.JSONValue;
LJsonArr := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(jValue.ToString),0) as TJSONArray;
//Process results
except
on e : Exception do
begin
Synchronize(
procedure ()
begin
node.labelNode.Text := 'Timeout';
node.RoundRect.Fill.Color := node.ColorOff;
end
);
end;
end;
end;
Synchronize(
procedure ()
begin
FAniIndicator.Visible := false;
FAniIndicator.Enabled := false;
end
);
end;

You can find the entire source code here:
Finally the application in action:

If you wonder :), I'm using Vysor Chrome app to mirror my android device and ScreenToGif to generate my Gifs.

You will find all the necessary details on my Github project (installation procedures on the Raspberry Pi, etc).

I look forward to your comments.
Jordi

Comments

Post a Comment

Popular Posts