├── chapter2-hello-wot ├── .gitignore ├── client │ ├── README.md │ ├── pir-websockets.html │ ├── ex-3.1-actuator-form.html │ ├── ex-2.1-polling-temp.html │ ├── ex-3.2-actuator-ajax-json.html │ ├── ex-2.2-polling-temp-chart.html │ ├── ex-3.2b-actuator-ajax-form.html │ ├── ex-2.3-websockets-temp-graph.html │ ├── parse-device-full.html │ ├── ex-4-parse-device.html │ └── ex-5-mashup.html ├── README.md └── requests │ └── responses.txt ├── chapter3-node-js ├── hello-modules │ ├── .idea │ │ ├── .name │ │ ├── misc.xml │ │ ├── scopes │ │ │ └── scope_settings.xml │ │ ├── encodings.xml │ │ ├── vcs.xml │ │ ├── modules.xml │ │ ├── hello-modules.iml │ │ └── workspace.xml │ ├── module-client.js │ ├── package.json │ └── lib │ │ └── operations.js ├── README.md ├── package.json ├── listing-3.7-request-lib.js ├── listing-3.1-hello-world-http-server.js ├── listing-3.2-webserver.js ├── mashupserver-local.js ├── listing-3.8-callbacks.js ├── listing-3.9-callbacks-named-functions.js ├── listing-3.11-callbacks-control-flow.js ├── listing-3.11-callbacks-control-flow-no-named.js └── mashupserver.js ├── chapter4-gpios ├── drivers │ ├── bcm2835-1.50.tar.gz │ └── README.md ├── README.md ├── package.json ├── pir.js ├── dht.js ├── more-examples │ ├── proximity-pi-gpio.js │ ├── proximity.py │ └── blink-pi-gpio.js └── blink.js ├── chapter7-implementation ├── part1-2-direct-gateway │ ├── resources │ │ ├── model.js │ │ └── resources.json │ ├── routes │ │ ├── things.js │ │ ├── actuators.js │ │ └── sensors.js │ ├── utils │ │ └── utils.js │ ├── structure.txt │ ├── package.json │ ├── public │ │ └── websocketsClient.html │ ├── servers │ │ ├── websockets.js │ │ ├── coap.js │ │ └── http.js │ ├── middleware │ │ └── converter.js │ ├── plugins │ │ ├── internal │ │ │ ├── pirPlugin.js │ │ │ ├── ledsPlugin.js │ │ │ └── DHT22SensorPlugin.js │ │ └── external │ │ │ └── coapPlugin.js │ └── wot-server.js ├── README.md └── part3-cloud │ ├── config-sample.json │ ├── package.json │ ├── simple-plug.js │ ├── plug-with-control.js │ ├── setup.sh │ └── client │ └── plug.html ├── chapter10-mashups ├── README.md ├── ui │ └── code-bits │ │ ├── actions.txt │ │ ├── pir-leds.txt │ │ ├── form-only.html │ │ └── properties.txt ├── mashup.css ├── mashup.html ├── node-red │ ├── pir-websockets-twitter.json │ └── pir-http-twitter.json └── UI │ ├── UI.html │ └── files │ └── docs.min.css ├── chapter5-networking └── README.md ├── chapter6-access └── README.md ├── chapter8-semantics ├── README.md └── mdns │ ├── README.md │ ├── mdns-pi.js │ └── package.json ├── chapter9-sharing ├── README.md └── social-auth │ ├── package.json │ ├── middleware │ ├── proxy.js │ └── auth.js │ ├── config │ ├── acl.json │ ├── change_me_caCert.pem │ └── change_me_privateKey.pem │ ├── authProxy.js │ ├── views │ ├── login.html │ └── account.html │ └── providers │ └── facebook.js ├── .gitmodules ├── .gitignore ├── README.md └── LICENSE /chapter2-hello-wot/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/.name: -------------------------------------------------------------------------------- 1 | hello-modules -------------------------------------------------------------------------------- /chapter4-gpios/drivers/bcm2835-1.50.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webofthings/wot-book/HEAD/chapter4-gpios/drivers/bcm2835-1.50.tar.gz -------------------------------------------------------------------------------- /chapter2-hello-wot/client/README.md: -------------------------------------------------------------------------------- 1 | wot-book: chapter 2: client examples 2 | ==================================== 3 | 4 | http-server --cors -c-1 -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/resources/model.js: -------------------------------------------------------------------------------- 1 | var resources = require('./resources.json'); 2 | module.exports = resources; 3 | 4 | -------------------------------------------------------------------------------- /chapter3-node-js/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter4-gpios/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter10-mashups/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter2-hello-wot/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter5-networking/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter6-access/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | 4 | -------------------------------------------------------------------------------- /chapter7-implementation/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/module-client.js: -------------------------------------------------------------------------------- 1 | var ops = require('./lib/operations'); //#A 2 | 3 | console.log(ops.add(42, 42)); 4 | console.log(ops.mul(42, 42)); 5 | console.log(ops.sub(42, 42)); 6 | 7 | //#A Imports the operations.js module -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chapter4-gpios/drivers/README.md: -------------------------------------------------------------------------------- 1 | # BCM2835 Library for DHT22 2 | This library is provided here to help the reader but you can find the orginal sources and the latest version on the [BCM2835 C Library Website](http://www.airspayce.com/mikem/bcm2835/index.html) 3 | -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "operatorApiKey":"EDIT-ME", 3 | "projectId":"EDIT-ME", 4 | "productId":"EDIT-ME", 5 | "thngId":"EDIT-ME", 6 | "appId":"EDIT-ME", 7 | "appApiKey":"EDIT-ME", 8 | "thngApiKey":"EDIT-ME" 9 | } 10 | -------------------------------------------------------------------------------- /chapter8-semantics/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | 4 | Note that this chapter builds on the [webofthings.js](https://github.com/webofthings/webofthings.js) Web Thing Gateway project. 5 | 6 | -------------------------------------------------------------------------------- /chapter9-sharing/README.md: -------------------------------------------------------------------------------- 1 | # Code Examples 2 | Code examples from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book. 3 | 4 | Note that this chapter builds on the [webofthings.js](https://github.com/webofthings/webofthings.js) Web Thing Gateway project. 5 | 6 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/hello-modules.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-npm", 3 | "version": "0.0.1", 4 | "description": "Experimenting with npm modules", 5 | "author": "Me self ", 6 | "repository": "https://github.com/webofthings/wot-book", 7 | "dependencies": { 8 | }, 9 | "devDependencies": {}, 10 | "engine": "node >= 0.10.0" 11 | } 12 | -------------------------------------------------------------------------------- /chapter8-semantics/mdns/README.md: -------------------------------------------------------------------------------- 1 | # mDNS Example for your Pi 2 | Code sample from the [Building the Web of Things](http://manning.com/guinard/?a_aid=wot&a_bid=16f48f14) book built using the [node_mdns](https://github.com/agnat/node_mdns) module. 3 | 4 | ## Prerequisite 5 | 6 | To run this sample on your Raspberry Pi or embedded Linux device you first need to install the 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "chapter8-semantics/webofthingsjs-unsecure"] 2 | path = chapter8-semantics/webofthingsjs-unsecure 3 | url = https://github.com/webofthings/webofthings.js.git 4 | branch = unsecure-chapter8 5 | [submodule "chapter9-sharing/webofthingsjs-secure"] 6 | path = chapter9-sharing/webofthingsjs-secure 7 | url = https://github.com/webofthings/webofthings.js.git 8 | branch = secure-chapter9 9 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/routes/things.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | router = express.Router(), 3 | resources = require('./../resources/model'); 4 | 5 | router.route('/coapDevice/sensors/:id').get(function (req, res, next) { 6 | var id = req.params.id; 7 | req.result = resources.things.coapDevice.sensors[id]; 8 | next(); 9 | }); 10 | 11 | 12 | module.exports = router; -------------------------------------------------------------------------------- /chapter3-node-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wot-book-node", 3 | "description": "Examples for the Web of Things Book", 4 | "author": "Vlad Trifa", 5 | "author": "Dominique Guinard", 6 | "repository": "https://github.com/webofthings/wot-book.git", 7 | "dependencies": { 8 | "async": "^0.9.0", 9 | "request": "^2.53.0" 10 | }, 11 | "devDependencies": {}, 12 | "engine": "node >= 4.2.2" 13 | } 14 | -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.7-request-lib.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | request('http://webofthings.org', function (error, response, body) { //#A 3 | if (!error && response.statusCode === 200) { 4 | console.log(body); //#B 5 | } 6 | }); 7 | 8 | //#A This is an anonymous callback that will be invoked when the request library did fetch the webofthings homepage from the Web 9 | //#B This will display the HTML code of the page 10 | -------------------------------------------------------------------------------- /chapter4-gpios/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wot-book-gpio", 3 | "description": "Examples for the Web of Things Book", 4 | "author": "Dominique Guinard ", 5 | "author": "Vlad Trifa ", 6 | "repository" : "https://github.com/webofthings/wot-book.git", 7 | "dependencies": { 8 | "onoff": "^1.0.3", 9 | "node-dht-sensor": "^0.0.8" 10 | }, 11 | "engine": "node >= 0.12.0" 12 | } 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wot-plug", 3 | "description": "Examples for the Web of Things Book", 4 | "author": "Dom Guinard ", 5 | "author": "Vlad Trifa ", 6 | "repository" : "https://github.com/webofthings/wot-book.git", 7 | "main": "plug-with-control.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "license": "MIT", 12 | "dependencies": { 13 | "mqtt": "^1.5.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/utils/utils.js: -------------------------------------------------------------------------------- 1 | var resources = require('./../resources/model'); 2 | 3 | exports.addDevice = function(id, name, description, sensors, actuators) { 4 | if(!resources.things) { 5 | resources.things = {}; 6 | } 7 | resources.things[id] = {'name' : name, 8 | 'description' : description, 9 | 'sensors' : sensors, 10 | 'actuators' : actuators 11 | } 12 | }; 13 | 14 | exports.randomInt = function(low, high) { 15 | return Math.floor(Math.random() * (high - low + 1) + low); 16 | }; -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.1-hello-world-http-server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); //#A 2 | http.createServer(function(req,res){ //#B 3 | res.writeHeader(200, {'Content-Type': 'text/plain'}); //#C 4 | res.end('Hello World'); 5 | }).listen(8585); //#D 6 | console.log('Server started!'); 7 | 8 | //#A “require” is used to “import” libraries 9 | //#B creates a new HTTP server and pass it a function to be called whenever a client sends a request 10 | //#C we start writing a response, beginning with the HTTP headers 11 | //#D starts the HTTP server on port 8585 12 | -------------------------------------------------------------------------------- /chapter8-semantics/mdns/mdns-pi.js: -------------------------------------------------------------------------------- 1 | var mdns = require('mdns'); 2 | 3 | // advertise a http server on port 4321 4 | var ad = mdns.createAdvertisement(mdns.tcp('http'), 4321); 5 | ad.start(); 6 | 7 | // watch all http servers 8 | var browser = mdns.createBrowser(mdns.tcp('http')); 9 | browser.on('serviceUp', function(service) { 10 | console.log("service up: ", service); 11 | }); 12 | browser.on('serviceDown', function(service) { 13 | console.log("service down: ", service); 14 | }); 15 | browser.start(); 16 | 17 | // discover all available service types 18 | var all_the_types = mdns.browseThemAll(); -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/structure.txt: -------------------------------------------------------------------------------- 1 | wot-pi 2 | ├── middleware 3 | │   └── converter.js 4 | ├── package.json 5 | ├── plugins 6 | │   ├── external 7 | │   │   └── coapPlugin.js 8 | │   └── internal 9 | │   ├── DHT22SensorPlugin.js 10 | │   ├── ledsPlugin.js 11 | │   └── pirPlugin.js 12 | ├── resources 13 | │   ├── model.js 14 | │   └── resources.json 15 | ├── routes 16 | │   ├── actuators.js 17 | │   ├── sensors.js 18 | │   └── things.js 19 | ├── servers 20 | │   ├── coap.js 21 | │   ├── http.js 22 | │   └── websockets.js 23 | ├── utils 24 | │   └── utils.js 25 | └── wot-server.js -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/lib/operations.js: -------------------------------------------------------------------------------- 1 | exports.add = function(a, b) { //#A 2 | logOp(a, b, '+'); 3 | return a + b; 4 | } 5 | 6 | exports.sub = function(a, b) { 7 | logOp(a, b, '-'); 8 | return a - b; 9 | } 10 | 11 | exports.mul = function(a, b) { 12 | logOp(a, b, '*'); 13 | return a * b; 14 | } 15 | 16 | function logOp(a, b, op) { //#B 17 | console.log('Computing ' + a + op + b); 18 | } 19 | 20 | //#A The exports object is used to make a function of our module available to the module users 21 | //#B The logOp function is internal to this module and will not be available from outside this file 22 | 23 | -------------------------------------------------------------------------------- /chapter10-mashups/ui/code-bits/actions.txt: -------------------------------------------------------------------------------- 1 | "actions": { 2 | "link": "/actions", 3 | "title": "Actions of this Web Thing", 4 | "resources": { 5 | "ledState": { 6 | "name": "Change LED state", 7 | "description": "Change the state of an LED", 8 | "values": { 9 | "ledId": { 10 | "type": "enum", 11 | "enum": { 12 | "1": "LED 1", 13 | "2": "LED 2", 14 | "ALL": "All LEDs" 15 | }, 16 | "required": true 17 | }, 18 | "state": { 19 | "type": "boolean", 20 | "required": true 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter4-gpios/pir.js: -------------------------------------------------------------------------------- 1 | var Gpio = require('onoff').Gpio, 2 | sensor = new Gpio(17, 'in', 'both'); //#A 3 | 4 | sensor.watch(function (err, value) { //#B 5 | if (err) exit(err); 6 | console.log(value ? 'there is someone!' : 'not anymore!'); 7 | }); 8 | 9 | function exit(err) { 10 | if (err) console.log('An error occurred: ' + err); 11 | sensor.unexport(); 12 | console.log('Bye, bye!') 13 | process.exit(); 14 | } 15 | process.on('SIGINT', exit); 16 | 17 | // #A Initialize pin 17 in input mode, 'both' means we want to handle both rising and falling interrupt edges 18 | // #B Listen for state changes on pin 17, if a change is detected the anonymous callback function will be called with the new value 19 | -------------------------------------------------------------------------------- /chapter8-semantics/mdns/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdns-pi", 3 | "version": "0.0.1", 4 | "description": "MDNS example of the Building the Web of Things Book", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/webofthings/wot-book/tree/master/chapter8-semantics" 12 | }, 13 | "keywords": [ 14 | "mdns", 15 | "wot", 16 | "book", 17 | "webofthings", 18 | "internetofthings", 19 | "discovery", 20 | "bonjour" 21 | ], 22 | "author": "Dom Guinard", 23 | "license": "Apache-2.0", 24 | "dependencies": { 25 | "mdns": "^2.2.10" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter9-sharing", 3 | "version": "1.0.0", 4 | "description": "TODO", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Dom Guinard", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": ">= 1.4.3", 13 | "consolidate": "^0.13.1", 14 | "cookie-parser": ">= 1.3.2", 15 | "ejs": ">= 0.0.0", 16 | "express": ">= 4.4.5", 17 | "express-session": ">= 1.6.1", 18 | "handlebars": "^4.0.0", 19 | "http-proxy": "^1.11.1", 20 | "method-override": ">= 2.0.2", 21 | "morgan": ">= 1.1.1", 22 | "passport": ">= 0.0.0", 23 | "passport-facebook": ">= 0.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter10-mashups/ui/code-bits/pir-leds.txt: -------------------------------------------------------------------------------- 1 | ... 2 | "pir": { 3 | "name": "Passive Infrared", 4 | "description": "A passive infrared sensor.", 5 | "values": { 6 | "presence": { 7 | "name": "Presence", 8 | "description": "Current sensor value (true=motion detected)", 9 | "type": "boolean", 10 | "customFields": {"gpio": 20} 11 | } 12 | }, 13 | "tags": ["sensor","public"] 14 | }, 15 | "leds": { 16 | "name": "LEDs", 17 | "description": "The LEDs of this device.", 18 | "values": { 19 | "1": { 20 | "name": "LED 1", 21 | "customFields": {"gpio": 17} 22 | }, 23 | "2": { 24 | "name": "LED 2", 25 | "customFields": {"gpio": 19} 26 | } 27 | }, 28 | "tags": ["sensor","public"] 29 | } 30 | ... -------------------------------------------------------------------------------- /chapter4-gpios/dht.js: -------------------------------------------------------------------------------- 1 | var sensorLib = require('node-dht-sensor'); 2 | 3 | sensorLib.initialize(22, 12); //#A 4 | var interval = setInterval(function () { //#B 5 | read(); 6 | }, 2000); 7 | 8 | function read() { 9 | var readout = sensorLib.read(); //#C 10 | console.log('Temperature: ' + readout.temperature.toFixed(2) + 'C, ' + //#D 11 | 'humidity: ' + readout.humidity.toFixed(2) + '%'); 12 | }; 13 | 14 | process.on('SIGINT', function () { 15 | clearInterval(interval); 16 | console.log('Bye, bye!'); 17 | process.exit(); 18 | }); 19 | 20 | //#A 22 is for DHT22/AM2302, 12 is the GPIO we connect to on the Pi 21 | //#B create an interval to read the values every 2 seconds 22 | //#C read the sensor values 23 | //#D readout contains two values: temperature and humidity 24 | -------------------------------------------------------------------------------- /chapter4-gpios/more-examples/proximity-pi-gpio.js: -------------------------------------------------------------------------------- 1 | var gpio = require("pi-gpio"); 2 | 3 | pin = 11; 4 | 5 | function readProximity() { 6 | gpio.open(pin, "input", function (err) { //#A 7 | gpio.read(pin, function (err, value) { //#B 8 | if (err) exit(err); 9 | console.log(value ? 'there is some one!' : 'not anymore!'); //#C 10 | readProximity(); 11 | }); 12 | }); 13 | } 14 | 15 | function exit(err) { 16 | gpio.close(pin); 17 | if (err) console.log('An error occurred: ' + err); 18 | console.log('Bye, bye!') 19 | process.exit(); 20 | } 21 | process.on('SIGINT', exit); 22 | 23 | readProximity(); 24 | 25 | // #A Open the GPIO pin in input mode 26 | // #B Read the digital value (1 or 0) on the pin 27 | // #C If the PIR sensor sees a warm body the value will be 1 otherwise it will be 0 -------------------------------------------------------------------------------- /chapter2-hello-wot/client/pir-websockets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PIR Sensor Over Websockets 6 | 7 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /chapter4-gpios/blink.js: -------------------------------------------------------------------------------- 1 | var onoff = require('onoff'); //#A 2 | 3 | var Gpio = onoff.Gpio, 4 | led = new Gpio(4, 'out'), //#B 5 | interval; 6 | 7 | interval = setInterval(function () { //#C 8 | var value = (led.readSync() + 1) % 2; //#D 9 | led.write(value, function() { //#E 10 | console.log("Changed LED state to: " + value); 11 | }); 12 | }, 2000); 13 | 14 | process.on('SIGINT', function () { //#F 15 | clearInterval(interval); 16 | led.writeSync(0); //#G 17 | led.unexport(); 18 | console.log('Bye, bye!'); 19 | process.exit(); 20 | }); 21 | 22 | // #A Import the onoff library 23 | // #B Initialize pin 4 to be an output pin 24 | // #C This interval will be called every 2 seconds 25 | // #D Synchronously read the value of pin 4 and transform 1 to 0 or 0 to 1 26 | // #E Asynchronously write the new value to pin 4 27 | // #F Listen to the event triggered on CTRL+C 28 | // #G Cleanly close the GPIO pin before exiting -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | *.DS_Store 5 | *.idea 6 | *.idea/ 7 | log.txt 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # Commenting this out is preferred by some people, see 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Users Environment Variables 32 | .lock-wscript 33 | 34 | .idea/workspace.xml 35 | 36 | chapter7-implementation/part3-cloud/private/ 37 | 38 | chapter7-implementation/part3-cloud/payloads/ 39 | 40 | 41 | 42 | 43 | chapter7-implementation/part3-cloud/config.json 44 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wot-pi", 3 | "version": "1.0.0", 4 | "description": "A Web of Things server for the Pi", 5 | "main": "wot-pi.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Vlad Trifa", 10 | "license": "Apache-2.0", 11 | "keywords": [ 12 | "web of things", 13 | "building the web of things", 14 | "wot", 15 | "REST", 16 | "HTTP", 17 | "mqtt", 18 | "pi", 19 | "coap", 20 | "m2m", 21 | "iot", 22 | "tcp", 23 | "internet of things", 24 | "messaging" 25 | ], 26 | "dependencies": { 27 | "bl": "^1.0.0", 28 | "body-parser": "^1.13.1", 29 | "coap": "^0.20.0", 30 | "cors": "^2.7.1", 31 | "express": "^4.12.4", 32 | "msgpack5": "^3.3.0", 33 | "node-json2html": "^1.0.0", 34 | "ws": "^7.3.1" 35 | }, 36 | "optionalDependencies": { 37 | "onoff": "^1.0.4", 38 | "node-dht-sensor": "^0.0.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-3.1-actuator-form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Exercise 3.1 - POST Command to actuator 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Display Message on WoT Pi

