Decentralized Fleet Tracking with Blockchain

Decentralized Fleet Tracking with Blockchain

Asset tracking is a trend, but this project intends to work in a decentralized way storing each action, event or alert in a blockchain.

AdvancedFull instructions providedOver 3 days11,842

Things used in this project

Story

Disclaimer: I'm assuming that you are familiar with the technologies involved, such as PCB/schematic design tasks, nodejs development, soldering, and so on, since this is not a tutorial on how to solder or design PCB. I just want to show you how I designed a solution around asset tracking and blockchains.

This project is still in the documentation process, so the steps will be updated through the time.

The English language is not my native language, so may be some errors.

Introduction

Asset tracking systems are a trend nowadays for the sake of security, resource optimisation and more. And as time goes on, we notice that by decentralizing asset tracking with a blockchain, we can validate each action of the assets as a transaction. That validation will be done by participants of the chain, making public (in some way) those actions and transactions in a secure space.

This approach allows the organisation to reach the CAP theorem, which affirms that there is no system that can cover that, but even this is violated by the blockchain. The blockchain can have:

  • Consistency

  • Availability

Having these 3 main features in an operation, the organisations can ensure that any action, event or transaction over its assets can be stored, validated and propagated, avoiding the missing information.

How the solutions works?

The project will be built using the following architecture:

The devices (the vehicles) will be collecting it's data using a custom device that can handle the OBDII messages, the gyroscope inclination and the GPS position and will be sent to the backend via GPRS module (Hologram Nova Modem). The message will be received and processed by the hologram platform. Then it will be routed to the Amazon API Gateway, which will act as a some sort of filter and will decide whether the message can be sent to the application or not.

Once the message is eligible to be processed, the message is sent to a microservice that runs in Amazon EC2. This service perform some decisions related to the message structure, the message history (based on a correlation ID) and other details that I'll cover later.

If the message can be processed according the rules, it's sent to Amazon IoT platform where each vehicle has a logical representation. This platform handles messages and acts according to configured rules. If the rules mark the message as eligible as an alarm or important event, that message will be sent to a lambda function, which will then send the message to a microservice that starts a transaction on the blockchain. If the transaction is stored in the blockchain succesfully, the service notifies all websockets subscribers (using the monitoring dashboard) to provide real time information (this step will not be covered in this phase of the project). As well, the service will send the message to Amazon Kinesis, which processes the event stream to allows us to have analytics over our realtime collected data. That stream also stores the valuable data on a S2 bucket.

When a transaction is stored in the blockchain, just to keep track of that transaction as a reference, the microservice will store that reference within as an Amazon Dynamo table.

The vehicle's data collector device

The first step is to build the device that will read the OBDII data & GPS and send it to the backend. We will use the ELM327 Bluetooth reader, which allows us to read via Bluetooth the vehicle's data. For the Bluetooth connection and processing I'll use a Raspberry PI Zero W, which has a Bluetooth embedded module; this Raspberry PI Zero W also processes the GPS incoming data via I2C protocol. Those readings will be sent to the Amazon backend using the Hologram Nova modem.

According to the next schematic, we have to connect the GPS's TX pin to the Raspberry's RX pin and GPS's RX to the Raspberry's TX pin to ensure the serial communication. Also remember to wire the 5v and GND between them.

After the schematics design, I made a quick PCB and I milled it using my CNC.

I do not want to keep the GPS module and the Raspberry within the PCB area; I just want to have the pads of them in the PCB area.

The PCB looks like this:

I added a gyroscope to detect changes in the the angle of the vehicle to know of a possible accident (This feature is not covered in this version at software level, but I want to have it at hardware level).

The Controller

Before writing the module software, we need a backend to talk to, so we need to setup our environment in order to get working on the server side software.

We need nodejs installed and some dependencies:

  • express

  • body-parser

As follows:

$ mkdir -p gateway gateway/lib
$ cd gateway
$ npm init
$ npm install --save express body-parser bunyan path express-handlebars express-ws http minimist hex2ascii path 

We need to setup global dependencies in order to access them from anywhere:

$ npm install -g bunyan forever 

The npm init command initialises our project in the current location; simply provide the information requested by npm. Now we have an empty structure with the node_modules directory within it.

