Introduction
This article will demonstrate how to build a smart task tracking board that can be used to track any set of tasks such as day to day work around the house, or even chores for children. Powered by an Intel® Edison breakout board, and a handful of inexpensive hardware components, this taskboard can run on a rechargeable battery and will auto synchronize its status to a backend data service.
The taskboard is designed to be used by simply touching the task that has been completed, resulting in an LED indicator letting the user know the task has been completed. In addition, the status of each task is stored remotely on an Intel® IOT Gateway using a NoSQL MongoDB* database.
The tutorial will walk through construction of a client side Node.js application running on the Intel Edison board, and a server side Node.js application running on an Intel® IOT gateway. Basic bread boarding and soldering skills are required, as well as familiarity of with the Intel® XDK and the Node.js development language.
Parts List
This project requires the following hardware and tools.
Components:
- Intel Edison with breakout board allowing both digital and analog interface GPIO pins.
- Intel® Edison compute module https://software.intel.com/en-us/iot/hardware/edison
- Examples: Arduino* Expansion board OR SparkFun* Blocks
- Intel® Edison compute module https://software.intel.com/en-us/iot/hardware/edison
- Micro USB cables – for connecting to the Intel® Edison board
- Hookup wire
- One of each of these per task:
- Green LED
- Resistor – Unique value for each task
- Resistor – 10Ω x 1
- Four pin male header
- Shift Register – 74LS164
- Perfboard
- Foam board
Tools:
- Soldering Iron
- Wire strippers and cutters
- OPTIONAL for housing
- Razor blade for cutting panels
- Crafter’s glue
Design
The system design is divided into two parts – client and server. The client side is composed of an Intel® Edison board which waits for users to click a button, marking a task as complete. When selected, the client will reach out to the server to update the status of the task. The server accepts TCP client connection requests, and task updates using a simple JSON object format. Clients can request to update a task or receive a list of the last recorded task status, an imperative feature during startup. Both requests by a client result in a reply that outputs the latest task status.
REQUEST: Client to Server
{
“request” : {‘UPDATE’;’GET’},
“user”:1,
“task”:3,
“status”: 1
}
RESPONSE: Server to Client
[1,0,1]
The following sequence diagrams explain the program execution including startup and task updates. The left side of the diagram represents client actions, followed by server actions listed on the right side (TaskServer.js).
STARTUP
[Figure 1 - Sequence Diagram of Startup Flow]
TASK UPDATE
[Figure 2 - Sequence Diagram of Update Flow]
Construction
The modular design of the taskboard is made up of a breadboard as the main unit accompanied by one child component board per task. The main board houses one shift register, and interfaces to the Intel® Edison using six connections (Ground, Ground, Vcc, AnalogIn, GPIO_DS, GPIO_CK), each of which are explained below.
Each task components plugs into the main board using a four pin interface (Vin, Ground, LEDStatus, ButtonStatus), and maintains a resistor with a unique value for that task, an LED, and a momentary button.
Sizing
Starting with the mainboard, determine how many tasks are able to fit by laying out individual perfboards side by side on top of the breadboard. The tasks will hang off the breadboard as in the following picture. Notice how the taskboards line up side by side.
Managing Multiple Inputs
If there are more than three tasks to manage, it is best to use an input multiplexing technique that allows many buttons to interface with one analog input. This is required because there are a limited number of Digital GPIO pins available. One method is to catalog the input voltage on an analog input pin when using different resistors.
First, determine resistor values that will be used for each button by connecting each resistor to 5 volts, and monitor the observed voltage inputs using the Analog Read example that comes with the Intel® XDK. Be sure to pull the input port to low when not applying any voltage. This will prevent erratic reads from the input port. Be sure to run many tests to capture a valid set of resistor ranges as these values can vary widely based on temperature and other noise that occurs on the circuit.
The Intel® XDK provides a sample AnalogRead that can be modified to write output data based on certain ranges. Adjust the
accuracy of the ranges by indicating the integer size in bits on the configuration object. A larger value will return a larger number. Start with this example to build the finalized Client.js application.
Be sure to add a dependency for node-sleep. This component allows all JS execution to stop for a configurable amount of time.
SAMPLE – Client package.json
…. "dependencies": { "node-sleep" : "git://github.com/erikdubbelboer/node-sleep" }
In the configuration file, set the bit precision to 16 or something that works best for the resistors used in the application. A larger precision will yield a larger number. The goal is to ensure a wide margin between resistor samplings to ensure a button can be uniquely identified.
SAMPLE – Client cfg-app-platform.js
cfg.io = {} ; // used by caller to hold mraa I/O object cfg.ioPin = 1 ; // set to unknown pin (will force a fail) cfg.ioBit = 16 ; // preferred return resolution for analog i/o reads
In main.js, add a minimum and maximum value parameter that represents the range for each button value. Because these values will vary based on your observations, creating constants ahead of time will make changing values in the conditionals easier later.
SAMPLE – Client main.js
…. var button1SignalMin = 100; var button1SignalMax = 900; var button2SignalMin = 901; var button2SignalMax = 1500;
The periodicActivity function is executed based on an interval timer. Modify this default callback code to indicate which button was pressed, and to also call the Status LED and backend updates indicated in the design. It is also good to implement a lock around this logic to ensure there are no erroneous reads that are captured while the system is updating the task status or during duplicate button reads due to voltage fluxuations (debouncing.)
SAMPLE – Client main.js
…. var periodicActivity = function() { var analogIn = cfg.io.read() ; var taskID; if ( !buttonClicked /* && (analogIn>MIN_INPUT) && (analogIn<MAX_INPUT) */) { process.stdout.write(analogIn + "") ; if ( (analogIn>button1SignalMin) && (analogIn<button1SignalMax) ){ toggleButtonStatus(0); taskID = 1; buttonClicked = true; }… else if other button range checks if (buttonClicked) { updateLEDStatus(); updateTaskOnGateway(taskID, buttonStatus[taskID-1]); //Give everything time to settle after the click setTimeout(function(){ buttonClicked = false; console.info("Polling allowed again.."); }, 1000); } } };
Run the client application and record the different values of the resistors into the constants previously created. Below is a debugging screenshot showing a few different values observed during testing. Once an accurate range of resistors has been identified, construct the rest of the mainboard by following the steps in the next section.
[Screenshot 1 - Intel XDK Debugging output]
[Screenshot 2 - Intel XDK Debugging output with Buttons mapped]
A nice Instructable is available that walks through the process of mapping multiple button inputs can be referenced here:
http://www.instructables.com/id/How-to-access-5-buttons-through-1-Arduino-input/?ALLSTEPS
Hardware – Task Construction
Each individual task can be constructed on a standalone module. In this example, we used a small 2”x3” perf board to add the minimal required components; an LED, button, and resistors. To help with the modularity of the design, we added a 4 pin male header to the board by pushing the pins into the header package and soldering it from the top down.
1 – Ground
2 – Status/LED
3 – Button Input
4 – Vcc
Hardware – Main Board Construction
[Image 1 - Fully assembled Taskboard with tasks]
Individual tasks plug into the task main-board. For simplicity, a breadboard is used in this example. After determining where each task will plug into the board, wire the interface connections to match the pinout of the taskboard.
1 – Ground
2 – Status/LED
3 – Button Input
4 – Vcc
The pinout of the 74LS164 shift register is as follows:
1, 9, 14 – Vcc
2 – Data In
7 – Ground
8 – Clock
3, 4, 5, 6 – Output 1,2,3,4
10, 11, 12, 13 – Output 5,6,7,8
[Figure 3 - Mainboard wiring diagram]
Software – Client
As mentioned previously, a shift register is used in this project to avoid a 1:1 mapping for LED to GPIO pins. The shift register works as a FIFO ordered array, meaning that a value (1 or 0) is set on the device and pushed into a stack, resulting in the current data to be shifted to the next register. Values set on the Data Input pin are pushed into the registers only when CLK is HIGH.
From client.js
//... cfg.io = new cfg.mraa.Aio(buttonPin) ; cfg.io.setBit(cfg.ioBit) ; if (LED_USING_SHIFT_REGISTER){ pinClock = new cfg.mraa.Gpio(pinNumberShiftRegisterClock); pinData = new cfg.mraa.Gpio(pinNumberShiftRegisterDataIn); pinClock.dir(cfg.mraa.DIR_OUT); pinData.dir(cfg.mraa.DIR_OUT); } … function updateLEDStatus(){ console.info("Updating LEDs " + buttonStatus.length); if (LED_USING_SHIFT_REGISTER){ for (var i=buttonStatus.length-1;i>=0;i--){ pinClock.write(0); sleep.msleep(50); pinData.write(buttonStatus[i]); console.info(buttonStatus[i]); sleep.msleep(50); pinClock.write(1); } } }
Before updating a client task to the backend, first update the status locally. This ensures the status and light are correct without depending on a backend connection. A local buttonStatus array holds the status of each task.
From client.js
function toggleButtonStatus(btn){ var btnPressed = btn+1; console.log("Button " + btnPressed + " pressed."); if (buttonStatus[btn] === 0) buttonStatus[btn] = 1; else buttonStatus[btn] = 0; }
Next, make a socket connection to the server to update the task status or to request the status of all tasks. The reply data is handled asynchronously, and is pushed back into the local status array. A simple JSON.Parse() can be used to convert the JSON format into a usable array. Both calls return the latest status of tasks.
From client.js– Update Task Status on Gateway
function updateTaskOnGateway(taskID, status){ client.connect(gatewayPortNumber, gatewayIPAddress, function(){ var updateStr = '{"user":1, "task":' + taskID + ', "status":' + status + '1}'; console.info("Connected to server. Updating status..."); client.write(updateStr); }); }
From client.js– Get Tasks Status from Gateway
function getTaskStatusFromServer(){ client.connect(gatewayPortNumber, gatewayIPAddress, function(){ console.info("Connected to server. Updating status..."); client.write('{"request" : "GET", "user" : 1}'); }); }
From client.js– Handle reply from Gateway
client.on('data', onServerReply); function onServerReply(data){ console.info('Received ' + data); //Example: (Pin1Status, .., PinNStatus) [0,0,1,0] buttonStatus = JSON.parse(data.toString('utf8')); updateLEDStatus(); client.destroy(); }
The full client.js software can be found at the end of this article. In this client section, we have described how to:
- Categorize unique resistor values and respond to button clicks given sets of ranges
- Toggle the matching task LED using a shift register
- Update status locally
- Update task status to a backend TCP service
- Update local task status given response from a backend TCP service
The next section will detail the server side solution, including how to store the tasks in an offline database and how to update and fetch status for any task.
Software – Server
To setup the server, in this example we used Ubuntu 16.04 Operating System installed on an Intel® IOT Gateway. Using Node.js, we created a small service that will accept connections using TCP, and manage data using MongoDB. All code is written in Node.js.
Installing nodejs*
sudo apt-get install nodejs
Installing MongoDB*
1. To install MongoDB NoSQL , start by importing the public key for the download repository and create the .list file that will help locate apt-get during installation.
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
2. Now reload the local package database
sudo apt-get update
3. Install NPM
Sudo apt-get npm
4. Install Mongo DB
sudo apt-get install mongodb-org
One final step is required to ensure the MongoDB service can be started correctly. This is due to the fact that starting with Ubuntu* 16.04, the process for starting services has changed. Instead of a proprietary launcher called upstart, Ubuntu is now using the more familiar systemd, which aligns to how other Debian* based Unix systems work as well. More background on this switch can be found here: https://wiki.ubuntu.com/SystemdForUpstartUsers
5. Create a service file that is required by the systemd to launch the mongodb. The file will tell the system what to launch and under which user to run the service.
File contents of /etc/systemd/system/mongodb.service
After=network.target [Service] User=mongodb Group=mongodb ExecStart=/usr/bin/mongod –quite –config /etc/mongod.conf ExecReload=/bin/kill –HUP #MAINPID StandardOutput=syslog StandardErr=syslog [Install] WantedBy=multi-user.target
6. Start the Mongo service
sudo systemctl start mongodb
7. To ensure it starts on next reboot
Sudo systemctl enable mongodb.service
To verify the server software is installed correctly, create a simply TestMongo.js file that opens a connection to the database. No errors will indicate that the connection from Node.js* to MongoDB* is working correctly.
SAMPLE - TestMongo.js
var MongoClient = required(‘mongodb’).MongoClient; MongoClient.connect(“mongodb://localhost:27017/exampleDb”, function(err,db){ if(err){ return console.dir(“Error connecting to MongoDB.”); } else{ return console.dir(“Connected successfully.”); } });
More information on MongoDB installation can be found here:
https://docs.mongodb.com/v3.0/tutorial/install-mongodb-on-ubuntu/
Now that dependencies are installed and verified, we can now write our gateway service that will accept TCP client connections for getting or updating task status. The complete source for the server.js can be found at the end of this article.
First, create the MongoDB collection that will hold our tasks. After creating a new database, each user will have a separate collection that maintains the tasks and active status. The naming convention for each user collection is to append a unique userID to the word ‘UserTasks’. After the collection is created, a few rows of sample data are inserted which represent each task. The names of the tasks can optionally be stored in the database, however they are not referenced in this sample.
SAMPLE CreateTasks.js
var MongoClient = require('mongodb').MongoClient, Db = require('mongodb').Db, Server = require('mongodb').Server assert = require('assert'); var db = new Db('MyTasks', new Server('localhost', 27017)); db.open(function(err, db){ if(err) { console.error(err); } else{ console.info("Database: " + db.Name + " created"); db.createCollection('UserTasks1', function(err, usertasks){ if(err){ return console.dir("Error creating collection usertasks"); } else{ //Insert the tasks var task1 = {id: 1, title: 'Make up bed', status: 0}; var task2 = {id: 2, title: 'Put away dishes', status: 0}; var task3 = {id: 3, title: 'Brush teeth', status: 0}; var task4 = {id: 4, title: 'Put shoes away', status: 0}; usertasks.insert([task1, task2, task3, task4], function(err, result){ if(err){ return console.dir("Error inserting tasks"); } else{ console.log('Records inserted: "_id"', result.length, result); } }); } }); } });
With the data storage completed, the service can now be created to allow updating and fetching of tasks. First, create a TCP server and assign a handler. The handler will parse the input data into appropriate fields representing the type of request, userID, taskID, and status to set the task too.
FROM server.js
var clientSocket = null; server = net.createServer(); server.listen(Constants.SERVER_PORT, Constants.SERVER_IP_ADDRESS); server.on('connection', handleClientConnection); function handleClientConnection(socket){ clientSocket = socket; socket.on('data', function(data){ var clientJSON = JSON.parse(data.toString('utf8'), (key,value)=>{ return value; }); switch(clientJSON.request) { case "UPDATE": updateTaskStatus(clientJSON); socket.write("Task updated"); case "GET": console.log("Getting all tasks for user " + clientJSON.user); var alltasks = getAllTasks(clientJSON.user); socket.write(alltasks); break; } }); }
To get all tasks for a user, a MongoDB cursor is referenced to iterate the requested collection of tasks. The data is pushed onto a global array and then returned to the caller.
FROM server.js
function getAllTasks(userID){ var qry = { "id": userID }; var collectionName = "UserTasks" + userID; MongoClient.connect("mongodb://localhost:" + Constants.SERVER_MONGO_PORT + "/MyTasks", function(err, db){ var taskCollection = db.collection(collectionName); var taskCursor = taskCollection.find().forEach(taskIterator); }); return userTasks; } function taskIterator(doc){ userTasks.push(doc.id + ":" + doc.status); if (++global.CurrentRecordCount===global.totalTaskCount) allRecordsCollected(); }
FROM server.js
function updateTaskStatus(inputJSON){ MongoClient.connect("mongodb://localhost:" + Constants.SERVER_MONGO_PORT + "/MyTasks", function (err, db) { var qry = { "id": inputJSON.task }; var collectionName = "UserTasks" + inputJSON.user; var taskCollection = db.collection(collectionName); var taskCursor = taskCollection.update(qry, {$set: {status:inputJSON.status} }); getAllTasks(inputJSON.user); });}
Complete Source Code
The full source code for the example described in this article is available below.
Client
/* client.js * - Poll for button clicks * - OnClick, send TCP request to update status * - Sync status of button with reply from backend [task1, task2, …, taskN] * * Matt Chandler * Intel, Corp. 2017 * */ /* jslint node:true */ /* jshint unused:true */ "use strict" ; var APP_NAME = "IoT Analog Read" ; var cfg = require("./utl/cfg-app-platform.js")() ; // init and config I/O resources var net = require('net'); var client = new net.Socket(); var sleep = require('sleep'); var TIMER_POLL_BUTTON_MS = 600; var TIMER_UPDATE_LEDS_MS = 10000000; var buttonPin = 2; //Analog input for button var button1SignalMin = 100; var button1SignalMax = 900; var button2SignalMin = 901; var button2SignalMax = 1500; var button3SignalMin = 3000; var button3SignalMax = 5000; var button4SignalMin = 2800; var button4SignalMax = 3500; var pinNumberShiftRegisterDataIn = 2; var pinNumberShiftRegisterClock = 4; var pinNumberTaskStatus1 = 2; var pinNumberTaskStatus2 = 3; var pinNumberTaskStatus3 = 4; var pinClock, pinData, pinTaskStatus1, pinTaskStatus2, pinTaskStatus3; var gatewayIPAddress = '192.168.1.225';//10.88.65.118 var gatewayPortNumber = 20174; var MIN_INPUT = -1; var MAX_INPUT = 1000000; var LED_USING_SHIFT_REGISTER = true; var TASK_STATUS_IN_CLOUD = false; var buttonStatus = [1,0,1]; var pinLEDs = []; var buttonClicked = false; console.log("\n\n\n\n\n\n") ; // poor man's clear console console.log("Initializing " + APP_NAME) ; process.on("exit", function(code) { // define up front, due to no "hoisting" clearInterval(intervalButtonPoll) ; clearInterval(intervalUpdateStatusLEDs); console.log("\nExiting " + APP_NAME + ", with code:", code) ; }) ; // confirm that we have a version of libmraa and Node.js that works // exit this app if we do not if( !cfg.test() ) { process.exit(1) ; } if( !cfg.init() ) { process.exit(2) ; } cfg.io = new cfg.mraa.Aio(buttonPin) ; // construct our I/O object cfg.io.setBit(cfg.ioBit) ; if (LED_USING_SHIFT_REGISTER){ pinClock = new cfg.mraa.Gpio(pinNumberShiftRegisterClock); pinData = new cfg.mraa.Gpio(pinNumberShiftRegisterDataIn); pinClock.dir(cfg.mraa.DIR_OUT); pinData.dir(cfg.mraa.DIR_OUT); } else{ pinTaskStatus1 = new cfg.mraa.Gpio(pinNumberTaskStatus1); pinTaskStatus2 = new cfg.mraa.Gpio(pinNumberTaskStatus2); pinTaskStatus3 = new cfg.mraa.Gpio(pinNumberTaskStatus3); pinTaskStatus1.dir(cfg.mraa.DIR_OUT); pinTaskStatus2.dir(cfg.mraa.DIR_OUT); pinTaskStatus3.dir(cfg.mraa.DIR_OUT); pinLEDs.push(pinTaskStatus1); pinLEDs.push(pinTaskStatus2); pinLEDs.push(pinTaskStatus3); } if (TASK_STATUS_IN_CLOUD){ getTaskStatusFromServer(); sleep.msleep(500); } else{ onServerReply('[0,0,0]') sleep.msleep(500); } console.log("Syncing Task LEDs..."); updateLEDStatus(); function getTaskStatusFromServer(){ client.connect(gatewayPortNumber, gatewayIPAddress, function(){ client.write('{“request”:”GET”, "user":1}'); }); } client.on('data', onServerReply); client.on('close', function(){ console.log("connection closed"); }); function onServerReply(data){ console.info('Received ' + data); //Example: (Pin1Status, .., PinNStatus) [0,0,1,0] //based on data received, set matching lights and persist settings buttonStatus = JSON.parse(data.toString('utf8')); client.destroy(); } //Callback for period button status read var periodicActivity = function() { var analogIn = cfg.io.read() ; if ( !buttonClicked /* && (analogIn>MIN_INPUT) && (analogIn<MAX_INPUT) */) { process.stdout.write(analogIn + "") ; if ( (analogIn>button1SignalMin) && (analogIn<button1SignalMax) ){ toggleButtonStatus(0); buttonClicked = true; } else if ( ( analogIn>button2SignalMin) && (analogIn<button2SignalMax) ){ toggleButtonStatus(1); buttonClicked = true; } else if ( ( analogIn>button3SignalMin) && (analogIn<button3SignalMax) ){ toggleButtonStatus(2); buttonClicked = true; } if (buttonClicked) { updateLEDStatus(); //Give everything time to settle after the click setTimeout(function(){ buttonClicked = false; console.info("Polling allowed again.."); }, 1000); } } } ; var intervalButtonPoll = setInterval(periodicActivity, TIMER_POLL_BUTTON_MS) ; var intervalUpdateStatusLEDs = setInterval(updateLEDStatus, TIMER_UPDATE_LEDS_MS); function toggleButtonStatus(btn){ var btnPressed = btn+1; console.log("Button " + btnPressed + " pressed."); if (buttonStatus[btn] === 0) buttonStatus[btn] = 1; else buttonStatus[btn] = 0; } function updateLEDStatus(){ if (LED_USING_SHIFT_REGISTER){ for (var i=buttonStatus.length-1;i>=0;i--){ pinClock.write(0); sleep.msleep(50); pinData.write(buttonStatus[i]); sleep.msleep(50); pinClock.write(1); } } else{ pinTaskStatus2.write(0); for (var idx=0;idx<pinLEDs.length;idx++){ pinLEDs[idx].write(buttonStatus[idx]); } } }
Server
/* - Task Tracker Gateway - - - Wait for Connections - UPDATE - Update Task Status and return all tasks for User - GET - Return all for User - - Matt Chandler - Intel, Corp. 2017 */ var MongoClient = require('mongodb').MongoClient; var net = require('net'); var Constants = require('./Constants'); global.Currentcount = 0; tasksToReturn = []; var socket = null; var clientSocket = null; global.totalTaskCount = 0; server = net.createServer(); server.listen(Constants.SERVER_PORT, Constants.SERVER_IP_ADDRESS); server.on('connection', handleClientConnection); server.on('error', function(err){ console.log(err);}); function handleClientConnection(socket) { global.CurrentRecordCount = 0; clientSocket = socket; socket.on('data', function (data) { var clientJSON = JSON.parse(data.toString('utf8'), (key, value) =>{ return value; }); socket.on('error', function(err){ console.log(err);}); switch(clientJSON.request) { case "UPDATE": updateTaskStatus(clientJSON); break; case "GET": var alltasks = getAllTasks(clientJSON.user, socket); break; } }); } function getAllTasks(userID){ var qry = { "id": userID }; var collectionName = "UserTasks" + userID; var taskCursor; MongoClient.connect("mongodb://localhost:" + Constants.SERVER_MONGO_PORT + "/MyTasks", function(err, db){ var taskCollection = db.collection(collectionName); taskCollection.count(function(err,count){ global.totalTaskCount = count;}); taskCursor = taskCollection.find().forEach(taskIterator); }); } function taskIterator(doc){ tasksToReturn.push(doc.id + ":" + doc.status); if (++global.CurrentRecordCount===global.totalTaskCount) allRecordsCollected(); } function updateTaskStatus(inputJSON) { MongoClient.connect("mongodb://localhost:" + Constants.SERVER_MONGO_PORT + "/MyTasks", function (err, db) { var qry = { "id": inputJSON.task }; var collectionName = "UserTasks" + inputJSON.user; var taskCollection = db.collection(collectionName); var taskCursor = taskCollection.update(qry, {$set: {status:inputJSON.status} }); getAllTasks(inputJSON.user); }); } function allRecordsCollected(){ clientSocket.write(JSON.stringify(tasksToReturn)); }
Summary
This article demonstrated how to construct an electronic taskboard that provides a user the ability to physically touch, and mark a task as completed in an offline system. Client software was described that demonstrates how to react to the physical touch of updating a task as well as calling out to a backend data service to update the status of the task. In addition, server software was outlined to listen for and manage incoming task updates and persist states into a MongoDB* database. Finally, custom hardware was constructed to detail how to manage multiple inputs and promote a modular design allowing additional tasks to be added in the future.
About the Author
Matt Chandler is a software engineer at Intel working on scale enabling projects for Internet of Things.
References
Installation of Mongo DB on Ubuntu: https://docs.mongodb.com/v3.0/tutorial/install-mongodb-on-ubuntu/
MongoDB introduction:https://mongodb.github.io/node-mongodb-native/api-articles/nodekoarticle1.html
Running mongodb on Ubuntu 16.04 LTS: http://stackoverflow.com/questions/37014186/running-mongodb-on-ubuntu-16-04-lts/37058244
The 74HC164 Shift Register and your Arduino: http://www.instructables.com/id/The-74HC164-Shift-Register-and-your-Arduino/
74LS164 Datasheet: http://html.alldatasheet.com/html-pdf/64008/HITACHI/74LS164/246/1/74LS164.html
Notices
No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.
Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.
This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.
The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.
Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.
Intel, the Intel logo, and Intel RealSense are trademarks of Intel Corporation in the U.S. and/or other countries.
*Other names and brands may be claimed as the property of others
**This sample source code is released under the Intel Sample Source Code License Agreement.
© 2017 Intel Corporation.