[apps/tidep0084.git] / tutorials / generic_sensor_tutorial / tutorial / SensorToCloud / example / commissioner / webserver / webserver.js
1 /******************************************************************************
3 @file webserver.js
5 @brief webserver implementation
7 Group: WCS LPC
8 $Target Devices: Linux: AM335x, Embedded Devices: CC1310, CC1350$
10 ******************************************************************************
11 $License: BSD3 2016 $
13 Copyright (c) 2015, Texas Instruments Incorporated
14 All rights reserved.
16 Redistribution and use in source and binary forms, with or without
17 modification, are permitted provided that the following conditions
18 are met:
20 * Redistributions of source code must retain the above copyright
21 notice, this list of conditions and the following disclaimer.
23 * Redistributions in binary form must reproduce the above copyright
24 notice, this list of conditions and the following disclaimer in the
25 documentation and/or other materials provided with the distribution.
27 * Neither the name of Texas Instruments Incorporated nor the names of
28 its contributors may be used to endorse or promote products derived
29 from this software without specific prior written permission.
31 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
33 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
34 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
37 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
38 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
39 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
40 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
41 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 ******************************************************************************
43 $Release Name: TI-15.4Stack Linux x64 SDK ENG$
44 $Release Date: Mar 08, 2017 (2.01.00.10)$
45 *****************************************************************************/
47 const spawn = require('child_process').spawn;
49 var express = require('express');
50 var events = require("events");
51 var socket = require("socket.io");
52 var http = require("http");
53 var os = require("os");
54 var path = require('path');
55 var formidable = require('formidable');
56 var fs = require('fs');
58 /* Webserver Instance */
59 var webserverInstance;
61 var theSocket;
63 var gatewayPID = 0;
65 var gatewayRunning = "Not Running";
67 var ipAddr;
69 var networkChange = false;
71 var ssid = "";
73 var connected = false;
75 /*!
76 * @brief Constructor for web-server
77 *
78 * @param none
79 *
80 * @retun none
81 */
82 function Webserver() {
84 /* There should be only one app client */
85 if (typeof webserverInstance !== "undefined") {
86 return webserverInstance;
87 }
89 /* Set up to emit events */
90 events.EventEmitter.call(this);
91 webserverInstance = this;
93 /* Set up webserver */
94 var app = express();
95 var server = http.createServer(app);
96 webserverInstance.io = socket.listen(server);
97 // 192.168.43.1 -> only listen to traffic via SitaraAP
98 // 0.0.0.0 listen to all interfaces
99 server.listen( 1310, '0.0.0.0');
100 var path = require('path');
101 app.use(express.static(path.join(__dirname, '..'+'/public')));
102 app.get('/', function(req, res){
103 res.sendFile(__dirname + '/commissioner.html');
104 });
106 app.post('/', function(req, res){
107 // create an incoming form object
108 var form = new formidable.IncomingForm();
109 var files = [];
110 // specify that we want to allow the user to upload multiple files in a single request
111 form.multiples = true;
113 // store all uploads in the /uploads directory
114 form.uploadDir = path.join(__dirname, '/uploads');
115 //console.log("upload dir: "+form.uploadDir);
117 var awsConfig = {
118 "certDir" : form.uploadDir+'/',
119 "clientId" : "BBBTI",
120 "region" : "us-east-1",
121 "port" : 8883,
122 "host" : "a23op339u3ex9t.iot.us-east-1.amazonaws.com",
123 "debug" : true
124 };
126 // every time a file has been uploaded successfully,
127 // rename it to it's orignal name
128 form.on('file', function(field, file) {
129 if (file.size == 0) {
130 fs.unlink(file.path);
131 return;
132 }
133 files.push(file.name);
134 if (field == 'private key') {
135 fs.rename(file.path, path.join(form.uploadDir, file.name));
136 awsConfig['keyPath'] = file.name;
137 }
138 if (field == 'certificate') {
139 fs.rename(file.path, path.join(form.uploadDir, file.name));
140 awsConfig['certPath'] = file.name;
141 }
142 if (field == 'root certificate') {
143 fs.rename(file.path, path.join(form.uploadDir, file.name));
144 awsConfig['caPath'] = file.name;
145 }
146 if (field == 'public key') {
147 fs.rename(file.path, path.join(form.uploadDir, file.name));
148 }
150 });
152 // log any errors that occur
153 form.on('error', function(err) {
154 console.log('An error has occured: \n' + err);
155 });
157 // once all the files have been uploaded, send a response to the client
158 form.on('end', function() {
159 if (awsConfig['keyPath'] && awsConfig['caPath'] && awsConfig['certPath']) {
160 fs.writeFile(path.join(__dirname, '/../../iot-gateway/cloudAdapter/awsConfig.json'),JSON.stringify(awsConfig), err => {
161 if (err)
162 console.log("error "+err+" writing awsConfig to: "+path.join(__dirname, '/../../iot-gateway/cloudAdapter/awsConfig.json'));
163 });
164 res.json({"files": files.toString()});
165 } else {
166 //res.sendFile(__dirname + '/commissioner.html');
167 res.end("Fail!");
168 }
169 });
171 // parse the incoming request containing the form data
172 form.parse(req);
173 });
175 var uploaded_files = [];
177 function syncUploads(socket){
178 uploaded_files = [];
179 fs.readdir(path.join(__dirname, '/uploads'), function(err, items) {
180 //console.log(items);
182 if (typeof(items) != "undefined") {
183 for (var i=0; i<items.length; i++) {
184 console.log(items[i]);
185 }
186 uploaded_files = items;
187 if (socket)
188 socket.emit('uploadedFiles', JSON.stringify(uploaded_files));
189 }
190 });
191 }
193 /* Handle socket events */
194 webserverInstance.io.sockets.on('connection', function (socket) {
196 syncUploads(socket);
198 networkChange = true;
199 checkConnection();
201 var conStat = setInterval(checkConnection,30000);
203 socket.on('connectToThisNetwork', function (data) {
204 var networkDetails = JSON.parse(data);
205 theSocket = this;
206 console.log("request to connect to: " + networkDetails.ssid + ", key: " + networkDetails.key);
207 console.log("remember: " + networkDetails.remember);
209 console.log("here2: " + JSON.stringify(os.networkInterfaces()));
211 //const connectToNetwork = spawn('sh', [ '/usr/share/wl18xx/sta_connect-ex.sh', networkDetails.ssid, 'WPA-PSK', networkDetails.key ], {
212 const connectToNetwork = spawn('sh', [ 'scripts/connect.sh', networkDetails.ssid, networkDetails.key, networkDetails.remember ], {
213 });
215 connectToNetwork.stdout.on('data', (data) => {
216 console.log(`connect stdout: ${data}`);
217 var StationIpAddr = data.toString();
218 console.log("StationIpAddr = " + StationIpAddr);
219 console.log("sending StationIpAddr = " + StationIpAddr);
220 console.log('{ipAddr:' + StationIpAddr.trim() + '}');
221 ipAddr = StationIpAddr.trim();
222 theSocket.emit('StationIpAddr2', {ipAddr:StationIpAddr.trim()});
223 networkChange = true;
224 });
226 connectToNetwork.stderr.on('data', (data) => {
227 console.log(`connect stderr: ${data}`);
228 });
230 connectToNetwork.on('close', (code) => {
231 console.log(`child process exited with code ${code}`);
232 checkConnection();
233 });
234 });
236 socket.on('wifiConfig', function (data) {
237 console.log("Lets start wifi config");
238 //start wifi access point, and collect list of available networks.
241 //const getAvailbleSsids = spawn('wpa_cli', [ '-iwlan0', 'scan' ], {
242 const getAvailbleSsids = spawn('sh',[ 'scripts/my_wpa_cli' ], {
243 })
245 getAvailbleSsids.stdout.on('data', (data) => {
246 console.log(`stdout: ${data}`);
247 if (data.toString().trim() == "OK") {
248 var count = 0;
249 var intervalObject = setTimeout(function(){
250 const getAvailbleSsidsList = spawn('wpa_cli', [ '-iwlan0', 'scan_results' ], {
251 });
253 function compareSignalLevels(a,b) {
254 if (Number(a.signalLevel) < Number(b.signalLevel))
255 return 1;
256 if (Number(a.signalLevel) > Number(b.signalLevel))
257 return -1;
258 return 0;
259 }
261 var lastReportedAvailableNetworks = [];
263 function checkSsidExist(networkInfo) {
264 return networkInfo.ssid == this;
265 }
267 getAvailbleSsidsList.stdout.on('data', (data) => {
268 console.log(`stdout: ${data}`);
269 var resultLines = data.toString().split("\n");
270 resultLines.shift(); //remove the title line
271 resultLines.pop(); //remove the ending empty line
272 var i;
273 var availableNetworks = [];
274 for (i = 0; i < resultLines.length; i++)
275 {
276 networkDetails = resultLines[i].split("\t");
277 console.log(networkDetails[2] + "<>" + networkDetails[4]);
278 availableNetworks.push({ssid: networkDetails[4], signalLevel: networkDetails[2]});
279 }
280 availableNetworks.sort(compareSignalLevels);
282 var changeDetected = false;
284 if (availableNetworks.length != lastReportedAvailableNetworks.length) {
285 changeDetected = true;
286 } else {
287 for (i = 0; i < availableNetworks.length; i++) {
288 if ((availableNetworks.ssid !== lastReportedAvailableNetworks.ssid) || (availableNetworks.signalLevel !== lastReportedAvailableNetworks.signalLevel)) {
289 changeDetected = true;
290 break;
291 }
292 }
293 }
295 if (changeDetected) {
296 lastReportedAvailableNetworks = availableNetworks.slice();
298 for (i = 0; i < availableNetworks.length; i++) {
299 console.log(availableNetworks[i].ssid + ":" + availableNetworks[i].signalLevel);
300 }
301 console.log(JSON.stringify(availableNetworks));
302 webserverInstance.webserverSendToClient("availableNetworks",JSON.stringify(availableNetworks)); //note that it will be sent to all clients - need to either send to the qlient requested, or to block on client if not requested.
303 }
305 });
307 getAvailbleSsidsList.stderr.on('data', (data) => {
308 console.log(`stderr: ${data}`);
309 });
311 getAvailbleSsidsList.on('close', (code) => {
312 console.log(`child process exited with code ${code}`);
313 });
314 count++;
316 if ( count == 15 ) {
317 clearInterval(intervalObject);
318 }
319 }, 5000);
320 }
321 else {
322 console.log(`ERROR`);
323 }
324 });
326 getAvailbleSsids.stderr.on('data', (data) => {
327 console.log(`stderr: ${data}`);
328 });
330 getAvailbleSsids.on('close', (code) => {
331 console.log(`child process exited with code ${code}`);
332 });
334 getAvailbleSsids.on('error', function (err) {
335 console.log('getAvailbleSsids error', err);
336 });
337 });
339 socket.on('configCollector', function(form) {
340 var configData = {};
341 for (var i in form){
342 switch(form[i]['name']){
343 case 'channel':
344 if (form[i]['value'])
345 configData['channel'] = form[i]['value'];
346 else
347 configData['channel'] = '0';
348 break;
349 case 'panID':
350 if (form[i]['value'])
351 configData['panID'] = "0x"+form[i]['value'];
352 else
353 configData['panID'] = '0xACDC';
354 break;
355 case 'reportInterval':
356 if (form[i]['value'])
357 configData['reportInterval'] = form[i]['value'];
358 else
359 configData['reportInterval'] = '10000';
360 break;
361 case 'pollInterval':
362 if (form[i]['value'])
363 configData['pollInterval'] = form[i]['value'];
364 else
365 configData['pollInterval'] = '6000';
366 break;
367 case 'frequencyRadios':
368 configData['phyID'] = form[i]['value'];
369 break;
370 case 'dataRateRadios':
371 configData['dataRate'] = form[i]['value'];
372 break;
373 case 'nvRestore':
374 configData['nvRestore'] = form[i]['value'];
375 break;
376 default:
377 console.log("unknown form item "+form[i]['name']);
378 }
379 }
380 if (configData['dataRate'] === '5'){
381 switch(configData['phyID']){
382 case '1':
383 configData['phyID'] = '129';
384 break;
385 case '3':
386 configData['phyID'] = '131';
387 break;
388 case '128':
389 configData['phyID'] = '130';
390 break;
391 default:
392 console.log("unknown phy "+configData['phyID']);
393 }
394 }
395 if (!configData['nvRestore']) {
396 configData['nvRestore'] = 'false';
397 }
398 console.log(configData);
399 const launcher = spawn('bash', ['scripts/configureCollector.sh',configData['channel'],configData['panID'],configData['reportInterval'],configData['pollInterval'],configData['phyID'],configData['nvRestore']], {//[ 'run_ibm.sh'], {
400 });
402 launcher.stdout.on('data', (data) => {
403 console.log(`connect stdout: ${data}`);
404 });
406 launcher.stderr.on('data', (data) => {
407 console.log(`connect stderr: ${data}`);
408 });
410 launcher.on('close', (code) => {
411 console.log(`child process exited with code ${code}`);
412 });
413 });
415 socket.on('launchIBMGateway', function(data) {
416 var ibmConfig = {
417 "domain": "internetofthings.ibmcloud.com",
418 "auth-method": "token",
419 "use-client-certs": [false]
420 };
422 for (var i in data) {
423 //console.log(data[i]);
424 if (data[i]['name'] == 'org'){
425 ibmConfig['org'] = data[i]['value'];
426 }
427 if (data[i]['name'] == 'type'){
428 ibmConfig['type'] = data[i]['value'];
429 }
430 if (data[i]['name'] == 'id'){
431 ibmConfig['id'] = data[i]['value'];
432 }
433 if (data[i]['name'] == 'auth-token'){
434 ibmConfig['auth-token'] = data[i]['value'];
435 }
436 }
437 console.log(ibmConfig);
439 fs.writeFile(path.join(__dirname, '/../../iot-gateway/cloudAdapter/ibmConfig.json'),JSON.stringify(ibmConfig), err => {
440 if (err)
441 console.log("error "+err+" writing ibmConfig to: "+path.join(__dirname, '/../../iot-gateway/cloudAdapter/ibmConfig.json'));
442 });
444 launchGateway('ibm');
445 });
447 socket.on('launchGateway',function(data){
448 launchGateway(data);
449 });
451 function launchGateway(cloudAdapter){
452 networkChange = true;
453 getGatewayStatus();
454 if (gatewayRunning != "Not Running") {
455 webserverInstance.webserverSendToClient("alert","Closing "+gatewayRunning.toLowerCase()+" gateway and starting "+cloudAdapter+" gateway.");
456 }
457 const launcher = spawn('bash', ['scripts/launch_gateway.sh',cloudAdapter], {
458 });
460 launcher.stdout.on('data', (data) => {
461 var str = data.toString().trim();
462 console.log(`connect stdout: ${data}`);
463 if (str.startsWith('Gateway is running as Process id:')){
464 gatewayPID = str.slice(34);
465 console.log("Gateway PID: " + gatewayPID);
466 gatewayRunning = cloudAdapter.toUpperCase();
467 }
468 if (str.startsWith('iotdash')){
469 console.log("AWS URL: "+str);
470 webserverInstance.webserverSendToClient('awsUrl',str);
471 }
472 if (str.startsWith('Quickstart DevID=')){
473 var quickstartDevId = str.slice(17);
474 console.log(quickstartDevId);
475 var quickstartUrl = "https://quickstart.internetofthings.ibmcloud.com/#/device/sensor-to-cloud"+quickstartDevId+"/sensor/";
476 webserverInstance.webserverSendToClient('quickstartUrl',quickstartUrl);
477 }
478 if (str.startsWith('AWS Cloud Adapter error')){
479 // AWS error
480 //alert(str);
481 console.log("awsErr: "+str);
482 webserverInstance.webserverSendToClient("alert",str);
483 }
484 if (str.startsWith('AWS Cloud Adapter offline')){
485 // AWS error
486 //alert(str);
487 console.log("aws offline: "+str);
488 webserverInstance.webserverSendToClient("alert",str);
489 }
490 if (str.startsWith('IBM Cloud Adapter error:')){
491 console.log("ibmErr: "+str);
492 webserverInstance.webserverSendToClient("alert",str);
493 }
494 if (str.startsWith("localhost port=")){
495 console.log("Local Gateway Started");
496 var port = parseInt(str.slice(15));
497 webserverInstance.webserverSendToClient("localRedirect",port);
498 }
499 });
501 launcher.stderr.on('data', (data) => {
502 console.log(`connect stderr: ${data}`);
503 });
505 launcher.on('close', (code) => {
506 var err = parseInt(code);
507 if (err){
508 console.log("Error launching gateway! ERR: "+err);
509 var error = "Error launching gateway. Error="+err+"\n";
510 switch(err){
511 case 1:
512 error += "Unknown Architecture...";
513 socket.emit("alert",error);
514 break;
515 case 2:
516 error += "Launchpad is not connected. Please Reconnect.";
517 socket.emit("alert",error);
518 break;
519 case 3:
520 error += "Cannont find collector binary.";
521 socket.emit("alert",error);
522 break;
523 case 4:
524 error += "Collector binary is not executable."
525 socket.emit("alert",error);
526 break;
527 case 5:
528 error += "Error starting collector. Please reset Co-Processor and try again.";
529 socket.emit("alert",error);
530 break;
531 case 6:
532 error += "Node is not installed.";
533 socket.emit("alert",error);
534 break;
535 case 7:
536 error += "Cannot start IOT gateway."
537 socket.emit("alert",error);
538 break;
539 case 8:
540 error += "Cannot start localhost gateway."
541 socket.emit("alert",error);
542 break;
543 }
544 }
545 console.log(`child process exited with code ${code}`);
546 });
547 }
550 socket.on('runScript',function(script){
551 var runScript = 'scripts/'+script;
552 const launcher = spawn('bash',[runScript], {
554 });
556 launcher.stdout.on('data', (data) => {
557 console.log(`connect stdout: ${data}`);
558 });
560 launcher.stderr.on('data', (data) => {
561 console.log(`connect stderr: ${data}`);
562 });
564 launcher.on('close', (code) => {
565 console.log(`child process exited with code ${code}`);
566 if (script == 'removeUploads.sh')
567 syncUploads(this);
568 });
569 });
571 function getSSID() {
572 const launcher = spawn('bash',['scripts/checkConnection.sh'], {
573 });
575 launcher.stdout.on('data', (data) => {
576 //console.log(`connect stdout: ${data}`);
577 if (data.toString().trim().startsWith('SSID')) {
578 if (data.toString().trim().slice(6) != ssid){
579 ssid = data.toString().trim().slice(6);
580 networkChange = true;
581 }
582 //console.log("ssid being set to "+ssid);
583 }
584 });
586 launcher.stderr.on('data', (data) => {
587 console.log(`connect stderr: ${data}`);
588 });
590 launcher.on('close', (code) => {
591 //console.log(`child process exited with code ${code}`);
592 if (code == 1)
593 connected = false;
594 else
595 connected = true;
596 });
597 }
599 function getIpAddr() {
600 console.log(ssid);
601 const launcher = spawn('bash',['scripts/getIpAddr.sh',ssid], {
602 });
604 launcher.stdout.on('data', (data) => {
605 //console.log(`connect stdout: ${data}`);
606 ipAddr = data.toString().trim();
607 });
609 launcher.stderr.on('data', (data) => {
610 console.log(`connect stderr: ${data}`);
611 });
613 launcher.on('close', (code) => {
614 //console.log(`child process exited with code ${code}`);
615 });
616 }
618 function getGatewayStatus() {
619 //console.log("gPID: "+gatewayPID);
620 const launcher = spawn('bash',['scripts/isGatewayRunning.sh',gatewayPID], {
621 });
623 launcher.stderr.on('data', (data) => {
624 console.log(`connect stderr: ${data}`);
625 });
627 launcher.on('close', (code) => {
628 if (code == 1){
629 gatewayRunning = "Not Running";
630 networkChange = true;
631 console.log("gateway "+gatewayPID+" not found")
632 }
633 //socket.emit('connectionStatus',connected);
634 });
635 }
637 function checkConnection(){
638 getSSID();
639 if (gatewayPID)
640 getGatewayStatus();
641 if (networkChange) {
642 getIpAddr();
643 setTimeout(function(){socket.emit('connectionStatus',{connected: connected,
644 ssid: ssid,
645 ipAddr: ipAddr,
646 gateway: gatewayRunning});},500);
647 networkChange = false;
648 }
649 }
653 });
655 /**********************************************************************
656 Public method to send Update Messages to the client
657 ***********************************************************************/
658 webserverInstance.webserverSendToClient = function(msgType, data){
659 webserverInstance.io.sockets.emit(msgType, data);
660 };
661 }
663 Webserver.prototype.__proto__ = events.EventEmitter.prototype;
665 module.exports = Webserver;