First we need to include the required dependencies in a script that will be loaded from a main script; this script will be called webserver.js within the lib directory:

"use strict"; 

const express = require('express'); 
const app = express(); 
const bodyParser = require('body-parser'); 
const bunyan = require('bunyan'); 
const exphbs  = require('express-handlebars'); 
const expressWs = require('express-ws')(app); 
const argv = require('minimist')(process.argv.slice(2)); 
const path = require('path'); 

var config; 
var log; 
var deviceHandler; 

app.use(bodyParser.urlencoded({ extended: true })); 
app.use(bodyParser.json());

var modules = module.exports = { 
 configure: (_config_, devHandler) => { 
   config = _config_; 
   deviceHandler = devHandler; 
   log = modules.createLogger("webserver"); 
 }, 
 start:() => { 
 }, 
 createLogger: (module) => { 
   return bunyan.createLogger({ 
     name: module, 
     src: false, 
     streams: [ 
       { 
         level:'debug', 
         stream: process.stdout 
       } 
     ] 
   }); 
 } 
}; 

Also I defined the functions to be exported. The main structure is done.

Now we need to configure all the artifacts that the module will use in the configure function:

app.engine('handlebars', exphbs({ 
     defaultLayout: 'main', 
     layoutsDir: path.join(__dirname, 'views/layouts') 
   })); 
app.set('view engine', 'handlebars'); 
app.use(express.static(path.join(__dirname, 'public'))); 
app.set('views', path.join(__dirname, 'views')); 
app.use('/dh7', express.static(__dirname + '/public')); 

Now, start the webserver to listen for incoming requests in the start function:

app.listen(config.port, function () { 
     log.info('Listening on port ' , config.port); 
});  

Now we need an entry point that loads the modules we're writing; let's create a index.js script as an entry point:

const webserver = require('./lib/webserver.js'); 
webserver.configure({ 
 "port":4500 
}); 
webserver.start(); 

Let's try our code:

node index.js | bunyan -L

Note: If you use nodemon rather than node, your changes will be reloaded on each file saving.

If you see a message like this:

... you are running a basic microservice so far.

The microservice endpoints

Now that we have our webserver running, we need to define our endpoints:

app.get('/dh7/index', (req, res) => { 
    //This method will be the UI entry point
}); 

//This method will receive the incoming vehicle's events.
app.post('/dh7/assetTracker/event', (req, res) => { 
 const body = req.body; 
 const payload = JSON.parse(body.payload); 
 const base64 = new Buffer(payload.data, 'base64').toString();; 
 const json = JSON.parse(new Buffer(base64, 'base64').toString()); 
 log.debug("Parsing event request ", json); 
 res.status(200).json({"message":"Event processed"}); 
}); 

//This method will receive the messages processed by Amazon IoT rule 
app.post('/dh7/assetTracker/rule', (req, res) => { 
    const json = req.body; 
    log.debug("Processing event from AWS rule ", json); 
    res.status(200).json({"message":"Event processed"}); 
}); 

//This method will receive the messages processed by Amazon IoT rule 
app.post('/dh7/assetTracker/alert', (req, res) => { 
    const json = req.body; 
    log.debug("Processing event from AWS alert ", json); 
    res.status(200).json({"message":"Event processed"}); 
}); 

Note that the received payload data is doubled base64 encoded, 1 from our device and 1 from the hologram cloud.

Amazon IoT

Before continuing writing our microservice code, we will define the IoT backend configuration to start receiving and sending data between the elements (see the reference architecture).

Go to https://console.aws.amazon.com and login to your AWS account to define the IoT configuration, and search for IoT:

On the IoT console, go to Manage > Types. From here we will define the basic structure for our things; a thing on Amazon IoT is a virtual representation of a real device.

Click on the 'Create' button and provide the data in the fields as follows:

Each searchable attribute is the data that differentiates each real thing; here I defined a vehicle's model and vehicle's VIN. You may define whatever you want.

Click on the 'create thing type' button to finish defining the new thing type.

Now, to create a virtual representation of our vehicle, go to Manage > Things and click on the 'Create' button.

Provide the data that will represent a real vehicle as follows:

See that we can define specific data for each vehicle based on the VehicleModelType we had defined before. Click on the 'Create thing' button.

