├── .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 | 
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 | 
108 |
--------------------------------------------------------------------------------