├── .gitignore ├── edison ├── package.json └── sensors.js ├── images ├── edison-arduino101-iot.png └── arduino101-serial-monitor.png ├── web ├── server │ ├── package.json │ ├── lib │ │ ├── config │ │ │ └── server.js │ │ └── routes │ │ │ ├── index.js │ │ │ └── socket.js │ └── server.js └── client │ ├── styles.css │ ├── app.js │ └── index.html ├── LICENSE.md ├── arduino └── imu │ └── imu.ino └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /edison/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "noble": "1.3.0", 4 | "socket.io-client": "1.4.5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /images/edison-arduino101-iot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/estherjk/edison-arduino101-iot/HEAD/images/edison-arduino101-iot.png -------------------------------------------------------------------------------- /images/arduino101-serial-monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/estherjk/edison-arduino101-iot/HEAD/images/arduino101-serial-monitor.png -------------------------------------------------------------------------------- /web/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "4.13.4", 4 | "morgan": "1.7.0", 5 | "socket.io": "1.4.5" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/client/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | } 4 | 5 | .sensor-value { 6 | font-size: 21px; 7 | font-weight: 300; 8 | line-height: 1.4; 9 | } 10 | -------------------------------------------------------------------------------- /web/server/lib/config/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | httpPort: 8080, 5 | staticFolder: path.join(__dirname + '/../../../client') 6 | }; 7 | -------------------------------------------------------------------------------- /web/server/lib/routes/index.js: -------------------------------------------------------------------------------- 1 | // Load the page 2 | // NB: This needs to be the last route added 3 | exports.serveIndex = function (app, staticFolder) { 4 | app.get('*', function (req, res) { 5 | res.sendFile('index.html', { root: staticFolder }); 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /web/server/server.js: -------------------------------------------------------------------------------- 1 | // modules 2 | var express = require('express') 3 | , http = require('http') 4 | , morgan = require('morgan'); 5 | 6 | // configuration files 7 | var configServer = require('./lib/config/server'); 8 | 9 | // app parameters 10 | var app = express(); 11 | app.set('port', configServer.httpPort); 12 | app.use(express.static(configServer.staticFolder)); 13 | app.use(morgan('dev')); 14 | 15 | // serve index 16 | require('./lib/routes').serveIndex(app, configServer.staticFolder); 17 | 18 | // HTTP server 19 | var server = http.createServer(app); 20 | server.listen(app.get('port'), function () { 21 | console.log('HTTP server listening on port ' + app.get('port')); 22 | }); 23 | 24 | // WebSocket server 25 | var io = require('socket.io')(server); 26 | io.on('connection', require('./lib/routes/socket')); 27 | 28 | module.exports.app = app; 29 | -------------------------------------------------------------------------------- /web/server/lib/routes/socket.js: -------------------------------------------------------------------------------- 1 | // WebSocket communications 2 | module.exports = function (socket) { 3 | socket.on('hello', function() { 4 | console.log('Client connected'); 5 | socket.emit('init', {}); 6 | }); 7 | 8 | socket.on('ax:edison', function(data) { 9 | socket.broadcast.emit('ax:web', data); 10 | }); 11 | 12 | socket.on('ay:edison', function(data) { 13 | socket.broadcast.emit('ay:web', data); 14 | }); 15 | 16 | socket.on('az:edison', function(data) { 17 | socket.broadcast.emit('az:web', data); 18 | }); 19 | 20 | socket.on('gx:edison', function(data) { 21 | socket.broadcast.emit('gx:web', data); 22 | }); 23 | 24 | socket.on('gy:edison', function(data) { 25 | socket.broadcast.emit('gy:web', data); 26 | }); 27 | 28 | socket.on('gz:edison', function(data) { 29 | socket.broadcast.emit('gz:web', data); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /web/client/app.js: -------------------------------------------------------------------------------- 1 | // MODIFY THIS WITH THE APPROPRIATE URL 2 | var socket = io.connect('WEB-SERVER-DOMAIN-HERE:8080'); 3 | 4 | socket.on('connect', function() { 5 | socket.emit('hello'); 6 | }); 7 | 8 | socket.on('ax:web', function(data) { 9 | document.getElementById('ax-value').innerText = data; 10 | }); 11 | 12 | socket.on('ay:web', function(data) { 13 | document.getElementById('ay-value').innerText = data; 14 | }); 15 | 16 | socket.on('az:web', function(data) { 17 | document.getElementById('az-value').innerText = data; 18 | }); 19 | 20 | socket.on('gx:web', function(data) { 21 | document.getElementById('gx-value').innerText = data; 22 | }); 23 | 24 | socket.on('gy:web', function(data) { 25 | document.getElementById('gy-value').innerText = data; 26 | }); 27 | 28 | socket.on('gz:web', function(data) { 29 | document.getElementById('gz-value').innerText = data; 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Esther Jun Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | edison-arduino101-iot 6 | 7 | 8 | 9 | 10 | 11 | 12 | 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 |
29 |