Now, an important thing to do in order to publish data on behalf of our vehicle, we need to define the security schema; to do that, go to our vehicle definition, click on security option and click in the 'Create certificate' button. After that, download and save each file we will need in the next step. Also download the root CA file and click 'Activate'.

Finally we have to define a policy to be attached to the thing to allow the connection from our client; go to Secure > Policies > Create:

Click on 'Advanced mode' option and create a policy as follows:

Click on 'Create' and let's attach that policy to the thing; go to Manage > Thing > MyVehicle > Security > Click in the current certificate > click in 'Actions' > attach policy and select the created policy.

Now that we have the vehicle configured, we can begin coding the Amazon IoT client to send data from our vehicle to the backend.

Amazon IoT client

The first thing to do is create a representation of our IoT thing locally; to do that, we'll create a javascript class within the lib directory as follows:

"use strict"; 
const aws = require('aws-iot-device-sdk'); 
class Thing { 
 constructor(metadata) { 
   this.deviceMetadata = metadata; 
   this.device = aws.device({ 
     keyPath: this.deviceMetadata.privateKey, 
     certPath: this.deviceMetadata.certificate, 
     caPath: "./security/ca.pem", 
     clientId: this.deviceMetadata.name, 
     host: "<CHANGE THIS>" 
   }); 
 } 
 publishMessage (topic, message) { 
   this.device.publish(topic, JSON.stringify(message)); 
 } 
 getName() {  
   return this.deviceMetadata.name;  
 } 
} 
module.exports = Thing;  

The only thing you need to update is the host property; that data can be obtained from the vehicle's details in the interact tab.

This class handles all the specific data for each device that can configure in the system.

Now, the handler will manage all the devices we configure and send messages on behalf of these to Amazon IoT:

Let's create a DeviceHandler.js file within the lib directory:

"use strict"; 

const Thing = require('./Thing.js'); 

var config; 
var things = []; 

module.exports = { 
 configure: (c) => { 
   config = c; 
   config.devices.forEach((device, idx) => { 
     var thing = new Thing({ 
       "name":device.name, 
       "privateKey": "./security/" + device.privateKey, 
       "certificate": "./security/" + device.certificate 
     }); 
     things.push(thing); 
   }); 
 }, 
 getThings: () => { 
   return things; 
 } 
}; 

Now we have to instantiate the DeviceHandler from index.js as follows:

const webserver = require('./lib/webserver.js'); 
const deviceHandler = require('./lib/DeviceHandler.js');

const config = { 
 "port":4500, 
 "devices":[ 
   { 
     "name":"MyVehicle ", 
     "certificate":"d91e5af26b-certificate.pem.crt", 
     "privateKey":"d91e5af26b-private.pem.key", 
     "publicKey":"d91e5af26b-public.pem.crt" 
   } 
 ]
 "multichain": { 
   "host":"<MULTICHAIN_IP>", 
   "port":<MULTICHAIN_RCP_PORT>, 
   "user":"multichainrpc", 
   "password":"<MULTICHAIN_RPC_PASSWORD>", 
   "mainAddress":"<MAIN_ADDRESS>" 
 } 
}; 

deviceHandler.configure(config); 

webserver.configure(config, deviceHandler); 
webserver.start();  

Now, let's process the incoming messages from our vehicle and send it to Amazon IoT; to do that, we need to receive the message in JSON format in the (already) defined endpoint and send it to Amazon IoT using the device definition we did in the previous step.

Add the next code to the /dh7/assetTracker/event end point:

deviceHandler.getThings().forEach((thing, idx) => { 
    if(thing.getName() == json.deviceId) { 
      thing.publishMessage("dh7/deviceMessage", json); 
    } 
 }); 

This will look for a Thing (loaded from our config) and, if it exists, send the received message to Amazon IoT.

Let's start the server and post some random message to see the incoming message in Amazon IoT:

$ curl -X POST -H 'Content-type: application/json' "http://localhost:4500/dh7/assetTracker/event" -d '{"deviceId":"MyVehicle","speed":60}' 

To see whether the message is received from MQTT, use the test client, subscribing to topic dh7/deviceMessage:

If the message is sent and received, you will see the message in the MQTT client.