16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.2-webserver.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | var port = 8686; 3 | 4 | function randomInt (low, high) { 5 | return Math.floor(Math.random() * (high - low) + low); 6 | } 7 | 8 | http.createServer(function(req,res){ 9 | console.log('New incoming client request for ' + req.url); 10 | res.writeHeader(200, {'Content-Type': 'application/json'}); //#A 11 | switch(req.url) { //#B 12 | case '/temperature': 13 | // And return the corresponding JSON 14 | res.write('{"temperature" :' + randomInt(1, 40) + '}'); //#C 15 | break; 16 | case '/light': 17 | res.write('{"light" :' + randomInt(1, 100) + '}'); 18 | break; 19 | default: 20 | res.write('{"hello" : "world"}'); 21 | } 22 | res.end(); //#D 23 | }).listen(port); 24 | console.log('Server listening on http://localhost:' + port); 25 | 26 | //#A Setting the header to announce we return JSON representations 27 | //#B Read the request URL and provide responses accordingly 28 | //#C Write the temperature result as JSON 29 | //#D Causes to return the results to the client 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building the Web of Things - Code 2 | This repository contains all the Node.js and JavaScript code examples from the [Building the Web of Things](http://book.webofthings.io) book. 3 | 4 | # Node version compatibility 5 | This code is meant to work with **Node 4.x** and will not work on newer Node version. You can find the 4.x releases on https://nodejs.org/en/download/releases/. We are working on a version that will support newer Node but it isn't quite ready yet. 6 | 7 | # Downloading the code examples 8 | 9 | Clone the repository recursively to ensure all the sub projects are downloaded: 10 | 11 | `git clone --recursive https://github.com/webofthings/wot-book.git` 12 | 13 | # What is the book about? 14 | The book is all about building apps for the Internet of Things using Web technologies. 15 | In more technical terms it is all about Node.js, JavaScript, HTTP, WebSockets for embedded devices! 16 | 17 | ![building the web of things](https://raw.githubusercontent.com/webofthings/webofthings.js/master/docs/building-the-web-of-things.png) 18 | 19 | [Get Building the Web of Things!](http://book.webofthings.io) 20 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/public/websocketsClient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/middleware/proxy.js: -------------------------------------------------------------------------------- 1 | var https = require('https'), 2 | fs = require('fs'), 3 | config = require('../config/acl.json').things[0], //#A 4 | httpProxy = require('http-proxy'); 5 | 6 | var proxyServer = httpProxy.createProxyServer({ //#B 7 | ssl: { 8 | key: fs.readFileSync('./config/change_me_privateKey.pem', 'utf8'), 9 | cert: fs.readFileSync('./config/change_me_caCert.pem', 'utf8'), 10 | passphrase: 'webofthings' 11 | }, 12 | secure: false //#C 13 | }); 14 | 15 | module.exports = function() { 16 | return function proxy(req, res, next) { 17 | req.headers['authorization'] = config.token; //#D 18 | proxyServer.web(req, res, {target: config.url}); //#E 19 | } 20 | }; 21 | 22 | //#A Load the Thing that can be proxied (there’s just one here) 23 | //#B Initialize the proxy server, making it an HTTPS proxy to ensure end-to-end encryption 24 | //#C Do not verify the certificate (true would refuse local certificate) 25 | //#D Proxy middleware function; add the secret token of the Thing 26 | //#E Proxy the request; notice that this middleware doesn’t call next() because it should be the last in the chain 27 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/config/acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "protected": [ 3 | { 4 | "uid": "facebook:1482479545406343", 5 | "resources": [ 6 | "/properties", "/properties/temperature", "/properties/humidity", "/properties/pir", "/leds/1", "/leds/2", "/actions/ledState" 7 | ] 8 | }, 9 | { 10 | "uid": "facebook:10104818212136076", 11 | "resources": [ 12 | "/properties", "/properties/temperature", "/properties/humidity", "/properties/pir", "/leds/1", "/leds/2", "/actions/ledState" 13 | ] 14 | }, 15 | { 16 | "uid": "facebook:10207489314897153", 17 | "resources": [ 18 | "/properties", "/properties/temperature", "/properties/humidity", "/properties/pir", "/leds/1", "/leds/2", "/actions/ledState" 19 | ] 20 | } 21 | ], 22 | "open": [ 23 | "/", "/model", "/account", "/login", "/logout", "/auth/facebook", "/auth/facebook/callback" 24 | ], 25 | "things": [ 26 | { 27 | "id": "WoTPi", 28 | "url": "https://127.0.0.1:8484", 29 | "token": "cKXRTaRylYWQiF3MICaKndG4WJMcVLFz" 30 | } 31 | ], 32 | "config": { 33 | "sourcePort" : 5050 34 | } 35 | } -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/resources/resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "pi": { 3 | "name": "WoT Pi", 4 | "description": "A simple WoT-connected Raspberry PI for the WoT book.", 5 | "port": 8484, 6 | "sensors": { 7 | "temperature": { 8 | "name": "Temperature Sensor", 9 | "description": "An ambient temperature sensor.", 10 | "unit": "celsius", 11 | "value": 0, 12 | "gpio": 12 13 | }, 14 | "humidity": { 15 | "name": "Humidity Sensor", 16 | "description": "An ambient humidity sensor.", 17 | "unit": "%", 18 | "value": 0, 19 | "gpio": 12 20 | }, 21 | "pir": { 22 | "name": "Passive Infrared", 23 | "description": "A passive infrared sensor. When 'true' someone is present.", 24 | "value": true, 25 | "gpio": 17 26 | } 27 | }, 28 | "actuators": { 29 | "leds": { 30 | "1": { 31 | "name": "LED 1", 32 | "value": false, 33 | "gpio": 4 34 | }, 35 | "2": { 36 | "name": "LED 2", 37 | "value": false, 38 | "gpio": 9 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /chapter4-gpios/more-examples/proximity.py: -------------------------------------------------------------------------------- 1 | # Import Python header files 2 | import RPi.GPIO as GPIO 3 | import time 4 | 5 | # Set the GPIO naming convention 6 | GPIO.setmode(GPIO.BCM) 7 | GPIO.setwarnings(False) 8 | 9 | # Set a variable to hold the GPIO Pin identity 10 | PinPIR = 17 11 | 12 | print "PIR Module Test (CTRL-C to exit)" 13 | 14 | # Set pin as input 15 | GPIO.setup(PinPIR, GPIO.IN) 16 | 17 | # Variables to hold the current and last states 18 | Current_State = 0 19 | Previous_State = 0 20 | 21 | try: 22 | print "Waiting for PIR to settle ..." 23 | # Loop until PIR output is 0 24 | while GPIO.input(PinPIR)==1: 25 | Current_State = 0 26 | 27 | print " Ready" 28 | while True: 29 | # Read PIR state 30 | Current_State = GPIO.input(PinPIR) 31 | 32 | # If the PIR is triggered 33 | if Current_State==1 and Previous_State==0: 34 | print " Motion detected!" 35 | # Record previous state 36 | Previous_State=1 37 | 38 | # If the PIR has returned to ready state 39 | elif Current_State==0 and Previous_State==1: 40 | print " Ready" 41 | Previous_State = 0 42 | 43 | # Wait for 10 milliseconds 44 | time.sleep(0.01) 45 | 46 | except KeyboardInterrupt: 47 | print " Quit" 48 | 49 | # Reset GPIO settings 50 | GPIO.cleanup() -------------------------------------------------------------------------------- /chapter4-gpios/more-examples/blink-pi-gpio.js: -------------------------------------------------------------------------------- 1 | var gpio = require('pi-gpio'); //#A 2 | var pin = 7; 3 | 4 | function blink(outPin, frequency, status) { //#B 5 | gpio.write(outPin, status, function () { //#D 6 | console.log('Setting GPIO to: ' + status); 7 | setTimeout(function () { //#E 8 | status = (status + 1) % 2; 9 | blink(outPin, frequency, status); 10 | }, frequency); 11 | }); 12 | } 13 | 14 | process.on('SIGINT', function () { //#F 15 | gpio.write(pin, 0, function () { 16 | gpio.close(pin); //#G 17 | console.log('Bye, bye!'); 18 | process.exit(); 19 | }); 20 | }); 21 | 22 | gpio.open(pin, "output", function (err) { //#C 23 | if (err) exit(err); 24 | blink(pin, 2000,1); //#H 25 | }); 26 | 27 | // #A Importing the GPIO management library 28 | // #B A blink function with the pin to activate, the blinking frequency and the initial status as parameters 29 | // #C Initialize the pin in output mode, once this is ready the anonymous function will be called 30 | // #D Write the current status to the pin 31 | // #E Once the status has been written we set a timer to recursively call blink 32 | // #F Listen to the even triggered when the program is about the exit 33 | // #G Cleanly close the GPIO pin before exiting 34 | // #H Call the blink function for pin #7 with a blinking speed of 2 seconds 35 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/authProxy.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | https = require('https'), 3 | fs = require('fs'), 4 | bodyParser = require('body-parser'), 5 | cons = require('consolidate'), 6 | auth = require('./middleware/auth'), 7 | fb = require('./providers/facebook.js'), 8 | proxy = require('./middleware/proxy.js'), 9 | config = require('./config/acl.json').config; 10 | 11 | var key_file = './config/change_me_privateKey.pem'; 12 | var cert_file = './config/change_me_caCert.pem'; 13 | var passphrase = 'webofthings'; 14 | 15 | var tlsConfig = { 16 | key: fs.readFileSync(key_file), 17 | cert: fs.readFileSync(cert_file), 18 | passphrase: passphrase 19 | }; 20 | 21 | 22 | var app = express(); 23 | app.use(bodyParser.json()); 24 | app.use(auth.socialTokenAuth()); 25 | 26 | // configure Express 27 | app.set('views', __dirname + '/views'); 28 | app.use(express.static(__dirname + '/public')); 29 | 30 | app.engine('html', cons.handlebars); 31 | app.set('view engine', 'html'); 32 | 33 | // add the FB auth support and pages 34 | fb.setupFacebookAuth(app); 35 | app.use(proxy()); 36 | 37 | var httpServer = https.createServer(tlsConfig, app); 38 | httpServer.listen(config.sourcePort, function () { 39 | console.log('WoT Social Authentication Proxy started on port: %d', config.sourcePort); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/servers/websockets.js: -------------------------------------------------------------------------------- 1 | var WebSocketServer = require('ws').Server, 2 | resources = require('./../resources/model'); 3 | 4 | exports.listen = function(server) { 5 | var wss = new WebSocketServer({server: server}); //#A 6 | console.info('WebSocket server started...'); 7 | wss.on('connection', function (ws) { //#B 8 | var url = ws.upgradeReq.url; 9 | console.info(url); 10 | try { 11 | Object.observe(selectResouce(url), function (changes) { //#C 12 | ws.send(JSON.stringify(changes[0].object), function () { 13 | }); 14 | }) 15 | } 16 | catch (e) { //#D 17 | console.log('Unable to observe %s resource!', url); 18 | }; 19 | }); 20 | }; 21 | 22 | function selectResouce(url) { //#E 23 | var parts = url.split('/'); 24 | parts.shift(); 25 | var result = resources; 26 | for (var i = 0; i < parts.length; i++) { 27 | result = result[parts[i]]; 28 | } 29 | return result; 30 | } 31 | 32 | 33 | //#A Create a WebSockets server by passing it the Express server 34 | //#B Triggered after a protocol upgrade when the client connected 35 | //#C Register an observer corresponding to the resource in the protocol upgrade URL 36 | //#D Use a try/catch to catch to intercept errors (e.g., malformed/unsupported URLs) 37 | //#E This function takes a request URL and returns the corresponding resource 38 | 39 | -------------------------------------------------------------------------------- /chapter10-mashups/ui/code-bits/form-only.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Change LED state

4 |
5 |
Change the state of an LED 6 |
7 |
8 | 9 |
10 | 15 |
16 |
17 |
18 | 19 |
20 | 24 |
25 |
26 |

27 | Create Action: ledState 28 |

29 |
30 |
31 |
-------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/servers/coap.js: -------------------------------------------------------------------------------- 1 | var coap = require('coap'), //#A 2 | utils = require('./../utils/utils'); 3 | 4 | var port = 5683; 5 | 6 | coap.createServer(function (req, res) { 7 | console.info('CoAP device got a request for %s', req.url); 8 | if (req.headers['Accept'] != 'application/json') { 9 | res.code = '4.06'; //#B 10 | return res.end(); 11 | } 12 | switch (req.url) { //#C 13 | case "/co2": 14 | respond(res, {'co2': utils.randomInt(0, 1000)}); //#D 15 | break; 16 | case "/temp": 17 | respond(res, {'temp': utils.randomInt(0, 40)}); 18 | break; 19 | default: 20 | respond(res); 21 | } 22 | }).listen(port, function () { 23 | console.log("CoAP server started on port %s", port) 24 | });//#E 25 | 26 | function respond(res, content) { //#F 27 | if (content) { 28 | res.setOption('Content-Format', 'application/json'); 29 | res.code = '2.05'; 30 | res.end(JSON.stringify(content)); 31 | } else { 32 | res.code = '4.04'; 33 | res.end(); 34 | } 35 | }; 36 | //#A Require the Node.js CoAP module you installed 37 | //#B You only serve JSON, so you reply with a 4.06 (= HTTP 406: Not acceptable) 38 | //#C Handle the different resources requested 39 | //#D This is the CO2 resource; generate a random value for it and respond 40 | //#E Start the CoAP server on port 5683 (CoAP’s default port) 41 | //#F Send the JSON content back or reply with a 4.04 (= HTTP 404: Not found) 42 | -------------------------------------------------------------------------------- /chapter3-node-js/mashupserver-local.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | request = require('request'), 3 | fs = require('fs'); 4 | 5 | 6 | var serviceRootUrl = 'http://localhost:8686'; 7 | 8 | http.createServer(function(servReq, servResp){ 9 | console.log('New incoming client request...'); 10 | 11 | if (servReq.url == '/log') { 12 | // 1) Get temperature 13 | request({url: serviceRootUrl + '/temperature', json: true}, 14 | function (err, resp, body) { 15 | if (!err && resp.statusCode == 200) { 16 | console.log(body); 17 | var temperature = body.temperature; 18 | 19 | // 2) Get the light level 20 | request({url: serviceRootUrl + '/light', json: true}, 21 | function (err, resp, body) { 22 | if (!err && resp.statusCode == 200) { 23 | console.log(body); 24 | var light = body.light; 25 | 26 | // 3) Log the values 27 | var logEntry = 'Temperature: ' + temperature + ' Light: ' + light; 28 | fs.appendFile('log.txt', logEntry + ' - ', encoding='utf8', function (err) { 29 | if (err) throw err; 30 | servResp.writeHeader(200, {"Content-Type": "text/plain"}); 31 | servResp.write(logEntry); 32 | servResp.end(); 33 | }); 34 | } 35 | }); 36 | } 37 | }); 38 | } else { 39 | servResp.writeHeader(200, {"Content-Type": "text/plain"}); 40 | servResp.write('Please use /log'); 41 | servResp.end(); 42 | } 43 | 44 | }).listen(8787); 45 | 46 | 47 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-2.1-polling-temp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ex 2.1 - Polling Temperature 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Current temperature

15 | 16 |

17 | 33 | 34 | -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.8-callbacks.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | request = require('request'), 3 | fs = require('fs'); 4 | 5 | var port = 8787; 6 | var serviceRootUrl = 'http://localhost:8686'; 7 | 8 | 9 | http.createServer(function (servReq, servResp) { 10 | console.log('New incoming client request...'); 11 | if (servReq.url === '/log') { 12 | request({url: serviceRootUrl + '/temperature', json: true}, //#A 13 | function (err, resp, body) { 14 | if (err) throw err; 15 | if (resp.statusCode === 200) { 16 | console.log(body); 17 | var temperature = body.temperature; 18 | 19 | request({url: serviceRootUrl + '/light', json: true}, //#B 20 | function (err, resp, body) { 21 | if (err) throw err; 22 | if (resp.statusCode === 200) { 23 | console.log(body); 24 | var light = body.light; 25 | var logEntry = 'Temperature: ' + temperature + ' Light: ' + light; 26 | fs.appendFile('log.txt', logEntry + '\n', encoding = 'utf8', function (err) { //#C 27 | if (err) throw err; 28 | servResp.writeHeader(200, {"Content-Type": "text/plain"}); 29 | servResp.write(logEntry); 30 | servResp.end(); 31 | }); 32 | } 33 | }); 34 | } 35 | }); 36 | } else { 37 | servResp.writeHeader(200, {"Content-Type": "text/plain"}); 38 | servResp.write('Please use /log'); 39 | servResp.end(); 40 | } 41 | 42 | }).listen(port); 43 | console.log('Server listening on http://localhost:' + port); 44 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/middleware/converter.js: -------------------------------------------------------------------------------- 1 | var msgpack = require('msgpack5')(), 2 | encode = msgpack.encode, //#A 3 | json2html = require('node-json2html'); 4 | 5 | module.exports = function () { //#B 6 | return function (req, res, next) { 7 | console.info('Representation converter middleware called!'); 8 | if (req.result) { //#C 9 | switch (req.accepts(['json', 'html', 'application/x-msgpack'])) { //#D 10 | case 'html': 11 | console.info('HTML representation selected!'); 12 | var transform = {'tag': 'div', 'html': '${name} : ${value}'}; 13 | res.send(json2html.transform(req.result, transform)); //#E 14 | return; 15 | case 'application/x-msgpack': 16 | console.info('MessagePack representation selected!'); 17 | res.type('application/x-msgpack'); 18 | res.send(encode(req.result)); //#F 19 | return; 20 | default: //#G 21 | console.info('Defaulting to JSON representation!'); 22 | res.send(req.result); 23 | return; 24 | } 25 | } 26 | else { 27 | next(); //#H 28 | } 29 | } 30 | }; 31 | //#A Require the two modules and instantiate a MessagePack encoder 32 | //#B In Express, a middleware is usually a function returning a function 33 | //#C Check if the previous middleware left a result for you in req.result 34 | //#D Get the best representation to serve from the Accept header 35 | //#E If HTML was requested, use json2html to transform the JSON into simple HTML 36 | //#F Encode the JSON result into MessagePack using the encoder and return the result to the client 37 | //#G For all other formats, default to JSON 38 | //#H If no result was present in req.result, there’s not much you can do, so call the next middleware 39 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/servers/http.js: -------------------------------------------------------------------------------- 1 | // Final version 2 | var express = require('express'), 3 | actuatorsRoutes = require('./../routes/actuators'), 4 | sensorRoutes = require('./../routes/sensors'), 5 | thingsRoutes = require('./../routes/things'), 6 | resources = require('./../resources/model'), 7 | converter = require('./../middleware/converter'), 8 | cors = require('cors'), 9 | bodyParser = require('body-parser'); 10 | 11 | var app = express(); 12 | 13 | app.use(bodyParser.json()); 14 | 15 | app.use(cors()); 16 | 17 | app.use('/pi/actuators', actuatorsRoutes); 18 | app.use('/pi/sensors', sensorRoutes); 19 | app.use('/things', thingsRoutes); 20 | 21 | app.get('/pi', function (req, res) { 22 | res.send('This is the WoT-Pi!') 23 | }); 24 | 25 | // For representation design 26 | app.use(converter()); 27 | module.exports = app; 28 | 29 | 30 | /* 31 | //Initial version: 32 | 33 | var express = require('express'), 34 | actuatorsRoutes = require('./../routes/actuators'), 35 | sensorRoutes = require('./../routes/sensors'), 36 | resources = require('./../resources/model'), //#A 37 | cors = require('cors'); 38 | 39 | var app = express(); //#B 40 | 41 | app.use(cors()); //#C 42 | 43 | app.use('/pi/actuators', actuatorsRoutes); //#D 44 | app.use('/pi/sensors', sensorRoutes); 45 | 46 | app.get('/pi', function (req, res) { //#E 47 | res.send('This is the WoT-Pi!') 48 | }); 49 | 50 | module.exports = app; 51 | 52 | //#A Requires the Express framework, your routes, and the model 53 | //#B Creates an application with the Express framework; this wraps an HTTP server 54 | //#C Enable CORS support (see section 6.1.5) 55 | //#D Binds your routes to the Express application; bind them to /pi/actuators/... and /pi/sensors/... 56 | //#E Create a default route for /pi 57 | 58 | */ -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/config/change_me_caCert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE0DCCA7igAwIBAgIJAMnis+YL+r3QMA0GCSqGSIb3DQEBCwUAMIGgMQswCQYD 3 | VQQGEwJVSzEUMBIGA1UECBMLTG9uZG9uc2hpcmUxDzANBgNVBAcTBkxvbmRvbjEW 4 | MBQGA1UEChMNV2ViIG9mIFRoaW5nczEWMBQGA1UECxMNV2ViIG9mIFRoaW5nczEW 5 | MBQGA1UEAxMNV2ViIG9mIFRoaW5nczEiMCAGCSqGSIb3DQEJARYTYm9va0B3ZWJv 6 | ZnRoaW5ncy5pbzAeFw0xNTA4MTUxNjQ3MTdaFw0xODA4MTQxNjQ3MTdaMIGgMQsw 7 | CQYDVQQGEwJVSzEUMBIGA1UECBMLTG9uZG9uc2hpcmUxDzANBgNVBAcTBkxvbmRv 8 | bjEWMBQGA1UEChMNV2ViIG9mIFRoaW5nczEWMBQGA1UECxMNV2ViIG9mIFRoaW5n 9 | czEWMBQGA1UEAxMNV2ViIG9mIFRoaW5nczEiMCAGCSqGSIb3DQEJARYTYm9va0B3 10 | ZWJvZnRoaW5ncy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANKQ 11 | /bP/Ns5+WVm1w1PPl2GEUJXGG3LK3wcMkQuIYm2mm4luv/mnyoGZFklZ5/Hdv7+A 12 | cluLYivel4/nPvOdwgKg8/1nobP1D49dhLC8q6NneA5cMmyPXJzX/cBnVSg54cWP 13 | LUCxChumJ7JT1c7IKBfBAGX6D3X+aZ9vTQnhyquNQr0GmQ2gtUFwcizAXrsh9wYU 14 | jc0ndS4jNHm8qXLUggE2uP0T+uUUBCaC5vpEfh1DcNSpwzNlbfvFYK+R9WewlDkY 15 | 2UPPcLvg6+ptJcqbaX+NH1GiY9c2WeQ7gCZN0hA67tDABpGoBaI8HnBvUCHMN8c1 16 | C6PboUgn+FGOLFa5MSsCAwEAAaOCAQkwggEFMB0GA1UdDgQWBBTlxSQDM/ICUI6M 17 | rO9O0S7vpm711jCB1QYDVR0jBIHNMIHKgBTlxSQDM/ICUI6MrO9O0S7vpm711qGB 18 | pqSBozCBoDELMAkGA1UEBhMCVUsxFDASBgNVBAgTC0xvbmRvbnNoaXJlMQ8wDQYD 19 | VQQHEwZMb25kb24xFjAUBgNVBAoTDVdlYiBvZiBUaGluZ3MxFjAUBgNVBAsTDVdl 20 | YiBvZiBUaGluZ3MxFjAUBgNVBAMTDVdlYiBvZiBUaGluZ3MxIjAgBgkqhkiG9w0B 21 | CQEWE2Jvb2tAd2Vib2Z0aGluZ3MuaW+CCQDJ4rPmC/q90DAMBgNVHRMEBTADAQH/ 22 | MA0GCSqGSIb3DQEBCwUAA4IBAQB6pp9Dx0yYenBLaxUFUK+2oRzINW4h/uiqeS30 23 | YmNrAVUL8Q53Ri/q18+Gr1XQkl4A9MS0g2sBqG5fEP1b5JzBriIWrBJDBVazGM7U 24 | wE+ei+IqvDqb092bpfVDe+7g3+58PmuugrPcZw4eZ8qUAKAiIdQA6V6V0N038x6f 25 | GB6xHwwJ1BfHThI0oLioJrg/qS/bbLa3PVMZS/k4Ulpa2xTPO1SwwBJ89VRMz1AW 26 | mqk8aTpC5KyUKWqbD84efkOdG0epOW3YoWi5te48VxlOd0GQ91aOh1wyzT27N7x1 27 | XCPVWkOHtNfxgASdIo+yLdxOjlDePjlGGtzSi78FU1JTCY8Q 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Web Thing 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |

Login

29 |

Login page

30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 | {{#if user}} 39 |

Hello, {{user.displayName}}.

40 | {{else}} 41 |

Welcome.

42 |

Login or Register with:

43 | 44 | Facebook 45 | {{/if}} 46 | 47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 | 55 |

Check the WoT Book.

56 | 57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.9-callbacks-named-functions.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | request = require('request'), 3 | fs = require('fs'); 4 | 5 | var serviceRootUrl = 'http://localhost:8686'; 6 | 7 | http.createServer(function (req, res) { 8 | console.log('New incoming client request...'); 9 | 10 | if (req.url === '/log') { 11 | getTemperature(res); //#A 12 | 13 | } else { 14 | res.writeHeader(200, {"Content-Type": "text/plain"}); 15 | res.write('Please use /log'); 16 | res.end(); 17 | } 18 | 19 | }).listen(8787); 20 | 21 | function getTemperature(res) { //#B 22 | request({url: serviceRootUrl + '/temperature', json: true}, function (err, resp, body) { 23 | if (err) throw err; 24 | if (resp.statusCode === 200) { 25 | console.log(body); 26 | var temp = body.temperature; 27 | 28 | getLight(res, temp); //#C 29 | } 30 | }); 31 | } 32 | 33 | function getLight(res, temp) { 34 | request({url: serviceRootUrl + '/light', json: true}, function (err, resp, body) { 35 | if (err) throw err; 36 | if (resp.statusCode === 200) { 37 | console.log(body); 38 | var light = body.light; 39 | 40 | logValuesReply(res, temp, light); //#D 41 | } 42 | }); 43 | } 44 | 45 | function logValuesReply(res, temp, light) { 46 | var logEntry = 'Temperature: ' + temp + ' Light: ' + light; 47 | fs.appendFile('log.txt', logEntry + '\n', encoding = 'utf8', function (err) { 48 | if (err) throw err; 49 | res.writeHeader(200, {"Content-Type": "text/plain"}); //#E 50 | res.write(logEntry); 51 | res.end(); 52 | }); 53 | } 54 | 55 | //#A Get the temperature and start the chain of calls 56 | //#B A named temperature function 57 | //#C Once the callback for temperature has been called we proceed with calling the getLight function 58 | //#D We then call the named function to log values 59 | //#E Return to the client 60 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/config/change_me_privateKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,44A2DEDA261477B8 4 | 5 | LiB+m2sQTIWoP6h3Yl+lCNHtyF5B7htWuBILA4Xf4+87GJGWguTXZGh+av4YlnAI 6 | syl3Yiyr9QhBSFUbsIkfnN0MhfO6C2PogJ82vOSuB6phtZk88yIfLMgbL2xvcI6l 7 | EgcVWsOTkC+c8ORPHMYsuXmKAAEi75VxPw+OCIMeGhgaHGf1ZBjKn6GbPYyi1doH 8 | LGsSmmBM+fiRsWiBYO/4lUmzF12WZuQo/zzS4g+VqTkerTQRI/9loPQMvUx/08s6 9 | ZOQNj9JwflYJZfLqHd84ddN0oh06GSe672rKRle1R9V9siULlRW8To/kuYBSxZTm 10 | UMLSc8cXgoMLxtTnBkkxg+oiJmO3+Hrtab9nLsXcL42XXDxvZ5L0vl/dOZxqzAKN 11 | YSJlCspaeF261xTMdGPnAkdRUt18/8XtLmySD4RbrwDysngO+ZyV7K4mDsuJ5pyO 12 | 4iwSD1izb/rTWKec0LYZ+iEUGg1pvHWFbDI7RqxOuBLCUnxCKY7LfMSmkKGGF7nK 13 | K/4bviR9MkiVqaSPivxp9+Byfx1V+MnaKMBnxhqpmBkKK/BruYyYDsFxuxFgvg2t 14 | 8TfIuuGR3yNfzzSHMmj15Zwro7cuuAzmMJ2aQJTTdFJy4fc6ja4rsZtHQZh949gZ 15 | VpqJO7OpYRu1CpagedF1LmOufShyUTSbJzaGQiy8u4Jgt0WOCUzZerPDgyr1QfAo 16 | G2B1jPQ+uMVe07wZrVZhik2V+egnXCNHK6jmwpMk26woMRMqpuuuPDrrLtRIOEXh 17 | Lfa93pSdXPqUjgtiLzCWtXJJCdAPZkR2Ljs5UVtsh5x/km73mUqme+c4mBvIMxAB 18 | zqJZm+bB/PBWwvPXeVSuh+RHVv1n/n5yhBibJKSlYbo78dneMi9ec3nF1+Nx0uAp 19 | HAnNvmfOGBThM9/lkHz0ui9sjMmCiddypn9XoqAIXLxMUGbm/pqhMjKMHDY5zYWu 20 | teog0OfiCUWlft+tOTem2BqnbdG5cKxzee6wBKbuAQWaiMGtV0/Mae0tnuRnMYfd 21 | NvoxOA8Ha1cy9nkOT9nX8lUhpFOiYFXSNA4ei7JdhGB+4Goyyq2uwZq9BXuPltro 22 | haTfflsPu7C4u3yNxehahIY3bPdAE3qU4Y4znn5R4HornNWenc5k7vB/Etni8Yey 23 | ZaaBOUhiZEaO8GCTpBbvaZRU/diKpWiGn/EeJY/e2iBetg8Bsme4k7SupnD+nrmG 24 | iUG3aMB5fwp1kNceaRxw2ofYctP+hkoO80wRhxfhhpW68Wqic9/P38DHnYSWQMdi 25 | SCe30hbny/D/ATTY5vrjdj4/89pxxSJFaJk+G6Q15IhuaJwj8pcclmvlTtgUTQZy 26 | LYSm87CixnsqnMjqWaUyOQWoE4c4IcO3MGPWWpEpfQN0Wp8L7ht8bP+Y0vlwWSVu 27 | VSdyj/Io0TcuHtV2Av2K6F+PixXuMgQpYj9ktHCs0HkeWGsGiLJbZ3eDBgBEpG/d 28 | 9D4/Z1SVJ8DkbOgkp4DaN9jSc5FfyiUpHbN528Zwcl0nGuO6onJnsgbnURxBxf4d 29 | mK235I7vYLm5o9EHecbrDzLsH7G/XvIYwD2ZVy57142QHC4u4fd3Yg== 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /chapter10-mashups/mashup.css: -------------------------------------------------------------------------------- 1 | .onoffswitch { 2 | position: relative; width: 200px; 3 | -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none; 4 | } 5 | .onoffswitch-checkbox { 6 | display: none; 7 | } 8 | .onoffswitch-label { 9 | display: block; overflow: hidden; cursor: pointer; 10 | border: 2px solid #999999; border-radius: 20px; 11 | } 12 | .onoffswitch-inner { 13 | display: block; width: 200%; margin-left: -100%; 14 | -moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s; 15 | -o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s; 16 | } 17 | .onoffswitch-inner:before, .onoffswitch-inner:after { 18 | display: block; float: left; width: 50%; height: 74px; padding: 0; line-height: 74px; 19 | font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; 20 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; 21 | } 22 | .onoffswitch-inner:before { 23 | content: "Intruder detected!"; 24 | padding-left: 10px; 25 | background-color: #F70519; color: #FFFFFF; 26 | } 27 | .onoffswitch-inner:after { 28 | content: "No one detected!"; 29 | padding-right: 10px; 30 | background-color: #0839FC; color: #999999; 31 | text-align: right; 32 | } 33 | .onoffswitch-switch { 34 | display: block; width: 50px; margin: 12px; 35 | background: #FFFFFF; 36 | border: 2px solid #999999; border-radius: 20px; 37 | position: absolute; top: 0; bottom: 0; right: 122px; 38 | -moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s; 39 | -o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s; 40 | } 41 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { 42 | margin-left: 0; 43 | } 44 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { 45 | right: 0px; 46 | } -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/plugins/internal/pirPlugin.js: -------------------------------------------------------------------------------- 1 | var resources = require('./../../resources/model'); 2 | 3 | var interval, sensor; 4 | var model = resources.pi.sensors.pir; 5 | var pluginName = resources.pi.sensors.pir.name; 6 | var localParams = {'simulate': false, 'frequency': 2000}; 7 | 8 | exports.start = function (params) { //#A 9 | localParams = params; 10 | if (localParams.simulate) { 11 | simulate(); 12 | } else { 13 | connectHardware(); 14 | } 15 | }; 16 | 17 | exports.stop = function () { //#A 18 | if (localParams.simulate) { 19 | clearInterval(interval); 20 | } else { 21 | sensor.unexport(); 22 | } 23 | console.info('%s plugin stopped!', pluginName); 24 | }; 25 | 26 | function connectHardware() { //#B 27 | var Gpio = require('onoff').Gpio; 28 | sensor = new Gpio(model.gpio, 'in', 'both'); //#C 29 | sensor.watch(function (err, value) { //#D 30 | if (err) exit(err); 31 | model.value = !!value; 32 | showValue(); 33 | }); 34 | console.info('Hardware %s sensor started!', pluginName); 35 | }; 36 | 37 | function simulate() { //#E 38 | interval = setInterval(function () { 39 | model.value = !model.value; 40 | showValue(); 41 | }, localParams.frequency); 42 | console.info('Simulated %s sensor started!', pluginName); 43 | }; 44 | 45 | function showValue() { 46 | console.info(model.value ? 'there is someone!' : 'not anymore!'); 47 | }; 48 | 49 | //#A starts and stops the plugin, should be accessible from other Node.js files so we export them 50 | //#B require and connect the actual hardware driver and configure it 51 | //#C configure the GPIO pin to which the PIR sensor is connected 52 | //#D start listening for GPIO events, the callback will be invoked on events 53 | //#E allows the plugin to be in simulation mode. This is very useful when developing or when you want to test your code on a device with no sensors connected, such as your laptop. 54 | 55 | 56 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/routes/actuators.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | router = express.Router(), 3 | resources = require('./../resources/model'); 4 | 5 | router.route('/').get(function (req, res, next) { 6 | req.result = resources.pi.actuators; 7 | next(); 8 | }); 9 | 10 | router.route('/leds').get(function (req, res, next) { 11 | req.result = resources.pi.actuators.leds; 12 | next(); 13 | }); 14 | 15 | router.route('/leds/:id').get(function (req, res, next) { //#A 16 | req.result = resources.pi.actuators.leds[req.params.id]; 17 | next(); 18 | }).put(function(req, res, next) { //#B 19 | var selectedLed = resources.pi.actuators.leds[req.params.id]; 20 | selectedLed.value = req.body.value; //#C 21 | req.result = selectedLed; 22 | next(); 23 | }); 24 | 25 | module.exports = router; 26 | 27 | //#A Callback for a GET request on an LED 28 | //#B Callback for a PUT request on an LED 29 | //#C Update the value of the selected LED in the model 30 | 31 | 32 | /* 33 | //Initial version: 34 | 35 | var express = require('express'), 36 | router = express.Router(), 37 | resources = require('./../resources/model'); 38 | 39 | router.route('/').get(function (req, res, next) { // #A 40 | res.send(resources.pi.actuators); // #B 41 | }); 42 | 43 | router.route('/leds').get(function (req, res, next) { // #C 44 | res.send(resources.pi.actuators.leds); 45 | }); 46 | 47 | router.route('/leds/:id').get(function (req, res, next) { //#D 48 | res.send(resources.pi.actuators.leds[req.params.id]); //#E 49 | }); 50 | 51 | module.exports = router; 52 | 53 | //#A Create a new route for a GET request 54 | //#B Reply with the actuators model when this route is selected 55 | //#C This route serves a list of LEDs 56 | //#D with :id we inject a variable in the path which will be the LED number 57 | //#E the path variables are accessible via req.params.id we use this to select the right object in our model and return it 58 | */ -------------------------------------------------------------------------------- /chapter10-mashups/mashup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 55 |
56 | 57 | 61 |
62 |
63 | 64 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/wot-server.js: -------------------------------------------------------------------------------- 1 | // Final version 2 | var httpServer = require('./servers/http'), 3 | wsServer = require('./servers/websockets'), 4 | resources = require('./resources/model'); 5 | 6 | // Internal Plugins 7 | var ledsPlugin = require('./plugins/internal/ledsPlugin'), //#A 8 | pirPlugin = require('./plugins/internal/pirPlugin'), //#A 9 | dhtPlugin = require('./plugins/internal/DHT22SensorPlugin'); //#A 10 | 11 | // Internal Plugins for sensors/actuators connected to the PI GPIOs 12 | // If you test this with real sensors do not forget to set simulate to 'false' 13 | pirPlugin.start({'simulate': true, 'frequency': 2000}); //#B 14 | ledsPlugin.start({'simulate': true, 'frequency': 10000}); //#B 15 | dhtPlugin.start({'simulate': true, 'frequency': 10000}); //#B 16 | 17 | // External Plugins 18 | var coapPlugin = require('./plugins/external/coapPlugin'); 19 | coapPlugin.start({'simulate': false, 'frequency': 10000}); 20 | 21 | // HTTP Server 22 | var server = httpServer.listen(resources.pi.port, function () { 23 | console.log('HTTP server started...'); 24 | 25 | // Websockets server 26 | wsServer.listen(server); 27 | 28 | console.info('Your WoT Pi is up and running on port %s', resources.pi.port); 29 | }); 30 | //#A Require all the sensor plugins you need 31 | //#B Start them with a parameter object; here you start them on a laptop so you activate the simulation function 32 | 33 | 34 | 35 | /* 36 | // Initial version: 37 | var httpServer = require('./servers/http'), //#A 38 | resources = require('./resources/model'); 39 | 40 | var server = httpServer.listen(resources.pi.port, function () { //#B 41 | console.info('Your WoT Pi is up and running on port %s', resources.pi.port); //#C 42 | }); 43 | 44 | //#A Load the http server and the model 45 | //#B Start the HTTP server by invoking listen() on the Express application 46 | //#C Once the server is started the callback is invoked 47 | */ 48 | 49 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/views/account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Web Thing 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |

Your Account

27 |

Social WoT Authentication Proxy

28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 |

You are logged in.

37 |

ID: {{user.id}}

38 |

Name: {{user.displayName}}

39 |

Access Token:

40 |

The properties: link

41 | 42 | Logout 43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 |

Check the WoT Book.

53 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/plugins/internal/ledsPlugin.js: -------------------------------------------------------------------------------- 1 | var resources = require('./../../resources/model'); 2 | 3 | var actuator, interval; 4 | var model = resources.pi.actuators.leds['1']; 5 | var pluginName = model.name; 6 | var localParams = {'simulate': false, 'frequency': 2000}; 7 | 8 | exports.start = function (params) { 9 | localParams = params; 10 | observe(model); //#A 11 | 12 | if (localParams.simulate) { 13 | simulate(); 14 | } else { 15 | connectHardware(); 16 | } 17 | }; 18 | 19 | exports.stop = function () { 20 | if (localParams.simulate) { 21 | clearInterval(interval); 22 | } else { 23 | actuator.unexport(); 24 | } 25 | console.info('%s plugin stopped!', pluginName); 26 | }; 27 | 28 | function observe(what) { 29 | Object.observe(what, function (changes) { 30 | console.info('Change detected by plugin for %s...', pluginName); 31 | switchOnOff(model.value); //#B 32 | }); 33 | }; 34 | 35 | function switchOnOff(value) { 36 | if (!localParams.simulate) { 37 | actuator.write(value === true ? 1 : 0, function () { //#C 38 | console.info('Changed value of %s to %s', pluginName, value); 39 | }); 40 | } 41 | }; 42 | 43 | function connectHardware() { 44 | var Gpio = require('onoff').Gpio; 45 | actuator = new Gpio(model.gpio, 'out'); //#D 46 | console.info('Hardware %s actuator started!', pluginName); 47 | }; 48 | 49 | function simulate() { 50 | interval = setInterval(function () { 51 | // Switch value on a regular basis 52 | if (model.value) { 53 | model.value = false; 54 | } else { 55 | model.value = true; 56 | } 57 | }, localParams.frequency); 58 | console.info('Simulated %s actuator started!', pluginName); 59 | }; 60 | 61 | //#A Observe the model for the LEDs 62 | //#B Listen for model changes, on changes call switchOnOff 63 | //#C Change the LED state by changing the GPIO state 64 | //#D Connect the GPIO in write (output) mode 65 | 66 | -------------------------------------------------------------------------------- /chapter10-mashups/ui/code-bits/properties.txt: -------------------------------------------------------------------------------- 1 | "properties": { 2 | "link": "/properties", 3 | "title": "List of Properties", 4 | "resources": { 5 | "temperature": { 6 | "name": "Temperature Sensor", 7 | "description": "An ambient temperature sensor.", 8 | "values": { 9 | "temp": { 10 | "name": "Temperature sensor", 11 | "description": "The temperature in celsius", 12 | "unit": "celsius", 13 | "customFields": { 14 | "gpio": 21 15 | } 16 | } 17 | }, 18 | "tags": ["sensor","public","indoors"] 19 | }, 20 | "humidity": { 21 | "name": "Humidity Sensor", 22 | "description": "An ambient humidity sensor.", 23 | "values": { 24 | "h": { 25 | "name": "Humidity", 26 | "description": "Percentage of Humidity", 27 | "unit": "%", 28 | "customFields": { 29 | "gpio": 21 30 | } 31 | } 32 | }, 33 | "tags": ["sensor","public"] 34 | }, 35 | "pir": { 36 | "name": "Passive Infrared", 37 | "description": "A passive infrared sensor.", 38 | "values": { 39 | "presence": { 40 | "name": "Presence", 41 | "description": "Current sensor value (true=motion detected)", 42 | "type": "boolean", 43 | "customFields": { 44 | "gpio": 20 45 | } 46 | } 47 | }, 48 | "tags": ["sensor","public"] 49 | }, 50 | "leds": { 51 | "name": "LEDs", 52 | "description": "The LEDs of this device.", 53 | "values": { 54 | "1": { 55 | "name": "LED 1", 56 | "customFields": { 57 | "gpio": 17 58 | } 59 | }, 60 | "2": { 61 | "name": "LED 2", 62 | "customFields": { 63 | "gpio": 19 64 | } 65 | } 66 | }, 67 | "tags": ["sensor","public"] 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.11-callbacks-control-flow.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | request = require('request'), 3 | fs = require('fs'), 4 | async = require('async'); 5 | 6 | var port = 8787; 7 | var serviceRootUrl = 'http://localhost:8686'; 8 | 9 | http.createServer(function (req, res) { 10 | console.log('New incoming client request...'); 11 | if (req.url === '/log') { 12 | async.series([ //#A 13 | getTemperature, 14 | getLight 15 | ], 16 | function (err, results) { //#B 17 | console.log(results); //#C 18 | var logEntry = 'Temperature: ' + results[0] + ' Light: ' + results[1]; 19 | fs.appendFile('log.txt', logEntry + '\n', encoding = 'utf8', function (err) { 20 | if (err) throw err; 21 | res.writeHeader(200, {"Content-Type": "text/plain"}); 22 | res.write(logEntry); 23 | res.end(); 24 | }) 25 | } 26 | ); 27 | 28 | } else { 29 | res.writeHeader(200, {"Content-Type": "text/plain"}); 30 | res.write('Please use /log'); 31 | res.end(); 32 | } 33 | }).listen(port); 34 | console.log('Server listening on http://localhost:' + port); 35 | 36 | function getTemperature(callback) { 37 | request({url: serviceRootUrl + '/temperature', json: true}, function (err, res, body) { 38 | if (err) callback(err); 39 | if (res && res.statusCode === 200) { 40 | console.log(body); 41 | var temp = body.temperature; 42 | callback(null, temp); //#D 43 | } else callback(null, null); 44 | }); 45 | } 46 | 47 | function getLight(callback) { 48 | request({url: serviceRootUrl + '/light', json: true}, function (err, res, body) { 49 | if (err) callback(err); 50 | if (res && res.statusCode === 200) { 51 | console.log(body); 52 | var light = body.light; 53 | callback(null, light); //#D 54 | } else callback(null, null); 55 | }); 56 | } 57 | 58 | //#A We create an array of functions to be invoked in series 59 | //#B This function is called when the last function in the series returned 60 | //#C results is now equal to [light, temperature] 61 | //#D Call the next function in the series 62 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/routes/sensors.js: -------------------------------------------------------------------------------- 1 | // Final version 2 | var express = require('express'), 3 | router = express.Router(), 4 | resources = require('./../resources/model'); 5 | 6 | router.route('/').get(function (req, res, next) { 7 | req.result = resources.pi.sensors; //#A 8 | next(); //#B 9 | }); 10 | 11 | router.route('/pir').get(function (req, res, next) { 12 | req.result = resources.pi.sensors.pir; 13 | next(); 14 | }); 15 | 16 | router.route('/temperature').get(function (req, res, next) { 17 | req.result = resources.pi.sensors.temperature; 18 | next(); 19 | }); 20 | 21 | router.route('/humidity').get(function (req, res, next) { 22 | req.result = resources.pi.sensors.humidity; 23 | next(); 24 | }); 25 | 26 | module.exports = router; 27 | 28 | //#A Assign the results to a new property of the req object that you pass along from middleware to middleware 29 | //#B Call the next middleware; the framework will ensure the next middleware gets access to req (including the req.result) and res 30 | 31 | 32 | 33 | /* 34 | // Initial version 35 | var express = require('express'), 36 | router = express.Router(), //#A 37 | resources = require('./../resources/model'); 38 | 39 | router.route('/').get(function (req, res, next) { //#B 40 | res.send(resources.pi.sensors); //#C 41 | }); 42 | 43 | router.route('/pir').get(function (req, res, next) { //#D 44 | res.send(resources.pi.sensors.pir); 45 | }); 46 | 47 | router.route('/temperature').get(function (req, res, next) { //#E 48 | res.send(resources.pi.sensors.temperature); 49 | }); 50 | 51 | router.route('/humidity').get(function (req, res, next) { //#E 52 | res.send(resources.pi.sensors.humidity); 53 | }); 54 | 55 | module.exports = router; //#F 56 | 57 | //#A We require and instantiate an Express Router to define the path to our resources 58 | //#B Create a new route for a GET request on all sensors and attach a callback function 59 | //#C Reply with the sensor model when this route is selected 60 | //#D This route serves the passive infrared sensor 61 | //#E These routes serve the temperature and humidity sensor 62 | //#F We export router to make it accessible for "requirers" of this file 63 | */ -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/simple-plug.js: -------------------------------------------------------------------------------- 1 | var mqtt = require('mqtt'); 2 | 3 | var config = require('./config.json'); // #A 4 | var thngId=config.thngId; 5 | var thngUrl='/thngs/'+thngId; 6 | var thngApiKey=config.thngApiKey; 7 | var interval; 8 | 9 | console.log('Using Thng #'+thngId+' with API Key: '+ thngApiKey); 10 | 11 | var client = mqtt.connect("mqtts://mqtt.evrythng.com:8883", {// #B 12 | username: 'authorization', 13 | password: thngApiKey 14 | }); 15 | 16 | client.on('connect', function () { // #C 17 | client.subscribe(thngUrl+'/properties/'); //#D 18 | updateProperty('livenow', true); //#E 19 | 20 | if (!interval) interval = setInterval(updateProperties, 5000); //#F 21 | }); 22 | 23 | client.on('message', function (topic, message) { // #G 24 | console.log(message.toString()); 25 | }); 26 | 27 | 28 | function updateProperties () { 29 | var voltage = (219.5 + Math.random()).toFixed(3); // #H 30 | updateProperty ('voltage',voltage); 31 | 32 | var current = (Math.random()*10).toFixed(3); // #I 33 | updateProperty ('current',current); 34 | 35 | var power = (voltage * current * (0.6+Math.random()/10)).toFixed(3); // #J 36 | updateProperty ('power',power); 37 | } 38 | 39 | function updateProperty (property,value) { 40 | client.publish(thngUrl+'/properties/'+property, '[{"value": '+value+'}]'); 41 | } 42 | 43 | // Let's close this connection cleanly 44 | process.on('SIGINT', function() { // #K 45 | clearInterval(interval); 46 | updateProperty ('livenow', false); 47 | client.end(); 48 | process.exit(); 49 | }); 50 | //#A Load configuration from file (Thng ID and Thng API key) 51 | //#B Connect to the secure MQTT server on EVRYTHNG 52 | //#C Callback called once when the MQTT connection suceeds 53 | //#D Subscribe to all properties 54 | //#E Set the property livenow to true 55 | //#F Call the function updateProperties() in 5 seconds 56 | //#G Called every time an MQTT message is received from the broker 57 | //#H Measures voltage (fluctuates around ~220 volts) 58 | //#I Measures current (fluctuates 0–10 amps) 59 | //#J Measures power using P=U*I*PF (PF=power factor fluctuates 60–70%) 60 | //#K Cleanly exit this code and set the livenow property to false 61 | -------------------------------------------------------------------------------- /chapter3-node-js/listing-3.11-callbacks-control-flow-no-named.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | request = require('request'), 3 | fs = require('fs'), 4 | async = require('async'); 5 | 6 | var port = 8787; 7 | var serviceRootUrl = 'http://localhost:8686'; 8 | 9 | http.createServer(function (req, res) { 10 | console.log('New incoming client request...'); 11 | if (req.url === '/log') { 12 | async.series([ //#A 13 | function (callback) { 14 | request({url: serviceRootUrl + '/temperature', json: true}, function (err, res, body) { 15 | if (err) callback(err); 16 | if (res && res.statusCode === 200) { 17 | console.log(body); 18 | var temp = body.temperature; 19 | callback(null, temp); //#B 20 | } else callback(null, null); 21 | }); 22 | }, 23 | function (callback) { 24 | request({url: serviceRootUrl + '/light', json: true}, function (err, res, body) { 25 | if (err) callback(err); 26 | if (res && res.statusCode === 200) { 27 | console.log(body); 28 | var light = body.light; 29 | callback(null, light); 30 | } else callback(null, null); 31 | }); 32 | }], 33 | function (err, results) { //#C 34 | console.log(results); //#D 35 | var logEntry = 'Temperature: ' + results[0] + ' Light: ' + results[1]; 36 | fs.appendFile('log.txt', logEntry + '\n', encoding = 'utf8', function (err) { 37 | if (err) throw err; 38 | res.writeHeader(200, {"Content-Type": "text/plain"}); 39 | res.write(logEntry); 40 | res.end(); 41 | }); 42 | }); 43 | 44 | } else { 45 | res.writeHeader(200, {"Content-Type": "text/plain"}); 46 | res.write('Please use /log'); 47 | res.end(); 48 | } 49 | }).listen(port); 50 | console.log('Server listening on http://localhost:' + port); 51 | 52 | //#A We create an array of functions to be invoked in series 53 | //#B Call the next function in the series 54 | //#C This function is called when the last function in the series returned 55 | //#D results is now equal to [light, temperature] 56 | -------------------------------------------------------------------------------- /chapter3-node-js/mashupserver.js: -------------------------------------------------------------------------------- 1 | var http = require("http"), 2 | request = require('request'), 3 | fs = require('fs'); 4 | 5 | 6 | var location = 'London, GB'; 7 | var piRootUrl = 'http://localhost:3000/pi/'; 8 | var name = 'Dom' 9 | 10 | http.createServer(function(webReq, webResp){ 11 | 12 | // 1) Get Yahoo Weather 13 | request({ 14 | url: prepareYahooWeatherUrl(location), 15 | json: true}, function (err, resp, yahooResult) { 16 | if (!err && resp.statusCode == 200) { 17 | 18 | console.log(yahooResult); 19 | var localTemp = yahooResult.query.results.channel.item.condition.temp; 20 | console.log('Local @ ' + location + ': ' + localTemp); 21 | 22 | // 2) Call the Pi 23 | request({ 24 | url: piRootUrl + 'sensors/temperature', 25 | json: true}, function (err, resp, piResult) { 26 | if (!err && resp.statusCode == 200) { 27 | 28 | console.log(piResult); 29 | var piTemp = piResult.value; 30 | console.log('Pi @ London: ' + piTemp); 31 | 32 | // 3) Compare and log 33 | var message = prepareMessage(name, location, localTemp, piTemp); 34 | fs.appendFile('log.txt', message, encoding='utf8', function (err) { 35 | if (err) throw err; 36 | webResp.writeHeader(200, {"Content-Type": "text/plain"}); 37 | webResp.write(message); 38 | webResp.end(); 39 | }); 40 | } 41 | }); 42 | } 43 | }); 44 | 45 | }).listen(8080); 46 | 47 | function prepareYahooWeatherUrl(location) { 48 | return "https://query.yahooapis.com/v1/public/yql?q=select item from weather.forecast where woeid in (select woeid from geo.places(1) where text='" + location + "') and u='c'&format=json"; 49 | } 50 | 51 | function prepareMessage(name, location, localTemp, piTemp) { 52 | var diff = localTemp - piTemp; 53 | var qualifier = ' higher '; 54 | if(diff < 0) { 55 | qualifier = ' lower '; 56 | } 57 | var result = 'Hello I\'m ' + name + ' from ' + location; 58 | result += ' my local temperature is ' + Math.abs(diff) + ' degrees'; 59 | result += qualifier + 'than yours!'; 60 | return result; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-3.2-actuator-ajax-json.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 3.2 - POST Command to Actuator 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Send Message to WoT Pi (AJAX+JSON)

17 | 18 |
19 |
 
20 |   
21 | 22 |
23 | 24 | 25 |
26 | 27 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /chapter10-mashups/node-red/pir-websockets-twitter.json: -------------------------------------------------------------------------------- 1 | [{"id":"f65a484f.a5375","type":"websocket-client","path":"ws://localhost:8484/properties/pir","wholemsg":"true"},{"id":"75ecda01.76cfa4","type":"http request","name":"get snapshot","method":"GET","ret":"bin","url":"http://devices.webofthings.io:9090/snapshot.cgi?user=snapshots&pwd=4MXfTSr0gH","x":602,"y":230,"z":"9d438066.56d85","wires":[["1df4799a.f61e1e"]]},{"id":"9d70a71d.27c8a8","type":"twitter out","twitter":"","name":"Tweet","x":959,"y":205,"z":"9d438066.56d85","wires":[]},{"id":"1df4799a.f61e1e","type":"function","name":"copy image","func":"msg.media = msg.payload;\nmsg.payload = 'Intruder Detected!';\nreturn msg;","outputs":1,"noerr":0,"x":805,"y":210,"z":"9d438066.56d85","wires":[["9d70a71d.27c8a8"]]},{"id":"f0212b36.2d5d58","type":"function","name":"check PIR value","func":"if(msg.presence) {\n return msg;\n}\nelse return null;","outputs":1,"noerr":0,"x":409,"y":237,"z":"9d438066.56d85","wires":[["75ecda01.76cfa4"]]},{"id":"eb0886fa.41c758","type":"http request","name":"turn led on/off","method":"POST","ret":"txt","url":"http://localhost:8484/actions/ledState","x":600,"y":306,"z":"9d438066.56d85","wires":[["1c339f4a.6ba069"]]},{"id":"1c339f4a.6ba069","type":"debug","name":"","active":false,"console":"false","complete":"true","x":797,"y":277,"z":"9d438066.56d85","wires":[]},{"id":"4e1f5118.fb3c38","type":"function","name":"prepare led msg","func":"msg.headers = {\n'Accept' : 'application/json',\n'Content-Type' : 'application/json',\n'Authorization' : 'cKXRTaRylYWQiF3MICaKndG4WJMcVLFz'\n}\nif(msg.presence) {\n msg.payload = {\"ledId\" : 1, \"state\" : true};\n return msg;\n} else {\n msg.payload = {\"ledId\" : 1, \"state\" : false};\n return msg;\n}\n","outputs":1,"noerr":0,"x":409,"y":280,"z":"9d438066.56d85","wires":[["eb0886fa.41c758","c7d4c3a5.f9ba28"]]},{"id":"f1bebcf6.694a5","type":"websocket in","name":"Listen for PIR","server":"","client":"f65a484f.a5375","x":203,"y":253,"z":"9d438066.56d85","wires":[["e1f1bbde.57fdc8","4e1f5118.fb3c38","f0212b36.2d5d58"]]},{"id":"e1f1bbde.57fdc8","type":"debug","name":"PIR event","active":true,"console":"false","complete":"true","x":345,"y":415,"z":"9d438066.56d85","wires":[]},{"id":"c7d4c3a5.f9ba28","type":"debug","name":"","active":true,"console":"false","complete":"false","x":574,"y":388,"z":"9d438066.56d85","wires":[]}] -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-2.2-polling-temp-chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-3.2b-actuator-ajax-form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Exercise 3.2 - POST Command to actuator 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Send Message to WoT Pi (AJAX)

16 | 17 |
18 |
 
19 | 		
20 |
21 | 22 | 23 |
24 | 25 | 26 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /chapter10-mashups/node-red/pir-http-twitter.json: -------------------------------------------------------------------------------- 1 | [{"id":"885a0bcd.7ad9a8","type":"http request","name":"get snapshot","method":"GET","ret":"bin","url":"http://devices.webofthings.io:9090/snapshot.cgi?user=snapshots&pwd=4MXfTSr0gH","x":782,"y":173,"z":"97f3d27.f8926b","wires":[["dfd3a912.773cb"]]},{"id":"cad032de.88225","type":"inject","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":126,"y":229,"z":"97f3d27.f8926b","wires":[["790d4158.a29f8"]]},{"id":"a6dfd96f.782b","type":"twitter out","twitter":"","name":"Tweet","x":1139,"y":148,"z":"97f3d27.f8926b","wires":[]},{"id":"dfd3a912.773cb","type":"function","name":"copy image","func":"msg.media = msg.payload;\nmsg.payload = 'Intruder Detected!';\nreturn msg;","outputs":1,"noerr":0,"x":985,"y":153,"z":"97f3d27.f8926b","wires":[["a6dfd96f.782b"]]},{"id":"1532e2c2.b307bd","type":"http request","name":"get PIR","method":"GET","ret":"obj","url":"http://localhost:8484/properties/pir","x":374,"y":98,"z":"97f3d27.f8926b","wires":[["79d3d9fa.e2ae18","e4e3834f.36ea8"]]},{"id":"790d4158.a29f8","type":"function","name":"add headers","func":"msg.headers = {\n'Accept' : 'application/json',\n'Authorization' : 'cKXRTaRylYWQiF3MICaKndG4WJMcVLFz'\n}\nreturn msg;","outputs":1,"noerr":0,"x":235,"y":143,"z":"97f3d27.f8926b","wires":[["1532e2c2.b307bd"]]},{"id":"79d3d9fa.e2ae18","type":"function","name":"check PIR value","func":"if(msg.payload[0].presence) {\n return msg;\n}\nelse return null;","outputs":1,"noerr":0,"x":589,"y":180,"z":"97f3d27.f8926b","wires":[["885a0bcd.7ad9a8"]]},{"id":"a6601dbf.1d9e3","type":"http request","name":"turn led on/off","method":"POST","ret":"obj","url":"http://localhost:8484/actions/ledState","x":780,"y":249,"z":"97f3d27.f8926b","wires":[["cfdc357b.2e6338"]]},{"id":"cfdc357b.2e6338","type":"debug","name":"","active":true,"console":"false","complete":"true","x":977,"y":220,"z":"97f3d27.f8926b","wires":[]},{"id":"e4e3834f.36ea8","type":"function","name":"prepare led msg","func":"msg.headers = {\n'Accept' : 'application/json',\n'Content-Type' : 'application/json',\n'Authorization' : 'cKXRTaRylYWQiF3MICaKndG4WJMcVLFz'\n}\nif(msg.payload) {\n msg.payload = {\"ledId\" : 1, \"state\" : true};\n return msg;\n} else {\n msg.payload = {\"ledId\" : 1, \"state\" : false};\n return msg;\n}\n","outputs":1,"noerr":0,"x":571,"y":238,"z":"97f3d27.f8926b","wires":[["a6601dbf.1d9e3"]]}] -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-2.3-websockets-temp-graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/plugins/internal/DHT22SensorPlugin.js: -------------------------------------------------------------------------------- 1 | var resources = require('./../../resources/model'), 2 | utils = require('./../../utils/utils.js'); 3 | 4 | var interval, sensor; 5 | var model = resources.pi.sensors; 6 | var pluginName = 'Temperature & Humidity'; 7 | var localParams = {'simulate': false, 'frequency': 5000}; 8 | 9 | exports.start = function (params) { 10 | localParams = params; 11 | if (params.simulate) { 12 | simulate(); 13 | } else { 14 | connectHardware(); 15 | } 16 | }; 17 | 18 | exports.stop = function () { 19 | if (params.simulate) { 20 | clearInterval(interval); 21 | } else { 22 | sensor.unexport(); 23 | } 24 | console.info('%s plugin stopped!', pluginName); 25 | }; 26 | 27 | function connectHardware() { 28 | var sensorDriver = require('node-dht-sensor'); 29 | var sensor = { 30 | initialize: function () { 31 | return sensorDriver.initialize(22, model.temperature.gpio); //#A 32 | }, 33 | read: function () { 34 | var readout = sensorDriver.read(); //#B 35 | model.temperature.value = parseFloat(readout.temperature.toFixed(2)); 36 | model.humidity.value = parseFloat(readout.humidity.toFixed(2)); //#C 37 | showValue(); 38 | 39 | setTimeout(function () { 40 | sensor.read(); //#D 41 | }, localParams.frequency); 42 | } 43 | }; 44 | if (sensor.initialize()) { 45 | console.info('Hardware %s sensor started!', pluginName); 46 | sensor.read(); 47 | } else { 48 | console.warn('Failed to initialize sensor!'); 49 | } 50 | }; 51 | 52 | function simulate() { 53 | interval = setInterval(function () { 54 | model.temperature.value = utils.randomInt(0, 40); 55 | model.humidity.value = utils.randomInt(0, 100); 56 | showValue(); 57 | }, localParams.frequency); 58 | console.info('Simulated %s sensor started!', pluginName); 59 | }; 60 | 61 | function showValue() { 62 | console.info('Temperature: %s C, humidity %s \%', 63 | model.temperature.value, model.humidity.value); 64 | }; 65 | 66 | //#A Initialize the driver for DHT22 on GPIO 12 (as specified in the model) 67 | //#B Fetch the values from the sensors 68 | //#C Update the model with the new temperature and humidity values; note that all observers will be notified 69 | //#D Because the driver doesn’t provide interrupts, you poll the sensors for new values on a regular basis with a regular timeout function and set sensor.read() as a callback 70 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/parse-device-full.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Exercise 4 - Parse a Device 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Browse a new device

16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 |

HTTP Response headers

26 |
 
27 | 		
28 | 29 |
30 |

JSON Response

31 |
 
32 | 		
33 | 34 |

Device Metadata

35 |

Metadata (general model used by this device) :

36 |

Doc (human-readable data for this device):

37 | 38 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/middleware/auth.js: -------------------------------------------------------------------------------- 1 | var acl = require('../config/acl.json'), //#A 2 | https = require('https'), 3 | fs = require('fs'); 4 | 5 | exports.socialTokenAuth = function () { 6 | return function (req, res, next) { 7 | if (isOpen(req.path)) { //#B 8 | next(); 9 | } else { 10 | var token = req.body.token || req.get('authorization') || req.query.token; 11 | if (!token) { 12 | return res.status(401).send({success: false, message: 'API token missing.'}); 13 | } else { 14 | checkUserAcl(token, req.path, function (err, user) { //#C 15 | if (err) { 16 | return res.status(403).send({success: false, message: err}); //#D 17 | } 18 | next(); //#E 19 | }); 20 | } 21 | } 22 | } 23 | }; 24 | 25 | function checkUserAcl(token, path, callback) { //#F 26 | var userAcl = findInAcl(function (current) { 27 | return current.token === token && current.resources.indexOf(path) !== -1; 28 | }); 29 | if (userAcl) { 30 | callback(null, userAcl); 31 | } else { 32 | callback('Not authorized for this resource!', null); 33 | } 34 | }; 35 | function findInAcl(filter) { 36 | return acl.protected.filter(filter)[0]; 37 | }; 38 | 39 | 40 | function isOpen(path) { //#G 41 | // Access to any CSS is open... 42 | if (path.substring(0, 5) === "/css/") return true; 43 | 44 | // Is the path an open path? 45 | if (acl.open.indexOf(path) !== -1) return true; 46 | } 47 | 48 | exports.checkUser = checkUser; 49 | function checkUser(socialUserId, token, callback) { //#H 50 | var result = findInAcl(function (current) { 51 | return current.uid === socialUserId; //#I 52 | }); 53 | if (result) { 54 | result.token = token; //#J 55 | callback(null, result); 56 | } else { 57 | callback('User ' + socialUserId + ' not found! Did you add it to acl.json?', null); 58 | } 59 | }; 60 | 61 | exports.getToken = getToken; 62 | function getToken(socialUserId, callback) { 63 | var result = findInAcl(function (current) { 64 | return current.uid === socialUserId; 65 | }); 66 | if (result) { 67 | callback(null, result); 68 | } else { 69 | callback('User ' + socialUserId + ' not found! Did you add it to acl.json?', null); 70 | } 71 | }; 72 | 73 | 74 | //#A Require your ACL config file 75 | //#B If the request is for an open path, call the next middleware 76 | //#C Otherwise, get the access token and check the ACL for this token 77 | //#D If there’s an error, return a 403 Forbidden status code 78 | //#E Otherwise, the user is good to go, and you call the next middleware 79 | //#F Can we find a user with the given token and the given path, for example, /temp? 80 | //#G Handle open resources 81 | //#H Called by facebook.js when a user is authenticated 82 | //#I If the user ID you got from Facebook is present in your ACL, you have a winner! 83 | //#J Store the user token to allow them to make subsequent calls to resources they can access 84 | -------------------------------------------------------------------------------- /chapter7-implementation/part1-2-direct-gateway/plugins/external/coapPlugin.js: -------------------------------------------------------------------------------- 1 | var utils = require('./../../utils/utils.js'), 2 | resources = require('./../../resources/model'); 3 | 4 | var interval, me, pluginName, pollInterval; 5 | var localParams = {'simulate': false, 'frequency': 5000}; 6 | 7 | function connectHardware() { 8 | var coap = require('coap'), 9 | bl = require('bl'); //#A 10 | 11 | var sensor = { 12 | read: function () { //#B 13 | coap 14 | .request({ //#C 15 | host: 'localhost', 16 | port: 5683, 17 | pathname: '/co2', 18 | options: {'Accept': 'application/json'} 19 | }) 20 | .on('response', function (res) { //#D 21 | console.info('CoAP response code', res.code); 22 | if (res.code !== '2.05') 23 | console.log("Error while contacting CoAP service: %s", res.code); 24 | res.pipe(bl(function (err, data) { //#E 25 | var json = JSON.parse(data); 26 | me.value = json.co2; 27 | showValue(); 28 | })); 29 | }) 30 | .end(); 31 | } 32 | }; 33 | pollInterval = setInterval(function () { //#F 34 | sensor.read(); 35 | }, localParams.frequency); 36 | }; 37 | 38 | function configure() { //#G 39 | utils.addDevice('coapDevice', 'A CoAP Device', 40 | 'A CoAP Device', 41 | { 42 | 'co2': { 43 | 'name': 'CO2 Sensor', 44 | 'description' : 'An ambient CO2 sensor', 45 | 'unit': 'ppm', 46 | 'value': 0 47 | } 48 | }); 49 | me = resources.things.coapDevice.sensors.co2; 50 | pluginName = resources.things.coapDevice.name; 51 | }; 52 | //#A Require the CoAP and BL library, a Buffer helper 53 | //#B Create a sensor object and give it a read function 54 | //#C The read function wraps a coap over UDP request with the enclosed parameters; replace localhost with the IP of the machine you’re simulating the CoAP device from (e.g., your laptop) 55 | //#D When CoAP device sends the result, the on response event is triggered 56 | //#E Fetch the results and update the model 57 | //#F Poll the CoAP device for new CO2 readings on a regular basis 58 | //#G Add the resources managed by this plugin to the model 59 | 60 | 61 | exports.start = function (params, app) { 62 | localParams = params; 63 | configure(app); 64 | 65 | if (params.simulate) { 66 | simulate(); 67 | } else { 68 | connectHardware(); 69 | } 70 | }; 71 | 72 | exports.stop = function () { 73 | if (params.simulate) { 74 | clearInterval(interval); 75 | } else { 76 | clearInterval(pollInterval); 77 | } 78 | console.info('%s plugin stopped!', pluginName); 79 | }; 80 | 81 | function simulate() { 82 | interval = setInterval(function () { 83 | me.value = utils.randomInt(0, 1000); 84 | showValue(); 85 | }, localParams.frequency); 86 | console.info('Simulated %s sensor started!', pluginName); 87 | }; 88 | 89 | function showValue() { 90 | console.info('CO2 Level: %s ppm', me.value); 91 | }; -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/plug-with-control.js: -------------------------------------------------------------------------------- 1 | var mqtt = require('mqtt'); 2 | var config = require('./config.json'); 3 | 4 | var thngId=config.thngId; 5 | var thngUrl='/thngs/'+thngId; 6 | var thngApiKey=config.thngApiKey; 7 | 8 | var status=false; 9 | var updateInterval; 10 | 11 | var client = mqtt.connect("mqtts://mqtt.evrythng.com:8883", { 12 | username: 'authorization', 13 | password: thngApiKey 14 | }); 15 | 16 | client.on('connect', function () { 17 | client.subscribe(thngUrl+'/properties/'); 18 | client.subscribe(thngUrl+'/actions/all'); // #A 19 | updateProperty('livenow',true); 20 | if (! updateInterval) updateInterval = setInterval(updateProperties, 5000); 21 | }); 22 | 23 | client.on('message', function(topic, message) { 24 | var resources = topic.split('/'); 25 | if (resources[1] && resources[1] === "thngs"){ // #B 26 | if (resources[2] && resources[2] === thngId){ // #C 27 | if (resources[3] && resources[3] === "properties"){ //#D 28 | var property = JSON.parse(message); 29 | console.log('Property was updated: '+property[0].key+'='+property[0].value); 30 | } else if (resources[3] && resources[3] === "actions"){ //#E 31 | var action = JSON.parse(message); 32 | handleAction(action); 33 | } 34 | } 35 | } 36 | }); 37 | 38 | function handleAction(action) { 39 | switch(action.type) { // #F 40 | case '_setStatus': 41 | console.log('ACTION: _setStatus changed to: '+action.customFields.status); // #G 42 | status=Boolean(action.customFields.status); 43 | updateProperty ('status',status); 44 | /* Do something else too */ 45 | break; 46 | case '_setLevel': 47 | console.log('ACTION: _setLevel changed to: '+action.customFields.level); 48 | break; 49 | default: 50 | console.log('ACTION: Unknown action type: '+action.type); 51 | break; 52 | } 53 | } 54 | 55 | //#A Subscribe to all actions on this thing 56 | //#B Verify if the MQTT message is on a Thng 57 | //#C Verify if the message is for the current Thng 58 | //#D Check if a property was changed; if so display it 59 | //#E Was it an action? If so call handleAction() 60 | //#F Check the type of this action 61 | //#G If action type is _setStatus, display the new value and do something with it 62 | 63 | 64 | function updateProperties() { 65 | var voltage = (219.5 + Math.random()).toFixed(3); // #H 66 | updateProperty ('voltage',voltage); 67 | 68 | var current = (Math.random()*10).toFixed(3); // #I 69 | if (status==false) current = 0.001; 70 | updateProperty ('current',current); 71 | 72 | var power = (voltage * current * (0.6+Math.random()/10)).toFixed(3); // #J 73 | updateProperty ('power',power); 74 | } 75 | 76 | function updateProperty(property,value) { 77 | client.publish(thngUrl+'/properties/'+property, '[{"value": '+value+'}]'); 78 | } 79 | 80 | process.on('SIGINT', function() { 81 | updateProperty('livenow',false); 82 | clearInterval(updateInterval); 83 | client.end(); 84 | process.exit(); 85 | }); -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-4-parse-device.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 4 - Parse a Device 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Browse a new device

17 | 18 |
19 | 20 | 21 |
22 | 23 |

Device Metadata

24 | 25 |

26 | Metadata. A general model used by this device can be found here:

27 |

28 | 29 |

30 | Documentation. A human-readable documentation specifically for this device can be found here:

31 |

32 | 33 |

34 | Sensors. The sensors offered by this device: 35 | 36 |

37 |

38 |
    39 |
40 | 41 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /chapter2-hello-wot/client/ex-5-mashup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My first WoT Mashup! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 75 |

Compare my Weather WoT Mashup

76 |

77 |

78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /chapter2-hello-wot/requests/responses.txt: -------------------------------------------------------------------------------- 1 | { 2 | "pi": { 3 | "id": "1", 4 | "name": "My WoT Raspberry PI", 5 | "description": "A simple WoT-connected Raspberry PI for the WoT book.", 6 | "url": "http://devices.webofthings.io/pi/", 7 | "currentStatus": "Live", 8 | "version": "v0.1", 9 | "tags": [ 10 | "raspberry", 11 | "pi", 12 | "WoT" 13 | ], 14 | "ip": "todo", 15 | "resources": { 16 | "sensors": { 17 | "url": "sensors/", 18 | "name": "The list of sensors" 19 | }, 20 | "actuators": { 21 | "url": "actuators/", 22 | "name": "The list of actuators" 23 | } 24 | }, 25 | "links": { 26 | "meta": { 27 | "rel": "http://webofthings.io/meta/device/", 28 | "title": "Metadata" 29 | }, 30 | "self": { 31 | "rel": "self/", 32 | "title": "Self" 33 | }, 34 | "doc": { 35 | "rel": "http://webofthings.io/docs/pi/", 36 | "title": "Documentation" 37 | }, 38 | "ui": { 39 | "rel": "ui/", 40 | "title": "User Interface" 41 | } 42 | } 43 | }, 44 | "camera": { 45 | "id": "2", 46 | "name": "My WoT Camera", 47 | "description": "A simple WoT-connected camera.", 48 | "url": "http://devices.webofthings.io/camera/", 49 | "currentStatus": "Live", 50 | "version": "v0.1", 51 | "tags": [ 52 | "camera", 53 | "WoT" 54 | ], 55 | "ip": "todo", 56 | "resources": { 57 | "sensors": { 58 | "url": "sensors/", 59 | "name": "The list of sensors" 60 | }, 61 | "actuators": { 62 | "url": "actuators/", 63 | "name": "The list of actuators" 64 | } 65 | }, 66 | "links": { 67 | "meta": { 68 | "rel": "http://webofthings.io/meta/device/", 69 | "title": "Metadata" 70 | }, 71 | "doc": { 72 | "rel": "http://webofthings.io/docs/camera/", 73 | "title": "Documentation" 74 | }, 75 | "ui": { 76 | "rel": "ui", 77 | "title": "User Interface" 78 | } 79 | } 80 | } 81 | } 82 | 83 | 84 | 85 | /// Shorter 86 | 87 | { 88 | "pi": { 89 | "id": "1", 90 | "name": "My WoT Raspberry PI", 91 | "description": "A simple WoT-connected Raspberry PI for the WoT book.", 92 | "url": "http://devices.webofthings.io/pi/", 93 | "currentStatus": "Live", 94 | "version": "v0.1", 95 | "tags": ["raspberry","pi","WoT"], 96 | "resources": {}, 97 | "links": {} 98 | }, 99 | "camera": { 100 | "id": "2", 101 | "name": "My WoT Camera", 102 | "description": "A simple WoT-connected camera.", 103 | "url": "http://devices.webofthings.io/camera/", 104 | "currentStatus": "Live", 105 | "version": "v0.1", 106 | "tags": ["camera","WoT"], 107 | "resources": {}, 108 | "links": {} 109 | } 110 | } 111 | 112 | 113 | 114 | /// Step 3 Get the sensors 115 | 116 | { 117 | "temperature": { 118 | "name": "Temperature Sensor", 119 | "description": "A temperature sensor.", 120 | "type": "float", 121 | "unit": "celsius", 122 | "value": 23.4, 123 | "timestamp": "2015-10-04T14:39:17.240Z", 124 | "frequency": 5000 125 | }, 126 | "humidity": { 127 | "name": "Humidity Sensor", 128 | "description": "A temperature sensor.", 129 | "type": "float", 130 | "unit": "percent", 131 | "value": 38.9, 132 | "timestamp": "2015-10-04T14:39:17.240Z", 133 | "frequency": 5000 134 | }, 135 | "pir": { 136 | "name": "Passive Infrared", 137 | "description": "A passive infrared sensor. When true someone is present.", 138 | "type": "boolean", 139 | "value": true, 140 | "timestamp": "2015-10-04T14:39:17.240Z", 141 | "gpio": 20 142 | } 143 | } 144 | 145 | 146 | "resources": { 147 | "sensors": { 148 | "url": "sensors/", 149 | "name": "The list of sensors" 150 | }, 151 | "actuators": { 152 | "url": "actuators/", 153 | "name": "The list of actuators" 154 | } 155 | }, 156 | 157 | 158 | -------------------------------------------------------------------------------- /chapter9-sharing/social-auth/providers/facebook.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'), 2 | util = require('util'), 3 | FacebookStrategy = require('passport-facebook').Strategy, 4 | session = require('express-session'), 5 | cookieParser = require('cookie-parser'), 6 | auth = require('../middleware/auth'), 7 | methodOverride = require('method-override'); 8 | 9 | var acl = require('../config/acl.json'); //#A 10 | var facebookAppId = '446871648832920'; //#A 11 | var facebookAppSecret = '7499c233a1e2c4d8234dedca5e6a0cc3'; //#A 12 | var socialNetworkName = 'facebook'; //#A 13 | var callbackResource = '/auth/facebook/callback'; //#A 14 | var callbackUrl = 'https://localhost:' + acl.config.sourcePort + callbackResource; //#A 15 | 16 | 17 | module.exports.setupFacebookAuth = setupFacebookAuth; 18 | function setupFacebookAuth(app) { 19 | app.use(cookieParser()); 20 | app.use(methodOverride()); 21 | app.use(session({secret: 'keyboard cat', resave: true, saveUninitialized: true})); 22 | app.use(passport.initialize()); //#B 23 | app.use(passport.session()); 24 | 25 | passport.serializeUser(function (user, done) { //#C 26 | done(null, user); 27 | }); 28 | 29 | passport.deserializeUser(function (obj, done) { 30 | done(null, obj); 31 | }); 32 | 33 | passport.use(new FacebookStrategy({ 34 | clientID: facebookAppId, //#D 35 | clientSecret: facebookAppSecret, 36 | callbackURL: callbackUrl //#E 37 | }, 38 | function (accessToken, refreshToken, profile, done) { 39 | 40 | auth.checkUser(socialId(profile.id), accessToken, function (err, res) { //#F 41 | if (err) return done(err, null); 42 | else return done(null, profile); 43 | }); 44 | })); 45 | 46 | app.get('/auth/facebook', 47 | passport.authenticate('facebook'), //#G 48 | function (req, res) {}); //#H 49 | 50 | app.get(callbackResource, //#I 51 | passport.authenticate('facebook', {session: true, failureRedirect: '/login'}), 52 | function (req, res) { 53 | res.redirect('/account'); 54 | }); 55 | 56 | app.get('/account', ensureAuthenticated, function (req, res) { //#J 57 | auth.getToken(socialId(req.user.id), function (err, user) { 58 | if (err) res.redirect('/login'); 59 | else { 60 | req.user.token = user.token; 61 | res.render('account', {user: req.user}); 62 | } 63 | }); 64 | }); 65 | 66 | function socialId(userId) { //#K 67 | return socialNetworkName + ':' + userId; 68 | }; 69 | 70 | app.get('/', ensureAuthenticated, function (req, res) { 71 | res.render('index', {user: req.user}); 72 | }); 73 | 74 | app.get('/login', function (req, res) { 75 | res.render('login', {user: req.user}); 76 | }); 77 | 78 | app.get('/logout', function (req, res) { 79 | req.logout(); 80 | res.redirect('/'); 81 | }); 82 | 83 | function ensureAuthenticated(req, res, next) { 84 | if (req.isAuthenticated()) { 85 | return next(); 86 | } 87 | res.redirect('/login'); 88 | }; 89 | 90 | }; 91 | 92 | // #A Configuration variables: FB app ID, app secret, name, and the URL to call back after a user authentication on Facebook 93 | // #B Initialize Passport and support storing the user login in sessions 94 | // #C If you had a database of users you’d use these two methods to load and save users 95 | // #D The credentials used to authenticate your auth proxy as a Facebook app 96 | // #E This URL will be called by Facebook after a successful login 97 | // #F The “verify” function, called by the framework after a successful authentication with the provider; here we check if the user is known by the proxy and store their token if so 98 | // #G Trigger the authentication process, and redirect the user to facebook.com 99 | // #H Facebook.com will redirect the user to the callbackUrl, so this function will never be called! 100 | // #I This route will be called by Facebook after user authentication. If it fails you redirect to /login, otherwise to /account 101 | // #J If the user is authenticated you get their token and display their account page; otherwise redirect to /login 102 | // #K A unique social identifier is formed by concatenating the social userId and the social network name -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## This file is a set of cURL requests used in Chapter 7 of the Book "Building the Web of Things" by Guinard & Trifa (bit.ly/wotbook) 3 | 4 | # Ensure we are running bash, if not run in bash 5 | if [ -z "$BASH_VERSION" ] 6 | then 7 | exec bash "$0" "$@" 8 | fi 9 | 10 | SERVER="https://api.evrythng.com" 11 | [ -z "$EVRYTHNG_API_KEY" ] && EVRYTHNG_API_KEY=$1 12 | 13 | mkdir -p payloads 14 | 15 | # Extract JSON content 16 | function parse_json() 17 | { 18 | echo $1 | \ 19 | sed -e 's/[{}]/''/g' | \ 20 | sed -e 's/", "/'\",\"'/g' | \ 21 | sed -e 's/" ,"/'\",\"'/g' | \ 22 | sed -e 's/" , "/'\",\"'/g' | \ 23 | sed -e 's/","/'\"---SEPARATOR---\"'/g' | \ 24 | awk -F=':' -v RS='---SEPARATOR---' "\$1~/\"$2\"/ {print}" | \ 25 | sed -e "s/\"$2\"://" | \ 26 | tr -d "\n\t" | \ 27 | sed -e 's/\\"/"/g' | \ 28 | sed -e 's/\\\\/\\/g' | \ 29 | sed -e 's/^[ \t]*//g' | \ 30 | sed -e 's/^"//' -e 's/"$//' 31 | } 32 | 33 | 34 | ##### 35 | ##### Step 0 - Let's create a config.json file which will be used later 36 | ##### 37 | echo "{" > config.json 38 | echo ' "operatorApiKey":"'$EVRYTHNG_API_KEY'",' >> config.json 39 | 40 | 41 | ##### 42 | ##### Step 1 - 1. Create a Project 43 | ##### 44 | curl -X POST "$SERVER/projects" \ 45 | -H "Authorization: $EVRYTHNG_API_KEY" \ 46 | -H "Content-Type: application/json" \ 47 | -d '{ "name": "Web of Things Book", "description": "My First WoT Project" }' > payloads/project.json 48 | 49 | # Parse the response to get the project ID 50 | PROJECT=`cat payloads/project.json` 51 | PROJECT_ID=`parse_json "$PROJECT" id` 52 | 53 | # Store the project ID in our config.json file 54 | echo "Created Project ID: $PROJECT_ID" 55 | echo "RESULT: $PROJECT" 56 | echo ' "projectId":"'$PROJECT_ID'",' >> config.json 57 | 58 | 59 | ##### 60 | ##### Step 1 - 2. Let's create an application within this project 61 | ##### 62 | curl -X POST "$SERVER/projects/$PROJECT_ID/applications" \ 63 | -H "Authorization: $EVRYTHNG_API_KEY" \ 64 | -H "Content-Type: application/json" \ 65 | -d '{ "name": "My Awesome WoT App", "description": "My First WoT Client Application","tags":["WoT","device","plug","energy"], "socialNetworks": {} }' > payloads/app.json 66 | 67 | # Parse the response to get the app ID and app API key 68 | APP=`cat payloads/app.json` 69 | APP_ID=`parse_json "$APP" id` 70 | APP_API_KEY=`parse_json "$APP" appApiKey` 71 | 72 | # Store the app ID and app API key in our config.json file 73 | echo "Created App ID: $APP_ID" 74 | echo "RESULT: $APP" 75 | echo ' "appId":"'$APP_ID'",' >> config.json 76 | echo ' "appApiKey":"'$APP_API_KEY'",' >> config.json 77 | 78 | 79 | 80 | 81 | ##### 82 | ##### Step 2 - 1. Let's now create a product within this project 83 | ##### 84 | curl -X POST "$SERVER/products?project=$PROJECT_ID" \ 85 | -H "Authorization: $EVRYTHNG_API_KEY" \ 86 | -H "Content-Type: application/json" \ 87 | -d '{ "fn": "WoT Smart Plug", "description": "A Web-connected Smart Plug","tags":["WoT","device","energy"],"photos":["https://webofthings.github.io/files/plug.jpg"] }' > payloads/product.json 88 | 89 | # Parse the response to get the product ID 90 | PRODUCT=`cat payloads/product.json` 91 | PRODUCT_ID=`parse_json "$PRODUCT" id` 92 | 93 | # Store the product ID in our config.json file 94 | echo "Created Product ID: $PRODUCT_ID" 95 | echo "RESULT: $PRODUCT" 96 | echo ' "productId":"'$PRODUCT_ID'",' >> config.json 97 | 98 | 99 | # Find this product in this project - You should see your product here 100 | curl -X GET "$SERVER/products/$PRODUCT_ID?project=$PROJECT_ID" \ 101 | -H "Authorization: $EVRYTHNG_API_KEY" \ 102 | -H "Accept: application/json" 103 | 104 | 105 | 106 | 107 | ##### 108 | ##### Step 2 - 2. Let's now create a thng (an instance of the product) within this project 109 | ##### 110 | curl -X POST "$SERVER/thngs?project=$PROJECT_ID" \ 111 | -H "Authorization: $EVRYTHNG_API_KEY" \ 112 | -H "Content-Type: application/json" \ 113 | -d '{ "name": "My WoT Plug", "product":"'$PRODUCT_ID'", "description": "My own Smart Plug","tags":["WoT","device","plug","energy"] }' > payloads/thng.json 114 | 115 | 116 | # Parse the response to get the thng ID 117 | THNG=`cat payloads/thng.json` 118 | THNG_ID=`parse_json "$THNG" id` 119 | 120 | 121 | # Store the thng ID in our config.json file 122 | echo "Created Thng ID: $THNG_ID" 123 | echo "RESULT: $THNG" 124 | echo ' "thngId":"'$THNG_ID'",' >> config.json 125 | 126 | 127 | 128 | ##### 129 | ##### Step 3 - Let's now create an API Key for this device 130 | ##### 131 | curl -X POST "$SERVER/auth/evrythng/thngs" \ 132 | -H "Authorization: $EVRYTHNG_API_KEY" \ 133 | -H "Content-Type: application/json" \ 134 | -d '{ "thngId": "'$THNG_ID'" }' > payloads/deviceApiKey.json 135 | 136 | # Parse the response to get the device API Key 137 | THNG_API=`cat payloads/deviceApiKey.json` 138 | THNG_API_KEY=`parse_json "$THNG_API" thngApiKey` 139 | 140 | 141 | # Store the device API Key in our config.json file 142 | echo ' "thngApiKey":"'$THNG_API_KEY'"' >> config.json 143 | 144 | # Close the file 145 | echo '}' >> config.json 146 | 147 | 148 | 149 | 150 | ##### 151 | ##### Step 4 - Let's update two properties of this thng 152 | ##### 153 | curl -X POST "$SERVER/thngs/$THNG_ID/properties" \ 154 | -H "Authorization: $THNG_API_KEY" \ 155 | -H "Content-Type: application/json" \ 156 | -d '[ 157 | { 158 | "key": "status", 159 | "value": true 160 | }, 161 | { 162 | "key": "power", 163 | "value": 71 164 | } 165 | ]' 166 | 167 | 168 | # Let's update this Thng a few times with random values 169 | for i in {1..5} 170 | do 171 | curl -X POST "$SERVER/thngs/$THNG_ID/properties" \ 172 | -H "Authorization: $THNG_API_KEY" \ 173 | -H "Content-Type: application/json" \ 174 | -d '[{"key": "voltage","value": '$(( $RANDOM%200 ))'},{"key": "current","value": '$(( $RANDOM%100 ))'},{"key": "power","value": '$(( $RANDOM%400 ))'}]' 175 | sleep 2 176 | done 177 | 178 | 179 | 180 | ##### 181 | ##### Section 7.4.3 - Let's use actions to control our plug 182 | ##### 183 | 184 | # First, we create a new action type 185 | # NOTE: obviously, this would fail if you have already run the script and this action type already exists in your account! 186 | curl -X POST "$SERVER/actions?project=$PROJECT_ID" \ 187 | -H "Authorization: $EVRYTHNG_API_KEY" \ 188 | -H "Content-Type: application/json" \ 189 | -d '{ "name": "_setStatus", "description": "Changes the Status of the Thng","tags":["WoT","device"] }' > payloads/setStatus.json 190 | 191 | 192 | # Creates a new instance of this action type (= sends a command to the device) 193 | curl -X POST "$SERVER/actions/_setStatus?project=$PROJECT_ID" \ 194 | -H "Authorization: $EVRYTHNG_API_KEY" \ 195 | -H "Content-Type: application/json" \ 196 | -d '{ "type": "_setStatus", "thng":"'$THNG_ID'", "customFields": {"status":false} }' 197 | 198 | ##### 199 | ##### Section 7.4.4 - Let's create a redirection for this app 200 | ##### 201 | curl -X POST "https://tn.gg/redirections" \ 202 | -H "Authorization: $EVRYTHNG_API_KEY" \ 203 | -H "Content-Type: application/json" \ 204 | -H "Accept: application/json" \ 205 | -d '{ "type": "thng", "evrythngId":"'$THNG_ID'", "defaultRedirectUrl":"http://webofthings.github.io/wot-book/plug.html?thngId={evrythngId}&key='$EVRYTHNG_API_KEY'" }' 206 | -------------------------------------------------------------------------------- /chapter10-mashups/UI/UI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 191 | 192 | WoT Controller 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 |
208 |
209 |

WoT UI

210 |

Control any Web Thing!

211 |
212 |
213 | 214 |
215 |
216 |
217 |
218 | 219 |

Smart Plug

220 |

221 | 222 |
223 |

Actions

224 |
225 |
226 | 227 | 228 |
229 |

Properties

230 |
231 |
232 | 233 | 234 | 261 | 262 |
263 | 264 |
265 |
266 | 267 | 269 |
270 |
271 | 272 |

Built with the amazing Bootstrap.

273 | 274 |
275 |
276 | 277 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /chapter7-implementation/part3-cloud/client/plug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 52 | 53 | Web Thing Controller 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
90 |

Web Thing Controller

91 |

Control Web Things via EVRYTHNG.

92 |
93 |
94 | 95 |
96 |
97 |
98 |
99 | 100 |

My WoT Plug

101 | 102 |
103 |

Actions

104 |
105 |
_setStatus
106 | 107 | 123 |
124 | 125 | 126 |
Response
127 | 128 |
129 |
130 |
131 |

DEBUG (Show HTTP Response)

132 |
133 |
134 |

135 |                 
136 |
137 |
138 | 139 |
140 |

Properties

141 | 142 |
    143 |
  • 144 | 0.0 145 | power 146 |
  • 147 |
148 | 149 | 150 | 151 | 152 | 269 | 270 | 271 |
    272 |
  • 273 | 0 274 | voltage 275 |
  • 276 |
  • 277 | 0 278 | current 279 |
  • 280 |
  • 281 | false 282 | status 283 |
  • 284 |
285 | 286 | 309 | 310 | 337 | 338 |
339 | 340 |
341 |
342 | 343 | 345 |
346 |
347 | 348 |

Built with the amazing D3, Bootstrap, and bootstraptoggle.

349 | 350 |
351 |
352 | 353 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /chapter3-node-js/hello-modules/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | 81 | 82 | 83 | true 84 | 85 | 86 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 120 | 121 | 122 | 123 | 126 | 127 | 130 | 131 | 132 | 133 | 136 | 137 | 140 | 141 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 1423914727342 207 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 240 | 243 | 244 | 245 | 247 | 248 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /chapter10-mashups/UI/files/docs.min.css: -------------------------------------------------------------------------------- 1 | .hll{background-color:#ffc}.c{color:#999}.err{color:#A00;background-color:#FAA}.k{color:#069}.o{color:#555}.cm{color:#999}.cp{color:#099}.c1{color:#999}.cs{color:#999}.gd{background-color:#FCC;border:1px solid #C00}.ge{font-style:italic}.gr{color:red}.gh{color:#030}.gi{background-color:#CFC;border:1px solid #0C0}.go{color:#AAA}.gp{color:#009}.gu{color:#030}.gt{color:#9C6}.kc{color:#069}.kd{color:#069}.kn{color:#069}.kp{color:#069}.kr{color:#069}.kt{color:#078}.m{color:#F60}.s{color:#d44950}.na{color:#4f9fcf}.nb{color:#366}.nc{color:#0A8}.no{color:#360}.nd{color:#99F}.ni{color:#999}.ne{color:#C00}.nf{color:#C0F}.nl{color:#99F}.nn{color:#0CF}.nt{color:#2f6f9f}.nv{color:#033}.ow{color:#000}.w{color:#bbb}.mf{color:#F60}.mh{color:#F60}.mi{color:#F60}.mo{color:#F60}.sb{color:#C30}.sc{color:#C30}.sd{color:#C30;font-style:italic}.s2{color:#C30}.se{color:#C30}.sh{color:#C30}.si{color:#A00}.sx{color:#C30}.sr{color:#3AA}.s1{color:#C30}.ss{color:#FC3}.bp{color:#366}.vc{color:#033}.vg{color:#033}.vi{color:#033}.il{color:#F60}.css .nt+.nt,.css .o,.css .o+.nt{color:#999}/*! 2 | * Bootstrap Docs (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 5 | * details, see https://creativecommons.org/licenses/by/3.0/. 6 | */body{position:relative}.table code{font-size:13px;font-weight:400}h2 code,h3 code,h4 code{background-color:inherit}.btn-outline{color:#563d7c;background-color:transparent;border-color:#563d7c}.btn-outline:active,.btn-outline:focus,.btn-outline:hover{color:#fff;background-color:#563d7c;border-color:#563d7c}.btn-outline-inverse{color:#fff;background-color:transparent;border-color:#cdbfe3}.btn-outline-inverse:active,.btn-outline-inverse:focus,.btn-outline-inverse:hover{color:#563d7c;text-shadow:none;background-color:#fff;border-color:#fff}.bs-docs-booticon{display:block;font-weight:500;color:#fff;text-align:center;cursor:default;background-color:#563d7c;border-radius:15%}.bs-docs-booticon-sm{width:30px;height:30px;font-size:20px;line-height:28px}.bs-docs-booticon-lg{width:144px;height:144px;font-size:108px;line-height:140px}.bs-docs-booticon-inverse{color:#563d7c;background-color:#fff}.bs-docs-booticon-outline{background-color:transparent;border:1px solid #cdbfe3}#skippy{display:block;padding:1em;color:#fff;background-color:#6f5499;outline:0}#skippy .skiplink-text{padding:.5em;outline:1px dotted}#content:focus{outline:0}.bs-docs-nav{margin-bottom:0;background-color:#fff;border-bottom:0}.bs-home-nav .bs-nav-b{display:none}.bs-docs-nav .navbar-brand,.bs-docs-nav .navbar-nav>li>a{font-weight:500;color:#563d7c}.bs-docs-nav .navbar-nav>.active>a,.bs-docs-nav .navbar-nav>.active>a:hover,.bs-docs-nav .navbar-nav>li>a:hover{color:#463265;background-color:#f9f9f9}.bs-docs-nav .navbar-toggle .icon-bar{background-color:#563d7c}.bs-docs-nav .navbar-header .navbar-toggle{border-color:#fff}.bs-docs-nav .navbar-header .navbar-toggle:focus,.bs-docs-nav .navbar-header .navbar-toggle:hover{background-color:#f9f9f9;border-color:#f9f9f9}.bs-docs-footer{padding-top:40px;padding-bottom:40px;margin-top:100px;color:#767676;text-align:center;border-top:1px solid #e5e5e5}.bs-docs-footer-links{padding-left:0;margin-top:20px}.bs-docs-footer-links li{display:inline;padding:0 2px}.bs-docs-footer-links li:first-child{padding-left:0}@media (min-width:768px){.bs-docs-footer p{margin-bottom:0}}.bs-docs-social{margin-bottom:20px;text-align:center}.bs-docs-social-buttons{display:inline-block;padding-left:0;margin-bottom:0;list-style:none}.bs-docs-social-buttons li{display:inline-block;padding:5px 8px;line-height:1}.bs-docs-social-buttons .twitter-follow-button{width:225px!important}.bs-docs-social-buttons .twitter-share-button{width:98px!important}.github-btn{overflow:hidden;border:0}.bs-docs-header,.bs-docs-masthead{position:relative;padding:30px 0;color:#cdbfe3;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.1);background-color:#6f5499;background-image:-webkit-gradient(linear,left top,left bottom,from(#563d7c),to(#6f5499));background-image:-webkit-linear-gradient(top,#563d7c 0,#6f5499 100%);background-image:-o-linear-gradient(top,#563d7c 0,#6f5499 100%);background-image:linear-gradient(to bottom,#563d7c 0,#6f5499 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#563d7c', endColorstr='#6F5499', GradientType=0);background-repeat:repeat-x}.bs-docs-masthead .bs-docs-booticon{margin:0 auto 30px}.bs-docs-masthead h1{font-weight:300;line-height:1;color:#fff}.bs-docs-masthead .lead{margin:0 auto 30px;font-size:20px;color:#fff}.bs-docs-masthead .version{margin-top:-15px;margin-bottom:30px;color:#9783b9}.bs-docs-masthead .btn{width:100%;padding:15px 30px;font-size:20px}@media (min-width:480px){.bs-docs-masthead .btn{width:auto}}@media (min-width:768px){.bs-docs-masthead{padding:80px 0}.bs-docs-masthead h1{font-size:60px}.bs-docs-masthead .lead{font-size:24px}}@media (min-width:992px){.bs-docs-masthead .lead{width:80%;font-size:30px}}.bs-docs-header{margin-bottom:40px;font-size:20px}.bs-docs-header h1{margin-top:0;color:#fff}.bs-docs-header p{margin-bottom:0;font-weight:300;line-height:1.4}.bs-docs-header .container{position:relative}@media (min-width:768px){.bs-docs-header{padding-top:60px;padding-bottom:60px;font-size:24px;text-align:left}.bs-docs-header h1{font-size:60px;line-height:1}}@media (min-width:992px){.bs-docs-header h1,.bs-docs-header p{margin-right:380px}}.carbonad{width:auto!important;height:auto!important;padding:20px!important;margin:30px -15px -31px!important;overflow:hidden;font-size:13px!important;line-height:16px!important;text-align:left;background:0 0!important;border:solid #866ab3!important;border-width:1px 0!important}.carbonad-img{margin:0!important}.carbonad-tag,.carbonad-text{display:block!important;float:none!important;width:auto!important;height:auto!important;margin-left:145px!important;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif!important}.carbonad-text{padding-top:0!important}.carbonad-tag{color:inherit!important;text-align:left!important}.carbonad-tag a,.carbonad-text a{color:#fff!important}.carbonad #azcarbon>img{display:none}@media (min-width:480px){.carbonad{width:330px!important;margin:20px auto!important;border-width:1px!important;border-radius:4px}.bs-docs-masthead .carbonad{margin:50px auto 0!important}}@media (min-width:768px){.carbonad{margin-right:0!important;margin-left:0!important}}@media (min-width:992px){.carbonad{position:absolute;top:0;right:15px;width:330px!important;padding:15px!important;margin:0!important}.bs-docs-masthead .carbonad{position:static}}.bs-docs-featurette{padding-top:40px;padding-bottom:40px;font-size:16px;line-height:1.5;color:#555;text-align:center;background-color:#fff;border-bottom:1px solid #e5e5e5}.bs-docs-featurette+.bs-docs-footer{margin-top:0;border-top:0}.bs-docs-featurette-title{margin-bottom:5px;font-size:30px;font-weight:400;color:#333}.half-rule{width:100px;margin:40px auto}.bs-docs-featurette h3{margin-bottom:5px;font-weight:400;color:#333}.bs-docs-featurette-img{display:block;margin-bottom:20px;color:#333}.bs-docs-featurette-img:hover{color:#337ab7;text-decoration:none}.bs-docs-featurette-img img{display:block;margin-bottom:15px}@media (min-width:480px){.bs-docs-featurette .img-responsive{margin-top:30px}}@media (min-width:768px){.bs-docs-featurette{padding-top:100px;padding-bottom:100px}.bs-docs-featurette-title{font-size:40px}.bs-docs-featurette .lead{max-width:80%;margin-right:auto;margin-left:auto}.bs-docs-featurette .img-responsive{margin-top:0}}.bs-docs-featured-sites{margin-right:-1px;margin-left:-1px}.bs-docs-featured-sites .col-xs-6{padding:1px}.bs-docs-featured-sites .img-responsive{margin-top:0}@media (min-width:768px){.bs-docs-featured-sites .col-sm-3:first-child img{border-top-left-radius:4px;border-bottom-left-radius:4px}.bs-docs-featured-sites .col-sm-3:last-child img{border-top-right-radius:4px;border-bottom-right-radius:4px}}.bs-examples .thumbnail{margin-bottom:10px}.bs-examples h4{margin-bottom:5px}.bs-examples p{margin-bottom:20px}@media (max-width:480px){.bs-examples{margin-right:-10px;margin-left:-10px}.bs-examples>[class^=col-]{padding-right:10px;padding-left:10px}}.bs-docs-sidebar.affix{position:static}@media (min-width:768px){.bs-docs-sidebar{padding-left:20px}}.bs-docs-sidenav{margin-top:20px;margin-bottom:20px}.bs-docs-sidebar .nav>li>a{display:block;padding:4px 20px;font-size:13px;font-weight:500;color:#767676}.bs-docs-sidebar .nav>li>a:focus,.bs-docs-sidebar .nav>li>a:hover{padding-left:19px;color:#563d7c;text-decoration:none;background-color:transparent;border-left:1px solid #563d7c}.bs-docs-sidebar .nav>.active:focus>a,.bs-docs-sidebar .nav>.active:hover>a,.bs-docs-sidebar .nav>.active>a{padding-left:18px;font-weight:700;color:#563d7c;background-color:transparent;border-left:2px solid #563d7c}.bs-docs-sidebar .nav .nav{display:none;padding-bottom:10px}.bs-docs-sidebar .nav .nav>li>a{padding-top:1px;padding-bottom:1px;padding-left:30px;font-size:12px;font-weight:400}.bs-docs-sidebar .nav .nav>li>a:focus,.bs-docs-sidebar .nav .nav>li>a:hover{padding-left:29px}.bs-docs-sidebar .nav .nav>.active:focus>a,.bs-docs-sidebar .nav .nav>.active:hover>a,.bs-docs-sidebar .nav .nav>.active>a{padding-left:28px;font-weight:500}.back-to-top,.bs-docs-theme-toggle{display:none;padding:4px 10px;margin-top:10px;margin-left:10px;font-size:12px;font-weight:500;color:#999}.back-to-top:hover,.bs-docs-theme-toggle:hover{color:#563d7c;text-decoration:none}.bs-docs-theme-toggle{margin-top:0}@media (min-width:768px){.back-to-top,.bs-docs-theme-toggle{display:block}}@media (min-width:992px){.bs-docs-sidebar .nav>.active>ul{display:block}.bs-docs-sidebar.affix,.bs-docs-sidebar.affix-bottom{width:213px}.bs-docs-sidebar.affix{position:fixed;top:20px}.bs-docs-sidebar.affix-bottom{position:absolute}.bs-docs-sidebar.affix .bs-docs-sidenav,.bs-docs-sidebar.affix-bottom .bs-docs-sidenav{margin-top:0;margin-bottom:0}}@media (min-width:1200px){.bs-docs-sidebar.affix,.bs-docs-sidebar.affix-bottom{width:263px}}.bs-docs-section{margin-bottom:60px}.bs-docs-section:last-child{margin-bottom:0}h1[id]{padding-top:20px;margin-top:0}.bs-callout{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-radius:3px}.bs-callout h4{margin-top:0;margin-bottom:5px}.bs-callout p:last-child{margin-bottom:0}.bs-callout code{border-radius:3px}.bs-callout+.bs-callout{margin-top:-5px}.bs-callout-danger{border-left-color:#ce4844}.bs-callout-danger h4{color:#ce4844}.bs-callout-warning{border-left-color:#aa6708}.bs-callout-warning h4{color:#aa6708}.bs-callout-info{border-left-color:#1b809e}.bs-callout-info h4{color:#1b809e}.color-swatches{margin:0 -5px;overflow:hidden}.color-swatch{float:left;width:60px;height:60px;margin:0 5px;border-radius:3px}@media (min-width:768px){.color-swatch{width:100px;height:100px}}.color-swatches .gray-darker{background-color:#222}.color-swatches .gray-dark{background-color:#333}.color-swatches .gray{background-color:#555}.color-swatches .gray-light{background-color:#999}.color-swatches .gray-lighter{background-color:#eee}.color-swatches .brand-primary{background-color:#337ab7}.color-swatches .brand-success{background-color:#5cb85c}.color-swatches .brand-warning{background-color:#f0ad4e}.color-swatches .brand-danger{background-color:#d9534f}.color-swatches .brand-info{background-color:#5bc0de}.color-swatches .bs-purple{background-color:#563d7c}.color-swatches .bs-purple-light{background-color:#c7bfd3}.color-swatches .bs-purple-lighter{background-color:#e5e1ea}.color-swatches .bs-gray{background-color:#f9f9f9}.bs-team .team-member{line-height:32px;color:#555}.bs-team .team-member:hover{color:#333;text-decoration:none}.bs-team .github-btn{float:right;width:180px;height:20px;margin-top:6px}.bs-team img{float:left;width:32px;margin-right:10px;border-radius:4px}.bs-docs-browser-bugs td p{margin-bottom:0}.bs-docs-browser-bugs th:first-child{width:18%}.show-grid{margin-bottom:15px}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;background-color:#eee;background-color:rgba(86,61,124,.15);border:1px solid #ddd;border:1px solid rgba(86,61,124,.2)}.bs-example{position:relative;padding:45px 15px 15px;margin:0 -15px 15px;border-color:#e5e5e5 #eee #eee;border-style:solid;border-width:1px 0;-webkit-box-shadow:inset 0 3px 6px rgba(0,0,0,.05);box-shadow:inset 0 3px 6px rgba(0,0,0,.05)}.bs-example:after{position:absolute;top:15px;left:15px;font-size:12px;font-weight:700;color:#959595;text-transform:uppercase;letter-spacing:1px;content:"Example"}.bs-example-padded-bottom{padding-bottom:24px}.bs-example+.highlight,.bs-example+.zero-clipboard+.highlight{margin:-15px -15px 15px;border-width:0 0 1px;border-radius:0}@media (min-width:768px){.bs-example{margin-right:0;margin-left:0;background-color:#fff;border-color:#ddd;border-width:1px;border-radius:4px 4px 0 0;-webkit-box-shadow:none;box-shadow:none}.bs-example+.highlight,.bs-example+.zero-clipboard+.highlight{margin-top:-16px;margin-right:0;margin-left:0;border-width:1px;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.bs-example-standalone{border-radius:4px}}.bs-example .container{width:auto}.bs-example>.alert:last-child,.bs-example>.form-control:last-child,.bs-example>.jumbotron:last-child,.bs-example>.list-group:last-child,.bs-example>.navbar:last-child,.bs-example>.panel:last-child,.bs-example>.progress:last-child,.bs-example>.table-responsive:last-child>.table,.bs-example>.table:last-child,.bs-example>.well:last-child,.bs-example>blockquote:last-child,.bs-example>ol:last-child,.bs-example>p:last-child,.bs-example>ul:last-child{margin-bottom:0}.bs-example>p>.close{float:none}.bs-example-type .table .type-info{color:#767676;vertical-align:middle}.bs-example-type .table td{padding:15px 0;border-color:#eee}.bs-example-type .table tr:first-child td{border-top:0}.bs-example-type h1,.bs-example-type h2,.bs-example-type h3,.bs-example-type h4,.bs-example-type h5,.bs-example-type h6{margin:0}.bs-example-bg-classes p{padding:15px}.bs-example>.img-circle,.bs-example>.img-rounded,.bs-example>.img-thumbnail{margin:5px}.bs-example>.table-responsive>.table{background-color:#fff}.bs-example>.btn,.bs-example>.btn-group{margin-top:5px;margin-bottom:5px}.bs-example>.btn-toolbar+.btn-toolbar{margin-top:10px}.bs-example-control-sizing input[type=text]+input[type=text],.bs-example-control-sizing select{margin-top:10px}.bs-example-form .input-group{margin-bottom:10px}.bs-example>textarea.form-control{resize:vertical}.bs-example>.list-group{max-width:400px}.bs-example .navbar:last-child{margin-bottom:0}.bs-navbar-bottom-example,.bs-navbar-top-example{z-index:1;padding:0;overflow:hidden}.bs-navbar-bottom-example .navbar-header,.bs-navbar-top-example .navbar-header{margin-left:0}.bs-navbar-bottom-example .navbar-fixed-bottom,.bs-navbar-top-example .navbar-fixed-top{position:relative;margin-right:0;margin-left:0}.bs-navbar-top-example{padding-bottom:45px}.bs-navbar-top-example:after{top:auto;bottom:15px}.bs-navbar-top-example .navbar-fixed-top{top:-1px}.bs-navbar-bottom-example{padding-top:45px}.bs-navbar-bottom-example .navbar-fixed-bottom{bottom:-1px}.bs-navbar-bottom-example .navbar{margin-bottom:0}@media (min-width:768px){.bs-navbar-bottom-example .navbar-fixed-bottom,.bs-navbar-top-example .navbar-fixed-top{position:absolute}}.bs-example .pagination{margin-top:10px;margin-bottom:10px}.bs-example>.pager{margin-top:0}.bs-example-modal{background-color:#f5f5f5}.bs-example-modal .modal{position:relative;top:auto;right:auto;bottom:auto;left:auto;z-index:1;display:block}.bs-example-modal .modal-dialog{left:auto;margin-right:auto;margin-left:auto}.bs-example>.dropdown>.dropdown-toggle{float:left}.bs-example>.dropdown>.dropdown-menu{position:static;display:block;margin-bottom:5px;clear:left}.bs-example-tabs .nav-tabs{margin-bottom:15px}.bs-example-tooltips{text-align:center}.bs-example-tooltips>.btn{margin-top:5px;margin-bottom:5px}.bs-example-tooltip .tooltip{position:relative;display:inline-block;margin:10px 20px;opacity:1}.bs-example-popover{padding-bottom:24px;background-color:#f9f9f9}.bs-example-popover .popover{position:relative;display:block;float:left;width:260px;margin:20px}.scrollspy-example{position:relative;height:200px;margin-top:10px;overflow:auto}.bs-example>.nav-pills-stacked-example{max-width:300px}#collapseExample .well{margin-bottom:0}.bs-events-table>tbody>tr>td:first-child,.bs-events-table>thead>tr>th:first-child{white-space:nowrap}.bs-events-table>thead>tr>th:first-child{width:150px}.js-options-table>thead>tr>th:nth-child(1),.js-options-table>thead>tr>th:nth-child(2){width:100px}.js-options-table>thead>tr>th:nth-child(3){width:50px}.highlight{padding:9px 14px;margin-bottom:14px;background-color:#f7f7f9;border:1px solid #e1e1e8;border-radius:4px}.highlight pre{padding:0;margin-top:0;margin-bottom:0;word-break:normal;white-space:nowrap;background-color:transparent;border:0}.highlight pre code{font-size:inherit;color:#333}.highlight pre code:first-child{display:inline-block;padding-right:45px}.table-responsive .highlight pre{white-space:normal}.bs-table th small,.responsive-utilities th small{display:block;font-weight:400;color:#999}.responsive-utilities tbody th{font-weight:400}.responsive-utilities td{text-align:center}.responsive-utilities td.is-visible{color:#468847;background-color:#dff0d8!important}.responsive-utilities td.is-hidden{color:#ccc;background-color:#f9f9f9!important}.responsive-utilities-test{margin-top:5px}.responsive-utilities-test .col-xs-6{margin-bottom:10px}.responsive-utilities-test span{display:block;padding:15px 10px;font-size:14px;font-weight:700;line-height:1.1;text-align:center;border-radius:4px}.hidden-on .col-xs-6 .hidden-lg,.hidden-on .col-xs-6 .hidden-md,.hidden-on .col-xs-6 .hidden-sm,.hidden-on .col-xs-6 .hidden-xs,.visible-on .col-xs-6 .hidden-lg,.visible-on .col-xs-6 .hidden-md,.visible-on .col-xs-6 .hidden-sm,.visible-on .col-xs-6 .hidden-xs{color:#999;border:1px solid #ddd}.hidden-on .col-xs-6 .visible-lg-block,.hidden-on .col-xs-6 .visible-md-block,.hidden-on .col-xs-6 .visible-sm-block,.hidden-on .col-xs-6 .visible-xs-block,.visible-on .col-xs-6 .visible-lg-block,.visible-on .col-xs-6 .visible-md-block,.visible-on .col-xs-6 .visible-sm-block,.visible-on .col-xs-6 .visible-xs-block{color:#468847;background-color:#dff0d8;border:1px solid #d6e9c6}.bs-glyphicons{margin:0 -10px 20px;overflow:hidden}.bs-glyphicons-list{padding-left:0;list-style:none}.bs-glyphicons li{float:left;width:25%;height:115px;padding:10px;font-size:10px;line-height:1.4;text-align:center;background-color:#f9f9f9;border:1px solid #fff}.bs-glyphicons .glyphicon{margin-top:5px;margin-bottom:10px;font-size:24px}.bs-glyphicons .glyphicon-class{display:block;text-align:center;word-wrap:break-word}.bs-glyphicons li:hover{color:#fff;background-color:#563d7c}@media (min-width:768px){.bs-glyphicons{margin-right:0;margin-left:0}.bs-glyphicons li{width:12.5%;font-size:12px}}.bs-customizer .toggle{float:right;margin-top:25px}.bs-customizer label{margin-top:10px;font-weight:500;color:#555}.bs-customizer h2{padding-top:30px;margin-top:0;margin-bottom:5px}.bs-customizer h3{margin-bottom:0}.bs-customizer h4{margin-top:15px;margin-bottom:0}.bs-customizer .bs-callout h4{margin-top:0;margin-bottom:5px}.bs-customizer input[type=text]{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;background-color:#fafafa}.bs-customizer .help-block{margin-bottom:5px;font-size:12px}#less-section label{font-weight:400}.bs-customize-download .btn-outline{padding:20px}.bs-customizer-alert{position:fixed;top:0;right:0;left:0;z-index:1030;padding:15px 0;color:#fff;background-color:#d9534f;border-bottom:1px solid #b94441;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25);box-shadow:inset 0 1px 0 rgba(255,255,255,.25)}.bs-customizer-alert .close{margin-top:-4px;font-size:24px}.bs-customizer-alert p{margin-bottom:0}.bs-customizer-alert .glyphicon{margin-right:5px}.bs-customizer-alert pre{margin:10px 0 0;color:#fff;background-color:#a83c3a;border-color:#973634;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 2px 4px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}.bs-dropzone{position:relative;padding:20px;margin-bottom:20px;color:#777;text-align:center;border:2px dashed #eee;border-radius:4px}.bs-dropzone .import-header{margin-bottom:5px}.bs-dropzone .glyphicon-download-alt{font-size:40px}.bs-dropzone hr{width:100px}.bs-dropzone .lead{margin-bottom:10px;font-weight:400;color:#333}#import-manual-trigger{cursor:pointer}.bs-dropzone p:last-child{margin-bottom:0}.bs-brand-logos{display:table;width:100%;margin-bottom:15px;overflow:hidden;color:#563d7c;background-color:#f9f9f9;border-radius:4px}.bs-brand-item{padding:60px 0;text-align:center}.bs-brand-item+.bs-brand-item{border-top:1px solid #fff}.bs-brand-logos .inverse{color:#fff;background-color:#563d7c}.bs-brand-item h1,.bs-brand-item h3{margin-top:0;margin-bottom:0}.bs-brand-item .bs-docs-booticon{margin-right:auto;margin-left:auto}.bs-brand-item .glyphicon{width:30px;height:30px;margin:10px auto -10px;line-height:30px;color:#fff;border-radius:50%}.bs-brand-item .glyphicon-ok{background-color:#5cb85c}.bs-brand-item .glyphicon-remove{background-color:#d9534f}@media (min-width:768px){.bs-brand-item{display:table-cell;width:1%}.bs-brand-item+.bs-brand-item{border-top:0;border-left:1px solid #fff}.bs-brand-item h1{font-size:60px}}.zero-clipboard{position:relative;display:none}.btn-clipboard{position:absolute;top:0;right:0;z-index:10;display:block;padding:5px 8px;font-size:12px;color:#767676;cursor:pointer;background-color:#fff;border:1px solid #e1e1e8;border-radius:0 4px 0 4px}.btn-clipboard-hover{color:#fff;background-color:#563d7c;border-color:#563d7c}@media (min-width:768px){.zero-clipboard{display:block}.bs-example+.zero-clipboard .btn-clipboard{top:-16px;border-top-right-radius:0}}.anchorjs-link{color:inherit}@media (max-width:480px){.anchorjs-link{display:none}}:hover>.anchorjs-link{opacity:.75;-webkit-transition:color .16s linear;-o-transition:color .16s linear;transition:color .16s linear}.anchorjs-link:focus,:hover>.anchorjs-link:hover{text-decoration:none;opacity:1}#focusedInput{border-color:#ccc;border-color:rgba(82,168,236,.8);outline:0;outline:thin dotted\9;-webkit-box-shadow:0 0 8px rgba(82,168,236,.6);box-shadow:0 0 8px rgba(82,168,236,.6)} --------------------------------------------------------------------------------