Accelerometer

30 |
31 |
32 |
33 | ax: 34 | 0.0 35 |
36 | 37 |
38 | ay: 39 | 0.0 40 |
41 | 42 |
43 | az: 44 | 0.0 45 |
46 |
47 |
48 | 49 |
50 |
51 |

Gyroscope

52 |
53 |
54 |
55 | gx: 56 | 0.0 57 |
58 | 59 |
60 | gy: 61 | 0.0 62 |
63 | 64 |
65 | gz: 66 | 0.0 67 |
68 |
69 |
70 | 71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /edison/sensors.js: -------------------------------------------------------------------------------- 1 | var noble = require('noble'); 2 | 3 | // MODIFY THIS WITH THE APPROPRIATE URL 4 | var socket = require('socket.io-client')('WEB-SERVER-DOMAIN-HERE:8080'); 5 | 6 | // These should correspond to the peripheral's service and characteristic UUIDs 7 | var IMU_SERVICE_UUID = '2947ac9efc3811e586aa5e5517507c66'; 8 | var AX_CHAR_UUID = '2947af14fc3811e586aa5e5517507c66'; 9 | var AY_CHAR_UUID = '2947b090fc3811e586aa5e5517507c66'; 10 | var AZ_CHAR_UUID = '2947b180fc3811e586aa5e5517507c66'; 11 | var GX_CHAR_UUID = '2947b252fc3811e586aa5e5517507c66'; 12 | var GY_CHAR_UUID = '2947b5aefc3811e586aa5e5517507c66'; 13 | var GZ_CHAR_UUID = '2947b694fc3811e586aa5e5517507c66'; 14 | 15 | socket.on('connect', function() { 16 | console.log('Connected to server'); 17 | 18 | socket.emit('hello'); 19 | }); 20 | 21 | noble.on('stateChange', function(state) { 22 | if(state === 'poweredOn') { 23 | console.log('Start BLE scan...') 24 | noble.startScanning([IMU_SERVICE_UUID], false); 25 | } 26 | else { 27 | console.log('Cannot scan... state is not poweredOn') 28 | noble.stopScanning(); 29 | } 30 | }); 31 | 32 | // Discover the peripheral's IMU service and corresponding characteristics 33 | // Then, emit each data point on the socket stream 34 | noble.on('discover', function(peripheral) { 35 | peripheral.connect(function(error) { 36 | console.log('Connected to peripheral: ' + peripheral.uuid); 37 | peripheral.discoverServices([IMU_SERVICE_UUID], function(error, services) { 38 | var imuService = services[0]; 39 | console.log('Discovered IMU service'); 40 | 41 | imuService.discoverCharacteristics([], function(error, characteristics) { 42 | characteristics.forEach(function(characteristic) { 43 | emitSensorData(characteristic); 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | function getSocketLabel(uuid) { 51 | var label = null; 52 | 53 | if(uuid == AX_CHAR_UUID) { 54 | label = 'ax:edison'; 55 | } 56 | else if(uuid == AY_CHAR_UUID) { 57 | label = 'ay:edison'; 58 | } 59 | else if(uuid == AZ_CHAR_UUID) { 60 | label = 'az:edison'; 61 | } 62 | else if(uuid == GX_CHAR_UUID) { 63 | label = 'gx:edison'; 64 | } 65 | else if(uuid == GY_CHAR_UUID) { 66 | label = 'gy:edison'; 67 | } 68 | else if(uuid == GZ_CHAR_UUID) { 69 | label = 'gz:edison'; 70 | } 71 | 72 | return label; 73 | } 74 | 75 | function emitSensorData(characteristic) { 76 | var socketLabel = getSocketLabel(characteristic.uuid); 77 | console.log(socketLabel); 78 | 79 | characteristic.on('read', function(data) { 80 | socket.emit(socketLabel, data.readInt32LE(0)); 81 | }); 82 | 83 | characteristic.notify('true', function(error) { if (error) throw error; }); 84 | } 85 | -------------------------------------------------------------------------------- /arduino/imu/imu.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Create my own UUIDs; used https://www.uuidgenerator.net/ 5 | #define IMU_SERVICE_UUID "2947ac9e-fc38-11e5-86aa-5e5517507c66" 6 | #define AX_CHAR_UUID "2947af14-fc38-11e5-86aa-5e5517507c66" 7 | #define AY_CHAR_UUID "2947b090-fc38-11e5-86aa-5e5517507c66" 8 | #define AZ_CHAR_UUID "2947b180-fc38-11e5-86aa-5e5517507c66" 9 | #define GX_CHAR_UUID "2947b252-fc38-11e5-86aa-5e5517507c66" 10 | #define GY_CHAR_UUID "2947b5ae-fc38-11e5-86aa-5e5517507c66" 11 | #define GZ_CHAR_UUID "2947b694-fc38-11e5-86aa-5e5517507c66" 12 | 13 | // Arduino 101 acts as a BLE peripheral 14 | BLEPeripheral blePeripheral; 15 | 16 | // IMU data is registered as a BLE service 17 | BLEService imuService(IMU_SERVICE_UUID); 18 | 19 | // Each IMU data point is its own characteristic 20 | BLEIntCharacteristic axChar(AX_CHAR_UUID, BLERead | BLENotify); 21 | BLEIntCharacteristic ayChar(AY_CHAR_UUID, BLERead | BLENotify); 22 | BLEIntCharacteristic azChar(AZ_CHAR_UUID, BLERead | BLENotify); 23 | BLEIntCharacteristic gxChar(GX_CHAR_UUID, BLERead | BLENotify); 24 | BLEIntCharacteristic gyChar(GY_CHAR_UUID, BLERead | BLENotify); 25 | BLEIntCharacteristic gzChar(GZ_CHAR_UUID, BLERead | BLENotify); 26 | 27 | // Assign pin to indicate BLE connection 28 | const int INDICATOR_PIN = 13; 29 | 30 | int ax = 0; 31 | int ay = 0; 32 | int az = 0; 33 | int gx = 0; 34 | int gy = 0; 35 | int gz = 0; 36 | 37 | long previousMillis = 0; 38 | 39 | void setup() { 40 | Serial.begin(9600); 41 | 42 | // Initialize IMU 43 | Serial.println("Initializing IMU..."); 44 | CurieIMU.begin(); 45 | CurieIMU.autoCalibrateGyroOffset(); 46 | CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0); 47 | CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0); 48 | CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1); 49 | 50 | // Initialize BLE peripheral 51 | blePeripheral.setLocalName("IMU"); 52 | blePeripheral.setAdvertisedServiceUuid(imuService.uuid()); 53 | blePeripheral.addAttribute(imuService); 54 | blePeripheral.addAttribute(axChar); 55 | blePeripheral.addAttribute(ayChar); 56 | blePeripheral.addAttribute(azChar); 57 | blePeripheral.addAttribute(gxChar); 58 | blePeripheral.addAttribute(gyChar); 59 | blePeripheral.addAttribute(gzChar); 60 | 61 | // Set initial values 62 | axChar.setValue(ax); 63 | ayChar.setValue(ay); 64 | azChar.setValue(az); 65 | gxChar.setValue(gx); 66 | gyChar.setValue(gy); 67 | gzChar.setValue(gz); 68 | 69 | // Now, activate the BLE peripheral 70 | blePeripheral.begin(); 71 | Serial.println("Bluetooth device active, waiting for connections..."); 72 | } 73 | 74 | void loop() { 75 | // Check if the connection to the central is active or not 76 | BLECentral central = blePeripheral.central(); 77 | 78 | if(central) { 79 | Serial.print("Connected to central: "); 80 | Serial.println(central.address()); 81 | digitalWrite(INDICATOR_PIN, HIGH); 82 | 83 | while(central.connected()) { 84 | updateImuData(); 85 | } 86 | 87 | Serial.print("Disconnected from central: "); 88 | Serial.println(central.address()); 89 | digitalWrite(INDICATOR_PIN, LOW); 90 | } 91 | } 92 | 93 | void updateImuData() { 94 | CurieIMU.readMotionSensor(ax, ay, az, gx, gy, gz); 95 | 96 | axChar.setValue(ax); 97 | ayChar.setValue(ay); 98 | azChar.setValue(az); 99 | gxChar.setValue(gx); 100 | gyChar.setValue(gy); 101 | gzChar.setValue(gz); 102 | 103 | Serial.print(ax); Serial.print("\t"); 104 | Serial.print(ay); Serial.print("\t"); 105 | Serial.print(az); Serial.print("\t"); 106 | Serial.print(gx); Serial.print("\t"); 107 | Serial.print(gy); Serial.print("\t"); 108 | Serial.print(gz); Serial.print("\t"); 109 | Serial.println(""); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edison-arduino101-iot 2 | 3 | Edison + Arduino 101 IoT demo 4 | 5 | This demo illustrates a simple IoT example by displaying an Arduino 101's IMU (accelerometer / gyroscope) data on a web page. In order to do this, an Intel Edison module is used to receive the data from the Arduino 101 via BLE (Bluetooth Low Energy), then sends it to a web server via WebSockets. 6 | 7 | Here's a picture that describes the overall architecture: 8 | 9 | ![Archiecture](https://raw.githubusercontent.com/drejkim/edison-arduino101-iot/master/images/edison-arduino101-iot.png) 10 | 11 | Arduino 101 uses the [CurieBLE library](https://www.arduino.cc/en/Reference/CurieBLE) to broadcast the IMU data via BLE. Edison is responsible for receiving this data using [noble](https://github.com/sandeepmistry/noble), a Node.js BLE central module, and sending it to a separate web server using [socket.io-client](https://github.com/socketio/socket.io-client). The web server—which also uses Node.js and [socket.io](http://socket.io/)—is responsible for receiving data from Edison and hosting a web page that displays the IMU data. For more details, see this [blog post](http://drejkim.com/blog/2016/04/08/using-edison-and-arduino-101-together). 12 | 13 | ## Setting up the demo 14 | 15 | ### Edison 16 | 17 | #### Installing software using opkg 18 | 19 | Add [AlexT's unofficial opkg repository](http://alextgalileo.altervista.org/edison-package-repo-configuration-instructions.html). It contains many precompiled packages that can be installed by simply typing `opkg install `. 20 | 21 | To configure the repository, add the following lines to `/etc/opkg/base-feeds.conf`: 22 | 23 | ```bash 24 | src/gz all http://repo.opkg.net/edison/repo/all 25 | src/gz edison http://repo.opkg.net/edison/repo/edison 26 | src/gz core2-32 http://repo.opkg.net/edison/repo/core2-32 27 | ``` 28 | 29 | Update the package manager and install the required packages: 30 | 31 | ```bash 32 | opkg update 33 | opkg install git systemd-dev 34 | ``` 35 | 36 | #### Setting up Node.js 37 | 38 | * Install `async` globally: `npm -g install async` 39 | * Navigate to `edison/` and type `npm install` 40 | * Modify the `socket` variable in `edison/sensors.js` with your web server's URL—see note below 41 | 42 | ### Web server 43 | 44 | Note: Your web server should NOT be running on the Edison that's running `sensors.js`. It can run on a personal computer, or even on a different Edison. 45 | 46 | * Navigate to `web/server/` and type `npm install` 47 | * Modify the `socket variable` in `web/client/app.js` with your web server's URL 48 | 49 | ## Running the demo 50 | 51 | ### Arduino 101 52 | 53 | Upload the sketch located in `arduino/imu/imu.ino` to your Arduino 101. After a few seconds, it should be ready to go. 54 | 55 | ### Edison 56 | 57 | Navigate to `edison/` and type `node sensors.js` to start the program. The Edison console should look similar to this: 58 | 59 | ```bash 60 | root@myedison:~# node sensors.js 61 | Start BLE scan... 62 | Connected to peripheral: 984fee0f3980 63 | Discovered IMU service 64 | ax:edison 65 | ay:edison 66 | az:edison 67 | gx:edison 68 | gy:edison 69 | gz:edison 70 | ``` 71 | If the output only goes up to "Connected to peripheral...", try restarting the program after a few seconds. This can help in discovering the BLE service. 72 | 73 | ### Web server & client 74 | 75 | * Start the web server by navigating to `web/server/` and typing `node server.js` 76 | * Open a browser window and navigate to the web server's URL 77 | 78 | You should now see the accelerometer / gyroscope data from your Arduino 101 streaming to the web page! 79 | 80 | ### Troubleshooting 81 | 82 | If you're not seeing the data change on the webpage, first check that the web server console looks something like this: 83 | 84 | ```bash 85 | HTTP server listening on port 8080 86 | Client connected 87 | ``` 88 | 89 | Also, check that the Edison console (where `sensors.js` is running) outputs a new line, **Connected to server**, and looks similar to this: 90 | 91 | ```bash 92 | root@myedison:~# node sensors.js 93 | Start BLE scan... 94 | Connected to peripheral: 984fee0f3980 95 | Discovered IMU service 96 | ax:edison 97 | ay:edison 98 | az:edison 99 | gx:edison 100 | gy:edison 101 | gz:edison 102 | Connected to server 103 | ``` 104 | 105 | On the Arduino 101, go to **Tools -> Serial Monitor**. The data should be outputting to the serial monitor and look something like this: 106 | 107 | ![Arduino 101 Serial Monitor](https://raw.githubusercontent.com/drejkim/edison-arduino101-iot/master/images/arduino101-serial-monitor.png) 108 | --------------------------------------------------------------------------------