If everything is going as mentioned, we are ready to send any message to out backend with the property "deviceId":"MyVehicle" and will be processed properly.

Amazon IoT rules

An important feature of Amazon IoT is that we can define rules to filter the incoming messages to be processed if they are important; this can be done by defining rules in a very simple way. To define a rule, go to Act and click on the 'Create' button and provide the rule parameters:

We will be looking for each message published to topic dh7/deviceMessage and they will be evaluated for property speed. Click on the 'Add action' button to define which action will be performed if the rule is true:

The message will be processed by a lambda function if the speed >= 50. Click on the 'Create new resource' button to create a new function. The new function widget will appear; we will define a function from scratch, so click on the 'Author from scratch' button. Give to the function a name, create a new role from the template and assign or provide a role name, select 'Basic Edge Lambda permissions' policy template and click 'Create function'.

Select the next values in the function screen:

The lambda function will only forward the messages filtered by the rule, and send them back to our backend; Amazon IoT handles the event stream and applies the rules to that stream, giving us only the interesting events.

var http = require('http'); 

exports.handler = (event, context, callback) => { 
 var eventData = JSON.stringify(event); 
 var options = { 
   hostname: '<public ip>', 
   port    : '4500', 
   path    : '/dh7/assetTracker/rule', 
   method  : 'POST', 
   headers : { 
     'Content-Type': 'application/json', 
     'Cache-Control': 'no-cache', 
     'Content-Length': eventData.length 
   } 
 }; 
   var request = http.request(options, function (res) { 
       res.setEncoding('utf8'); 
       res.on('data', function (chunk) { 
           console.log('Response: ', chunk); 
       }); 
   }); 
   request.on('error', function(e) { 
       console.log('problem with request: ' + e.message); 
   }); 
   request.write(eventData); 
   request.end(); 
   callback(null, 'Hello from Lambda'); 
}; 

Remember to update the hostname with the public IP of your server. At this point, you may need to open the port on your firewall, create a forward rule in your home router or publish the project in a public server like Amazon EC2.

Once your lambda function is created, you can go back to IoT rule definition and the new function will appear:

Select the created function and click on 'Add action' and save the rule.

At this point you will be able to process only the important messages in your backend passing the IoT rules.

Multichain

Now it's time to configure the multichain. Multichain is a piece of code that enables any number of separate blockchains to be created, and it's optimised for permissioned rather than open blockchains. In that sense, it's just like a database platform.

Rather than ethereum, multichain doesn't have smart contracts and it's oriented to asset handling. I decided to use multichain with this solution because I want to handle assets (transferring it between accounts) and track the operations around those assets.

Note: To install and get running multichain refer to https://www.multichain.com/download-install/

Since the installation is pretty straightforward , I won't go through the installation process.

Once the blockchain is up and running, we need to define the addresses, the assets and the permissions to operate with the blockchain.

First we have to create a chain to store all blocks. This chain is where all transactions will be stored in a block form.

$ multichain-util create asset-tracker-chain

Start the server using the created chain to allow nodes to connect and clients to begin transactions.

$ multichaind asset-tracker-chain -daemon 

Connect from outside the main process via console to check the chain status and manage it is as follows:

$ multichain-cli asset-tracker-chain 

Let's set the permissions to connect from outside the main process (other nodes or applications). This is needed to allow clients and nodes to connect from outside.

$ cat ~/.multichain/asset-tracker-chain/multichain.conf 
rpcuser=multichainrpc 
rpcpassword=GHWPaxdUBv3p75QkK7jSub9zfEXmRn8oDNyesmYGMCCy 
rpcport=6760 
rpcallowip=<gateway ip>

The credentials are generated by the engine once the chain is created. The rpc port also is assigned by the creation process, and it's defined in ~/.multichain/asset-tracker-chain/params.dat as default-rpc-port key. The rpcallowip is the node's client IP or the gateway IP.

We need a second address in the wallet that represents our vehicle.

asset-tracker-chain: getnewaddress

Now, let's query for the addresses:

