Disclaimer: I'm assuming that you are familiar with the technologies involved, such as PCB/schematic design tasks, node js 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.
IntroductionFleet 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
- Partition tolerance
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.
To give it a twist, we are going to create our own cryptocurrency to transact over our blockchain.
The mesh networksIs a local network topology in which the infrastructure nodes (i.e. bridges, switches and other infrastructure devices) connect directly, dynamically and non-hierarchically to as many other nodes as possible and cooperate with one another to efficiently route data from/to clients. This lack of dependency on one node allows for every node to participate in the relay of information. Mesh networks dynamically self-organize and self-configure, which can reduce installation overhead. The ability to self-configure enables dynamic distribution of workloads, particularly in the event that a few nodes should fail. This in turn contributes to fault-tolerance and reduced maintenance costs.
This is where the Helium hardware is an amazing solution to allow the communication be done without "network configuration stuff" (like SSID, authentication, IP assignation, and so).
The Atom device (inside each car) is going to look for a gateway (the Helium Element) which is connected to an Internet Gateway and will allow the Atom to send data to the Helium channels. This is useful if we have many Helium Elements in the facilities allowing the Atom's to send data through the nearly Element found.
Bottom lineThe devices (the vehicles on a fleet) will be collecting it's data using a custom device that can handle the OBDII messages will be sent to the backend via Helium channels.
It's important to note that the information won't be in real time, due we don't have a 3G/2G modem, so the way this will work is, when a device arrives in a location (checkpoint), let's say, to fulfill the vehicle of packets, the facility will receive all the route through the Atom (whitin the vehicle) info and send it via the Helium Element to the cloud. This will be done on each checkpoint that the vehicle do until the last stop.
The message will be routed to the Google IoT core to be processed via MQTT, and routed to our backend. This backend will add a block to the blockchain with the relevant information, and that transactions can be accessed via the UI dashboard.
We will create out own private blockchain based on ethereum; that will give us a bitcoin/ether fashion blockchain and we'll deploy our contracts using solidity.
First you have to install geth following the instructions.
Once you have installed geth we have to perform the next steps :
- Define our genesis block.
- Init the chain with that genesis definition.
- Start the private blockchain.
- Define out genesis block
From here you have to define the <BLOCKCHAIN_DIR> (let's say ~/blockchain/), where we'll be working and defining the blockchain instance.
Create a file called <BLOCKCHAIN_DIR>/genesis.json with the next content:
{
"config": {
"chainId": 1994,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0
},
"difficulty": "400",
"gasLimit": "2000000",
"alloc": {
"34b35496e9654b510a0b4d4760a541546a08062ff": {
"balance": "100000000000000000000000"
}
}
}
config- chainId — this is your chain’s identifier, and is used in replay protection.
- homesteadBlock, eip155Block, eip158Block, byzantiumBlock — these relate to chain forking and versioning, so in our case lets leave them 0 since we’re starting a new blockchain.
This dictates how difficult it is to mine a block. Setting this value low (~10–10000) is helpful in a private blockchain as it lets you mine blocks quickly, which equals fast transactions, and plenty of ETH to test with. For comparison, the Ethereum mainnet Genesis file defines a difficulty of 17179869184.
gasLimitThis is the the total amount of gas that can be used in each block. With such a low mining difficulty, blocks will be moving pretty quick, but you should still set this value pretty high to avoid hitting the limit and slowing down your network.
allocHere you can allocate ETH to specific addresses. This won’t create the account for you, so make sure its an account you already have control of. You will need to add the account to your private chain in order to use it, and to do that you need access to the keystore/utc file. For example, Geth and MyEtherWallet give you access to this file when you create an account, but Metamask and Coinbase do not. The addresses provided are not real addresses, they are just examples. Here we allocate 100,000 and 120,000 ETH respectively.
Note: You have to create your own wallet using myetherwallet (I recommend that) and get your own address. Remember to update that data with your own address.
Init the blockchain
geth --datadir <BLOCKCHAIN_DIR>/<DATADIR> init <BLOCKCHAIN_DIR>/genesis.json
This step will init all the project structure of the blockchain.
Start the node
There is two things to do in order to handle your own blockchain :
- Put the wallet (exported from myetherwallet) in the blockchain <DATADIR>.
- Point the geth.ipc in the ethereum root.
geth --rpc --rpcaddr localhost --identity "master" --datadir --datadir <BLOCKCHAIN_DIR>/<DATADIR> --networkid 1114 --unlock "0x<YOUT WALLET ADDRESS>" --rpccorsdomain "*" --rpcapi "web3,eth" --rpcport 8545 --port 30303 --password ./password --ipcpath ~/Library/Ethereum/geth.ipc console
When you create your wallet, you were asked to provide a password; you have to create a file named password (or whatever you want) and write that password in the file to unlock the wallet to perform operations.
Note: Prefix your address with 0x
That command will bring you to the console and the output would be like this:
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.2-stable/darwin-amd64/go1.10
INFO [03-20|23:05:51] Etherbase automatically configured address=0x34b35496E9654B510A0B4d4760A541546a08062C
coinbase: 0x34b35496e9654b510a0b4d4760a541546a08062c
at block: 8083 (Tue, 20 Mar 2018 20:19:14 CST)
datadir: ~/data
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> INFO [03-20|23:05:52] Mapped network port proto=udp extport=30303 intport=30303 interface="UPNP IGDv1-PPP1"
INFO [03-20|23:05:54] Mapped network port proto=tcp extport=30303 intport=30303 interface="UPNP IGDv1-PPP1"
Within the console you can start to mining issuing the command:
miner.start()
That will pay using ethers, and you can query the balance with the command:
eth.getBalance(eth.coinbase)
You'll notice that the balance is increasing when the node is mining.
Optional Step: Blockchain explorerYou can install a blockchain explorere just to navigate around the blocks and transaction within your private blockchain. There is many options out there, but this explorer is pretty simple to install and use.
You have to type these commands :
git clone
cd
npm install
npm start
Perhaps you'd get an error related to the listening port. You have to edit the package.json and change the command http-server ./app -a localhost -p 8000 -c-1 to http-server ./app -a localhost -p 8001 -c-1 . Allowing the explorer to listen other port that isn't in use.
You will get something like this
Now visit http://localhost:8000 and you should see the transactions.
We are going to deploy our smart contract. The smart contract dictates how we can operate on the blockchain network.
To this, download the attached smart contract and keep handy. Basically, the contract allows the ecosystem to transfer our own tokens between ethereum addresses with some minimal validations. That tokens would be used to buy fuel for the vehicle, removing the necessity of giving money to the driver to get fuel whenever he want's (even if the vehicle doesn't need it).
Now, you will need the Ethereum Wallet to deploy your contract. Once you've installed, open it, but remember that you'll need to keep running you blockchain node, cause the wall will try to connect to the ethereum test-net if you don't keep running your node.
You will see something like this. The important thing is the "big red square" that point us to the private-net connection. Also, you have to have the two addresses defined earlier.
Now, if you're running a miner, you will see how the balance is increasing, that is a expected thing as well.
Now go to the contracts menu and then "deploy new contract". Select your main account (which will be used as the owner of the contract), and paste the attached code (see the downloads section). The compiler will perform the proper validations, and if you don't have any errors (fingers crossed), select the contract to deploy. Provide the required data as follows:
And click the Deploy button. You'll see the confirmation data and you will be asked for you passphrase:
We have to wait until the transaction is validated.
After all the confirmation are done, we are ready to use the deployed contract.
We also will need the contract interface; go to the contracts section and select the deployed contract, clik in the show interface button and keep handy the interfae definicion in json format (we'll need it later).
There are some dependencies to install in order to get ready to develop our dapp:
npm install --save express body-parser bunyan express-handlebars express-ws minimist path web3@0.20.0
Once we have those dependencies, let's try a simple script to test our work so far. Copy and paste the next code:
const Web3 = require('web3');
const account = "<your main address>";
const web3 = new Web3();
const abi = <yout smart contract interface>;
const address = "<the secondary address>";
web3.setProvider(new web3.providers.HttpProvider('http://localhost:
web3.eth.defaultAccount= account;
console.log(web3.eth.accounts);
var contract = web3.eth.contract(abi).at(address);
contract.transfer.sendTransaction("0xc0D69b9c0cB475C01C94F924946149A644e593DB",66, function(error, value) {
console.log("Transaction ", value);
console.log("Transaction ", web3.eth.getTransaction(value));
console.log("Transaction Receipt ", web3.eth.getTransactionReceipt(value));
});
console.log("Gas ", web3.eth.gasPrice);
console.log("Mining ", web3.eth.mining);
Remember to update the code as follows:
- account : Your main address
- address: The secondary address
- abi : The json you have copied from mist (without ").
Now, try running the script:
node index.js
You have to get something like this:
And in the explorer:
So far so good.
The vehicle deviceI'm using raspberry PI cause I have a raspberry PI Atom adapter, but as far as I been testing, there is no limitation to use your Atom without any shield or adapter; you only have to wire the RX/TX/5v/GND between the Atom and your raspberry PI/Arduino board.
This is what you need to know (if you are using the rpi adapter). This is important due we are going to create a custom PCB that can has a GPS and the Rpi Atom Adapter for raspberry pi.
The PCB should be something like this. The NPN transistor allows to switch off the GPS while we want to connect the Atom, and vice versa.
You can use an USB to TTL cable to connect the GPS module to the raspberry's usb port.
The current prototype I have is like this
The PCB design is based on this prototype.
Now, we'll need also a python module (see it). This module will allow us to fetch metrics from our car (via OBD) and send it to the Helium platform.
pip install obd
Now, this module uses a serial communication, and remember that we are going to use the ELM327 bluetooth adapter, and the bluetooth communication also is a serial communication. So, we need to pair our raspberry with the ELM327 adapter as follows:
Be sure to install the proper dependencies to work with the raspberry pi bluetooth:
sudo apt-get install bluetooth bluez blueman
From our raspberry command line :
pi@raspberrypi:~ $ sudo bluetoothctl
[NEW] Controller B8:27:EB:4A:50:4D raspberrypi [default]
[bluetooth]# scan
Missing on/off argument
[bluetooth]# scan on
Discovery started
[CHG] Controller B8:27:EB:4A:50:4D Discovering: yes
The addresses will be different, but it's easy to follow the steps with your own hardware.
This command will scan for near bluetooth devices, and after few seconds, you should see your ELM327 adapter:
[CHG] Device 00:1D:A5:68:98:8A Name: OBDII
[CHG] Device 00:1D:A5:68:98:8A Alias: OBDII
The shown address is the address you need to pair the device; to do that :
[bluetooth]# pair 00:1D:A5:68:98:8A
Attempting to pair with 00:1D:A5:68:98:8A
[CHG] Device 00:1D:A5:68:98:8A Connected: yes
Request PIN code
[agent] Enter PIN code: 1234
[CHG] Device 00:1D:A5:68:98:8A UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device 00:1D:A5:68:98:8A ServicesResolved: yes
[CHG] Device 00:1D:A5:68:98:8A Paired: yes
Pairing successful
[OBDII]# trust 00:1D:A5:68:98:8A
Changing 00:1D:A5:68:98:8A trust succeeded
After doing that, I needed to bind that address to a RF port :
sudo rfcomm connect 0 00:1D:A5:68:98:8A 1 &
OBD Testing scriptThat is what you need to do to have an ELM327 paired and ready to be used. Now lets test or work so far; we will test the configuration and the device with the next script:
import obd
connection = obd.OBD()
ports = obd.scan_serial()
print ports
Run your script as follows:
pi@raspberrypi-obd:~/obd $ sudo python test.py
['/dev/rfcomm0']
The script detects that the rfcomm0 port is ready to be used by the obd module and as for RPM metric, if there is a proper response from the car, you're done.
I've attached an OLED display to get informed about the basic information from the ELM327.
To attach the OLED (i2c), follow the instructions at adafruit blog, and see the full code that prints the values in the OLED.
You would need to create a custom "shield" to allow the communication between Atom/Raspberry PI/OLED. See the downloads section where I put the schematic of that shield.
You don't need to connect the ELM327 to the car to test the connection; I've looking how to provide a power (12v) to the adapter and found that following the pinout of the obd, you should achieve the (at least) bluetooth communication.
Be careful on how you number the pins.
The Helium configuration
I won't cover the full installation/configuration of the hardware, because the Helium team has a really good documentation related to this process here.
Now, add the next code to the scanner script:
from helium_client import Helium
helium = Helium("/dev/serial0")
helium.connect()
channel = helium.create_channel("Helium MQTT")
channel.send("hello from Python")
That will be the basic structure of our code that will publish the data to the cloud.
The full script is located in the Downloads section, and contains the most basic information about the car. It's important to mention that every car would not be capable to provide all OBDII codes, but you have to try the commands that your car can provide you.
After that, also we need to control our devices using GPIO. Let me explain why; currently the ATOM module uses UART (RX/TX) pins to read and send data from/to our raspberry, and also we are going to use the GPS Module, which also use RX/TX, and due the raspberry PI only has one TX/RX port pair, we need to switch between ATOM and GPS; that will be done using a couple of NPN transistor; remember that the NPN need voltage in the base pin to "turn on" the device, so, when the vehicle is on route (the RPM reading is greater than 0), the GPIO that controls the GPS will send voltage and the GPIO that controls de ATOM wont send voltage. When the vehicle is stopped (the RPM reading is 0) the opposite thing will be done in order to turn off the GPS and use the ATOM.
To install GPIO library for python, you will need to perform the next steps.
wget https://pypi.python.org/packages/source/R/RPi.GPIO/RPi.GPIO-0.5.4.tar.gz
tar zxf RPi.GPIO-0.5.4.tar.gz
cd RPi.GPIO-0.5.4.tar.gz
sudo python setup.py install
After the route logging, you will see in the "log" file something like this.
I'm using a random vehicle ID to identify each of them. And once the sync is performed, you should see something like these entries in the Helium Dashboard.
Remember that everything that comes from an Atom (on a vehicle) to Helium cloud, has to be sent to Google IoT Core; for that, we have to create the channel on the Helium dashboard as follows:
- You have to create a device registry.
- You have to create a topic
In the attachments section, you will find a script that allows you to create the registry (or remove it) to be more efficient, as well as the creation of the topic.
To run the script you need to open it and change the configuration on the top of the file. After that, just type in the terminal
./setIoTCore.sh <create|delete>
Remember to edit the settings whithin the file to match your data.
After that, you'll need to get a private key to allow the communication between Helium and Google; for that, you'll need to go to IAM & Admin > Service accounts.
Create a service account
Provide the required data and be sure that the selected role is "Cloud IoT Editor" and select the "Furnish a new private key" of JSON type. That has to download the private key.
Now, go to the Helium dashboard and let's create a new channel; to do that, click on the green icon next to channels option and select "Google Cloud IoT Core" as a channel type. The next data is pretty straightforward, just type the correct data (remember that the provided script ask you for a registry name and region, that would be provided in this screen).
If you run again your car sync program, you should see the calls to Google IoT core :
That means that the Helium channel is sending the data to IoT core successfully.
Now, there is many options to retreive the data from IoT core, and the more common (as I see) are :
- Subscribe to the MQTT topic directly from you application
- Create a subscription that will send data to an endpoint
- Create a firebase function.
I've been trying those 3 options and these are the implications:
MQTT Subscription:
This is the more common when we deal with real time messaging, but there is a complication related to the Google IoT core security. You have to deal with a lot of configurations in order to allow your app to communicate with the service. So, after a lot of time, I discarded as a prototype option.
Subscription to send data to my endpoint
This has other complication related to the validation of your domain. You have to validate your domain and that domain has to process https traffic. That means that you need to have a certificate (as far as I see, a self signed certificate is not a valid option). So that, in terms of prototyping, I discarded this option as well.
Firebase functions
The final option I went to, is a firebase function. The firebase functions is the Google's serverless offering, that allow us to write a very little function that performs a "business" logic without the needed of create a full application that listend ports/events .
Firebase configurationIn order to configure firebase as a datastore, you have to create a project going to the firebase console.
You have to select your existent project :
After that, you have to initialize the local firebase functions:
firebase init functions
That will ask for the project you want to use to deploy your functions. Firebase functions is the Google's serverless, so you have to implement a very small fragment of logic in a specific language (javascript in our project).
Also you will be asked which language you want to use.
Select Javascript. Now, in the project directory you'll find a functions directory, which contains any function defined. Open the index.js file and replace the content with the following stub :
const functions = require('firebase-functions');
exports.receivePublishedMessage = functions.pubsub.topic('atom').onPublish(event => {
console.log("Event ", event);
return Promise.all([]);
});
Bottom line, the function will subscribe to the topic atom (that matches with the topic created in the pubsub service):
And this first implementation only print the received message. Let's publish the function and test it:
firebase deploy --only functions
You will see a confirmation like this:
And go to your project overview you have to see the deployed function:
Now, go to pubsub service and send a message to the topic to simulate the helium incoming message and see how is "catched" by the function; to do that, click on the publish message button :
And send a test message:
Go back to the firebase console > functions > records and you should see the published message.
The message structure is like this:
{ data: 'eyJzaW11bGF0aW9uTWVzc2FnZSI6IlRoaXMgaXMgYSBzaW11bGF0ZWQgbWVzc2FnZSJ9', attributes: {}, _json: undefined }
The "data" property is what we are interested in, but is base64 encoded; let's decode and validate that is the same message we sent. Go to base64decode web site, and paste de data content as follows:
That match, so we have the current message in firebase function. Now, let's add a request module to the function to allow forward the base64 encoded message to our endpoint. You can use ngrok to expose your microservice (which we don't have yet). You'll see an error on response on the firebase console :
and in the ngrok console :
... so, the request is forwarded to our ngrok, but as we don't have an endpoint exposed to process the request, we get an 502 error. So far, so good ...
Test the 'so far' cycleIf we run the vehicle program, we have to see the message in the firebase function as follows:
Let's decode the data
The message is received, and also the attributes are send to our functions; those attributes has the device that sent de message, that would be useful, but by now, we omit that data.
Microservice developmentTODO
Define the steps to create the transaction micro service that will interact with the blockchain.
Remember that we had installed all the nodejs dependencies that we are going to need, so let's keep going with the service development.
Our service will be composed by 3 files (the files are available in the downloads section):
- index.js: this is the entry point of the application, and will load the http and ethereum module, as well as the config file.
- lib/http.js: This is the http service that will process all the http requests.
- lib/ethereum.js: This is the module that handle all the communication with our blockchain.
- config/config.json: The config file with the needed info. Remember the test that we performed to try our smart contract? You needed 2 addresses and the json interface of the contract; well, we will set those values in the config.json.
const ethereum = require('./lib/ethereum.js');
const http = require('./lib/http.js');
const config = require('./config/config.json');
ethereum.configure(config);
http.configure(config, ethereum);
http.start();
lib/http.js
"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 ethereum;
var routesCache = [];
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var modules = module.exports = {
configure: (_config_, _eth_) => {
config = _config_;
ethereum = _eth_;
log = modules.createLogger("webserver");
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'));
},
start:() => {
app.listen(config.port, function () {
log.info('Listening on port ' , config.port);
});
},
createLogger: (module) => {
return bunyan.createLogger({
name: module,
src: false,
streams: [
{
level:'debug',
stream: process.stdout
}
]
});
}
};
app.get('/dh7/index', (req, res) => {
});
app.post('/events', (req, res) => {
console.log(req.body);
const body = new Buffer(req.body.base64, 'base64').toString();
const json = JSON.parse(body);
console.log(json);
res.status(200).json({"message":"Event processed"});
});
lib/ethereum.js
const Web3 = require('web3');
const web3 = new Web3();
var config;
var contract;
const self = module.exports = {
configure: (c) => {
config = c;
web3.setProvider(new web3.providers.HttpProvider(config.ethereum.provider));
web3.eth.defaultAccount= config.ethereum.defaultAddress;
contract = web3.eth.contract(config.ethereum.abi).at(config.ethereum.contractAddress);
},
transfer: (to, amount, callback) => {
contract.transfer.sendTransaction(to, amount, callback(error, value));
}
};
config/config.json
{
"ethereum": {
"provider":"http://localhost:8000",
"defaultAddress":"0x34b35496e9654b510a0b4d4760a541546a08062c",
"contractAddress":"0xf6FD694674A92C05f2889e8eFD1aA2BE5665E1a5",
"abi":[ { "constant": false, "inputs": [ { "name": "newSellPrice", "type": "uint256" }, { "name": "newBuyPrice", "type": "uint256" } ], "name": "setPrices", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string", "value": "DH7 Token v1.0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256", "value": "500" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "difficulty", "outputs": [ { "name": "", "type": "uint256", "value": "1e+32" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "success", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8", "value": "2" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "sellPrice", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "currentChallenge", "outputs": [ { "name": "", "type": "bytes32", "value": "0x0000000000000000000000000000000000000000000000000000000000000000" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "standard", "outputs": [ { "name": "", "type": "string", "value": "DH7Token v1.0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "newAdmin", "type": "address" } ], "name": "transferAdminship", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "nonce", "type": "uint256" } ], "name": "proofOfWork", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "target", "type": "address" }, { "name": "mintedAmount", "type": "uint256" } ], "name": "mintToken", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "timeOfLastProof", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "buyPrice", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "giveBlockreward", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string", "value": "DH7" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "buy", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" } ], "name": "frozenAccount", "outputs": [ { "name": "", "type": "bool", "value": false } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "address" }, { "name": "", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "amount", "type": "uint256" } ], "name": "sell", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "target", "type": "address" }, { "name": "freeze", "type": "bool" } ], "name": "freezeAccount", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "admin", "outputs": [ { "name": "", "type": "address", "value": "0x34b35496e9654b510a0b4d4760a541546a08062c" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [ { "name": "initialSupply", "type": "uint256", "index": 0, "typeShort": "uint", "bits": "256", "displayName": "initial Supply", "template": "elements_input_uint", "value": "500" }, { "name": "tokenName", "type": "string", "index": 1, "typeShort": "string", "bits": "", "displayName": "token Name", "template": "elements_input_string", "value": "DH7 Token v1.0" }, { "name": "tokenSymbol", "type": "string", "index": 2, "typeShort": "string", "bits": "", "displayName": "token Symbol", "template": "elements_input_string", "value": "DH7" }, { "name": "decimalUnits", "type": "uint8", "index": 3, "typeShort": "uint", "bits": "8", "displayName": "decimal Units", "template": "elements_input_uint", "value": "2" }, { "name": "centralAdmin", "type": "address", "index": 4, "typeShort": "address", "bits": "", "displayName": "central Admin", "template": "elements_input_address", "value": "0x34b35496e9654b510a0b4d4760a541546a08062c" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "target", "type": "address" }, { "indexed": false, "name": "frozen", "type": "bool" } ], "name": "FrozenFund", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ]
},
"port":3001
}
To start the server just type:
nodemon index.js | bunyan -L
Nodemon will reload the server on file change and buyan will parse the log output.
Test the python obd module and see the trace until our microservice:
You will receive the base64 encoded body, and after decoding you will get the json. That base64 encoded string is forwarded by our firebase function.
Now, there will be two options on how to throw a transaction to our blockchain :
- Create a block on each event (geolocation event).
- Create a block whenever the route is closed (the vehicle is stopped and all the data is collected).
For simplicity's sake, we are going to use the first approach. Now, you had deployed a contract that can send/receive custom tokens to buy gas (let's say), and record the locations where the driver were; by now we only had tested the token transactions, but pick the "Add location" function from the contract and provide the required data as follows:
It is important to have running and mining your ethereum node.
After a moment, after a validations, you will see at the bottom of the screen our transaction with the provided data. That means that the block was validated by the miners.
Now, our event is within our blockchain. Lets keep going developing the integration.
In the config function (in ethereum.js), we need to setup our connection to the blockchain :
web3.setProvider(new web3.providers.HttpProvider(config.ethereum.provider));
web3.eth.defaultAccount= config.ethereum.defaultAddress;
log.debug("Accounts ", web3.eth.accounts);
contract = web3.eth.contract(config.ethereum.abi).at(config.ethereum.contractAddress);
contract.NewLocation().watch(function(error, result){
if (!error) {
console.log(result);
} else {
console.error(error);
}
});
This code will get an instance of our contract and listen for the NewLocation event defined within the contract to get informed whenever a location is added to the blockchain.
Now, in the /events endpoint, we need to call the addLocation event when the message is coming from firebase function:
contract.addLocation.sendTransaction(config.ethereum.defaultAddress, json.s, json.la, json.lon, (error, value) => {
console.log("Transaction ", value);
console.log("Transaction ", web3.eth.getTransaction(value));
});
When the event is fired, you will see something like these:
Now we're able to send transaction to the blockchain and listen for events. You can see the transaction in the explorer:
The UI will be pretty simple due this is only a prototype, but will allow the user to see the vehicles in real time and transfer founds from a master account (let's say, a cost center) for fuel to a drivers account (blockchain address).
The objective with this schema, is allow to transact using the custom token rather than use money; with this, the business won't be trying to trust in the drivers to spend money in no-needed fuel.
The app is not (yet) a dApp, so as a prototype, I just wan't to deploy a server that can serve a webpage that can push events via websockets.
To do that, we have to set up a websocket feature for express as follows:
//Add the endpoint definition
var wsEndpoint = expressWs.getWss('/ws');
//Add the endpoint to be listening for new connections. Once we got a new connection, send a hello message
app.ws('/ws', function(ws, req) {
ws.send(JSON.stringify({"contest":"helium"}));
});
Now, we have to modify our method that is listening for blockchain events (that method was defined before and only prints the blockchain transaction information):
//ethereum.js
startWatching: (callback) => {
contract.NewLocation().watch(function(error, result){
if (!error) {
log.debug("New location ", result);
callback(result);
} else {
log.error("Error processing tranction ", error);
}
});
}
The callback will send the transaction information to the websocket clients :
//http.js
ethereum.startWatching((event) => {
console.log("Trying to push to websockets ", event);
wsEndpoint.clients.forEach(function(client, idx) {
client.send(JSON.stringify({"event":event}));
});
});
Note: The code can be confusing at this point, so it's better to see the attachments section to see the full code and continue reading.
By now, whenever the transaction is sent to the blockchain and validated by the nodes, you can see the websocket client log.
Use the websocket client chrome extension to debug websockets messages
Once you got the message via websockets, you are ready to go with the UI.
The map has to update with the new location :
The client JS
We will be splitting the logic/backend communication within a JS file as follows:
var APP = {
init: (eventCallback) => {
map = L.map('mapid').setView([37.785171, -122.397198], 16);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.hackster.io/maleficarum" target="_blank"> Malleus Maleficarum </a>'
}).addTo(map);
L.marker([37.785171, -122.397198]).addTo(map)
.bindPopup('Hackster.io.')
.openPopup();
if(!("WebSocket" in window)){
alert("Websockets not supported");
} else {
socket = new WebSocket("ws://localhost:3001/ws");
socket.onopen = function(){
console.log("Connected to websocket server");
}
socket.onerror = function() {
console.error("Error connecting to websocket server");
setTimeout(function() {
location.reload();
}, 60000);
}
socket.onmessage = function(msg) {
const json = JSON.parse(msg.data);
console.log("Message received by WS ", json);
if(json.alert) {
self.notifications.send(json.alert);
} else {
eventCallback(JSON.parse(msg.data));
}
}
}
}
};
That code will init the map where the assets are shown and will handle the blockchain messages (as well as other messages we can send to the clients).
The initialization of the module has to be as follows:
APP.init(((message) => {
const event = message.event;
if(!event) {
return;
}
console.log("Message received ", event);
var marker;
//Check if the asset already is in the map
assets.forEach(pin => {
if(pin) {
marker = pin;
}
});
if(marker) {
marker.marker.setLatLng([event.args.latitude, event.args.longitude]);
} else {
var m = L.marker([event.args.latitude, event.args.longitude]);
m.addTo(map)
.bindPopup(event.address)
.openPopup();
const marker = {
"address":event.address,
"marker":m
}
assets.push(marker);
}
console.info(assets);
}));
Now, the last thing ... let's say that you want to transfer your coins to a specific vehicle to spend in fuel or maintenance, you have to send via your contract; let's remember the contract's method:
you have to define also a listener that handle messages whenever a transfer is done:
//ethereum.js
startWatching: (callback) => {
contract.NewLocation().watch(function(error, result){
if (!error) {
log.debug("New location ", result);
callback(result);
} else {
log.error("Error processing tranction ", error);
}
});
contract.Transfer().watch(function(error, result){
if (!error) {
log.debug("Transfer ", result);
} else {
log.error("Error processing tranction ", error);
}
});
}
And you should see something like:
Now, finally, let's handle the message when the websockets client receives a transaction; by now, the easy way to do that is reload the page to reload the balance when the page is loading:
//index.handlebars
if(event.event === "Transfer") {
location.reload();
}
Now, when the admin (from the admin page which is not developed so far) transfer coins to the asset address, the driver application (which is only showing the location of the tracker) reloads to reflect the new balance.
Todo'sThis is only a prototype so far, so there is a lot of space for improvements, enhancements and error handling, but the goal of this small project is to try the communication from the OBD port to the UI passing though the Helium's/Google's cloud and be validated by ethereum's nodes.
Perhaps the approach can be enhanced switching from the centralized web app to a dApp. Also, is needed the driver identity (which has to be associated with an address) and added more functions.
Comments