asset-tracker-chain: getaddresses 
{"method":"getaddresses","params":[],"id":1,"chain_name":"asset-tracker-chain"} 
[ 
   "1nRUaeQHddj6VU6urHsGEK5qaqwp3uj6Z7Vq6", 
   "1MNXjkGZGpET3vUBKuFNAbHVFxc3K4vZBXZDhE"
] 

The first address is the master node address and all the transaction should be started by this address. The second address is the new address for our vehicle.

The concept of an asset between the mainchain and the asset tracker solution is different. Here an asset is a 'thing' we can transact on - not a truck nor a car -. The address is the source address; in this case, the master address is the one that issues the assets.

asset-tracker-chain: issue 1nRUaeQHddj6VU6urHsGEK5qaqwp3uj6Z7Vq6 Fuel 1000000 1

The stream allows us to publish data to it. This stream is useful for allowing the system to track the data; otherwise we have to transfer "assets" to an address, which we don't want here. Let's say, if we have to update a location and we don't want to transfer the location to another truck, but we want to store or record that location in a stream.

asset-tracker-chain: create stream TruckEventStream false 
asset-tracker-chain: create stream TruckAlertStream false 

Let's grant the new address to send, receive and subscribe to the streams:

asset-tracker-chain:  grant 1MNXjkGZGpET3vUBKuFNAbHVFxc3K4vZBXZDhE receive,send 
asset-tracker-chain:  grant 1MNXjkGZGpET3vUBKuFNAbHVFxc3K4vZBXZDhE TruckEventStream.write 
asset-tracker-chain:  grant 1MNXjkGZGpET3vUBKuFNAbHVFxc3K4vZBXZDhE TruckAlertStream.write

I'm not going into detail with these commands since this is well documented in the multichain website.

Blockchain client

It's time to write the blockchain client to connect to the server in order to raise a transaction and query for all the data needed to operate.

Let's create a BlockchainHandler.js within the lib directory as follows:

var config; 
var multichain; 

var self = module.exports = { 
 configure: (_c_) => { 
   config = _c_; 
   multichain = require("multichain-node")({ 
       port: config.multichain.port, 
       host: config.multichain.host, 
       user: config.multichain.user, 
       pass: config.multichain.password 
   }); 
   multichain.getInfo((err, info) => { 
       if(err == undefined){ 
        console.log("Blockchain info ", info);
         multichain.subscribe({"stream":"TruckEventStream"}, (error, data) => {}); 
         multichain.subscribe({"stream":"TruckAlertStream"}, (error, data) => {}); 
         multichain.subscribe({"stream":"TruckAlertStream"}, (error, data) => {}); 
         multichain.subscribe({"asset":"Fuel"}, (error, data) => {}); 
       } 
   }); 
 }, 
 sendTransaction: (deviceName, stream, key, payload, callback) => { 
   config.devices.forEach((dev, idx) => { 
     if(dev.id === deviceName) { 
       multichain.publishFrom({ 
         "from":dev.address, 
         "stream":stream, 
         "key":key, 
         "data": new Buffer(JSON.stringify(payload)).toString("hex") 
       }, (error, tx) => { 
         if(!error) { 
           callback(tx); 
         } 
       }); 
     } 
   }); 
 }, 
 transfer: (from, to, amount, callback) => { 
   multichain.sendFromAddress({ 
     "from":from, 
     "to":to, 
     amount: amount 
   }, (error, tx) => { 
     if(!error) { 
       callback(tx); 
     } 
   }); 
 }, 
 sendAsset: (from, to, comment, amount, callback) => { 
   multichain.sendFromAddress({ 
     "from":from, 
     "to":to, 
     "amount":amount, 
     "comment": comment, 
     "comment-to": comment 
   }, callback); 
 } 
}; 

Basically, the handler functions sends a transactions between addresses, send assets (fuel credits) to vehicles, and record events on the blockchain.

For more detailed information regarding the nodejs multichain API, see the documentation.

Now, we have to instantiate the BlochchainHandler from index.js:

const webserver = require('./lib/webserver.js'); 
const deviceHandler = require('./lib/DeviceHandler.js'); 
const blockchainHandler = require('./lib/BlockchainHandler.js'); 

const config = { 
 "port":4500, 
 "devices":[ 
   { 
     "name":"MyVehicle", 
     "certificate":"d91e5af26b-certificate.pem.crt", 
     "privateKey":"d91e5af26b-private.pem.key", 
     "publicKey":"d91e5af26b-public.pem.crt" 
   } 
 ], 
 "multichain": { 
   "host":"<MULTICHAIN_IP>", 
   "port":<MULTICHAIN_RCP_PORT>, 
   "user":"multichainrpc", 
   "password":"<MULTICHAIN_RPC_PASSWORD>", 
   "mainAddress":"<MAIN_ADDRESS>" 
 }
}; 

deviceHandler.configure(config); 
blockchainHandler.configure(config); 
webserver.configure(config, deviceHandler); 
webserver.start(); 

Start the server again and you will see (if everything goes fine):

Well done, the gateway is now connected to the blockchain.

First full test

Let's try our work so far; attached is the simulator code that allows you to send random data to the backend and see how the information passes through the architecture.

To start your simulator, download the simulator.tgz and uncompress it wherever you want, install the node dependencies and start it as follows:

$ node Vehicle.js --deviceName=MyVehicle

If you see data passing through like this...

...your work is going well.

Now, let's call our blockchain handler from the event handler. Add the next lines to webserver.js:

var blockchainHandler; //Add next to variables definition 

Change the configure function signature to receive the blockchain handler instance.

configure: (_config_, devHandler, bchainHandler) => { 

Assign the blockchain handler to an instance variable:

 blockchainHandler = bchainHandler; 

And modify index.js as follows:

 webserver.configure(config, deviceHandler, blockchainHandler); 

Now, we're ready to call the blockchain methods as needed. Modify the rule endpoint as follows:

 app.post('/dh7/assetTracker/rule', (req, res) => { 
     const json = req.body; 
     log.debug("Processing event from AWS rule ", json); 
     blockchainHandler.sendTransaction(json.device, "TruckEventStream", "TruckEvent", json, (txid) => { 
       log.info("Transaction recorded ", txid); 
       blockchainHandler.getWalletTransaction(txid, (error2, tx) => { 
         log.debug("Got detailed transaction ", tx); 
       }); 
     }); 
     res.status(200).json({"message":"Event processed"}); 
}); 

Once Amazon IoT rules filter the important messages, the lambda function will forward that message to the rule endpoint. This is where the transaction is raised and recorded within the blockchain, asking for the detailed transaction once it is saved and confirmed.

Tip: Let's assume that the confirmed transaction is d3475fd225574f419a8298ae870f4810ee800b4a4838337eb32606d512173312, you can query the blockchain for that transaction as follows:

asset-tracker-chain: gettransaction d3475fd225574f419a8298ae870f4810ee800b4a4838337eb32606d512173312 

This would return the detailed transaction:

The HEX field is all the payload received from the lambda function and contains the vehicle's data encoded in HEX format.

Tip: You can use multichain explorer to have a UI and interact with your blockchain.

Also, we have to record a transaction on the blockchain whenever an alert is received; to do that let's modify the alert endpoint as follows:

app.post('/dh7/assetTracker/alert', (req, res) => { 
 const json = req.body; 
 log.debug("Processing event from AWS alert ", json); 
 blockchainHandler.sendTransaction(json.device, "TruckAlertStream", "TruckAlert", json, (txid) => { 
   log.info("Transaction recorded for alert ", txid); 
   blockchainHandler.getWalletTransaction(txid, (error2, tx) => { 
   }); 
 }); 
}); 

Homework: I will omit the lambda function creation that routes the incoming request to the alert point, but the code is in the attachments section. You won't be running into problems because the main steps to define a rule and connect it to the lambda function is covered steps before.

OBDII client

I'm assuming that you have a Raspberry PI up and running.

We need to start coding the nodejs client that will be running in the Raspberry; this client will connect via Bluetooth with the OBDII device, handle the data stream and send it to the gateway we have running via the Hologram Nova USB modem.

To get your Nova modem up and running, check out this Hackster document.

You should have something like this:

Now, let's construct the client that will run in the Raspberry PI and will connect to the car's OBDII port:

Let's initiate the project in the desired working directory (let's say RPICarClient):

$ npm init

After providing the required information, you will have a basic project structure. Now let's add a lib directory where the client util libs will be:

$ mkdir lib 

Now, let's create a lib/obd.js file, which will contain the connection and messaging handling functions.

'use strict'; 

const SerialPort = require('serialport'); 
const OBDReader = require('bluetooth-obd'); 
const GPS = require('gps'); 
const shortid = require('shortid'); 
const bunyan = require("bunyan"); 
const request = require('request'); 
const parsers = SerialPort.parsers; 
const port = '/dev/ttyS0'; 
const gps = new GPS; 
const btOBDReader = new OBDReader(); 
const moduleName = "vehicle"; 
const waitToRestart = 1000 * 60 * 5;//Every 5 minutes reload the program if there is no OBD 

var dataAvailable = false; 
var correlationId ; 
var log; 
var hologramKey; 
var deviceId; 
var parser; 

var currentMessage = { 
 "vss":0, 
 "rpm":0, 
 "temp":1, 
 "location":{} 
}; 

const self = module.exports = { 
 configure:() => { 
   log = bunyan.createLogger({ 
       name: moduleName, 
       src: false, 
       streams: [ 
         { 
           level:'debug', 
           stream: process.stdout 
         }, 
         { 
           level: 'debug', 
           path: '/tmp/' + moduleName + ".log" 
         }, 
         { 
           level: 'error', 
           path: '/tmp/' + moduleName + ".err" 
         } 
       ] 
     }); 
     parser = new parsers.Readline({ 
       delimiter: '\r\n' 
     }); 
     const serialPort = new SerialPort(port, { 
       baudRate: 9600 
     }); 
     serialPort.pipe(parser); 
     hologramKey = process.env.HOLOGRAM_KEY; 
     deviceId = process.env.HOLOGRAM_DEVICE_ID; 
 }, 
 start:() => { 
   gps.on('data', function(data) { 
     if(data.type === "GGA") { 
       log.debug("DATA ", data); 
     } 
   }); 
   parser.on('data', function(data) { 
     gps.update(data); 
   }); 
   btOBDReader.on('dataReceived', function (json) { 
     dataAvailable = true; 
     if(json.mode) { 
       log.debug("Data received from car ", json); 
       currentMessage[json.name] = json.value; 
     } 
   }); 
   btOBDReader.on('connected', function () { 
     this.addPoller("vss"); 
     this.addPoller("rpm"); 
     this.addPoller("temp"); 
     this.startPolling(5000); 
   }); 
   btOBDReader.on('error', function (data) { 
     dataAvailable = false; 
     log.error('Restarting due error: ' , data); 
     setTimeout(() => { 
       process.exit(1); 
     }, waitToRestart); 
   }); 
   btOBDReader.autoconnect('OBDII'); 
   const base64 = Buffer.from(JSON.stringify(currentMessage)).toString('base64'); 
   setInterval(() => { 
     var me = "{\"deviceid\": " + deviceId + ", \"data\": \"" + base64 + "\"}"; 
     console.log(me); 
     request({ 
       method: 'POST', 
       url: 'https://dashboard.hologram.io/api/1/csr/rdm?apikey=' + hologramKey, 
       headers: { 
         'Content-Type': 'application/json' 
       }, 
       body: me 
     }, function (error, response, body) { 
       console.log('Status:', response.statusCode); 
       console.log('Headers:', JSON.stringify(response.headers)); 
       console.log('Response:', body); 
     }); 
   }, 1000 * 15); 
 } 
};  

This will connect to the obd port and request the configured data (through the addPoller method) every 5 seconds for the already paired ELM device called OBDII.

As a final part, we will need an entry point (index.js):

'user strict'; 
const obd = require("./lib/obd.js"); 
obd.configure(); 
obd.start();  

Now, we have almost all the parts. Let's try to fetch the car's data; it is important to pair your ELM device with the Raspberry beforehand.

You have to export HOLOGRAM_KEY and HOLOGRAM_DEVICE_ID before running the node; these variables have to match with your own settings.

If everything goes fine, you should see the new incoming message on the Hologram dashboard:

Now, we have to route the incoming message to our backend. Click on the create route button near the new message:

This will bring you to the 'new route' widget; provide all the required information as follows:

Click on the 'Custom Webhook URL' and provide the public IP of your gateway. Add your (random) custom KEY to validate that the message that is received by your backend were sent by Hologram.

Once the message is processed by the Hologram platform, the message will be routed to your backend.

And you should see in your gateway terminal a message like this:

We are sending our JSON encoded in base64; this is because it is more efficient to send the full payload in base64.

Amazon API Gateway

Now its time to configure the API gateway. An API gateway is useful to filter and validate the incoming messages before the message reaches the backend; here we will validate the hologram token we had configured befor.

Go to your amazon console and load the API Gateway section. Create new API and give whatever name you want.

First we have to create a resource. Name your resource and use the resource path dh7

Now, create a new method in the created resource

With the POST verb

And configure it as HTTP integration type and use your gateway public IP and use the event endpoint (http://<PUBLIC IP>:4500/dh7/assetTracker/event)

You will have the configured method as follows:

Now, test the endpoint. Perhaps you will get an error, but remember that at this would be normal due we have to post a full hologram message.

Now, lets update the hologram route to send the message to Amazon API gateway instead of our gateway's ip:

Now, deploy your created API to obtain the listening domain:

The domain will be created; this will be listening for incoming requests

With that domain, we will update the hologram route as follows:

Now, lets try the flow 'Simulating from device':

And finally we should see the incoming message in the gateway's log.

Dynamo transaction store.

Finally, we are going to save the gas transactions on a dynamo table. To do that, create a file called dynamoDataSource.js within the lib directory :

"use strict"; 

const utils = require('./utils.js'); 
const AWS = require("aws-sdk"); 
const argv = require('minimist')(process.argv.slice(2)); 

var log; 
var dynamoClient; 

const self = module.exports = { 
 configure:() => { 
   log = utils.createLogger("messaging-client"); 
   AWS.config.update({ 
     region: "us-east-1", 
       "accessKeyId": argv.aws.dynamo.accessKey , 
       "secretAccessKey": argv.aws.dynamo.secretKey 
     }); 
   dynamoClient = new AWS.DynamoDB.DocumentClient(); 
 }, 
 saveTransaction: (transaction) => { 
   var params = { 
       TableName:"transactions", 
       Item:{ 
           "tx": transaction.tx, 
           "info":transaction.associatedTransaction 
       } 
   }; 

     dynamoClient.put(params, function(err, data) { 
       if (err) { 
         console.error("Unable to add item. Error JSON:", JSON.stringify(err, null, 2)); 
       } else { 
         console.log("Added item:", JSON.stringify(data, null, 2)); 
       } 
     }); 
 } 
}; 

This file connects to dynamo and save the transaction on it. To store the data, we need to create a table. For this, go to the dynamo console and click in the 'Create table' button:

Give a name to the table (this name is configured in the dynamoDataSource.js; the primary key will be the blockchain transaction id.

After creation we will have a screen like this:

Finally we need to include the created file in the webserver.js

const dynamoDS = require('./dynamoDatasource.js'); 

dynamoDS.configure();

and save the transaction modifying the endpoint as follows:

 app.post('/dh7/assetTracker/rule', (req, res) => { 
     const json = req.body; 
     log.debug("Processing event from AWS rule ", json); 
     blockchainHandler.sendTransaction(json.device, "TruckEventStream", "TruckEvent", json, (txid) => { 
       log.info("Transaction recorded ", txid); 
       blockchainHandler.getWalletTransaction(txid, (error2, tx) => { 
         log.debug("Got detailed transaction ", tx); 
         dynamoDS.saveTransaction({"tx":txid, "info":tx});
       }); 
     }); 
     res.status(200).json({"message":"Event processed"}); 
});

Once the rule is raised, that will be recorded in the blockchain and sent to the dynamo table:

We have a transaction (event) that raises a new "transfer" transaction with its details.

The end and next steps

I'll be working on a push notificarion services to send notifications via APN or GMS to the desired devices.

Also I have to work with the UI to track all the assets and transactions.

Hope this project will inspire you to make something cool. If you found errors, do not hesitate to reach me to let me know.

Schematics

Module schematic

Module PCB design

Code

  • Vehicle Simulator

  • AsstTracker

Vehicle Simulator

JavaScriptThis is the vehicle simulator. If you dont have any OBDII device, you can try this.

No preview (download only).

Last updated