├── .gitignore ├── LICENSE.MIT ├── README.md ├── chapter2 ├── README.md ├── basic.py ├── final.py ├── get.py ├── gpio.py ├── nginx.conf └── www │ ├── api.json │ └── index.html ├── chapter4 ├── README.md ├── connect-db.js ├── hello-world.js ├── nginx.conf ├── rest-db │ ├── README.md │ ├── app.js │ ├── db.js │ ├── package.json │ └── views │ │ └── index.jade └── restfull │ ├── README.md │ ├── app.js │ └── package.json ├── chapter5 ├── README.md ├── WebClient │ └── WebClient.ino ├── arduino-serial │ └── arduino-serial.ino ├── esp8266Client │ └── esp8266Client.ino ├── final-actuator │ └── final-actuator.ino ├── final-sensors │ └── final-sensors.ino ├── irrecive │ └── irrecive.ino ├── irsenddemo │ └── irsenddemo.ino ├── moisture │ └── moisture.ino ├── post-temperature.py ├── python-serail.py ├── rest │ ├── README.md │ ├── app.js │ ├── db.js │ ├── package.json │ └── views │ │ └── index.jade ├── temperature │ └── temperature.ino └── test-post.py ├── chapter6 ├── README.md ├── aws-example │ ├── README.md │ ├── aws.js │ ├── iot-mqtt-subscriber.py │ └── package.json ├── makeRequest.js └── rest │ ├── README.md │ ├── app.js │ ├── db.js │ ├── package.json │ ├── public │ └── scripts │ │ ├── echarts-ajax-devices.js │ │ ├── echarts-all.js │ │ ├── echarts-example.js │ │ ├── highcharts-ajax-devices.js │ │ ├── highcharts-example.js │ │ ├── highcharts.js │ │ └── jquery-2.1.4.min.js │ └── views │ ├── dashboard.jade │ └── index.jade ├── chapter7 ├── README.md ├── esp8266-mqtt │ └── esp8266-mqtt.ino └── lan │ ├── README.md │ ├── app.js │ ├── coapServer.js │ ├── db.js │ ├── mqttServer.js │ ├── package.json │ ├── public │ └── scripts │ │ ├── echarts-ajax-devices.js │ │ ├── echarts-all.js │ │ ├── echarts-example.js │ │ ├── highcharts-ajax-devices.js │ │ ├── highcharts-example.js │ │ ├── highcharts.js │ │ └── jquery-2.1.4.min.js │ └── views │ ├── dashboard.jade │ └── index.jade ├── chapter8 ├── README.md └── nlp │ ├── .editorconfig │ ├── README.md │ ├── app.js │ ├── geng.js │ └── index.html └── dashboard ├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── assets ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── images │ └── logo.png ├── javascripts │ ├── application.coffee │ ├── d3-3.2.8.js │ ├── dashing.gridster.coffee │ ├── gridster │ │ ├── jquery.gridster.js │ │ └── jquery.leanModal.min.js │ ├── jquery.knob.js │ └── rickshaw-1.4.3.min.js └── stylesheets │ ├── application.scss │ ├── font-awesome.css │ └── jquery.gridster.css ├── config.ru ├── dashboards ├── layout.erb └── sample.erb ├── jobs └── convergence.rb ├── public ├── 404.html └── favicon.ico └── widgets ├── clock ├── clock.coffee ├── clock.html └── clock.scss ├── comments ├── comments.coffee ├── comments.html └── comments.scss ├── graph ├── graph.coffee ├── graph.html └── graph.scss ├── iframe ├── iframe.coffee ├── iframe.html └── iframe.scss ├── image ├── image.coffee ├── image.html └── image.scss ├── list ├── list.coffee ├── list.html └── list.scss ├── meter ├── meter.coffee ├── meter.html └── meter.scss ├── number ├── number.coffee ├── number.html └── number.scss └── text ├── text.coffee ├── text.html └── text.scss /.gitignore: -------------------------------------------------------------------------------- 1 | **/*/.idea 2 | **/*/node_modules 3 | **/*/*.log 4 | .idea 5 | **/certs 6 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Phodal Huang, http://www.phodal.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Source Code of Design IoT 2 | 3 | 4 | ##目录及结构 5 | 6 | 目录 | 功能 | 技术栈 7 | ----------|----------------------------------|------ 8 | chapter 2 | Nginx示例、Python基础 | Nginx、Python 9 | chapter 4 | 基础Web服务器代码、RESTful服务代码 | Node.js、MongoDB 10 | chapter 5 | 硬件、传感器示例、RESTful服务代码 | Node.js、MongoDB、Arduino 11 | chapter 6 | RESTful服务器代码、趋势图代码 | Dashing 12 | chapter 7 | MQTT、CoAP协议的服务器代码 | Node.js、NodeMCU 13 | chapter 8 | 自然语言处理示例 | JavaScript 14 | app | 移动应用相关代码 | Cordova、JavaScript 15 | dashboard | 仪表盘相关代码 | Ruby、CoffeeScript 16 | 17 | APP 相关代码下载 [http://github.com/designiot/app](http://github.com/designiot/app) 18 | 19 | ##License 20 | 21 | © 2015 [Phodal Huang][phodal]. This code is distributed under the MIT license. 22 | 23 | [phodal]:http://www.phodal.com/ 24 | -------------------------------------------------------------------------------- /chapter2/README.md: -------------------------------------------------------------------------------- 1 | #物联网系统设计实战 第2章 2 | 3 | 文件 /文件夹 | 用途 4 | ------------|--------------- 5 | gpio.py | 控制Raspberry Pi GPIO示例 6 | get.py | 获取服务器Led状态 7 | basic.py | 结合网络控制Raspberry Pi 8 | final.py | 控制Raspberry Pi Led最后代码 9 | nginx.conf | Nginx配置 10 | www | HTTP服务器代码 11 | -------------------------------------------------------------------------------- /chapter2/basic.py: -------------------------------------------------------------------------------- 1 | import urllib2,json 2 | import RPi.GPIO as GPIO 3 | 4 | GPIO.setmode(GPIO.BOARD) 5 | GPIO.setup(5, GPIO.OUT) 6 | 7 | results = urllib2.urlopen('http://192.168.168.84/api.json').read() 8 | status = json.loads(results)['led'] 9 | if status == True: 10 | GPIO.output(5, GPIO.HIGH) 11 | else: 12 | GPIO.output(5, GPIO.LOW) 13 | -------------------------------------------------------------------------------- /chapter2/final.py: -------------------------------------------------------------------------------- 1 | import urllib2,json 2 | import RPi.GPIO as GPIO 3 | 4 | GPIO.setmode(GPIO.BOARD) 5 | GPIO.setup(5, GPIO.OUT) 6 | 7 | while 1: 8 | results = urllib2.urlopen('http://192.168.168.84/api.json').read() 9 | status = json.loads(results)['led'] 10 | if status == True: 11 | GPIO.output(5, GPIO.HIGH) 12 | else: 13 | GPIO.output(5, GPIO.LOW) -------------------------------------------------------------------------------- /chapter2/get.py: -------------------------------------------------------------------------------- 1 | import urllib2,json 2 | results = urllib2.urlopen('http://192.168.168.84/api.json').read() 3 | json.loads(results)['led'] -------------------------------------------------------------------------------- /chapter2/gpio.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | GPIO.setmode(GPIO.BOARD) 3 | GPIO.setup(5, GPIO.OUT) 4 | GPIO.output(5, GPIO.HIGH) 5 | GPIO.output(5, GPIO.LOW) 6 | -------------------------------------------------------------------------------- /chapter2/nginx.conf: -------------------------------------------------------------------------------- 1 | events 2 | { 3 | worker_connections 51200; 4 | } 5 | 6 | http { 7 | server { 8 | root /Users/fdhuang/designiot/chapter2/www; 9 | } 10 | } -------------------------------------------------------------------------------- /chapter2/www/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "led": true 3 | } -------------------------------------------------------------------------------- /chapter2/www/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/chapter2/www/index.html -------------------------------------------------------------------------------- /chapter4/README.md: -------------------------------------------------------------------------------- 1 | #物联网系统设计实战 第4章 2 | 3 | 文件 /文件夹 | 用途 4 | -----------------|------------ 5 | hello-world.js | Node.js HTTP服务器示例 6 | connect-db.js | 连接MongoDB示例 7 | restfull | REST API示例(不含数据库) 8 | rest-db | MongoDB Node API与前端示例 9 | -------------------------------------------------------------------------------- /chapter4/connect-db.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | var url = 'mongodb://localhost:27017/designiot'; 4 | MongoClient.connect(url, function(err, db) { 5 | console.log("Connected correctly to server"); 6 | db.close(); 7 | }); -------------------------------------------------------------------------------- /chapter4/hello-world.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | http.createServer(function (request, response) { 4 | response.writeHead(200, {'Content-Type': 'text/plain'}); 5 | response.end('{"led": true}'); 6 | }).listen(8080); 7 | 8 | console.log('Server running at http://127.0.0.1:8080/'); 9 | -------------------------------------------------------------------------------- /chapter4/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | server_name lan.designiot.cn; 5 | 6 | location / { 7 | proxy_pass http://127.0.0.1:3000; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter4/rest-db/README.md: -------------------------------------------------------------------------------- 1 | 2 | GET请求: 3 | 4 | ``` 5 | curl http://localhost:3000/api 6 | ``` 7 | 8 | POST请求: 9 | 10 | ``` 11 | curl -X PUT -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 12 | ``` 13 | 14 | PUT请求: 15 | 16 | ``` 17 | curl -X POST -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 18 | ``` 19 | 20 | DELETE请求: 21 | 22 | ``` 23 | curl -X DELETE http://localhost:3000/api 24 | ``` 25 | -------------------------------------------------------------------------------- /chapter4/rest-db/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var path = require('path'); 4 | 5 | var Database = require('./db'); 6 | var db = new Database(); 7 | 8 | var app = express(); 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({extended: true})); 11 | 12 | app.set('views', path.join(__dirname + '/', 'views')); 13 | app.set('view engine', 'jade'); 14 | 15 | app.get('/', function (req, res) { 16 | 'use strict'; 17 | res.render('index', { 18 | title: 'Home' 19 | }); 20 | }); 21 | 22 | app.post('/', function (req, res) { 23 | var userID = req.body.user; 24 | 25 | var payload = { user: userID}; 26 | var led = req.body.led === "on"; 27 | 28 | var data = { user: userID, led: led}; 29 | db.find(payload, function (results) { 30 | if (results.length > 0) { 31 | db.update(data); 32 | res.send({db: "update"}); 33 | } else { 34 | db.insert(data); 35 | res.send({db: "insert"}); 36 | } 37 | }); 38 | }); 39 | 40 | app.get('/api/', function (req, res) { 41 | var payload = {}; 42 | db.find(payload, function (results) { 43 | return res.json(results); 44 | }); 45 | }); 46 | 47 | app.get('/api/:user_id', function (req, res) { 48 | var payload = {user: req.params.user_id}; 49 | db.find(payload, function (results) { 50 | return res.json(results); 51 | }); 52 | }); 53 | 54 | function updateData(req, res) { 55 | var payload = {user: req.params.user_id}; 56 | var data = {user: req.params.user_id, led: false}; 57 | if (req.body.led === true) { 58 | data.led = true; 59 | } 60 | 61 | db.find(payload, function (results) { 62 | if (results.length > 0) { 63 | db.update(data); 64 | res.send({db: "update"}); 65 | } else { 66 | db.insert(data); 67 | res.send({db: "insert"}); 68 | } 69 | }); 70 | } 71 | 72 | app.post('/api/:user_id', function (req, res) { 73 | updateData(req, res); 74 | }); 75 | 76 | app.put('/api/:user_id', function (req, res) { 77 | updateData(req, res); 78 | }); 79 | 80 | app.delete('/api/:user_id', function (req, res) { 81 | res.send({}); 82 | }); 83 | 84 | app.listen(3000); -------------------------------------------------------------------------------- /chapter4/rest-db/db.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | var url = "mongodb://localhost:27017/designiot"; 4 | 5 | function MongoPersistence() { 6 | 7 | } 8 | 9 | MongoPersistence.prototype.insert = function (payload) { 10 | 'use strict'; 11 | MongoClient.connect(url, function (err, db) { 12 | var insertDocuments = function (db, callback) { 13 | var collection = db.collection("documents"); 14 | collection.insert(payload, function (err, result) { 15 | callback(result); 16 | }); 17 | }; 18 | insertDocuments(db, function () { 19 | db.close(); 20 | }); 21 | }); 22 | }; 23 | 24 | MongoPersistence.prototype.update = function (payload) { 25 | 'use strict'; 26 | MongoClient.connect(url, function (err, db) { 27 | var updateDocument = function (db, callback) { 28 | var collection = db.collection("documents"); 29 | collection.update({user: payload.user}, {$set: {led: payload.led}}, function (err, result) { 30 | callback(); 31 | }); 32 | }; 33 | updateDocument(db, function () { 34 | db.close(); 35 | }); 36 | }); 37 | }; 38 | 39 | MongoPersistence.prototype.find = function (queryOptions, queryCB) { 40 | 'use strict'; 41 | MongoClient.connect(url, function (err, db) { 42 | var findDocuments = function (db, query, callback) { 43 | var collection = db.collection("documents"); 44 | collection.find(query).toArray(function (err, docs) { 45 | callback(docs); 46 | }); 47 | }; 48 | 49 | findDocuments(db, queryOptions, function (result) { 50 | db.close(); 51 | queryCB(result); 52 | }); 53 | }); 54 | }; 55 | 56 | 57 | module.exports = MongoPersistence; -------------------------------------------------------------------------------- /chapter4/rest-db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iot-restful-example", 3 | "version": "0.0.1", 4 | "description": "Internet of Things Example", 5 | "author": "", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.14.1", 9 | "express": "^4.13.3", 10 | "jade": "~1.11.0", 11 | "mongodb": "^2.0.46" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter4/rest-db/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | form(method="post", action="/") 7 | div 8 | label(from="user") User 9 | input(type='number', name="user") 10 | div 11 | label(from="led") Led 12 | input(type='checkbox', checked=false, name="led") 13 | 14 | input(type="submit", value="OK") 15 | 16 | -------------------------------------------------------------------------------- /chapter4/restfull/README.md: -------------------------------------------------------------------------------- 1 | #RESTful Example 2 | 3 | ``` 4 | curl -X PUT -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 5 | ``` 6 | 7 | ``` 8 | curl http://localhost:3000/api 9 | ``` 10 | 11 | ``` 12 | curl -X DELETE http://localhost:3000/api 13 | ``` -------------------------------------------------------------------------------- /chapter4/restfull/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | 4 | var app = express(); 5 | app.use(bodyParser.json()); 6 | 7 | var data = {led: false}; 8 | 9 | app.get('/api', function (req, res) { 10 | data.method = 'get'; 11 | res.send(data); 12 | }); 13 | 14 | app.post('/api', function (req, res) { 15 | data.method = 'post'; 16 | if (req.body.led === true) { 17 | data.led = true; 18 | } else if (req.body.led === false) { 19 | data.led = false; 20 | } 21 | res.send(data); 22 | }); 23 | 24 | app.put('/api', function (req, res) { 25 | data.method = 'put'; 26 | if (req.body.led === true) { 27 | data.led = true; 28 | } else if (req.body.led === false) { 29 | data.led = false; 30 | } 31 | res.send(data); 32 | }); 33 | 34 | app.delete('/api', function (req, res) { 35 | data.method = 'delete'; 36 | data = {}; 37 | res.send(data); 38 | }); 39 | 40 | app.listen(3000); -------------------------------------------------------------------------------- /chapter4/restfull/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iot-restful-example", 3 | "version": "0.0.1", 4 | "description": "Internet of Things Example", 5 | "author": "", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.14.1", 9 | "express": "^4.13.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter5/README.md: -------------------------------------------------------------------------------- 1 | #第五章 2 | 3 | ##Arduino Library 4 | 5 | OneWire: http://www.pjrc.com/teensy/arduino_libraries/OneWire.zip 6 | Temperature: :https://github.com/milesburton/Arduino-Temperature-Control-Library 7 | -------------------------------------------------------------------------------- /chapter5/WebClient/WebClient.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Enter a MAC address for your controller below. 5 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 6 | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 7 | char server[] = "192.168.3.12"; 8 | 9 | // Set the static IP address to use if the DHCP fails to assign 10 | IPAddress ip(192,168,3,200); 11 | 12 | // Initialize the Ethernet client library 13 | // with the IP address and port of the server 14 | // that you want to connect to (port 80 is default for HTTP): 15 | EthernetClient client; 16 | 17 | void setup() { 18 | // Open serial communications and wait for port to open: 19 | Serial.begin(9600); 20 | while (!Serial) { 21 | ; // wait for serial port to connect. Needed for Leonardo only 22 | } 23 | 24 | // start the Ethernet connection: 25 | if (Ethernet.begin(mac) == 0) { 26 | Serial.println("Failed to configure Ethernet using DHCP"); 27 | // no point in carrying on, so do nothing forevermore: 28 | // try to congifure using IP address instead of DHCP: 29 | Ethernet.begin(mac, ip); 30 | } 31 | // give the Ethernet shield a second to initialize: 32 | delay(1000); 33 | Serial.println("connecting..."); 34 | 35 | // if you get a connection, report back via serial: 36 | if (client.connect(server, 3000)) { 37 | Serial.println("connected"); 38 | // Make a HTTP request: 39 | client.println("GET /api/1 HTTP/1.1"); 40 | client.println("Host: 192.168.3.12"); 41 | client.println("Connection: close"); 42 | client.println(); 43 | } 44 | else { 45 | Serial.println("connection failed"); 46 | } 47 | } 48 | 49 | void loop() 50 | { 51 | // if there are incoming bytes available 52 | // from the server, read them and print them: 53 | if (client.available()) { 54 | char c = client.read(); 55 | Serial.print(c); 56 | } 57 | 58 | // if the server's disconnected, stop the client: 59 | if (!client.connected()) { 60 | Serial.println(); 61 | Serial.println("disconnecting."); 62 | client.stop(); 63 | 64 | // do nothing forevermore: 65 | while (true); 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /chapter5/arduino-serial/arduino-serial.ino: -------------------------------------------------------------------------------- 1 | void setup() { 2 | Serial.begin(9600); 3 | } 4 | 5 | void loop() { 6 | if (Serial.available()) { 7 | char inChar = (char)Serial.read(); 8 | Serial.write(inChar ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter5/esp8266Client/esp8266Client.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const char* ssid = "Phodal"; //WiFi名 4 | const char* password = "12345678"; //WiFi密码 5 | 6 | const char* server = "192.168.3.12"; //服务器/PC的IP 7 | 8 | WiFiClient client; 9 | 10 | void setup() { 11 | Serial.begin(9600); 12 | delay(10); 13 | 14 | Serial.println(); 15 | Serial.print("Connecting to "); 16 | Serial.println(ssid); 17 | 18 | WiFi.begin(ssid, password); 19 | 20 | while (WiFi.status() != WL_CONNECTED) { 21 | delay(500); 22 | Serial.print("."); 23 | } 24 | 25 | Serial.println("WiFi connected"); 26 | Serial.println("IP address: "); 27 | Serial.println(WiFi.localIP()); 28 | 29 | Serial.print("connecting to "); 30 | Serial.println(server); 31 | 32 | // give the Ethernet shield a second to initialize: 33 | delay(1000); 34 | Serial.println("connecting..."); 35 | 36 | // if you get a connection, report back via serial: 37 | if (client.connect(server, 3000)) { 38 | Serial.println("connected"); 39 | // Make a HTTP request: 40 | client.println("GET /api/1 HTTP/1.1"); 41 | client.println("Host: 192.168.3.12"); 42 | client.println("Connection: close"); 43 | client.println(); 44 | } 45 | else { 46 | Serial.println("connection failed"); 47 | } 48 | 49 | } 50 | 51 | void loop() { 52 | // if there are incoming bytes available 53 | // from the server, read them and print them: 54 | if (client.available()) { 55 | char c = client.read(); 56 | Serial.print(c); 57 | } 58 | 59 | // if the server's disconnected, stop the client: 60 | if (!client.connected()) { 61 | Serial.println(); 62 | Serial.println("disconnecting."); 63 | client.stop(); 64 | 65 | // do nothing forevermore: 66 | while (true); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /chapter5/final-actuator/final-actuator.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int port = 12; 4 | IRsend irsend; 5 | int startArduino = 1; 6 | int irRemote = 2; 7 | int stopArduino = 3; 8 | 9 | void setup() { 10 | Serial.begin(9600); 11 | pinMode(port, OUTPUT); 12 | } 13 | 14 | int serialData; 15 | void loop() { 16 | String inString = ""; 17 | while (Serial.available()> 0) 18 | { 19 | int inChar = Serial.read(); 20 | if (isDigit(inChar)) { 21 | inString += (char)inChar; 22 | } 23 | serialData=inString.toInt(); 24 | Serial.print(serialData); 25 | } 26 | if(serialData == startArduino){ 27 | digitalWrite(port, HIGH); 28 | } else if( serialData == irRemote){ 29 | irsend.sendNEC(0xFFA25D,32); 30 | delay(1000); 31 | } else if (serialData == stopArduino) { 32 | digitalWrite(port, LOW); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter5/final-sensors/final-sensors.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define ONE_WIRE_BUS 2 5 | 6 | int DataPin = A0; 7 | int LEDPin = 13; 8 | 9 | OneWire oneWire(ONE_WIRE_BUS); 10 | 11 | DallasTemperature sensors(&oneWire); 12 | 13 | 14 | void setup(void) 15 | { 16 | pinMode(DataPin, INPUT); 17 | Serial.begin(9600); 18 | sensors.begin(); 19 | } 20 | 21 | void loop(void) 22 | { 23 | int sensorValue = analogRead(DataPin); 24 | Serial.println(sensorValue); 25 | 26 | sensors.requestTemperatures(); // Send the command to get temperatures 27 | Serial.println(sensors.getTempCByIndex(0)); 28 | 29 | delay(500); 30 | } -------------------------------------------------------------------------------- /chapter5/irrecive/irrecive.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int RECV_PIN = 11; 4 | 5 | IRrecv irrecv(RECV_PIN); 6 | 7 | decode_results results; 8 | 9 | void setup() 10 | { 11 | Serial.begin(9600); 12 | irrecv.enableIRIn(); // Start the receiver 13 | } 14 | 15 | void loop() { 16 | if (irrecv.decode(&results)) { 17 | Serial.println(results.value, HEX); 18 | irrecv.resume(); // Receive the next value 19 | } 20 | delay(100); 21 | } -------------------------------------------------------------------------------- /chapter5/irsenddemo/irsenddemo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | IRsend irsend; 4 | 5 | void setup() 6 | { 7 | } 8 | 9 | void loop() { 10 | for (int i = 0; i < 3; i++) { 11 | irsend.sendNEC(0xFFA25D,32); 12 | delay(40); 13 | } 14 | delay(5000); 15 | } -------------------------------------------------------------------------------- /chapter5/moisture/moisture.ino: -------------------------------------------------------------------------------- 1 | int ASignal = A0; 2 | int LEDPin = 13; 3 | int val = 380; 4 | 5 | void setup() { 6 | pinMode(LEDPin, OUTPUT); 7 | pinMode(ASignal, INPUT); 8 | digitalWrite(LEDPin,LOW); 9 | Serial.begin(9600); 10 | 11 | } 12 | 13 | 14 | void loop() { 15 | int sensorValue = analogRead(ASignal); 16 | Serial.println(sensorValue); 17 | if(analogRead(ASignal) > val) { 18 | digitalWrite(LEDPin, HIGH); 19 | delay(300); 20 | digitalWrite(LEDPin, LOW); 21 | delay(300); 22 | } else { 23 | digitalWrite(LEDPin,LOW); 24 | delay(1000); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter5/post-temperature.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import serial 3 | import time 4 | 5 | while 1: 6 | ser = serial.Serial("/dev/tty.usbmodem1411", 9600) 7 | temperature = ser.read(5) 8 | print temperature 9 | r = requests.put("http://localhost:3000/api/4", {"temperature": temperature, "user": 4, "led": 1}) 10 | r.text 11 | time.sleep(1) 12 | -------------------------------------------------------------------------------- /chapter5/python-serail.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib2 3 | import serial 4 | import time 5 | 6 | url = "http://localhost:3000/api/1" 7 | ser = serial.Serial("/dev/cu.usbmodem1451", 9600) 8 | 9 | while 1: 10 | date = urllib2.urlopen(url) 11 | status = json.load(date)[0]['led'] 12 | print status 13 | if status: 14 | ser.write('1') 15 | else: 16 | ser.write('0') 17 | time.sleep(1) 18 | -------------------------------------------------------------------------------- /chapter5/rest/README.md: -------------------------------------------------------------------------------- 1 | 2 | GET请求: 3 | 4 | ``` 5 | curl http://localhost:3000/api 6 | ``` 7 | 8 | POST请求: 9 | 10 | ``` 11 | curl -X PUT -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 12 | ``` 13 | 14 | PUT请求: 15 | 16 | ``` 17 | curl -X POST -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 18 | ``` 19 | 20 | DELETE请求: 21 | 22 | ``` 23 | curl -X DELETE http://localhost:3000/api 24 | ``` 25 | -------------------------------------------------------------------------------- /chapter5/rest/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var path = require('path'); 4 | 5 | var Database = require('./db'); 6 | var db = new Database(); 7 | 8 | var app = express(); 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({extended: true})); 11 | 12 | app.set('views', path.join(__dirname + '/', 'views')); 13 | app.set('view engine', 'jade'); 14 | 15 | app.get('/', function (req, res) { 16 | 'use strict'; 17 | res.render('index', { 18 | title: 'Home' 19 | }); 20 | }); 21 | 22 | app.post('/', function (req, res) { 23 | var userId = req.params.user_id; 24 | var payload = {user: userId}; 25 | 26 | var data = req.body; 27 | data.user = userId; 28 | 29 | db.find(payload, function (results) { 30 | if (results.length > 0) { 31 | db.update(data); 32 | res.send({db: "update"}); 33 | } else { 34 | db.insert(data); 35 | res.send({db: "insert"}); 36 | } 37 | }); 38 | }); 39 | 40 | app.get('/api/', function (req, res) { 41 | var payload = {}; 42 | db.find(payload, function (results) { 43 | return res.json(results); 44 | }); 45 | }); 46 | 47 | app.get('/api/:user_id', function (req, res) { 48 | var payload = {user: req.params.user_id}; 49 | db.find(payload, function (results) { 50 | return res.json(results); 51 | }); 52 | }); 53 | 54 | function updateData(req, res) { 55 | var userId = req.params.user_id; 56 | var payload = {user: userId}; 57 | 58 | var data = req.body; 59 | data.user = userId; 60 | 61 | db.find(payload, function (results) { 62 | console.log(results); 63 | if (results.length > 0) { 64 | db.update(data); 65 | res.send({db: "update"}); 66 | } else { 67 | db.insert(data); 68 | res.send({db: "insert"}); 69 | } 70 | }); 71 | } 72 | 73 | app.post('/api/:user_id', function (req, res) { 74 | updateData(req, res); 75 | }); 76 | 77 | app.put('/api/:user_id', function (req, res) { 78 | updateData(req, res); 79 | }); 80 | 81 | app.delete('/api/:user_id', function (req, res) { 82 | res.send({}); 83 | }); 84 | 85 | app.listen(3000); -------------------------------------------------------------------------------- /chapter5/rest/db.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | var url = "mongodb://localhost:27017/designiot"; 4 | 5 | function MongoPersistence() { 6 | 7 | } 8 | 9 | MongoPersistence.prototype.insert = function (payload) { 10 | 'use strict'; 11 | MongoClient.connect(url, function (err, db) { 12 | var insertDocuments = function (db, callback) { 13 | var collection = db.collection("documents"); 14 | collection.insert(payload, function (err, result) { 15 | callback(result); 16 | }); 17 | }; 18 | insertDocuments(db, function () { 19 | db.close(); 20 | }); 21 | }); 22 | }; 23 | 24 | MongoPersistence.prototype.update = function (payload) { 25 | 'use strict'; 26 | MongoClient.connect(url, function (err, db) { 27 | var updateDocument = function (db, callback) { 28 | var collection = db.collection("documents"); 29 | collection.update({user: payload.user}, {$set: payload}, function (err, result) { 30 | callback(); 31 | }); 32 | }; 33 | updateDocument(db, function () { 34 | db.close(); 35 | }); 36 | }); 37 | }; 38 | 39 | MongoPersistence.prototype.find = function (queryOptions, queryCB) { 40 | 'use strict'; 41 | MongoClient.connect(url, function (err, db) { 42 | var findDocuments = function (db, query, callback) { 43 | var collection = db.collection("documents"); 44 | collection.find(query).toArray(function (err, docs) { 45 | callback(docs); 46 | }); 47 | }; 48 | 49 | findDocuments(db, queryOptions, function (result) { 50 | db.close(); 51 | queryCB(result); 52 | }); 53 | }); 54 | }; 55 | 56 | 57 | module.exports = MongoPersistence; -------------------------------------------------------------------------------- /chapter5/rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iot-restful-example", 3 | "version": "0.0.1", 4 | "description": "Internet of Things Example", 5 | "author": "", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.14.1", 9 | "express": "^4.13.3", 10 | "jade": "~1.11.0", 11 | "mongodb": "^2.0.46" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter5/rest/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | form(method="post", action="/") 7 | div 8 | label(from="user") User 9 | input(type='number', name="user") 10 | div 11 | label(from="led") Led 12 | input(type='checkbox', checked=false, name="led") 13 | 14 | input(type="submit", value="OK") 15 | 16 | -------------------------------------------------------------------------------- /chapter5/temperature/temperature.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define ONE_WIRE_BUS 2 5 | 6 | OneWire oneWire(ONE_WIRE_BUS); 7 | 8 | DallasTemperature sensors(&oneWire); 9 | 10 | 11 | void setup(void) 12 | { 13 | Serial.begin(9600); 14 | sensors.begin(); 15 | } 16 | 17 | void loop(void) 18 | { 19 | sensors.requestTemperatures(); // Send the command to get temperatures 20 | Serial.println(sensors.getTempCByIndex(0)); 21 | delay(500); 22 | } -------------------------------------------------------------------------------- /chapter5/test-post.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | 4 | while 1: 5 | r = requests.put("http://localhost:3000/api/4", data={"temperature": 24, "led": 1}) 6 | print r.text 7 | time.sleep(1) -------------------------------------------------------------------------------- /chapter6/README.md: -------------------------------------------------------------------------------- 1 | #第六章 2 | 3 | 目录 | 说明 4 | -----------------|----------------------------- 5 | aws-example | AWS IoT示例 6 | rest | 物联网系统 7 | makeRequest.js | Ajax请求示例 -------------------------------------------------------------------------------- /chapter6/aws-example/README.md: -------------------------------------------------------------------------------- 1 | https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -------------------------------------------------------------------------------- /chapter6/aws-example/aws.js: -------------------------------------------------------------------------------- 1 | var awsIot = require('aws-iot-device-sdk'); 2 | 3 | var thingName = 'Led'; 4 | var thingShadows = awsIot.thingShadow({ 5 | keyPath: 'certs/fa635d3140-private.pem.key', 6 | certPath: 'certs/fa635d3140-certificate.pem.crt', 7 | caPath: 'certs/root-CA.crt', 8 | clientId: thingName, 9 | region: 'us-west-2' 10 | }); 11 | 12 | thingShadows.on('connect', function () { 13 | console.log("Connected..."); 14 | thingShadows.register(thingName); 15 | 16 | // An update right away causes a timeout error, so we wait about 2 seconds 17 | setTimeout(function () { 18 | console.log("Updating Led Status..."); 19 | var led = thingShadows.update(thingName, { 20 | "state": { 21 | "reported": { 22 | "led": true 23 | } 24 | } 25 | }); 26 | console.log("Update:" + led); 27 | }, 2500); 28 | 29 | 30 | // Code below just logs messages for info/debugging 31 | thingShadows.on('status', 32 | function (thingName, stat, clientToken, stateObject) { 33 | console.log('received ' + stat + ' on ' + thingName + ': ' + 34 | JSON.stringify(stateObject)); 35 | }); 36 | 37 | thingShadows.on('update', 38 | function (thingName, stateObject) { 39 | console.log('received update ' + ' on ' + thingName + ': ' + 40 | JSON.stringify(stateObject)); 41 | }); 42 | 43 | thingShadows.on('delta', 44 | function (thingName, stateObject) { 45 | console.log('received delta ' + ' on ' + thingName + ': ' + 46 | JSON.stringify(stateObject)); 47 | }); 48 | 49 | thingShadows.on('timeout', 50 | function (thingName, clientToken) { 51 | console.log('received timeout for ' + clientToken) 52 | }); 53 | 54 | thingShadows 55 | .on('close', function () { 56 | console.log('close'); 57 | }); 58 | thingShadows 59 | .on('reconnect', function () { 60 | console.log('reconnect'); 61 | }); 62 | thingShadows 63 | .on('offline', function () { 64 | console.log('offline'); 65 | }); 66 | thingShadows 67 | .on('error', function (error) { 68 | console.log('error', error); 69 | }); 70 | 71 | }); -------------------------------------------------------------------------------- /chapter6/aws-example/iot-mqtt-subscriber.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/python3 2 | 3 | # required libraries 4 | import sys 5 | import ssl 6 | import paho.mqtt.client as mqtt 7 | 8 | 9 | # called while client tries to establish connection with the server 10 | def on_connect(mqttc, obj, flags, rc): 11 | if rc == 0: 12 | print ("Subscriber Connection status code: " + str(rc) + " | Connection status: successful") 13 | elif rc == 1: 14 | print ("Subscriber Connection status code: " + str(rc) + " | Connection status: Connection refused") 15 | 16 | 17 | # called when a topic is successfully subscribed to 18 | def on_subscribe(mqttc, obj, mid, granted_qos): 19 | print("Subscribed: " + str(mid) + " " + str(granted_qos) + "data" + str(obj)) 20 | 21 | 22 | # called when a message is received by a topic 23 | def on_message(mqttc, obj, msg): 24 | print( 25 | "Received message from topic: " + msg.topic + " | QoS: " + str(msg.qos) + " | Data Received: " + str(msg.payload)) 26 | 27 | 28 | # creating a client with client-id=mqtt-test 29 | mqttc = mqtt.Client(client_id="mqtt-test") 30 | 31 | mqttc.on_connect = on_connect 32 | mqttc.on_subscribe = on_subscribe 33 | mqttc.on_message = on_message 34 | 35 | # Configure network encryption and authentication options. Enables SSL/TLS support. 36 | # adding client-side certificates and enabling tlsv1.2 support as required by aws-iot service 37 | 38 | # https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem 39 | mqttc.tls_set("./certs/root-CA.crt", 40 | certfile="./certs/fa635d3140-certificate.pem.crt", 41 | keyfile="./certs/fa635d3140-private.pem.key", 42 | tls_version=ssl.PROTOCOL_TLSv1_2, 43 | ciphers=None) 44 | 45 | # connecting to aws-account-specific-iot-endpoint 46 | mqttc.connect("a1dul237m8y7r3.iot.us-west-2.amazonaws.com", port=8883) # AWS IoT service hostname and portno 47 | 48 | # the topic to publish to 49 | mqttc.subscribe("$aws/things/phodal/shadow/update/#", 50 | qos=1) # The names of these topics start with $aws/things/thingName/shadow." 51 | 52 | # automatically handles reconnecting 53 | mqttc.loop_forever() 54 | -------------------------------------------------------------------------------- /chapter6/aws-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "aws.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "aws-iot-device-sdk": "^1.0.9" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter6/makeRequest.js: -------------------------------------------------------------------------------- 1 | function makeRequest(url) { 2 | httpRequest = new XMLHttpRequest(); 3 | 4 | if (!httpRequest) { 5 | alert('Giving up :( Cannot create an XMLHTTP instance'); 6 | return false; 7 | } 8 | httpRequest.onreadystatechange = function() { 9 | if (httpRequest.readyState === XMLHttpRequest.DONE) { 10 | if (httpRequest.status === 200) { 11 | alert(httpRequest.responseText); 12 | } else { 13 | alert('There was a problem with the request.'); 14 | } 15 | } 16 | }; 17 | httpRequest.open('GET', url); 18 | httpRequest.send(); 19 | } -------------------------------------------------------------------------------- /chapter6/rest/README.md: -------------------------------------------------------------------------------- 1 | 2 | GET请求: 3 | 4 | ``` 5 | curl http://localhost:3000/api 6 | ``` 7 | 8 | POST请求: 9 | 10 | ``` 11 | curl -X PUT -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 12 | ``` 13 | 14 | PUT请求: 15 | 16 | ``` 17 | curl -X POST -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 18 | ``` 19 | 20 | DELETE请求: 21 | 22 | ``` 23 | curl -X DELETE http://localhost:3000/api 24 | ``` 25 | 26 | 27 | POST Devices: 28 | 29 | ``` 30 | curl -X POST -d '{ "led": true,"temperature": 15 }' -H "Content-Type: application/json" http://localhost:3000/user/1/devices/1 31 | ``` -------------------------------------------------------------------------------- /chapter6/rest/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var path = require('path'); 4 | 5 | var Database = require('./db'); 6 | var db = new Database(); 7 | 8 | var app = express(); 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({extended: true})); 11 | app.use(express.static(path.join(__dirname + '/', 'public'))); 12 | 13 | app.set('views', path.join(__dirname + '/', 'views')); 14 | app.set('view engine', 'jade'); 15 | 16 | app.use(function (req, res, next) { 17 | res.header("Access-Control-Allow-Origin", "*"); 18 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 19 | next(); 20 | }); 21 | 22 | app.get('/', function (req, res) { 23 | 'use strict'; 24 | res.render('index', { 25 | title: 'Home' 26 | }); 27 | }); 28 | 29 | app.get('/dashboard', function (req, res) { 30 | 'use strict'; 31 | res.render('dashboard', { 32 | title: 'Dashboard' 33 | }); 34 | }); 35 | 36 | app.get('/user/:user_id/devices/:device_id/result/:result_id', function (req, res) { 37 | var payload = {user: parseInt(req.params.user_id), device: parseInt(req.params.device_id)}; 38 | db.findOrder(payload, parseInt(req.params.result_id), function (results) { 39 | return res.json(results); 40 | }); 41 | }); 42 | 43 | app.get('/user/:user_id/devices/:device_id/results', function (req, res) { 44 | var payload = {user: parseInt(req.params.user_id), device: parseInt(req.params.device_id)}; 45 | db.find(payload, function (results) { 46 | return res.json(results); 47 | }); 48 | }); 49 | 50 | app.post('/user/:user_id/devices/:device_id', function (req, res) { 51 | var data = req.body; 52 | data.user = parseInt(req.params.user_id); 53 | data.device = parseInt(req.params.device_id); 54 | 55 | db.insert(data); 56 | res.send({db: "insert"}); 57 | }); 58 | 59 | app.get('/user/:user_id/devices', function (req, res) { 60 | var payload = {user: parseInt(req.params.user_id), devices: true }; 61 | db.find(payload, function (results) { 62 | return res.json(results); 63 | }); 64 | }); 65 | 66 | app.post('/user/:user_id/devices', function (req, res) { 67 | var data = req.body; 68 | data.user = parseInt(req.params.user_id); 69 | data.devices = true; 70 | 71 | var payload = {user: parseInt(req.params.user_id), devices: true }; 72 | db.find(payload, function (results) { 73 | if (results.length > 0) { 74 | db.update(data); 75 | res.send({db: "update"}); 76 | } else { 77 | db.insert(data); 78 | res.send({db: "insert"}); 79 | } 80 | }); 81 | }); 82 | 83 | app.listen(3000, function () { 84 | console.log("server run on http://localhost:%d", 3000); 85 | }); -------------------------------------------------------------------------------- /chapter6/rest/db.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | var url = "mongodb://localhost:27017/designiot"; 4 | 5 | function MongoPersistence() { 6 | 7 | } 8 | 9 | MongoPersistence.prototype.insert = function (payload) { 10 | 'use strict'; 11 | MongoClient.connect(url, function (err, db) { 12 | var insertDocuments = function (db, callback) { 13 | payload.date = new Date(); 14 | var collection = db.collection("documents"); 15 | collection.insert(payload, function (err, result) { 16 | callback(result); 17 | }); 18 | }; 19 | insertDocuments(db, function () { 20 | db.close(); 21 | }); 22 | }); 23 | }; 24 | 25 | MongoPersistence.prototype.update = function (payload) { 26 | 'use strict'; 27 | MongoClient.connect(url, function (err, db) { 28 | var updateDocument = function (db, callback) { 29 | var collection = db.collection("documents"); 30 | collection.update({user: payload.user}, {$set: payload}, function (err, result) { 31 | callback(); 32 | }); 33 | }; 34 | updateDocument(db, function () { 35 | db.close(); 36 | }); 37 | }); 38 | }; 39 | 40 | MongoPersistence.prototype.find = function (queryOptions, queryCB) { 41 | 'use strict'; 42 | MongoClient.connect(url, function (err, db) { 43 | var findDocuments = function (db, query, callback) { 44 | var collection = db.collection("documents"); 45 | collection.find(query).toArray(function (err, docs) { 46 | callback(docs); 47 | }); 48 | }; 49 | 50 | findDocuments(db, queryOptions, function (result) { 51 | db.close(); 52 | queryCB(result); 53 | }); 54 | }); 55 | }; 56 | 57 | MongoPersistence.prototype.findOrder = function (queryOptions, order, queryCB) { 58 | 'use strict'; 59 | MongoClient.connect(url, function (err, db) { 60 | var findDocuments = function (db, query, callback) { 61 | var collection = db.collection("documents"); 62 | collection.find(query).limit(1).skip(order).toArray(function (err, docs) { 63 | callback(docs); 64 | }); 65 | }; 66 | 67 | findDocuments(db, queryOptions, function (result) { 68 | db.close(); 69 | queryCB(result); 70 | }); 71 | }); 72 | }; 73 | 74 | 75 | module.exports = MongoPersistence; -------------------------------------------------------------------------------- /chapter6/rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iot-restful-example", 3 | "version": "0.0.1", 4 | "description": "Internet of Things Example", 5 | "author": "", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.14.1", 9 | "express": "^4.13.3", 10 | "jade": "~1.11.0", 11 | "mongodb": "^2.0.46" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter6/rest/public/scripts/echarts-ajax-devices.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var data = []; 3 | $.get('/user/1/devices/1/results', function(response){ 4 | $.each(response, function(key, val) { 5 | data.push(val.temperature); 6 | }); 7 | var myChart = echarts.init(document.getElementById('main')); 8 | 9 | var option = { 10 | legend: { // 图例配置 11 | padding: 5, // 图例内边距,单位px,默认上下左右内边距为5 12 | itemGap: 10, // Legend各个item之间的间隔,横向布局时为水平间隔,纵向布局时为纵向间隔 13 | data: ['月平均气温'] 14 | }, 15 | tooltip: { // 气泡提示配置 16 | trigger: 'item' // 触发类型,默认数据触发,可选为:'axis' 17 | }, 18 | xAxis: [ // 直角坐标系中横轴数组 19 | { 20 | type: 'category', // 坐标轴类型,横轴默认为类目轴,数值轴则参考yAxis说明 21 | data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 22 | } 23 | ], 24 | yAxis: [ // 直角坐标系中纵轴数组 25 | { 26 | type: 'value', // 坐标轴类型,纵轴默认为数值轴,类目轴则参考xAxis说明 27 | boundaryGap: [0.1, 0.1], // 坐标轴两端空白策略,数组内数值代表百分比 28 | splitNumber: 4 // 数值轴用,分割段数,默认为5 29 | } 30 | ], 31 | series: [ 32 | { 33 | name: '气温', // 系列名称 34 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 35 | data: data 36 | }] 37 | }; 38 | 39 | myChart.setOption(option); 40 | }); 41 | }); -------------------------------------------------------------------------------- /chapter6/rest/public/scripts/echarts-example.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var myChart = echarts.init(document.getElementById('main')); 3 | 4 | var option = { 5 | legend: { // 图例配置 6 | padding: 5, // 图例内边距,单位px,默认上下左右内边距为5 7 | itemGap: 10, // Legend各个item之间的间隔,横向布局时为水平间隔,纵向布局时为纵向间隔 8 | data: ['月平均气温'] 9 | }, 10 | tooltip: { // 气泡提示配置 11 | trigger: 'item' // 触发类型,默认数据触发,可选为:'axis' 12 | }, 13 | xAxis: [ // 直角坐标系中横轴数组 14 | { 15 | type: 'category', // 坐标轴类型,横轴默认为类目轴,数值轴则参考yAxis说明 16 | data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 17 | } 18 | ], 19 | yAxis: [ // 直角坐标系中纵轴数组 20 | { 21 | type: 'value', // 坐标轴类型,纵轴默认为数值轴,类目轴则参考xAxis说明 22 | boundaryGap: [0.1, 0.1], // 坐标轴两端空白策略,数组内数值代表百分比 23 | splitNumber: 4 // 数值轴用,分割段数,默认为5 24 | } 25 | ], 26 | series: [ 27 | { 28 | name: '气温', // 系列名称 29 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 30 | data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] 31 | }] 32 | }; 33 | 34 | myChart.setOption(option); 35 | }); -------------------------------------------------------------------------------- /chapter6/rest/public/scripts/highcharts-ajax-devices.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var data = []; 3 | $.get('/user/1/devices/1/results', function(response){ 4 | $.each(response, function(key, val) { 5 | data.push(val.temperature); 6 | }); 7 | $('#container').highcharts({ 8 | title: { 9 | text: '月平均气温', 10 | x: -20 //center 11 | }, 12 | xAxis: { 13 | categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 14 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 15 | }, 16 | yAxis: { 17 | title: { 18 | text: 'Temperature (°C)' 19 | }, 20 | plotLines: [{ 21 | value: 0, 22 | width: 1, 23 | color: '#808080' 24 | }] 25 | }, 26 | tooltip: { 27 | valueSuffix: '°C' 28 | }, 29 | legend: { 30 | layout: 'vertical', 31 | align: 'right', 32 | verticalAlign: 'middle', 33 | borderWidth: 0 34 | }, 35 | series: [{ 36 | name: 'Today', 37 | data: data 38 | }] 39 | }); 40 | }); 41 | }); -------------------------------------------------------------------------------- /chapter6/rest/public/scripts/highcharts-example.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('#container').highcharts({ 3 | title: { 4 | text: '月平均气温', 5 | x: -20 //center 6 | }, 7 | xAxis: { 8 | categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 9 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 10 | }, 11 | yAxis: { 12 | title: { 13 | text: 'Temperature (°C)' 14 | }, 15 | plotLines: [{ 16 | value: 0, 17 | width: 1, 18 | color: '#808080' 19 | }] 20 | }, 21 | tooltip: { 22 | valueSuffix: '°C' 23 | }, 24 | legend: { 25 | layout: 'vertical', 26 | align: 'right', 27 | verticalAlign: 'middle', 28 | borderWidth: 0 29 | }, 30 | series: [{ 31 | name: 'Today', 32 | data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] 33 | }] 34 | }); 35 | }); -------------------------------------------------------------------------------- /chapter6/rest/views/dashboard.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | script(src='./scripts/jquery-2.1.4.min.js') 6 | 7 | //script(src='./scripts/echarts-all.js') 8 | //script(src='./scripts/echarts-example.js') 9 | 10 | script(src='./scripts/highcharts.js') 11 | //script(src='./scripts/highcharts-example.js') 12 | script(src='./scripts/highcharts-ajax-devices.js') 13 | body 14 | div(id="container" style="min-width: 310px; height: 400px; margin: 0 auto") 15 | div(id="main" style="min-width: 310px; height: 400px; margin: 0 auto") 16 | 17 | -------------------------------------------------------------------------------- /chapter6/rest/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | form(method="post", action="/") 7 | div 8 | label(from="user") User 9 | input(type='number', name="user") 10 | div 11 | label(from="led") Led 12 | input(type='checkbox', checked=false, name="led") 13 | 14 | input(type="submit", value="OK") 15 | 16 | -------------------------------------------------------------------------------- /chapter7/README.md: -------------------------------------------------------------------------------- 1 | #第七章 2 | 3 | 目录 | 说明 4 | -----------------|----------------------------- 5 | esp8266-mqtt | ESP8266 MQTT客户端发布消息示例 6 | lan | 支持MQTT,CoAP的Lan物联网 -------------------------------------------------------------------------------- /chapter7/esp8266-mqtt/esp8266-mqtt.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const char* ssid = "..."; 5 | const char* password = "..."; 6 | 7 | char* topic = "device/2"; 8 | char* server = "192.168.1.31"; 9 | 10 | 11 | WiFiClient wifiClient; 12 | PubSubClient client(server, 1883, callback, wifiClient); 13 | 14 | void callback(char* topic, byte* payload, unsigned int length) { 15 | // handle message arrived 16 | } 17 | 18 | void setup() { 19 | Serial.begin(115200); 20 | delay(10); 21 | 22 | Serial.print("Connecting to "); 23 | Serial.println(ssid); 24 | 25 | WiFi.begin(ssid, password); 26 | 27 | while (WiFi.status() != WL_CONNECTED) { 28 | delay(500); 29 | Serial.print("."); 30 | } 31 | Serial.println(""); 32 | Serial.println("WiFi connected"); 33 | Serial.println("IP address: "); 34 | Serial.println(WiFi.localIP()); 35 | 36 | Serial.print("Connecting to "); 37 | Serial.print(server); 38 | 39 | if (client.connect("ESP8266Client", "1", " ")) { 40 | Serial.println("Connected to MQTT broker"); 41 | Serial.print("Topic is: "); 42 | Serial.println(topic); 43 | } 44 | else { 45 | Serial.println("MQTT connect failed"); 46 | Serial.println("Will reset and try again..."); 47 | abort(); 48 | } 49 | } 50 | 51 | void loop() { 52 | static int counter = 0; 53 | 54 | String payload = "{\"temperature\":3}"; 55 | 56 | if (client.connected()){ 57 | Serial.print("Sending payload: "); 58 | Serial.println(payload); 59 | 60 | if (client.publish(topic, (char*) payload.c_str())) { 61 | Serial.println("Publish ok"); 62 | } 63 | else { 64 | Serial.println("Publish failed"); 65 | } 66 | } 67 | ++counter; 68 | delay(5000); 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /chapter7/lan/README.md: -------------------------------------------------------------------------------- 1 | #Lan IoT 2 | 3 | ##HTTP 4 | 5 | GET请求: 6 | 7 | ``` 8 | curl http://localhost:3000/api 9 | ``` 10 | 11 | POST请求: 12 | 13 | ``` 14 | curl -X PUT -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 15 | ``` 16 | 17 | PUT请求: 18 | 19 | ``` 20 | curl -X POST -d '{ "led": true }' -H "Content-Type: application/json" http://localhost:3000/api/ 21 | ``` 22 | 23 | DELETE请求: 24 | 25 | ``` 26 | curl -X DELETE http://localhost:3000/api 27 | ``` 28 | 29 | 30 | POST Devices: 31 | 32 | ``` 33 | curl -X POST -d '{ "led": true,"temperature": 15 }' -H "Content-Type: application/json" http://localhost:3000/user/1/devices/1 34 | ``` 35 | 36 | ##MQTT 37 | 38 | publish 39 | 40 | ``` 41 | mosquitto_pub -t 'device/2' -m '{"temperature":3}' -u 1 42 | ``` 43 | 44 | subscribe 45 | 46 | ``` 47 | mosquitto_sub -v -t 'test/topic' -u 1 48 | ``` 49 | 50 | mqtt.js 51 | 52 | ``` 53 | mqtt sub -v -t 'device/2' 54 | mqtt pub -t 'device/2' -m 35 55 | ``` 56 | 57 | ##CoAP 58 | 59 | get 60 | 61 | ```bash 62 | coap-client -m get 'coap://127.0.0.1:5683/result?user=1&device=1' 63 | ``` 64 | 65 | put 66 | 67 | ```bash 68 | coap-client -e '{"temperature": 3}' -m put 'coap://127.0.0.1:5683/result?user=1&device=1' 69 | ``` 70 | 71 | post 72 | 73 | ```bash 74 | coap-client -e '{"temperature": 3}' -m post 'coap://127.0.0.1:5683/result?user=1&device=1' 75 | ``` -------------------------------------------------------------------------------- /chapter7/lan/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var path = require('path'); 4 | 5 | var Database = require('./db'); 6 | var db = new Database(); 7 | 8 | var mqtt = require('mqtt'); 9 | var mqttServer = require('./mqttServer'); 10 | 11 | var coap = require('coap'); 12 | var coapServer = require('./coapServer'); 13 | 14 | var app = express(); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({extended: true})); 17 | app.use(express.static(path.join(__dirname + '/', 'public'))); 18 | 19 | app.set('views', path.join(__dirname + '/', 'views')); 20 | app.set('view engine', 'jade'); 21 | 22 | app.use(function (req, res, next) { 23 | res.header("Access-Control-Allow-Origin", "*"); 24 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 25 | next(); 26 | }); 27 | 28 | app.get('/', function (req, res) { 29 | 'use strict'; 30 | res.render('index', { 31 | title: 'Home' 32 | }); 33 | }); 34 | 35 | app.get('/dashboard', function (req, res) { 36 | 'use strict'; 37 | res.render('dashboard', { 38 | title: 'Dashboard' 39 | }); 40 | }); 41 | 42 | app.get('/user/:user_id/devices/:device_id/result/:result_id', function (req, res) { 43 | var payload = {user: parseInt(req.params.user_id), device: parseInt(req.params.device_id)}; 44 | db.findOrder(payload, parseInt(req.params.result_id), function (results) { 45 | return res.json(results); 46 | }); 47 | }); 48 | 49 | app.get('/user/:user_id/devices/:device_id/results', function (req, res) { 50 | var payload = {user: parseInt(req.params.user_id), device: parseInt(req.params.device_id)}; 51 | db.find(payload, function (results) { 52 | return res.json(results); 53 | }); 54 | }); 55 | 56 | app.post('/user/:user_id/devices/:device_id', function (req, res) { 57 | var data = req.body; 58 | data.user = parseInt(req.params.user_id); 59 | data.device = parseInt(req.params.device_id); 60 | 61 | db.insert(data); 62 | res.send({db: "insert"}); 63 | }); 64 | 65 | app.get('/user/:user_id/devices', function (req, res) { 66 | var payload = {user: parseInt(req.params.user_id), devices: true }; 67 | db.find(payload, function (results) { 68 | return res.json(results); 69 | }); 70 | }); 71 | 72 | app.post('/user/:user_id/devices', function (req, res) { 73 | var data = req.body; 74 | data.user = parseInt(req.params.user_id); 75 | data.devices = true; 76 | 77 | var payload = {user: parseInt(req.params.user_id), devices: true }; 78 | db.find(payload, function (results) { 79 | if (results.length > 0) { 80 | db.update(data); 81 | res.send({db: "update"}); 82 | } else { 83 | db.insert(data); 84 | res.send({db: "insert"}); 85 | } 86 | }); 87 | }); 88 | 89 | app.listen(3000, function () { 90 | console.log("server run on http://localhost:%d", 3000); 91 | }); 92 | 93 | mqtt.MqttServer(mqttServer).listen(1883, function () { 94 | console.log("mqtt server listening on port %d", 1883); 95 | }); 96 | 97 | coap.createServer(coapServer).listen(5683, function () { 98 | console.log("coap server listening on port %d", 5683); 99 | }); -------------------------------------------------------------------------------- /chapter7/lan/coapServer.js: -------------------------------------------------------------------------------- 1 | var Database = require('./db'); 2 | var db = new Database(); 3 | 4 | module.exports = function (req, res) { 5 | var errorHandle = function () { 6 | res.code = '4.00'; 7 | res.end(JSON.stringify({method: 'not support'})); 8 | }; 9 | 10 | // 1. URI Query 11 | var uriQuery = {}; 12 | var existBlock = false; 13 | for (var i = 0; i < req.options.length; i++) { 14 | if (req.options[i].name === 'Uri-Query') { 15 | var query = req.options[i].value.toString().split('='); 16 | uriQuery[query[0]] = parseInt(query[1]); 17 | existBlock = true; 18 | } 19 | } 20 | if (!existBlock) { 21 | return errorHandle(); 22 | } 23 | // 2. RESTful API方法 24 | //var urlArray = req.url.split("/"); 25 | //for (var i = 1; i < urlArray.length; i = i + 2) { 26 | // uriQuery[urlArray[i]] = parseInt(urlArray[i + 1]); 27 | //} 28 | var handlerGet = function () { 29 | var payload = {user: uriQuery.user, device: uriQuery.device}; 30 | db.find(payload, function (dbResult) { 31 | res.code = '2.05'; 32 | res.end(JSON.stringify({result: dbResult})); 33 | }); 34 | }; 35 | 36 | var handlePut = function () { 37 | var userId = parseInt(uriQuery.user); 38 | var deviceId = parseInt(uriQuery.device); 39 | var payload = {user: userId, device: deviceId}; 40 | 41 | if (isNaN(userId) || isNaN(deviceId)) { 42 | res.code = '4.01'; 43 | return res.end(JSON.stringify({"error": "username or device undefined"})); 44 | } 45 | 46 | var data; 47 | try { 48 | data = JSON.parse(req.payload.toString()); 49 | } catch (err) { 50 | res.code = '4.04'; 51 | res.end(err); 52 | } 53 | data.user = userId; 54 | data.device = deviceId; 55 | 56 | db.find(payload, function (results) { 57 | if (results.length > 0) { 58 | db.update(data); 59 | res.code = '2.01'; 60 | res.end(JSON.stringify({method: 'update'})); 61 | } else { 62 | db.insert(data); 63 | res.code = '2.01'; 64 | res.end(JSON.stringify({method: 'insert'})); 65 | } 66 | }); 67 | }; 68 | 69 | var handlePost = function () { 70 | var userId = parseInt(uriQuery.user); 71 | var deviceId = parseInt(uriQuery.device); 72 | var payload = {user: userId, device: deviceId}; 73 | 74 | if (isNaN(userId) || isNaN(deviceId)) { 75 | res.code = '4.04'; 76 | return res.end(JSON.stringify({"error": "username or device undefined"})); 77 | } 78 | var data; 79 | try { 80 | data = JSON.parse(req.payload.toString()); 81 | } catch (err) { 82 | res.code = '4.04'; 83 | return res.end(err); 84 | } 85 | data.user = userId; 86 | data.device = deviceId; 87 | 88 | db.find(payload, function (results) { 89 | if (results.length > 0) { 90 | db.update(data); 91 | res.code = '2.01'; 92 | res.end(JSON.stringify({method: 'update'})); 93 | } else { 94 | db.insert(data); 95 | res.code = '2.01'; 96 | res.end(JSON.stringify({method: 'insert'})); 97 | } 98 | }); 99 | }; 100 | 101 | switch (req.method) { 102 | case 'GET': 103 | handlerGet(); 104 | break; 105 | case 'PUT': 106 | handlePut(); 107 | break; 108 | case 'POST': 109 | handlePost(); 110 | break; 111 | default: 112 | return errorHandle(); 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /chapter7/lan/db.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | var url = "mongodb://localhost:27017/designiot"; 4 | 5 | function MongoPersistence() { 6 | 7 | } 8 | 9 | MongoPersistence.prototype.insert = function (payload) { 10 | 'use strict'; 11 | MongoClient.connect(url, function (err, db) { 12 | var insertDocuments = function (db, callback) { 13 | payload.date = new Date(); 14 | var collection = db.collection("documents"); 15 | collection.insert(payload, function (err, result) { 16 | callback(result); 17 | }); 18 | }; 19 | insertDocuments(db, function () { 20 | db.close(); 21 | }); 22 | }); 23 | }; 24 | 25 | MongoPersistence.prototype.update = function (payload) { 26 | 'use strict'; 27 | MongoClient.connect(url, function (err, db) { 28 | var updateDocument = function (db, callback) { 29 | var collection = db.collection("documents"); 30 | collection.update({user: payload.user}, {$set: payload}, function (err, result) { 31 | callback(); 32 | }); 33 | }; 34 | updateDocument(db, function () { 35 | db.close(); 36 | }); 37 | }); 38 | }; 39 | 40 | MongoPersistence.prototype.find = function (queryOptions, queryCB) { 41 | 'use strict'; 42 | MongoClient.connect(url, function (err, db) { 43 | var findDocuments = function (db, query, callback) { 44 | var collection = db.collection("documents"); 45 | collection.find(query).toArray(function (err, docs) { 46 | callback(docs); 47 | }); 48 | }; 49 | 50 | findDocuments(db, queryOptions, function (result) { 51 | db.close(); 52 | queryCB(result); 53 | }); 54 | }); 55 | }; 56 | 57 | MongoPersistence.prototype.findOrder = function (queryOptions, order, queryCB) { 58 | 'use strict'; 59 | MongoClient.connect(url, function (err, db) { 60 | var findDocuments = function (db, query, callback) { 61 | var collection = db.collection("documents"); 62 | collection.find(query).limit(1).skip(order).toArray(function (err, docs) { 63 | callback(docs); 64 | }); 65 | }; 66 | 67 | findDocuments(db, queryOptions, function (result) { 68 | db.close(); 69 | queryCB(result); 70 | }); 71 | }); 72 | }; 73 | 74 | /* 75 | A MongoDB tailable cursor would work a bit like a queue. It can work with a capped collection so you do not have to explicitly delete items in the collection. It is quite efficient, but keep in mind that MongoDB will lock the whole collection (the database actually) at each write operation, so it limits the scalability. Another scalability limitation is the number of connections. Each client connection will add a connection thread in the mongod servers (or mongos). 76 | 77 | Still you can expect tens of thousands of items per second without major problems, which may be enough for a range of applications. 78 | 79 | On the other hand, Redis can generally handle much more connections simultaneously, because each connection does not create a thread (Redis is a single-theaded event loop). It is also extremely CPU efficient, because it does not queue at all the items. With Redis pub/sub, the items are propagated to the subscribers in the same event loop iteration than the publication. The items are not even stored in memory, Redis does not even have a single index to maintain. They are only retrieved from a socket buffer to be pushed in another socket buffer. 80 | 81 | However, because there is no queuing, delivery of Redis pub/sub messages is not guaranteed at all. If a subscriber is down when a message is published, the message will be lost for this subscriber. 82 | 83 | With Redis, you can expect hundreds of thousands of items per second on a single core, especially if you use pipelining, and multiple publication clients. 84 | */ 85 | 86 | MongoPersistence.prototype.subscribe = function (queryOptions, queryCB) { 87 | 'use strict'; 88 | MongoClient.connect(url, function (err, db) { 89 | var subDocuments = function (db, query, callback) { 90 | var collection = db.collection("documents"); 91 | collection.find(query).sort({$natural: 1}).limit(1).toArray(function (err, doc) { 92 | callback(doc); 93 | }); 94 | }; 95 | 96 | subDocuments(db, queryOptions, function (result) { 97 | db.close(); 98 | queryCB(result); 99 | }); 100 | }); 101 | }; 102 | 103 | module.exports = MongoPersistence; -------------------------------------------------------------------------------- /chapter7/lan/mqttServer.js: -------------------------------------------------------------------------------- 1 | var Database = require('./db'); 2 | var db = new Database(); 3 | 4 | module.exports = function (client) { 5 | var self = this; 6 | var user = null; 7 | 8 | if (!self.clients) self.clients = {}; 9 | 10 | client.on('connect', function (packet) { 11 | self.clients[packet.clientId] = client; 12 | client.id = packet.clientId; 13 | console.log("CONNECT: client id: " + client.id); 14 | if (packet.username === undefined) { 15 | client.connack({returnCode: 4}); 16 | } 17 | user = packet.username; 18 | client.subscriptions = []; 19 | client.connack({returnCode: 0}); 20 | }); 21 | 22 | client.on('subscribe', function (packet) { 23 | var topic = packet.subscriptions[0].topic.toString(); 24 | if (!/device\/(\d)/.test(topic) || /device\/(\d)/.exec(topic).length < 1) { 25 | return client.connack({returnCode: 6}); 26 | } 27 | console.log("SUBSCRIBE(%s): %j", client.id, packet); 28 | var deviceId = parseInt(/device\/(\d)/.exec(topic)[1]); 29 | var payload = {user: parseInt(user), device: deviceId}; 30 | var granted = []; 31 | for (var i = 0; i < packet.subscriptions.length; i++) { 32 | var qos = packet.subscriptions[i].qos 33 | , topic = packet.subscriptions[i].topic 34 | , reg = new RegExp(topic.replace('+', '[^\/]+').replace('#', '.+') + '$'); 35 | 36 | granted.push(qos); 37 | client.subscriptions.push(reg); 38 | console.log(reg); 39 | } 40 | 41 | client.suback({messageId: packet.messageId, granted: granted}); 42 | 43 | db.subscribe(payload, function (results) { 44 | client.publish({ 45 | topic: topic, 46 | payload: JSON.stringify(results) 47 | }); 48 | }); 49 | }); 50 | 51 | client.on('publish', function (packet) { 52 | var topic = packet.topic.toString(); 53 | var topicRegex = /device\/(\d)/; 54 | if (!topicRegex.test(topic) || topicRegex.exec(topic).length < 1) { 55 | return client.connack({returnCode: 6}); 56 | } 57 | console.log("PUBLISH(%s): %j", packet.clientId, packet); 58 | var deviceId = parseInt(topicRegex.exec(topic)[1]); 59 | 60 | var payload; 61 | try { 62 | payload = JSON.parse(packet.payload); 63 | } catch (err) { 64 | console.log(err); 65 | return client.connack({returnCode: 6}); 66 | } 67 | payload.user = parseInt(user); 68 | payload.device = deviceId; 69 | db.insert(payload); 70 | 71 | for (var k in self.clients) { 72 | var _client = self.clients[k]; 73 | 74 | for (var i = 0; i < _client.subscriptions.length; i++) { 75 | var subscription = _client.subscriptions[i]; 76 | 77 | if (subscription.test(packet.topic)) { 78 | _client.publish({topic: packet.topic, payload: payload}); 79 | break; 80 | } 81 | } 82 | } 83 | }); 84 | 85 | client.on('pingreq', function (packet) { 86 | console.log('PINGREQ(%s)', client.id); 87 | client.pingresp(); 88 | }); 89 | 90 | client.on('disconnect', function (packet) { 91 | client.stream.end(); 92 | }); 93 | 94 | client.on('close', function (packet) { 95 | delete self.clients[client.id]; 96 | }); 97 | 98 | client.on('error', function (e) { 99 | client.stream.end(); 100 | console.log(e); 101 | }); 102 | }; -------------------------------------------------------------------------------- /chapter7/lan/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iot-restful-example", 3 | "version": "0.0.1", 4 | "description": "Internet of Things Example", 5 | "author": "", 6 | "license": "MIT", 7 | "dependencies": { 8 | "body-parser": "^1.14.1", 9 | "coap": "^0.12.1", 10 | "express": "^4.13.3", 11 | "jade": "~1.11.0", 12 | "mongodb": "^2.0.50", 13 | "mqtt": "^1.6.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter7/lan/public/scripts/echarts-ajax-devices.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var data = []; 3 | $.get('/user/1/devices/1/results', function(response){ 4 | $.each(response, function(key, val) { 5 | data.push(val.temperature); 6 | }); 7 | var myChart = echarts.init(document.getElementById('main')); 8 | 9 | var option = { 10 | legend: { // 图例配置 11 | padding: 5, // 图例内边距,单位px,默认上下左右内边距为5 12 | itemGap: 10, // Legend各个item之间的间隔,横向布局时为水平间隔,纵向布局时为纵向间隔 13 | data: ['月平均气温'] 14 | }, 15 | tooltip: { // 气泡提示配置 16 | trigger: 'item' // 触发类型,默认数据触发,可选为:'axis' 17 | }, 18 | xAxis: [ // 直角坐标系中横轴数组 19 | { 20 | type: 'category', // 坐标轴类型,横轴默认为类目轴,数值轴则参考yAxis说明 21 | data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 22 | } 23 | ], 24 | yAxis: [ // 直角坐标系中纵轴数组 25 | { 26 | type: 'value', // 坐标轴类型,纵轴默认为数值轴,类目轴则参考xAxis说明 27 | boundaryGap: [0.1, 0.1], // 坐标轴两端空白策略,数组内数值代表百分比 28 | splitNumber: 4 // 数值轴用,分割段数,默认为5 29 | } 30 | ], 31 | series: [ 32 | { 33 | name: '气温', // 系列名称 34 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 35 | data: data 36 | }] 37 | }; 38 | 39 | myChart.setOption(option); 40 | }); 41 | }); -------------------------------------------------------------------------------- /chapter7/lan/public/scripts/echarts-example.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var myChart = echarts.init(document.getElementById('main')); 3 | 4 | var option = { 5 | legend: { // 图例配置 6 | padding: 5, // 图例内边距,单位px,默认上下左右内边距为5 7 | itemGap: 10, // Legend各个item之间的间隔,横向布局时为水平间隔,纵向布局时为纵向间隔 8 | data: ['月平均气温'] 9 | }, 10 | tooltip: { // 气泡提示配置 11 | trigger: 'item' // 触发类型,默认数据触发,可选为:'axis' 12 | }, 13 | xAxis: [ // 直角坐标系中横轴数组 14 | { 15 | type: 'category', // 坐标轴类型,横轴默认为类目轴,数值轴则参考yAxis说明 16 | data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 17 | } 18 | ], 19 | yAxis: [ // 直角坐标系中纵轴数组 20 | { 21 | type: 'value', // 坐标轴类型,纵轴默认为数值轴,类目轴则参考xAxis说明 22 | boundaryGap: [0.1, 0.1], // 坐标轴两端空白策略,数组内数值代表百分比 23 | splitNumber: 4 // 数值轴用,分割段数,默认为5 24 | } 25 | ], 26 | series: [ 27 | { 28 | name: '气温', // 系列名称 29 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 30 | data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] 31 | }] 32 | }; 33 | 34 | myChart.setOption(option); 35 | }); -------------------------------------------------------------------------------- /chapter7/lan/public/scripts/highcharts-ajax-devices.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | var data = []; 3 | $.get('/user/1/devices/1/results', function(response){ 4 | $.each(response, function(key, val) { 5 | data.push(val.temperature); 6 | }); 7 | $('#container').highcharts({ 8 | title: { 9 | text: '月平均气温', 10 | x: -20 //center 11 | }, 12 | xAxis: { 13 | categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 14 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 15 | }, 16 | yAxis: { 17 | title: { 18 | text: 'Temperature (°C)' 19 | }, 20 | plotLines: [{ 21 | value: 0, 22 | width: 1, 23 | color: '#808080' 24 | }] 25 | }, 26 | tooltip: { 27 | valueSuffix: '°C' 28 | }, 29 | legend: { 30 | layout: 'vertical', 31 | align: 'right', 32 | verticalAlign: 'middle', 33 | borderWidth: 0 34 | }, 35 | series: [{ 36 | name: 'Today', 37 | data: data 38 | }] 39 | }); 40 | }); 41 | }); -------------------------------------------------------------------------------- /chapter7/lan/public/scripts/highcharts-example.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $('#container').highcharts({ 3 | title: { 4 | text: '月平均气温', 5 | x: -20 //center 6 | }, 7 | xAxis: { 8 | categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 9 | 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 10 | }, 11 | yAxis: { 12 | title: { 13 | text: 'Temperature (°C)' 14 | }, 15 | plotLines: [{ 16 | value: 0, 17 | width: 1, 18 | color: '#808080' 19 | }] 20 | }, 21 | tooltip: { 22 | valueSuffix: '°C' 23 | }, 24 | legend: { 25 | layout: 'vertical', 26 | align: 'right', 27 | verticalAlign: 'middle', 28 | borderWidth: 0 29 | }, 30 | series: [{ 31 | name: 'Today', 32 | data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] 33 | }] 34 | }); 35 | }); -------------------------------------------------------------------------------- /chapter7/lan/views/dashboard.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | script(src='./scripts/jquery-2.1.4.min.js') 6 | 7 | //script(src='./scripts/echarts-all.js') 8 | //script(src='./scripts/echarts-example.js') 9 | 10 | script(src='./scripts/highcharts.js') 11 | //script(src='./scripts/highcharts-example.js') 12 | script(src='./scripts/highcharts-ajax-devices.js') 13 | body 14 | div(id="container" style="min-width: 310px; height: 400px; margin: 0 auto") 15 | div(id="main" style="min-width: 310px; height: 400px; margin: 0 auto") 16 | 17 | -------------------------------------------------------------------------------- /chapter7/lan/views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | body 6 | form(method="post", action="/") 7 | div 8 | label(from="user") User 9 | input(type='number', name="user") 10 | div 11 | label(from="led") Led 12 | input(type='checkbox', checked=false, name="led") 13 | 14 | input(type="submit", value="OK") 15 | 16 | -------------------------------------------------------------------------------- /chapter8/README.md: -------------------------------------------------------------------------------- 1 | #第八章 2 | 3 | 目录 | 说明 4 | ----------|----------- 5 | nlp | 自然语言处理相关 -------------------------------------------------------------------------------- /chapter8/nlp/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /chapter8/nlp/README.md: -------------------------------------------------------------------------------- 1 | #自然语言处理 2 | 3 | 代码见``app.js`` 4 | -------------------------------------------------------------------------------- /chapter8/nlp/app.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | var _lexer, _trie, dict; 3 | 4 | var clock = 0; 5 | var deviceId = 0; 6 | var open = false; 7 | 8 | _lexer = new Geng.lexer(); 9 | _trie = new Geng.trie(); 10 | 11 | dict = ['晚上', '八点', '打开', '开启', '电暖']; 12 | _trie.init(dict); 13 | 14 | document.getElementById('dict').innerText = dict.toString(); 15 | 16 | _lexer.addRule(/晚上/, function () { 17 | clock = clock + 12; 18 | }); 19 | 20 | _lexer.addRule(/八点/, function () { 21 | clock = clock + 8; 22 | }); 23 | 24 | _lexer.addRule(/开启|打开/, function () { 25 | open = true; 26 | }); 27 | 28 | _lexer.addRule(/电暖气|电暖|暖气/, function () { 29 | deviceId = 1; 30 | }); 31 | 32 | var test_words = '晚上八点打开电暖气'; 33 | var words = _trie.splitWords(test_words); 34 | 35 | function devices(id) { 36 | this.deviceId = id; 37 | } 38 | 39 | devices.prototype.openAt = function (clock) { 40 | console.log("Device " + this.deviceId + " will open at" + clock); 41 | }; 42 | 43 | words.forEach(function (word) { 44 | _lexer.setInput(word); 45 | _lexer.lex(); 46 | }); 47 | 48 | if (open) { 49 | var device = new devices(deviceId); 50 | device.openAt(clock); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /chapter8/nlp/geng.js: -------------------------------------------------------------------------------- 1 | (function (root, undefined) { 2 | 3 | "use strict"; 4 | 5 | //(The MIT License) 6 | // 7 | //Copyright (c) by Tolga Tezel tolgatezel11@gmail.com 8 | // 9 | //Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | // keys we use to serialize a classifier's state 16 | 17 | var defaultTokenizer = function (text) { 18 | //remove punctuation from text - remove anything that isn't a word char or a space 19 | var rgxPunctuation = /[^\w\s]/g; 20 | var sanitized = text.replace(rgxPunctuation, ' '); 21 | return sanitized.split(/\s+/); 22 | }; 23 | 24 | var ChineseTokenizer = function (text) { 25 | //remove punctuation from text - remove anything that isn't a word char or a space 26 | return text.split(/\s+/); 27 | }; 28 | 29 | /** 30 | * Naive-Bayes Classifier 31 | * 32 | * This is a naive-bayes classifier that uses Laplace Smoothing. 33 | * 34 | * Takes an (optional) options object containing: 35 | * - `tokenizer` => custom tokenization function 36 | * 37 | */ 38 | function Bayes(options) { 39 | // set options object 40 | this.options = {}; 41 | if (typeof options !== 'undefined') { 42 | if (!options || typeof options !== 'object' || Array.isArray(options)) { 43 | throw new TypeError('Bayes got invalid `options`: `' + options + '`. Pass in an object.'); 44 | } 45 | this.options = options; 46 | } 47 | 48 | this.tokenizer = this.options.tokenizer || defaultTokenizer; 49 | 50 | if (this.options.tokenizer === "Chinese") { 51 | this.tokenizer = ChineseTokenizer; 52 | } 53 | 54 | //initialize our vocabulary and its size 55 | this.vocabulary = {}; 56 | this.vocabularySize = 0; 57 | 58 | //number of documents we have learned from 59 | this.totalDocuments = 0; 60 | 61 | //document frequency table for each of our categories 62 | //=> for each category, how often were documents mapped to it 63 | this.docCount = {}; 64 | 65 | //for each category, how many words total were mapped to it 66 | this.wordCount = {}; 67 | 68 | //word frequency table for each category 69 | //=> for each category, how frequent was a given word mapped to it 70 | this.wordFrequencyCount = {}; 71 | 72 | //hashmap of our category names 73 | this.categories = {}; 74 | } 75 | 76 | /** 77 | * Initialize each of our data structure entries for this new category 78 | * 79 | * @param {String} categoryName 80 | */ 81 | Bayes.prototype.initializeCategory = function (categoryName) { 82 | if (!this.categories[categoryName]) { 83 | this.docCount[categoryName] = 0; 84 | this.wordCount[categoryName] = 0; 85 | this.wordFrequencyCount[categoryName] = {}; 86 | this.categories[categoryName] = true; 87 | } 88 | return this; 89 | }; 90 | 91 | /** 92 | * train our naive-bayes classifier by telling it what `category` 93 | * the `text` corresponds to. 94 | * 95 | * @param {String} text 96 | * @param {String} class 97 | */ 98 | Bayes.prototype.learn = function (text, category) { 99 | var self = this; 100 | 101 | //initialize category data structures if we've never seen this category 102 | self.initializeCategory(category); 103 | 104 | //update our count of how many documents mapped to this category 105 | self.docCount[category]++; 106 | 107 | //update the total number of documents we have learned from 108 | self.totalDocuments++; 109 | 110 | //normalize the text into a word array 111 | var tokens = self.tokenizer(text); 112 | 113 | //get a frequency count for each token in the text 114 | var frequencyTable = self.frequencyTable(tokens); 115 | 116 | /* 117 | Update our vocabulary and our word frequency count for this category 118 | */ 119 | 120 | Object 121 | .keys(frequencyTable) 122 | .forEach(function (token) { 123 | //add this word to our vocabulary if not already existing 124 | if (!self.vocabulary[token]) { 125 | self.vocabulary[token] = true; 126 | self.vocabularySize++; 127 | } 128 | 129 | var frequencyInText = frequencyTable[token]; 130 | 131 | //update the frequency information for this word in this category 132 | if (!self.wordFrequencyCount[category][token]) { 133 | self.wordFrequencyCount[category][token] = frequencyInText; 134 | } else { 135 | self.wordFrequencyCount[category][token] += frequencyInText; 136 | } 137 | 138 | //update the count of all words we have seen mapped to this category 139 | self.wordCount[category] += frequencyInText; 140 | }); 141 | 142 | return self; 143 | }; 144 | 145 | /** 146 | * Determine what category `text` belongs to. 147 | * 148 | * @param {String} text 149 | * @return {String} category 150 | */ 151 | Bayes.prototype.categorize = function (text) { 152 | var self = this; 153 | var maxProbability = -Infinity; 154 | var chosenCategory = null; 155 | 156 | var tokens = self.tokenizer(text); 157 | var frequencyTable = self.frequencyTable(tokens); 158 | 159 | //iterate thru our categories to find the one with max probability for this text 160 | Object 161 | .keys(self.categories) 162 | .forEach(function (category) { 163 | 164 | //start by calculating the overall probability of this category 165 | //=> out of all documents we've ever looked at, how many were 166 | // mapped to this category 167 | var categoryProbability = self.docCount[category] / self.totalDocuments; 168 | 169 | //take the log to avoid underflow 170 | var logProbability = Math.log(categoryProbability); 171 | 172 | //now determine P( w | c ) for each word `w` in the text 173 | Object 174 | .keys(frequencyTable) 175 | .forEach(function (token) { 176 | var frequencyInText = frequencyTable[token]; 177 | var tokenProbability = self.tokenProbability(token, category); 178 | // console.log('token: %s category: `%s` tokenProbability: %d', token, category, tokenProbability) 179 | 180 | //determine the log of the P( w | c ) for this word 181 | logProbability += frequencyInText * Math.log(tokenProbability); 182 | }); 183 | 184 | if (logProbability > maxProbability) { 185 | maxProbability = logProbability; 186 | chosenCategory = category; 187 | } 188 | }); 189 | 190 | return chosenCategory; 191 | }; 192 | 193 | /** 194 | * Calculate probability that a `token` belongs to a `category` 195 | * 196 | * @param {String} token 197 | * @param {String} category 198 | * @return {Number} probability 199 | */ 200 | Bayes.prototype.tokenProbability = function (token, category) { 201 | //how many times this word has occurred in documents mapped to this category 202 | var wordFrequencyCount = this.wordFrequencyCount[category][token] || 0; 203 | //what is the count of all words that have ever been mapped to this category 204 | var wordCount = this.wordCount[category]; 205 | 206 | //use laplace Add-1 Smoothing equation 207 | return ( wordFrequencyCount + 1 ) / ( wordCount + this.vocabularySize ); 208 | }; 209 | 210 | /** 211 | * Build a frequency hashmap where 212 | * - the keys are the entries in `tokens` 213 | * - the values are the frequency of each entry in `tokens` 214 | * 215 | * @param {Array} tokens Normalized word array 216 | * @return {Object} 217 | */ 218 | Bayes.prototype.frequencyTable = function (tokens) { 219 | var frequencyTable = {}; 220 | 221 | tokens.forEach(function (token) { 222 | if (!frequencyTable[token]) { 223 | frequencyTable[token] = 1; 224 | } else { 225 | frequencyTable[token]++; 226 | } 227 | }); 228 | 229 | return frequencyTable; 230 | }; 231 | 232 | // code base on http://my.oschina.net/goal/blog/201674 233 | 234 | // 停止词 235 | var stop = { 236 | "的": 1 237 | }; 238 | 239 | // 节点 240 | function Node() { 241 | this.childs = {}; // 子节点集合 242 | this._isWord = false; // 边界保存,表示是否可以组成一个词 243 | this._count = 0; 244 | } 245 | 246 | Node.prototype = { 247 | isWord: function () { 248 | return (this._isWord && (this._count === 0)); 249 | }, 250 | asWord: function () { 251 | this._isWord = true; 252 | }, 253 | addCount: function () { 254 | this._count++; 255 | } 256 | }; 257 | 258 | // Trie树 259 | function Trie() { 260 | this.root = new Node(); 261 | } 262 | 263 | Trie.prototype = { 264 | /** 265 | * 将Unicode转成UTF8的三字节 266 | */ 267 | toBytes: function (word) { 268 | var result = []; 269 | for (var i = 0; i < word.length; i++) { 270 | var code = word.charCodeAt(i); 271 | // 单字节 272 | if (code < 0x80) { 273 | result.push(code); 274 | } else { 275 | // 三字节 276 | result = result.concat(this.toUTF8(code)); 277 | } 278 | } 279 | 280 | return result; 281 | }, 282 | toUTF8: function (c) { 283 | // 1110xxxx 10xxxxxx 10xxxxxx 284 | // 1110xxxx 285 | var byte1, byte2, byte3; 286 | byte1 = 0xE0 | ((c >> 12) & 0x0F); 287 | // 10xxxxxx 288 | byte2 = 0x80 | ((c >> 6) & 0x3F); 289 | // 10xxxxxx 290 | byte3 = 0x80 | (c & 0x3F); 291 | 292 | return [byte1, byte2, byte3]; 293 | }, 294 | toUTF16: function (b1, b2, b3) { 295 | // 1110xxxx 10xxxxxx 10xxxxxx 296 | var byte1, byte2, utf16; 297 | byte1 = (b1 << 4) | ((b2 >> 2) & 0x0F); 298 | byte2 = ((b2 & 0x03) << 6) | (b3 & 0x3F); 299 | utf16 = ((byte1 & 0x00FF) << 8) | byte2; 300 | return utf16; 301 | }, 302 | /** 303 | * 添加每个词到Trie树 304 | */ 305 | add: function (word) { 306 | var node = this.root, bytes = this.toBytes(word), len = bytes.length; 307 | for (var i = 0; i < len; i++) { 308 | var c = bytes[i]; 309 | // 如果不存在则添加,否则不需要再保存了,因为共用前缀 310 | if (!(c in node.childs)) { 311 | node.childs[c] = new Node(c); 312 | } 313 | node = node.childs[c]; 314 | } 315 | node.asWord(); // 成词边界 316 | }, 317 | /** 318 | * 按字节在Trie树中搜索 319 | */ 320 | search: function (bytes) { 321 | var node = this.root, len = bytes.length, result = []; 322 | var word = [], j = 0; 323 | for (var i = 0; i < len; i++) { 324 | var c = bytes[i], childs = node.childs; 325 | if (!(c in childs)) { 326 | return result; 327 | } 328 | 329 | if (c < 0x80) { 330 | word.push(String.fromCharCode(c)); 331 | } else { 332 | j++; 333 | if (j % 3 === 0) { 334 | var b1 = bytes[i - 2]; 335 | var b2 = bytes[i - 1]; 336 | var b3 = c; 337 | word.push(String.fromCharCode(this.toUTF16(b1, b2, b3))); 338 | } 339 | } 340 | // 如果是停止词,则退出 341 | if (word.join('') in stop) { 342 | return result; 343 | } 344 | 345 | // 成词 346 | var cnode = childs[c]; 347 | if (cnode.isWord()) { 348 | cnode.addCount(); // 用于计数判断 349 | result.push(word.join('')); 350 | } 351 | 352 | node = cnode; 353 | } 354 | 355 | return result; 356 | }, 357 | /** 358 | * 分词 359 | */ 360 | splitWords: function (words) { 361 | // 转换成单字节进行搜索 362 | var bytes = this.toBytes(words); 363 | var start = 0, end = bytes.length - 1, result = []; 364 | 365 | while (start != end) { 366 | var word = [], b, finds; 367 | for (var i = start; i <= end; i++) { 368 | b = bytes[i]; // 逐个取出字节 369 | word.push(b); 370 | 371 | finds = this.search(word); 372 | if (finds !== false && finds.length > 0) { 373 | // 如果在字典中,则添加到分词结果集 374 | result = result.concat(finds); 375 | } 376 | } 377 | 378 | start++; 379 | } 380 | 381 | return result; 382 | }, 383 | /** 384 | * 词始化整棵Trie树 385 | */ 386 | init: function (dict) { 387 | for (var i = 0; i < dict.length; i++) { 388 | this.add(dict[i]); 389 | } 390 | } 391 | }; 392 | 393 | 394 | //The MIT License (MIT) 395 | // 396 | //Copyright (c) 2013 Aadit M Shah 397 | // 398 | //Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 399 | // 400 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 401 | // 402 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 403 | // 404 | //Github: https://github.com/aaditmshah/lexer 405 | 406 | //Lexer.defunct = function (char) { 407 | // throw new Error("Unexpected character at index " + (this.index - 1) + ": " + char); 408 | //}; 409 | 410 | /*jshint curly: false */ 411 | 412 | function Lexer(defunct) { 413 | if (typeof defunct !== "function") { 414 | defunct = function (char) { 415 | throw new TypeError("Unexpected character at index " + (this.index - 1) + ": " + char); 416 | }; 417 | } 418 | 419 | var tokens = []; 420 | var rules = []; 421 | var remove = 0; 422 | this.state = 0; 423 | this.index = 0; 424 | this.input = ""; 425 | 426 | this.addRule = function (pattern, action, start) { 427 | var global = pattern.global; 428 | 429 | if (!global) { 430 | var flags = "g"; 431 | if (pattern.multiline) flags += "m"; 432 | if (pattern.ignoreCase) flags += "i"; 433 | pattern = new RegExp(pattern.source, flags); 434 | } 435 | 436 | if (Object.prototype.toString.call(start) !== "[object Array]") start = [0]; 437 | 438 | rules.push({ 439 | pattern: pattern, 440 | global: global, 441 | action: action, 442 | start: start 443 | }); 444 | 445 | return this; 446 | }; 447 | 448 | this.setInput = function (input) { 449 | remove = 0; 450 | this.state = 0; 451 | this.index = 0; 452 | tokens.length = 0; 453 | this.input = input; 454 | return this; 455 | }; 456 | 457 | this.lex = function () { 458 | if (tokens.length) return tokens.shift(); 459 | 460 | this.reject = true; 461 | 462 | while (this.index <= this.input.length) { 463 | var matches = scan.call(this).splice(remove); 464 | var index = this.index; 465 | 466 | while (matches.length) { 467 | if (this.reject) { 468 | var match = matches.shift(); 469 | var result = match.result; 470 | var length = match.length; 471 | this.index += length; 472 | this.reject = false; 473 | remove++; 474 | 475 | var token = match.action.apply(this, result); 476 | if (this.reject) this.index = result.index; 477 | else if (typeof token !== "undefined") { 478 | switch (Object.prototype.toString.call(token)) { 479 | case "[object Array]": 480 | tokens = token.slice(1); 481 | token = token[0]; 482 | break; 483 | default: 484 | if (length) remove = 0; 485 | return token; 486 | } 487 | } 488 | } else break; 489 | } 490 | 491 | var input = this.input; 492 | 493 | if (index < input.length) { 494 | if (this.reject) { 495 | remove = 0; 496 | var token2 = defunct.call(this, input.charAt(this.index++)); 497 | if (typeof token2 !== "undefined") { 498 | if (Object.prototype.toString.call(token2) === "[object Array]") { 499 | tokens = token2.slice(1); 500 | return token2[0]; 501 | } else return token2; 502 | } 503 | } else { 504 | if (this.index !== index) remove = 0; 505 | this.reject = true; 506 | } 507 | } else if (matches.length) 508 | this.reject = true; 509 | else break; 510 | } 511 | }; 512 | 513 | function scan() { 514 | /*jshint validthis: true */ 515 | var that = this; 516 | var matches = []; 517 | var index = 0; 518 | 519 | for (var i = 0, length = rules.length; i < length; i++) { 520 | var rule = rules[i]; 521 | var start = rule.start; 522 | var states = start.length; 523 | 524 | if ((!states || start.indexOf(that.state) >= 0) || 525 | (that.state % 2 && states === 1 && !start[0])) { 526 | var pattern = rule.pattern; 527 | pattern.lastIndex = that.index; 528 | var result = pattern.exec(that.input); 529 | 530 | if (result && result.index === that.index) { 531 | var j = matches.push({ 532 | result: result, 533 | action: rule.action, 534 | length: result[0].length 535 | }); 536 | 537 | if (rule.global) index = j; 538 | 539 | while (--j > index) { 540 | var k = j - 1; 541 | 542 | if (matches[j].length > matches[k].length) { 543 | var temple = matches[j]; 544 | matches[j] = matches[k]; 545 | matches[k] = temple; 546 | } 547 | } 548 | } 549 | } 550 | } 551 | 552 | return matches; 553 | } 554 | } 555 | 556 | var dict = ['古代', '现在', '此时', '此刻', '等于', '是', '今天']; 557 | var combinedDict = []; 558 | 559 | //子丑寅卯辰巳午未申酉戌亥 560 | var oldTime = [ 561 | {time: '子时', from: '23', to: '1'}, 562 | {time: '丑时', from: '1', to: '3'}, 563 | {time: '寅时', from: '3', to: '5'}, 564 | {time: '卯时', from: '5', to: '7'}, 565 | {time: '辰时', from: '7', to: '9'}, 566 | {time: '巳时', from: '9', to: '11'}, 567 | {time: '午时', from: '11', to: '13'}, 568 | {time: '未时', from: '13', to: '15'}, 569 | {time: '申时', from: '15', to: '17'}, 570 | {time: '酉时', from: '17', to: '19'}, 571 | {time: '戌时', from: '19', to: '21'}, 572 | {time: '亥时', from: '21', to: '23'} 573 | ]; 574 | 575 | var nowWords = ['现在', 'Today', '此时', '此刻', '今天']; 576 | var eq = ['是', '等于']; 577 | 578 | var Utils = {}; 579 | 580 | Utils.combinedObjString = function (dict, str) { 581 | if (typeof str[0] !== 'object') { 582 | str.forEach(function (data) { 583 | dict.push(data); 584 | }); 585 | } else { 586 | str.forEach(function (time) { 587 | dict.push(time.time); 588 | }); 589 | } 590 | 591 | return dict; 592 | }; 593 | 594 | Utils.objectToStringRegex = function (str) { 595 | var result = ""; 596 | str.forEach(function (time) { 597 | result = result + time.time + "|"; 598 | }); 599 | 600 | return result.substring(0, result.length - 1); 601 | }; 602 | 603 | Utils.arrayToStringRegex = function (str) { 604 | var result = ""; 605 | str.forEach(function (data) { 606 | result = result + data + "|"; 607 | }); 608 | 609 | return result.substring(0, result.length - 1); 610 | }; 611 | 612 | Utils.stringToRegex = function (str) { 613 | return new RegExp(str); 614 | }; 615 | 616 | Utils.extend = function (object) { 617 | var source, prop; 618 | for (var i = 1, length = arguments.length; i < length; i++) { 619 | source = arguments[i]; 620 | for (prop in source) { 621 | if (hasOwnProperty.call(source, prop)) { 622 | object[prop] = source[prop]; 623 | } 624 | } 625 | } 626 | return object; 627 | }; 628 | 629 | combinedDict = Utils.combinedObjString(dict, oldTime); 630 | combinedDict = Utils.combinedObjString(combinedDict, nowWords); 631 | combinedDict = Utils.combinedObjString(combinedDict, eq); 632 | 633 | var Geng = function () { 634 | }; 635 | 636 | Geng.parser = function (time) { 637 | this.time = time; 638 | return this; 639 | }; 640 | 641 | Geng.convert = function () { 642 | var results = {}, 643 | result, 644 | trieTree = new Geng.trie(), 645 | lexer = new Geng.lexer(); 646 | 647 | trieTree.init(combinedDict); 648 | 649 | var oldTimeRegex = Utils.stringToRegex(Utils.objectToStringRegex(oldTime)); 650 | lexer.addRule(oldTimeRegex, function (lexme) { 651 | var result = {}; 652 | oldTime.forEach(function (time) { 653 | if (time.time === lexme) { 654 | result = time; 655 | delete result.time; 656 | } 657 | }); 658 | 659 | return {time: result}; 660 | }); 661 | 662 | var newWordsRegex = Utils.stringToRegex(Utils.arrayToStringRegex(nowWords)); 663 | lexer.addRule(newWordsRegex, function () { 664 | return {now: true}; 665 | }); 666 | 667 | var eqRegex = Utils.stringToRegex(Utils.arrayToStringRegex(eq)); 668 | lexer.addRule(eqRegex, function () { 669 | return {condition: "equal"}; 670 | }); 671 | 672 | var words = trieTree.splitWords(this.time); 673 | words.forEach(function (word) { 674 | lexer.setInput(word); 675 | result = lexer.lex(); 676 | Utils.extend(results, result); 677 | }); 678 | 679 | return results.time; 680 | }; 681 | 682 | Geng.version = Geng.VERSION = '0.0.2'; 683 | 684 | Geng.trie = Trie; 685 | Geng.lexer = Lexer; 686 | Geng.bayes = Bayes; 687 | 688 | root.Geng = Geng; 689 | 690 | }(this)); 691 | -------------------------------------------------------------------------------- /chapter8/nlp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Geng NLP 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | *DS_STORE 2 | history.yml 3 | -------------------------------------------------------------------------------- /dashboard/.ruby-version: -------------------------------------------------------------------------------- 1 | 1.9.3-p374 2 | -------------------------------------------------------------------------------- /dashboard/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'dashing' 4 | 5 | ## Remove this if you don't need a twitter widget. 6 | gem 'twitter', '>= 5.0.0' -------------------------------------------------------------------------------- /dashboard/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.3.8) 5 | backports (3.6.7) 6 | buftok (0.2.0) 7 | coffee-script (2.2.0) 8 | coffee-script-source 9 | execjs 10 | coffee-script-source (1.9.1.1) 11 | daemons (1.2.3) 12 | dashing (1.3.4) 13 | coffee-script (~> 2.2.0) 14 | execjs (~> 2.0.2) 15 | rack (~> 1.5.2) 16 | rufus-scheduler (~> 2.0.24) 17 | sass (~> 3.2.12) 18 | sinatra (~> 1.4.4) 19 | sinatra-contrib (~> 1.4.2) 20 | sprockets (~> 2.10.1) 21 | thin (~> 1.6.1) 22 | thor (~> 0.18.1) 23 | domain_name (0.5.25) 24 | unf (>= 0.0.5, < 1.0.0) 25 | equalizer (0.0.10) 26 | eventmachine (1.0.8) 27 | execjs (2.0.2) 28 | faraday (0.9.2) 29 | multipart-post (>= 1.2, < 3) 30 | hike (1.2.3) 31 | http (0.9.8) 32 | addressable (~> 2.3) 33 | http-cookie (~> 1.0) 34 | http-form_data (~> 1.0.1) 35 | http_parser.rb (~> 0.6.0) 36 | http-cookie (1.0.2) 37 | domain_name (~> 0.5) 38 | http-form_data (1.0.1) 39 | http_parser.rb (0.6.0) 40 | json (1.8.3) 41 | memoizable (0.4.2) 42 | thread_safe (~> 0.3, >= 0.3.1) 43 | multi_json (1.11.2) 44 | multipart-post (2.0.0) 45 | naught (1.1.0) 46 | rack (1.5.5) 47 | rack-protection (1.5.3) 48 | rack 49 | rack-test (0.6.3) 50 | rack (>= 1.0) 51 | rufus-scheduler (2.0.24) 52 | tzinfo (>= 0.3.22) 53 | sass (3.2.19) 54 | simple_oauth (0.3.1) 55 | sinatra (1.4.6) 56 | rack (~> 1.4) 57 | rack-protection (~> 1.4) 58 | tilt (>= 1.3, < 3) 59 | sinatra-contrib (1.4.6) 60 | backports (>= 2.0) 61 | multi_json 62 | rack-protection 63 | rack-test 64 | sinatra (~> 1.4.0) 65 | tilt (>= 1.3, < 3) 66 | sprockets (2.10.2) 67 | hike (~> 1.2) 68 | multi_json (~> 1.0) 69 | rack (~> 1.0) 70 | tilt (~> 1.1, != 1.3.0) 71 | thin (1.6.4) 72 | daemons (~> 1.0, >= 1.0.9) 73 | eventmachine (~> 1.0, >= 1.0.4) 74 | rack (~> 1.0) 75 | thor (0.18.1) 76 | thread_safe (0.3.5) 77 | tilt (1.4.1) 78 | twitter (5.15.0) 79 | addressable (~> 2.3) 80 | buftok (~> 0.2.0) 81 | equalizer (= 0.0.10) 82 | faraday (~> 0.9.0) 83 | http (>= 0.4, < 0.10) 84 | http_parser.rb (~> 0.6.0) 85 | json (~> 1.8) 86 | memoizable (~> 0.4.0) 87 | naught (~> 1.0) 88 | simple_oauth (~> 0.3.0) 89 | tzinfo (1.2.2) 90 | thread_safe (~> 0.1) 91 | unf (0.1.4) 92 | unf_ext 93 | unf_ext (0.0.7.1) 94 | 95 | PLATFORMS 96 | ruby 97 | 98 | DEPENDENCIES 99 | dashing 100 | twitter (>= 5.0.0) 101 | 102 | BUNDLED WITH 103 | 1.10.6 104 | -------------------------------------------------------------------------------- /dashboard/README.md: -------------------------------------------------------------------------------- 1 | #Dashboard 2 | 3 | 4 | 1.Install Deps 5 | 6 | bundle install 7 | 8 | 2.Run 9 | 10 | dashing start 11 | 12 | 3.Open [http://localhost:3030](http://localhost:3030) -------------------------------------------------------------------------------- /dashboard/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/dashboard/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /dashboard/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/dashboard/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dashboard/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/dashboard/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /dashboard/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/dashboard/assets/images/logo.png -------------------------------------------------------------------------------- /dashboard/assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # dashing.js is located in the dashing framework 2 | # It includes jquery & batman for you. 3 | #= require dashing.js 4 | 5 | #= require_directory . 6 | #= require_tree ../../widgets 7 | 8 | console.log("Yeah! The dashboard has started!") 9 | 10 | Dashing.on 'ready', -> 11 | Dashing.widget_margins ||= [5, 5] 12 | Dashing.widget_base_dimensions ||= [300, 360] 13 | Dashing.numColumns ||= 4 14 | 15 | contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns 16 | 17 | Batman.setImmediate -> 18 | $('.gridster').width(contentWidth) 19 | $('.gridster ul:first').gridster 20 | widget_margins: Dashing.widget_margins 21 | widget_base_dimensions: Dashing.widget_base_dimensions 22 | avoid_overlapped_widgets: !Dashing.customGridsterLayout 23 | draggable: 24 | stop: Dashing.showGridsterInstructions 25 | start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() 26 | -------------------------------------------------------------------------------- /dashboard/assets/javascripts/dashing.gridster.coffee: -------------------------------------------------------------------------------- 1 | #= require_directory ./gridster 2 | 3 | # This file enables gridster integration (http://gridster.net/) 4 | # Delete it if you'd rather handle the layout yourself. 5 | # You'll miss out on a lot if you do, but we won't hold it against you. 6 | 7 | Dashing.gridsterLayout = (positions) -> 8 | Dashing.customGridsterLayout = true 9 | positions = positions.replace(/^"|"$/g, '') 10 | positions = $.parseJSON(positions) 11 | widgets = $("[data-row^=]") 12 | for widget, index in widgets 13 | $(widget).attr('data-row', positions[index].row) 14 | $(widget).attr('data-col', positions[index].col) 15 | 16 | Dashing.getWidgetPositions = -> 17 | $(".gridster ul:first").gridster().data('gridster').serialize() 18 | 19 | Dashing.showGridsterInstructions = -> 20 | newWidgetPositions = Dashing.getWidgetPositions() 21 | 22 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 23 | Dashing.currentWidgetPositions = newWidgetPositions 24 | $('#save-gridster').slideDown() 25 | $('#gridster-code').text(" 26 | 31 | ") 32 | 33 | $ -> 34 | $('#save-gridster').leanModal() 35 | 36 | $('#save-gridster').click -> 37 | $('#save-gridster').slideUp() 38 | -------------------------------------------------------------------------------- /dashboard/assets/javascripts/gridster/jquery.leanModal.min.js: -------------------------------------------------------------------------------- 1 | // leanModal v1.1 by Ray Stone - http://finelysliced.com.au 2 | // Dual licensed under the MIT and GPL 3 | 4 | (function($){$.fn.extend({leanModal:function(options){var defaults={top:100,overlay:0.5,closeButton:null};var overlay=$("
");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); 5 | $("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); 6 | -------------------------------------------------------------------------------- /dashboard/assets/javascripts/jquery.knob.js: -------------------------------------------------------------------------------- 1 | /*!jQuery Knob*/ 2 | /** 3 | * Downward compatible, touchable dial 4 | * 5 | * Version: 1.2.0 (15/07/2012) 6 | * Requires: jQuery v1.7+ 7 | * 8 | * Copyright (c) 2012 Anthony Terrien 9 | * Under MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | * Thanks to vor, eskimoblood, spiffistan, FabrizioC 14 | */ 15 | $(function () { 16 | 17 | /** 18 | * Kontrol library 19 | */ 20 | "use strict"; 21 | 22 | /** 23 | * Definition of globals and core 24 | */ 25 | var k = {}, // kontrol 26 | max = Math.max, 27 | min = Math.min; 28 | 29 | k.c = {}; 30 | k.c.d = $(document); 31 | k.c.t = function (e) { 32 | return e.originalEvent.touches.length - 1; 33 | }; 34 | 35 | /** 36 | * Kontrol Object 37 | * 38 | * Definition of an abstract UI control 39 | * 40 | * Each concrete component must call this one. 41 | * 42 | * k.o.call(this); 43 | * 44 | */ 45 | k.o = function () { 46 | var s = this; 47 | 48 | this.o = null; // array of options 49 | this.$ = null; // jQuery wrapped element 50 | this.i = null; // mixed HTMLInputElement or array of HTMLInputElement 51 | this.g = null; // 2D graphics context for 'pre-rendering' 52 | this.v = null; // value ; mixed array or integer 53 | this.cv = null; // change value ; not commited value 54 | this.x = 0; // canvas x position 55 | this.y = 0; // canvas y position 56 | this.$c = null; // jQuery canvas element 57 | this.c = null; // rendered canvas context 58 | this.t = 0; // touches index 59 | this.isInit = false; 60 | this.fgColor = null; // main color 61 | this.pColor = null; // previous color 62 | this.dH = null; // draw hook 63 | this.cH = null; // change hook 64 | this.eH = null; // cancel hook 65 | this.rH = null; // release hook 66 | 67 | this.run = function () { 68 | var cf = function (e, conf) { 69 | var k; 70 | for (k in conf) { 71 | s.o[k] = conf[k]; 72 | } 73 | s.init(); 74 | s._configure() 75 | ._draw(); 76 | }; 77 | 78 | if(this.$.data('kontroled')) return; 79 | this.$.data('kontroled', true); 80 | 81 | this.extend(); 82 | this.o = $.extend( 83 | { 84 | // Config 85 | min : this.$.data('min') || 0, 86 | max : this.$.data('max') || 100, 87 | stopper : true, 88 | readOnly : this.$.data('readonly'), 89 | 90 | // UI 91 | cursor : (this.$.data('cursor') === true && 30) 92 | || this.$.data('cursor') 93 | || 0, 94 | thickness : this.$.data('thickness') || 0.35, 95 | width : this.$.data('width') || 200, 96 | height : this.$.data('height') || 200, 97 | displayInput : this.$.data('displayinput') == null || this.$.data('displayinput'), 98 | displayPrevious : this.$.data('displayprevious'), 99 | fgColor : this.$.data('fgcolor') || '#87CEEB', 100 | inline : false, 101 | 102 | // Hooks 103 | draw : null, // function () {} 104 | change : null, // function (value) {} 105 | cancel : null, // function () {} 106 | release : null // function (value) {} 107 | }, this.o 108 | ); 109 | 110 | // routing value 111 | if(this.$.is('fieldset')) { 112 | 113 | // fieldset = array of integer 114 | this.v = {}; 115 | this.i = this.$.find('input') 116 | this.i.each(function(k) { 117 | var $this = $(this); 118 | s.i[k] = $this; 119 | s.v[k] = $this.val(); 120 | 121 | $this.bind( 122 | 'change' 123 | , function () { 124 | var val = {}; 125 | val[k] = $this.val(); 126 | s.val(val); 127 | } 128 | ); 129 | }); 130 | this.$.find('legend').remove(); 131 | 132 | } else { 133 | // input = integer 134 | this.i = this.$; 135 | this.v = this.$.val(); 136 | (this.v == '') && (this.v = this.o.min); 137 | 138 | this.$.bind( 139 | 'change' 140 | , function () { 141 | s.val(s.$.val()); 142 | } 143 | ); 144 | } 145 | 146 | (!this.o.displayInput) && this.$.hide(); 147 | 148 | this.$c = $(''); 151 | this.c = this.$c[0].getContext("2d"); 152 | 153 | this.$ 154 | .wrap($('
')) 157 | .before(this.$c); 158 | 159 | if (this.v instanceof Object) { 160 | this.cv = {}; 161 | this.copy(this.v, this.cv); 162 | } else { 163 | this.cv = this.v; 164 | } 165 | 166 | this.$ 167 | .bind("configure", cf) 168 | .parent() 169 | .bind("configure", cf); 170 | 171 | this._listen() 172 | ._configure() 173 | ._xy() 174 | .init(); 175 | 176 | this.isInit = true; 177 | 178 | this._draw(); 179 | 180 | return this; 181 | }; 182 | 183 | this._draw = function () { 184 | 185 | // canvas pre-rendering 186 | var d = true, 187 | c = document.createElement('canvas'); 188 | 189 | c.width = s.o.width; 190 | c.height = s.o.height; 191 | s.g = c.getContext('2d'); 192 | 193 | s.clear(); 194 | 195 | s.dH 196 | && (d = s.dH()); 197 | 198 | (d !== false) && s.draw(); 199 | 200 | s.c.drawImage(c, 0, 0); 201 | c = null; 202 | }; 203 | 204 | this._touch = function (e) { 205 | 206 | var touchMove = function (e) { 207 | 208 | var v = s.xy2val( 209 | e.originalEvent.touches[s.t].pageX, 210 | e.originalEvent.touches[s.t].pageY 211 | ); 212 | 213 | if (v == s.cv) return; 214 | 215 | if ( 216 | s.cH 217 | && (s.cH(v) === false) 218 | ) return; 219 | 220 | 221 | s.change(v); 222 | s._draw(); 223 | }; 224 | 225 | // get touches index 226 | this.t = k.c.t(e); 227 | 228 | // First touch 229 | touchMove(e); 230 | 231 | // Touch events listeners 232 | k.c.d 233 | .bind("touchmove.k", touchMove) 234 | .bind( 235 | "touchend.k" 236 | , function () { 237 | k.c.d.unbind('touchmove.k touchend.k'); 238 | 239 | if ( 240 | s.rH 241 | && (s.rH(s.cv) === false) 242 | ) return; 243 | 244 | s.val(s.cv); 245 | } 246 | ); 247 | 248 | return this; 249 | }; 250 | 251 | this._mouse = function (e) { 252 | 253 | var mouseMove = function (e) { 254 | var v = s.xy2val(e.pageX, e.pageY); 255 | if (v == s.cv) return; 256 | 257 | if ( 258 | s.cH 259 | && (s.cH(v) === false) 260 | ) return; 261 | 262 | s.change(v); 263 | s._draw(); 264 | }; 265 | 266 | // First click 267 | mouseMove(e); 268 | 269 | // Mouse events listeners 270 | k.c.d 271 | .bind("mousemove.k", mouseMove) 272 | .bind( 273 | // Escape key cancel current change 274 | "keyup.k" 275 | , function (e) { 276 | if (e.keyCode === 27) { 277 | k.c.d.unbind("mouseup.k mousemove.k keyup.k"); 278 | 279 | if ( 280 | s.eH 281 | && (s.eH() === false) 282 | ) return; 283 | 284 | s.cancel(); 285 | } 286 | } 287 | ) 288 | .bind( 289 | "mouseup.k" 290 | , function (e) { 291 | k.c.d.unbind('mousemove.k mouseup.k keyup.k'); 292 | 293 | if ( 294 | s.rH 295 | && (s.rH(s.cv) === false) 296 | ) return; 297 | 298 | s.val(s.cv); 299 | } 300 | ); 301 | 302 | return this; 303 | }; 304 | 305 | this._xy = function () { 306 | var o = this.$c.offset(); 307 | this.x = o.left; 308 | this.y = o.top; 309 | return this; 310 | }; 311 | 312 | this._listen = function () { 313 | 314 | if (!this.o.readOnly) { 315 | this.$c 316 | .bind( 317 | "mousedown" 318 | , function (e) { 319 | e.preventDefault(); 320 | s._xy()._mouse(e); 321 | } 322 | ) 323 | .bind( 324 | "touchstart" 325 | , function (e) { 326 | e.preventDefault(); 327 | s._xy()._touch(e); 328 | } 329 | ); 330 | this.listen(); 331 | } else { 332 | this.$.attr('readonly', 'readonly'); 333 | } 334 | 335 | return this; 336 | }; 337 | 338 | this._configure = function () { 339 | 340 | // Hooks 341 | if (this.o.draw) this.dH = this.o.draw; 342 | if (this.o.change) this.cH = this.o.change; 343 | if (this.o.cancel) this.eH = this.o.cancel; 344 | if (this.o.release) this.rH = this.o.release; 345 | 346 | if (this.o.displayPrevious) { 347 | this.pColor = this.h2rgba(this.o.fgColor, "0.4"); 348 | this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); 349 | } else { 350 | this.fgColor = this.o.fgColor; 351 | } 352 | 353 | return this; 354 | }; 355 | 356 | this._clear = function () { 357 | this.$c[0].width = this.$c[0].width; 358 | }; 359 | 360 | // Abstract methods 361 | this.listen = function () {}; // on start, one time 362 | this.extend = function () {}; // each time configure triggered 363 | this.init = function () {}; // each time configure triggered 364 | this.change = function (v) {}; // on change 365 | this.val = function (v) {}; // on release 366 | this.xy2val = function (x, y) {}; // 367 | this.draw = function () {}; // on change / on release 368 | this.clear = function () { this._clear(); }; 369 | 370 | // Utils 371 | this.h2rgba = function (h, a) { 372 | var rgb; 373 | h = h.substring(1,7) 374 | rgb = [parseInt(h.substring(0,2),16) 375 | ,parseInt(h.substring(2,4),16) 376 | ,parseInt(h.substring(4,6),16)]; 377 | return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; 378 | }; 379 | 380 | this.copy = function (f, t) { 381 | for (var i in f) { t[i] = f[i]; } 382 | }; 383 | }; 384 | 385 | 386 | /** 387 | * k.Dial 388 | */ 389 | k.Dial = function () { 390 | k.o.call(this); 391 | 392 | this.startAngle = null; 393 | this.xy = null; 394 | this.radius = null; 395 | this.lineWidth = null; 396 | this.cursorExt = null; 397 | this.w2 = null; 398 | this.PI2 = 2*Math.PI; 399 | 400 | this.extend = function () { 401 | this.o = $.extend( 402 | { 403 | bgColor : this.$.data('bgcolor') || '#EEEEEE', 404 | angleOffset : this.$.data('angleoffset') || 0, 405 | angleArc : this.$.data('anglearc') || 360, 406 | inline : true 407 | }, this.o 408 | ); 409 | }; 410 | 411 | this.val = function (v) { 412 | if (null != v) { 413 | this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; 414 | this.v = this.cv; 415 | this.$.val(this.v); 416 | this._draw(); 417 | } else { 418 | return this.v; 419 | } 420 | }; 421 | 422 | this.xy2val = function (x, y) { 423 | var a, ret; 424 | 425 | a = Math.atan2( 426 | x - (this.x + this.w2) 427 | , - (y - this.y - this.w2) 428 | ) - this.angleOffset; 429 | 430 | if(this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { 431 | // if isset angleArc option, set to min if .5 under min 432 | a = 0; 433 | } else if (a < 0) { 434 | a += this.PI2; 435 | } 436 | 437 | ret = ~~ (0.5 + (a * (this.o.max - this.o.min) / this.angleArc)) 438 | + this.o.min; 439 | 440 | this.o.stopper 441 | && (ret = max(min(ret, this.o.max), this.o.min)); 442 | 443 | return ret; 444 | }; 445 | 446 | this.listen = function () { 447 | // bind MouseWheel 448 | var s = this, 449 | mw = function (e) { 450 | e.preventDefault(); 451 | 452 | var ori = e.originalEvent 453 | ,deltaX = ori.detail || ori.wheelDeltaX 454 | ,deltaY = ori.detail || ori.wheelDeltaY 455 | ,v = parseInt(s.$.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); 456 | 457 | if ( 458 | s.cH 459 | && (s.cH(v) === false) 460 | ) return; 461 | 462 | s.val(v); 463 | } 464 | , kval, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; 465 | 466 | this.$ 467 | .bind( 468 | "keydown" 469 | ,function (e) { 470 | var kc = e.keyCode; 471 | kval = parseInt(String.fromCharCode(kc)); 472 | 473 | if (isNaN(kval)) { 474 | 475 | (kc !== 13) // enter 476 | && (kc !== 8) // bs 477 | && (kc !== 9) // tab 478 | && (kc !== 189) // - 479 | && e.preventDefault(); 480 | 481 | // arrows 482 | if ($.inArray(kc,[37,38,39,40]) > -1) { 483 | e.preventDefault(); 484 | 485 | var v = parseInt(s.$.val()) + kv[kc] * m; 486 | 487 | s.o.stopper 488 | && (v = max(min(v, s.o.max), s.o.min)); 489 | 490 | s.change(v); 491 | s._draw(); 492 | 493 | // long time keydown speed-up 494 | to = window.setTimeout( 495 | function () { m*=2; } 496 | ,30 497 | ); 498 | } 499 | } 500 | } 501 | ) 502 | .bind( 503 | "keyup" 504 | ,function (e) { 505 | if (isNaN(kval)) { 506 | if (to) { 507 | window.clearTimeout(to); 508 | to = null; 509 | m = 1; 510 | s.val(s.$.val()); 511 | } 512 | } else { 513 | // kval postcond 514 | (s.$.val() > s.o.max && s.$.val(s.o.max)) 515 | || (s.$.val() < s.o.min && s.$.val(s.o.min)); 516 | } 517 | 518 | } 519 | ); 520 | 521 | this.$c.bind("mousewheel DOMMouseScroll", mw); 522 | this.$.bind("mousewheel DOMMouseScroll", mw) 523 | }; 524 | 525 | this.init = function () { 526 | 527 | if ( 528 | this.v < this.o.min 529 | || this.v > this.o.max 530 | ) this.v = this.o.min; 531 | 532 | this.$.val(this.v); 533 | this.w2 = this.o.width / 2; 534 | this.cursorExt = this.o.cursor / 100; 535 | this.xy = this.w2; 536 | this.lineWidth = this.xy * this.o.thickness; 537 | this.radius = this.xy - this.lineWidth / 2; 538 | 539 | this.o.angleOffset 540 | && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); 541 | 542 | this.o.angleArc 543 | && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); 544 | 545 | // deg to rad 546 | this.angleOffset = this.o.angleOffset * Math.PI / 180; 547 | this.angleArc = this.o.angleArc * Math.PI / 180; 548 | 549 | // compute start and end angles 550 | this.startAngle = 1.5 * Math.PI + this.angleOffset; 551 | this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; 552 | 553 | var s = max( 554 | String(Math.abs(this.o.max)).length 555 | , String(Math.abs(this.o.min)).length 556 | , 2 557 | ) + 2; 558 | 559 | this.o.displayInput 560 | && this.i.css({ 561 | 'width' : ((this.o.width / 2 + 4) >> 0) + 'px' 562 | ,'height' : ((this.o.width / 3) >> 0) + 'px' 563 | ,'position' : 'absolute' 564 | ,'vertical-align' : 'middle' 565 | ,'margin-top' : ((this.o.width / 3) >> 0) + 'px' 566 | ,'margin-left' : '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px' 567 | ,'border' : 0 568 | ,'background' : 'none' 569 | ,'font' : 'bold ' + ((this.o.width / s) >> 0) + 'px Arial' 570 | ,'text-align' : 'center' 571 | ,'color' : this.o.fgColor 572 | ,'padding' : '0px' 573 | ,'-webkit-appearance': 'none' 574 | }) 575 | || this.i.css({ 576 | 'width' : '0px' 577 | ,'visibility' : 'hidden' 578 | }); 579 | }; 580 | 581 | this.change = function (v) { 582 | this.cv = v; 583 | this.$.val(v); 584 | }; 585 | 586 | this.angle = function (v) { 587 | return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); 588 | }; 589 | 590 | this.draw = function () { 591 | 592 | var c = this.g, // context 593 | a = this.angle(this.cv) // Angle 594 | , sat = this.startAngle // Start angle 595 | , eat = sat + a // End angle 596 | , sa, ea // Previous angles 597 | , r = 1; 598 | 599 | c.lineWidth = this.lineWidth; 600 | 601 | this.o.cursor 602 | && (sat = eat - this.cursorExt) 603 | && (eat = eat + this.cursorExt); 604 | 605 | c.beginPath(); 606 | c.strokeStyle = this.o.bgColor; 607 | c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true); 608 | c.stroke(); 609 | 610 | if (this.o.displayPrevious) { 611 | ea = this.startAngle + this.angle(this.v); 612 | sa = this.startAngle; 613 | this.o.cursor 614 | && (sa = ea - this.cursorExt) 615 | && (ea = ea + this.cursorExt); 616 | 617 | c.beginPath(); 618 | c.strokeStyle = this.pColor; 619 | c.arc(this.xy, this.xy, this.radius, sa, ea, false); 620 | c.stroke(); 621 | r = (this.cv == this.v); 622 | } 623 | 624 | c.beginPath(); 625 | c.strokeStyle = r ? this.o.fgColor : this.fgColor ; 626 | c.arc(this.xy, this.xy, this.radius, sat, eat, false); 627 | c.stroke(); 628 | }; 629 | 630 | this.cancel = function () { 631 | this.val(this.v); 632 | }; 633 | }; 634 | 635 | $.fn.dial = $.fn.knob = function (o) { 636 | return this.each( 637 | function () { 638 | var d = new k.Dial(); 639 | d.o = o; 640 | d.$ = $(this); 641 | d.run(); 642 | } 643 | ).parent(); 644 | }; 645 | 646 | }); -------------------------------------------------------------------------------- /dashboard/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | //=require_directory . 3 | //=require_tree ../../widgets 4 | */ 5 | // ---------------------------------------------------------------------------- 6 | // Sass declarations 7 | // ---------------------------------------------------------------------------- 8 | $background-color: #222; 9 | $text-color: #fff; 10 | 11 | $background-warning-color-1: #e82711; 12 | $background-warning-color-2: #9b2d23; 13 | $text-warning-color: #fff; 14 | 15 | $background-danger-color-1: #eeae32; 16 | $background-danger-color-2: #ff9618; 17 | $text-danger-color: #fff; 18 | 19 | @-webkit-keyframes status-warning-background { 20 | 0% { background-color: $background-warning-color-1; } 21 | 50% { background-color: $background-warning-color-2; } 22 | 100% { background-color: $background-warning-color-1; } 23 | } 24 | @-webkit-keyframes status-danger-background { 25 | 0% { background-color: $background-danger-color-1; } 26 | 50% { background-color: $background-danger-color-2; } 27 | 100% { background-color: $background-danger-color-1; } 28 | } 29 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 30 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 31 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 32 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 33 | } 34 | 35 | // ---------------------------------------------------------------------------- 36 | // Base styles 37 | // ---------------------------------------------------------------------------- 38 | html { 39 | font-size: 100%; 40 | -webkit-text-size-adjust: 100%; 41 | -ms-text-size-adjust: 100%; 42 | } 43 | 44 | body { 45 | margin: 0; 46 | background-color: $background-color; 47 | font-size: 20px; 48 | color: $text-color; 49 | font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; 50 | } 51 | 52 | b, strong { 53 | font-weight: bold; 54 | } 55 | 56 | a { 57 | text-decoration: none; 58 | color: inherit; 59 | } 60 | 61 | img { 62 | border: 0; 63 | -ms-interpolation-mode: bicubic; 64 | vertical-align: middle; 65 | } 66 | 67 | img, object { 68 | max-width: 100%; 69 | } 70 | 71 | iframe { 72 | max-width: 100%; 73 | } 74 | 75 | table { 76 | border-collapse: collapse; 77 | border-spacing: 0; 78 | width: 100%; 79 | } 80 | 81 | td { 82 | vertical-align: middle; 83 | } 84 | 85 | ul, ol { 86 | padding: 0; 87 | margin: 0; 88 | } 89 | 90 | h1, h2, h3, h4, h5, p { 91 | padding: 0; 92 | margin: 0; 93 | } 94 | h1 { 95 | margin-bottom: 12px; 96 | text-align: center; 97 | font-size: 30px; 98 | font-weight: 400; 99 | } 100 | h2 { 101 | text-transform: uppercase; 102 | font-size: 76px; 103 | font-weight: 700; 104 | color: $text-color; 105 | } 106 | h3 { 107 | font-size: 25px; 108 | font-weight: 600; 109 | color: $text-color; 110 | } 111 | 112 | // ---------------------------------------------------------------------------- 113 | // Base widget styles 114 | // ---------------------------------------------------------------------------- 115 | .gridster { 116 | margin: 0px auto; 117 | } 118 | 119 | .icon-background { 120 | width: 100%!important; 121 | height: 100%; 122 | position: absolute; 123 | left: 0; 124 | top: 0; 125 | opacity: 0.1; 126 | font-size: 275px; 127 | text-align: center; 128 | margin-top: 82px; 129 | } 130 | 131 | .list-nostyle { 132 | list-style: none; 133 | } 134 | 135 | .gridster ul { 136 | list-style: none; 137 | } 138 | 139 | .gs_w { 140 | width: 100%; 141 | display: table; 142 | cursor: pointer; 143 | } 144 | 145 | .widget { 146 | padding: 25px 12px; 147 | text-align: center; 148 | width: 100%; 149 | display: table-cell; 150 | vertical-align: middle; 151 | } 152 | 153 | .widget.status-warning { 154 | background-color: $background-warning-color-1; 155 | @include animation(status-warning-background, 2s, ease, infinite); 156 | 157 | .icon-warning-sign { 158 | display: inline-block; 159 | } 160 | 161 | .title, .more-info { 162 | color: $text-warning-color; 163 | } 164 | } 165 | 166 | .widget.status-danger { 167 | color: $text-danger-color; 168 | background-color: $background-danger-color-1; 169 | @include animation(status-danger-background, 2s, ease, infinite); 170 | 171 | .icon-warning-sign { 172 | display: inline-block; 173 | } 174 | 175 | .title, .more-info { 176 | color: $text-danger-color; 177 | } 178 | } 179 | 180 | .more-info { 181 | font-size: 15px; 182 | position: absolute; 183 | bottom: 32px; 184 | left: 0; 185 | right: 0; 186 | } 187 | 188 | .updated-at { 189 | font-size: 15px; 190 | position: absolute; 191 | bottom: 12px; 192 | left: 0; 193 | right: 0; 194 | } 195 | 196 | #save-gridster { 197 | display: none; 198 | position: fixed; 199 | top: 0; 200 | margin: 0px auto; 201 | left: 50%; 202 | z-index: 1000; 203 | background: black; 204 | width: 190px; 205 | text-align: center; 206 | border: 1px solid white; 207 | border-top: 0px; 208 | margin-left: -95px; 209 | padding: 15px; 210 | } 211 | 212 | #save-gridster:hover { 213 | padding-top: 25px; 214 | } 215 | 216 | #saving-instructions { 217 | display: none; 218 | padding: 10px; 219 | width: 500px; 220 | height: 122px; 221 | z-index: 1000; 222 | background: white; 223 | top: 100px; 224 | color: black; 225 | font-size: 15px; 226 | padding-bottom: 4px; 227 | 228 | textarea { 229 | white-space: nowrap; 230 | width: 494px; 231 | height: 80px; 232 | } 233 | } 234 | 235 | #lean_overlay { 236 | position: fixed; 237 | z-index:100; 238 | top: 0px; 239 | left: 0px; 240 | height:100%; 241 | width:100%; 242 | background: #000; 243 | display: none; 244 | } 245 | 246 | #container { 247 | padding-top: 5px; 248 | } 249 | 250 | 251 | // ---------------------------------------------------------------------------- 252 | // Clearfix 253 | // ---------------------------------------------------------------------------- 254 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 255 | .clearfix:after { clear: both; } 256 | .clearfix { zoom: 1; } 257 | 258 | -------------------------------------------------------------------------------- /dashboard/assets/stylesheets/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.2.1 3 | * the iconic font designed for Bootstrap 4 | * ------------------------------------------------------------------------------ 5 | * The full suite of pictographic icons, examples, and documentation can be 6 | * found at http://fontawesome.io. Stay up to date on Twitter at 7 | * http://twitter.com/fontawesome. 8 | * 9 | * License 10 | * ------------------------------------------------------------------------------ 11 | * - The Font Awesome font is licensed under SIL OFL 1.1 - 12 | * http://scripts.sil.org/OFL 13 | * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - 14 | * http://opensource.org/licenses/mit-license.html 15 | * - Font Awesome documentation licensed under CC BY 3.0 - 16 | * http://creativecommons.org/licenses/by/3.0/ 17 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 18 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 19 | * 20 | * Author - Dave Gandy 21 | * ------------------------------------------------------------------------------ 22 | * Email: dave@fontawesome.io 23 | * Twitter: http://twitter.com/davegandy 24 | * Work: Lead Product Designer @ Kyruus - http://kyruus.com 25 | */ 26 | /* FONT PATH 27 | * -------------------------- */ 28 | @font-face { 29 | font-family: 'FontAwesome'; 30 | src: url('../assets/fontawesome-webfont.eot?v=3.2.1'); 31 | src: url('../assets/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'), url('../assets/fontawesome-webfont.woff?v=3.2.1') format('woff'), url('../assets/fontawesome-webfont.ttf?v=3.2.1') format('truetype'), url('../assets/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg'); 32 | font-weight: normal; 33 | font-style: normal; 34 | } 35 | /* FONT AWESOME CORE 36 | * -------------------------- */ 37 | [class^="icon-"], 38 | [class*=" icon-"] { 39 | font-family: FontAwesome; 40 | font-weight: normal; 41 | font-style: normal; 42 | text-decoration: inherit; 43 | -webkit-font-smoothing: antialiased; 44 | *margin-right: .3em; 45 | } 46 | [class^="icon-"]:before, 47 | [class*=" icon-"]:before { 48 | text-decoration: inherit; 49 | display: inline-block; 50 | speak: none; 51 | } 52 | /* makes the font 33% larger relative to the icon container */ 53 | .icon-large:before { 54 | vertical-align: -10%; 55 | font-size: 1.3333333333333333em; 56 | } 57 | /* makes sure icons active on rollover in links */ 58 | a [class^="icon-"], 59 | a [class*=" icon-"] { 60 | display: inline; 61 | } 62 | /* increased font size for icon-large */ 63 | [class^="icon-"].icon-fixed-width, 64 | [class*=" icon-"].icon-fixed-width { 65 | display: inline-block; 66 | width: 1.1428571428571428em; 67 | text-align: right; 68 | padding-right: 0.2857142857142857em; 69 | } 70 | [class^="icon-"].icon-fixed-width.icon-large, 71 | [class*=" icon-"].icon-fixed-width.icon-large { 72 | width: 1.4285714285714286em; 73 | } 74 | .icons-ul { 75 | margin-left: 2.142857142857143em; 76 | list-style-type: none; 77 | } 78 | .icons-ul > li { 79 | position: relative; 80 | } 81 | .icons-ul .icon-li { 82 | position: absolute; 83 | left: -2.142857142857143em; 84 | width: 2.142857142857143em; 85 | text-align: center; 86 | line-height: inherit; 87 | } 88 | [class^="icon-"].hide, 89 | [class*=" icon-"].hide { 90 | display: none; 91 | } 92 | .icon-muted { 93 | color: #eeeeee; 94 | } 95 | .icon-light { 96 | color: #ffffff; 97 | } 98 | .icon-dark { 99 | color: #333333; 100 | } 101 | .icon-border { 102 | border: solid 1px #eeeeee; 103 | padding: .2em .25em .15em; 104 | -webkit-border-radius: 3px; 105 | -moz-border-radius: 3px; 106 | border-radius: 3px; 107 | } 108 | .icon-2x { 109 | font-size: 2em; 110 | } 111 | .icon-2x.icon-border { 112 | border-width: 2px; 113 | -webkit-border-radius: 4px; 114 | -moz-border-radius: 4px; 115 | border-radius: 4px; 116 | } 117 | .icon-3x { 118 | font-size: 3em; 119 | } 120 | .icon-3x.icon-border { 121 | border-width: 3px; 122 | -webkit-border-radius: 5px; 123 | -moz-border-radius: 5px; 124 | border-radius: 5px; 125 | } 126 | .icon-4x { 127 | font-size: 4em; 128 | } 129 | .icon-4x.icon-border { 130 | border-width: 4px; 131 | -webkit-border-radius: 6px; 132 | -moz-border-radius: 6px; 133 | border-radius: 6px; 134 | } 135 | .icon-5x { 136 | font-size: 5em; 137 | } 138 | .icon-5x.icon-border { 139 | border-width: 5px; 140 | -webkit-border-radius: 7px; 141 | -moz-border-radius: 7px; 142 | border-radius: 7px; 143 | } 144 | .pull-right { 145 | float: right; 146 | } 147 | .pull-left { 148 | float: left; 149 | } 150 | [class^="icon-"].pull-left, 151 | [class*=" icon-"].pull-left { 152 | margin-right: .3em; 153 | } 154 | [class^="icon-"].pull-right, 155 | [class*=" icon-"].pull-right { 156 | margin-left: .3em; 157 | } 158 | /* BOOTSTRAP SPECIFIC CLASSES 159 | * -------------------------- */ 160 | /* Bootstrap 2.0 sprites.less reset */ 161 | [class^="icon-"], 162 | [class*=" icon-"] { 163 | display: inline; 164 | width: auto; 165 | height: auto; 166 | line-height: normal; 167 | vertical-align: baseline; 168 | background-image: none; 169 | background-position: 0% 0%; 170 | background-repeat: repeat; 171 | margin-top: 0; 172 | } 173 | /* more sprites.less reset */ 174 | .icon-white, 175 | .nav-pills > .active > a > [class^="icon-"], 176 | .nav-pills > .active > a > [class*=" icon-"], 177 | .nav-list > .active > a > [class^="icon-"], 178 | .nav-list > .active > a > [class*=" icon-"], 179 | .navbar-inverse .nav > .active > a > [class^="icon-"], 180 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 181 | .dropdown-menu > li > a:hover > [class^="icon-"], 182 | .dropdown-menu > li > a:hover > [class*=" icon-"], 183 | .dropdown-menu > .active > a > [class^="icon-"], 184 | .dropdown-menu > .active > a > [class*=" icon-"], 185 | .dropdown-submenu:hover > a > [class^="icon-"], 186 | .dropdown-submenu:hover > a > [class*=" icon-"] { 187 | background-image: none; 188 | } 189 | /* keeps Bootstrap styles with and without icons the same */ 190 | .btn [class^="icon-"].icon-large, 191 | .nav [class^="icon-"].icon-large, 192 | .btn [class*=" icon-"].icon-large, 193 | .nav [class*=" icon-"].icon-large { 194 | line-height: .9em; 195 | } 196 | .btn [class^="icon-"].icon-spin, 197 | .nav [class^="icon-"].icon-spin, 198 | .btn [class*=" icon-"].icon-spin, 199 | .nav [class*=" icon-"].icon-spin { 200 | display: inline-block; 201 | } 202 | .nav-tabs [class^="icon-"], 203 | .nav-pills [class^="icon-"], 204 | .nav-tabs [class*=" icon-"], 205 | .nav-pills [class*=" icon-"], 206 | .nav-tabs [class^="icon-"].icon-large, 207 | .nav-pills [class^="icon-"].icon-large, 208 | .nav-tabs [class*=" icon-"].icon-large, 209 | .nav-pills [class*=" icon-"].icon-large { 210 | line-height: .9em; 211 | } 212 | .btn [class^="icon-"].pull-left.icon-2x, 213 | .btn [class*=" icon-"].pull-left.icon-2x, 214 | .btn [class^="icon-"].pull-right.icon-2x, 215 | .btn [class*=" icon-"].pull-right.icon-2x { 216 | margin-top: .18em; 217 | } 218 | .btn [class^="icon-"].icon-spin.icon-large, 219 | .btn [class*=" icon-"].icon-spin.icon-large { 220 | line-height: .8em; 221 | } 222 | .btn.btn-small [class^="icon-"].pull-left.icon-2x, 223 | .btn.btn-small [class*=" icon-"].pull-left.icon-2x, 224 | .btn.btn-small [class^="icon-"].pull-right.icon-2x, 225 | .btn.btn-small [class*=" icon-"].pull-right.icon-2x { 226 | margin-top: .25em; 227 | } 228 | .btn.btn-large [class^="icon-"], 229 | .btn.btn-large [class*=" icon-"] { 230 | margin-top: 0; 231 | } 232 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 233 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x, 234 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 235 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 236 | margin-top: .05em; 237 | } 238 | .btn.btn-large [class^="icon-"].pull-left.icon-2x, 239 | .btn.btn-large [class*=" icon-"].pull-left.icon-2x { 240 | margin-right: .2em; 241 | } 242 | .btn.btn-large [class^="icon-"].pull-right.icon-2x, 243 | .btn.btn-large [class*=" icon-"].pull-right.icon-2x { 244 | margin-left: .2em; 245 | } 246 | /* Fixes alignment in nav lists */ 247 | .nav-list [class^="icon-"], 248 | .nav-list [class*=" icon-"] { 249 | line-height: inherit; 250 | } 251 | /* EXTRAS 252 | * -------------------------- */ 253 | /* Stacked and layered icon */ 254 | .icon-stack { 255 | position: relative; 256 | display: inline-block; 257 | width: 2em; 258 | height: 2em; 259 | line-height: 2em; 260 | vertical-align: -35%; 261 | } 262 | .icon-stack [class^="icon-"], 263 | .icon-stack [class*=" icon-"] { 264 | display: block; 265 | text-align: center; 266 | position: absolute; 267 | width: 100%; 268 | height: 100%; 269 | font-size: 1em; 270 | line-height: inherit; 271 | *line-height: 2em; 272 | } 273 | .icon-stack .icon-stack-base { 274 | font-size: 2em; 275 | *line-height: 1em; 276 | } 277 | /* Animated rotating icon */ 278 | .icon-spin { 279 | display: inline-block; 280 | -moz-animation: spin 2s infinite linear; 281 | -o-animation: spin 2s infinite linear; 282 | -webkit-animation: spin 2s infinite linear; 283 | animation: spin 2s infinite linear; 284 | } 285 | /* Prevent stack and spinners from being taken inline when inside a link */ 286 | a .icon-stack, 287 | a .icon-spin { 288 | display: inline-block; 289 | text-decoration: none; 290 | } 291 | @-moz-keyframes spin { 292 | 0% { 293 | -moz-transform: rotate(0deg); 294 | } 295 | 100% { 296 | -moz-transform: rotate(359deg); 297 | } 298 | } 299 | @-webkit-keyframes spin { 300 | 0% { 301 | -webkit-transform: rotate(0deg); 302 | } 303 | 100% { 304 | -webkit-transform: rotate(359deg); 305 | } 306 | } 307 | @-o-keyframes spin { 308 | 0% { 309 | -o-transform: rotate(0deg); 310 | } 311 | 100% { 312 | -o-transform: rotate(359deg); 313 | } 314 | } 315 | @-ms-keyframes spin { 316 | 0% { 317 | -ms-transform: rotate(0deg); 318 | } 319 | 100% { 320 | -ms-transform: rotate(359deg); 321 | } 322 | } 323 | @keyframes spin { 324 | 0% { 325 | transform: rotate(0deg); 326 | } 327 | 100% { 328 | transform: rotate(359deg); 329 | } 330 | } 331 | /* Icon rotations and mirroring */ 332 | .icon-rotate-90:before { 333 | -webkit-transform: rotate(90deg); 334 | -moz-transform: rotate(90deg); 335 | -ms-transform: rotate(90deg); 336 | -o-transform: rotate(90deg); 337 | transform: rotate(90deg); 338 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 339 | } 340 | .icon-rotate-180:before { 341 | -webkit-transform: rotate(180deg); 342 | -moz-transform: rotate(180deg); 343 | -ms-transform: rotate(180deg); 344 | -o-transform: rotate(180deg); 345 | transform: rotate(180deg); 346 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 347 | } 348 | .icon-rotate-270:before { 349 | -webkit-transform: rotate(270deg); 350 | -moz-transform: rotate(270deg); 351 | -ms-transform: rotate(270deg); 352 | -o-transform: rotate(270deg); 353 | transform: rotate(270deg); 354 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 355 | } 356 | .icon-flip-horizontal:before { 357 | -webkit-transform: scale(-1, 1); 358 | -moz-transform: scale(-1, 1); 359 | -ms-transform: scale(-1, 1); 360 | -o-transform: scale(-1, 1); 361 | transform: scale(-1, 1); 362 | } 363 | .icon-flip-vertical:before { 364 | -webkit-transform: scale(1, -1); 365 | -moz-transform: scale(1, -1); 366 | -ms-transform: scale(1, -1); 367 | -o-transform: scale(1, -1); 368 | transform: scale(1, -1); 369 | } 370 | /* ensure rotation occurs inside anchor tags */ 371 | a .icon-rotate-90:before, 372 | a .icon-rotate-180:before, 373 | a .icon-rotate-270:before, 374 | a .icon-flip-horizontal:before, 375 | a .icon-flip-vertical:before { 376 | display: inline-block; 377 | } 378 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 379 | readers do not read off random characters that represent icons */ 380 | .icon-glass:before { 381 | content: "\f000"; 382 | } 383 | .icon-music:before { 384 | content: "\f001"; 385 | } 386 | .icon-search:before { 387 | content: "\f002"; 388 | } 389 | .icon-envelope-alt:before { 390 | content: "\f003"; 391 | } 392 | .icon-heart:before { 393 | content: "\f004"; 394 | } 395 | .icon-star:before { 396 | content: "\f005"; 397 | } 398 | .icon-star-empty:before { 399 | content: "\f006"; 400 | } 401 | .icon-user:before { 402 | content: "\f007"; 403 | } 404 | .icon-film:before { 405 | content: "\f008"; 406 | } 407 | .icon-th-large:before { 408 | content: "\f009"; 409 | } 410 | .icon-th:before { 411 | content: "\f00a"; 412 | } 413 | .icon-th-list:before { 414 | content: "\f00b"; 415 | } 416 | .icon-ok:before { 417 | content: "\f00c"; 418 | } 419 | .icon-remove:before { 420 | content: "\f00d"; 421 | } 422 | .icon-zoom-in:before { 423 | content: "\f00e"; 424 | } 425 | .icon-zoom-out:before { 426 | content: "\f010"; 427 | } 428 | .icon-power-off:before, 429 | .icon-off:before { 430 | content: "\f011"; 431 | } 432 | .icon-signal:before { 433 | content: "\f012"; 434 | } 435 | .icon-gear:before, 436 | .icon-cog:before { 437 | content: "\f013"; 438 | } 439 | .icon-trash:before { 440 | content: "\f014"; 441 | } 442 | .icon-home:before { 443 | content: "\f015"; 444 | } 445 | .icon-file-alt:before { 446 | content: "\f016"; 447 | } 448 | .icon-time:before { 449 | content: "\f017"; 450 | } 451 | .icon-road:before { 452 | content: "\f018"; 453 | } 454 | .icon-download-alt:before { 455 | content: "\f019"; 456 | } 457 | .icon-download:before { 458 | content: "\f01a"; 459 | } 460 | .icon-upload:before { 461 | content: "\f01b"; 462 | } 463 | .icon-inbox:before { 464 | content: "\f01c"; 465 | } 466 | .icon-play-circle:before { 467 | content: "\f01d"; 468 | } 469 | .icon-rotate-right:before, 470 | .icon-repeat:before { 471 | content: "\f01e"; 472 | } 473 | .icon-refresh:before { 474 | content: "\f021"; 475 | } 476 | .icon-list-alt:before { 477 | content: "\f022"; 478 | } 479 | .icon-lock:before { 480 | content: "\f023"; 481 | } 482 | .icon-flag:before { 483 | content: "\f024"; 484 | } 485 | .icon-headphones:before { 486 | content: "\f025"; 487 | } 488 | .icon-volume-off:before { 489 | content: "\f026"; 490 | } 491 | .icon-volume-down:before { 492 | content: "\f027"; 493 | } 494 | .icon-volume-up:before { 495 | content: "\f028"; 496 | } 497 | .icon-qrcode:before { 498 | content: "\f029"; 499 | } 500 | .icon-barcode:before { 501 | content: "\f02a"; 502 | } 503 | .icon-tag:before { 504 | content: "\f02b"; 505 | } 506 | .icon-tags:before { 507 | content: "\f02c"; 508 | } 509 | .icon-book:before { 510 | content: "\f02d"; 511 | } 512 | .icon-bookmark:before { 513 | content: "\f02e"; 514 | } 515 | .icon-print:before { 516 | content: "\f02f"; 517 | } 518 | .icon-camera:before { 519 | content: "\f030"; 520 | } 521 | .icon-font:before { 522 | content: "\f031"; 523 | } 524 | .icon-bold:before { 525 | content: "\f032"; 526 | } 527 | .icon-italic:before { 528 | content: "\f033"; 529 | } 530 | .icon-text-height:before { 531 | content: "\f034"; 532 | } 533 | .icon-text-width:before { 534 | content: "\f035"; 535 | } 536 | .icon-align-left:before { 537 | content: "\f036"; 538 | } 539 | .icon-align-center:before { 540 | content: "\f037"; 541 | } 542 | .icon-align-right:before { 543 | content: "\f038"; 544 | } 545 | .icon-align-justify:before { 546 | content: "\f039"; 547 | } 548 | .icon-list:before { 549 | content: "\f03a"; 550 | } 551 | .icon-indent-left:before { 552 | content: "\f03b"; 553 | } 554 | .icon-indent-right:before { 555 | content: "\f03c"; 556 | } 557 | .icon-facetime-video:before { 558 | content: "\f03d"; 559 | } 560 | .icon-picture:before { 561 | content: "\f03e"; 562 | } 563 | .icon-pencil:before { 564 | content: "\f040"; 565 | } 566 | .icon-map-marker:before { 567 | content: "\f041"; 568 | } 569 | .icon-adjust:before { 570 | content: "\f042"; 571 | } 572 | .icon-tint:before { 573 | content: "\f043"; 574 | } 575 | .icon-edit:before { 576 | content: "\f044"; 577 | } 578 | .icon-share:before { 579 | content: "\f045"; 580 | } 581 | .icon-check:before { 582 | content: "\f046"; 583 | } 584 | .icon-move:before { 585 | content: "\f047"; 586 | } 587 | .icon-step-backward:before { 588 | content: "\f048"; 589 | } 590 | .icon-fast-backward:before { 591 | content: "\f049"; 592 | } 593 | .icon-backward:before { 594 | content: "\f04a"; 595 | } 596 | .icon-play:before { 597 | content: "\f04b"; 598 | } 599 | .icon-pause:before { 600 | content: "\f04c"; 601 | } 602 | .icon-stop:before { 603 | content: "\f04d"; 604 | } 605 | .icon-forward:before { 606 | content: "\f04e"; 607 | } 608 | .icon-fast-forward:before { 609 | content: "\f050"; 610 | } 611 | .icon-step-forward:before { 612 | content: "\f051"; 613 | } 614 | .icon-eject:before { 615 | content: "\f052"; 616 | } 617 | .icon-chevron-left:before { 618 | content: "\f053"; 619 | } 620 | .icon-chevron-right:before { 621 | content: "\f054"; 622 | } 623 | .icon-plus-sign:before { 624 | content: "\f055"; 625 | } 626 | .icon-minus-sign:before { 627 | content: "\f056"; 628 | } 629 | .icon-remove-sign:before { 630 | content: "\f057"; 631 | } 632 | .icon-ok-sign:before { 633 | content: "\f058"; 634 | } 635 | .icon-question-sign:before { 636 | content: "\f059"; 637 | } 638 | .icon-info-sign:before { 639 | content: "\f05a"; 640 | } 641 | .icon-screenshot:before { 642 | content: "\f05b"; 643 | } 644 | .icon-remove-circle:before { 645 | content: "\f05c"; 646 | } 647 | .icon-ok-circle:before { 648 | content: "\f05d"; 649 | } 650 | .icon-ban-circle:before { 651 | content: "\f05e"; 652 | } 653 | .icon-arrow-left:before { 654 | content: "\f060"; 655 | } 656 | .icon-arrow-right:before { 657 | content: "\f061"; 658 | } 659 | .icon-arrow-up:before { 660 | content: "\f062"; 661 | } 662 | .icon-arrow-down:before { 663 | content: "\f063"; 664 | } 665 | .icon-mail-forward:before, 666 | .icon-share-alt:before { 667 | content: "\f064"; 668 | } 669 | .icon-resize-full:before { 670 | content: "\f065"; 671 | } 672 | .icon-resize-small:before { 673 | content: "\f066"; 674 | } 675 | .icon-plus:before { 676 | content: "\f067"; 677 | } 678 | .icon-minus:before { 679 | content: "\f068"; 680 | } 681 | .icon-asterisk:before { 682 | content: "\f069"; 683 | } 684 | .icon-exclamation-sign:before { 685 | content: "\f06a"; 686 | } 687 | .icon-gift:before { 688 | content: "\f06b"; 689 | } 690 | .icon-leaf:before { 691 | content: "\f06c"; 692 | } 693 | .icon-fire:before { 694 | content: "\f06d"; 695 | } 696 | .icon-eye-open:before { 697 | content: "\f06e"; 698 | } 699 | .icon-eye-close:before { 700 | content: "\f070"; 701 | } 702 | .icon-warning-sign:before { 703 | content: "\f071"; 704 | } 705 | .icon-plane:before { 706 | content: "\f072"; 707 | } 708 | .icon-calendar:before { 709 | content: "\f073"; 710 | } 711 | .icon-random:before { 712 | content: "\f074"; 713 | } 714 | .icon-comment:before { 715 | content: "\f075"; 716 | } 717 | .icon-magnet:before { 718 | content: "\f076"; 719 | } 720 | .icon-chevron-up:before { 721 | content: "\f077"; 722 | } 723 | .icon-chevron-down:before { 724 | content: "\f078"; 725 | } 726 | .icon-retweet:before { 727 | content: "\f079"; 728 | } 729 | .icon-shopping-cart:before { 730 | content: "\f07a"; 731 | } 732 | .icon-folder-close:before { 733 | content: "\f07b"; 734 | } 735 | .icon-folder-open:before { 736 | content: "\f07c"; 737 | } 738 | .icon-resize-vertical:before { 739 | content: "\f07d"; 740 | } 741 | .icon-resize-horizontal:before { 742 | content: "\f07e"; 743 | } 744 | .icon-bar-chart:before { 745 | content: "\f080"; 746 | } 747 | .icon-twitter-sign:before { 748 | content: "\f081"; 749 | } 750 | .icon-facebook-sign:before { 751 | content: "\f082"; 752 | } 753 | .icon-camera-retro:before { 754 | content: "\f083"; 755 | } 756 | .icon-key:before { 757 | content: "\f084"; 758 | } 759 | .icon-gears:before, 760 | .icon-cogs:before { 761 | content: "\f085"; 762 | } 763 | .icon-comments:before { 764 | content: "\f086"; 765 | } 766 | .icon-thumbs-up-alt:before { 767 | content: "\f087"; 768 | } 769 | .icon-thumbs-down-alt:before { 770 | content: "\f088"; 771 | } 772 | .icon-star-half:before { 773 | content: "\f089"; 774 | } 775 | .icon-heart-empty:before { 776 | content: "\f08a"; 777 | } 778 | .icon-signout:before { 779 | content: "\f08b"; 780 | } 781 | .icon-linkedin-sign:before { 782 | content: "\f08c"; 783 | } 784 | .icon-pushpin:before { 785 | content: "\f08d"; 786 | } 787 | .icon-external-link:before { 788 | content: "\f08e"; 789 | } 790 | .icon-signin:before { 791 | content: "\f090"; 792 | } 793 | .icon-trophy:before { 794 | content: "\f091"; 795 | } 796 | .icon-github-sign:before { 797 | content: "\f092"; 798 | } 799 | .icon-upload-alt:before { 800 | content: "\f093"; 801 | } 802 | .icon-lemon:before { 803 | content: "\f094"; 804 | } 805 | .icon-phone:before { 806 | content: "\f095"; 807 | } 808 | .icon-unchecked:before, 809 | .icon-check-empty:before { 810 | content: "\f096"; 811 | } 812 | .icon-bookmark-empty:before { 813 | content: "\f097"; 814 | } 815 | .icon-phone-sign:before { 816 | content: "\f098"; 817 | } 818 | .icon-twitter:before { 819 | content: "\f099"; 820 | } 821 | .icon-facebook:before { 822 | content: "\f09a"; 823 | } 824 | .icon-github:before { 825 | content: "\f09b"; 826 | } 827 | .icon-unlock:before { 828 | content: "\f09c"; 829 | } 830 | .icon-credit-card:before { 831 | content: "\f09d"; 832 | } 833 | .icon-rss:before { 834 | content: "\f09e"; 835 | } 836 | .icon-hdd:before { 837 | content: "\f0a0"; 838 | } 839 | .icon-bullhorn:before { 840 | content: "\f0a1"; 841 | } 842 | .icon-bell:before { 843 | content: "\f0a2"; 844 | } 845 | .icon-certificate:before { 846 | content: "\f0a3"; 847 | } 848 | .icon-hand-right:before { 849 | content: "\f0a4"; 850 | } 851 | .icon-hand-left:before { 852 | content: "\f0a5"; 853 | } 854 | .icon-hand-up:before { 855 | content: "\f0a6"; 856 | } 857 | .icon-hand-down:before { 858 | content: "\f0a7"; 859 | } 860 | .icon-circle-arrow-left:before { 861 | content: "\f0a8"; 862 | } 863 | .icon-circle-arrow-right:before { 864 | content: "\f0a9"; 865 | } 866 | .icon-circle-arrow-up:before { 867 | content: "\f0aa"; 868 | } 869 | .icon-circle-arrow-down:before { 870 | content: "\f0ab"; 871 | } 872 | .icon-globe:before { 873 | content: "\f0ac"; 874 | } 875 | .icon-wrench:before { 876 | content: "\f0ad"; 877 | } 878 | .icon-tasks:before { 879 | content: "\f0ae"; 880 | } 881 | .icon-filter:before { 882 | content: "\f0b0"; 883 | } 884 | .icon-briefcase:before { 885 | content: "\f0b1"; 886 | } 887 | .icon-fullscreen:before { 888 | content: "\f0b2"; 889 | } 890 | .icon-group:before { 891 | content: "\f0c0"; 892 | } 893 | .icon-link:before { 894 | content: "\f0c1"; 895 | } 896 | .icon-cloud:before { 897 | content: "\f0c2"; 898 | } 899 | .icon-beaker:before { 900 | content: "\f0c3"; 901 | } 902 | .icon-cut:before { 903 | content: "\f0c4"; 904 | } 905 | .icon-copy:before { 906 | content: "\f0c5"; 907 | } 908 | .icon-paperclip:before, 909 | .icon-paper-clip:before { 910 | content: "\f0c6"; 911 | } 912 | .icon-save:before { 913 | content: "\f0c7"; 914 | } 915 | .icon-sign-blank:before { 916 | content: "\f0c8"; 917 | } 918 | .icon-reorder:before { 919 | content: "\f0c9"; 920 | } 921 | .icon-list-ul:before { 922 | content: "\f0ca"; 923 | } 924 | .icon-list-ol:before { 925 | content: "\f0cb"; 926 | } 927 | .icon-strikethrough:before { 928 | content: "\f0cc"; 929 | } 930 | .icon-underline:before { 931 | content: "\f0cd"; 932 | } 933 | .icon-table:before { 934 | content: "\f0ce"; 935 | } 936 | .icon-magic:before { 937 | content: "\f0d0"; 938 | } 939 | .icon-truck:before { 940 | content: "\f0d1"; 941 | } 942 | .icon-pinterest:before { 943 | content: "\f0d2"; 944 | } 945 | .icon-pinterest-sign:before { 946 | content: "\f0d3"; 947 | } 948 | .icon-google-plus-sign:before { 949 | content: "\f0d4"; 950 | } 951 | .icon-google-plus:before { 952 | content: "\f0d5"; 953 | } 954 | .icon-money:before { 955 | content: "\f0d6"; 956 | } 957 | .icon-caret-down:before { 958 | content: "\f0d7"; 959 | } 960 | .icon-caret-up:before { 961 | content: "\f0d8"; 962 | } 963 | .icon-caret-left:before { 964 | content: "\f0d9"; 965 | } 966 | .icon-caret-right:before { 967 | content: "\f0da"; 968 | } 969 | .icon-columns:before { 970 | content: "\f0db"; 971 | } 972 | .icon-sort:before { 973 | content: "\f0dc"; 974 | } 975 | .icon-sort-down:before { 976 | content: "\f0dd"; 977 | } 978 | .icon-sort-up:before { 979 | content: "\f0de"; 980 | } 981 | .icon-envelope:before { 982 | content: "\f0e0"; 983 | } 984 | .icon-linkedin:before { 985 | content: "\f0e1"; 986 | } 987 | .icon-rotate-left:before, 988 | .icon-undo:before { 989 | content: "\f0e2"; 990 | } 991 | .icon-legal:before { 992 | content: "\f0e3"; 993 | } 994 | .icon-dashboard:before { 995 | content: "\f0e4"; 996 | } 997 | .icon-comment-alt:before { 998 | content: "\f0e5"; 999 | } 1000 | .icon-comments-alt:before { 1001 | content: "\f0e6"; 1002 | } 1003 | .icon-bolt:before { 1004 | content: "\f0e7"; 1005 | } 1006 | .icon-sitemap:before { 1007 | content: "\f0e8"; 1008 | } 1009 | .icon-umbrella:before { 1010 | content: "\f0e9"; 1011 | } 1012 | .icon-paste:before { 1013 | content: "\f0ea"; 1014 | } 1015 | .icon-lightbulb:before { 1016 | content: "\f0eb"; 1017 | } 1018 | .icon-exchange:before { 1019 | content: "\f0ec"; 1020 | } 1021 | .icon-cloud-download:before { 1022 | content: "\f0ed"; 1023 | } 1024 | .icon-cloud-upload:before { 1025 | content: "\f0ee"; 1026 | } 1027 | .icon-user-md:before { 1028 | content: "\f0f0"; 1029 | } 1030 | .icon-stethoscope:before { 1031 | content: "\f0f1"; 1032 | } 1033 | .icon-suitcase:before { 1034 | content: "\f0f2"; 1035 | } 1036 | .icon-bell-alt:before { 1037 | content: "\f0f3"; 1038 | } 1039 | .icon-coffee:before { 1040 | content: "\f0f4"; 1041 | } 1042 | .icon-food:before { 1043 | content: "\f0f5"; 1044 | } 1045 | .icon-file-text-alt:before { 1046 | content: "\f0f6"; 1047 | } 1048 | .icon-building:before { 1049 | content: "\f0f7"; 1050 | } 1051 | .icon-hospital:before { 1052 | content: "\f0f8"; 1053 | } 1054 | .icon-ambulance:before { 1055 | content: "\f0f9"; 1056 | } 1057 | .icon-medkit:before { 1058 | content: "\f0fa"; 1059 | } 1060 | .icon-fighter-jet:before { 1061 | content: "\f0fb"; 1062 | } 1063 | .icon-beer:before { 1064 | content: "\f0fc"; 1065 | } 1066 | .icon-h-sign:before { 1067 | content: "\f0fd"; 1068 | } 1069 | .icon-plus-sign-alt:before { 1070 | content: "\f0fe"; 1071 | } 1072 | .icon-double-angle-left:before { 1073 | content: "\f100"; 1074 | } 1075 | .icon-double-angle-right:before { 1076 | content: "\f101"; 1077 | } 1078 | .icon-double-angle-up:before { 1079 | content: "\f102"; 1080 | } 1081 | .icon-double-angle-down:before { 1082 | content: "\f103"; 1083 | } 1084 | .icon-angle-left:before { 1085 | content: "\f104"; 1086 | } 1087 | .icon-angle-right:before { 1088 | content: "\f105"; 1089 | } 1090 | .icon-angle-up:before { 1091 | content: "\f106"; 1092 | } 1093 | .icon-angle-down:before { 1094 | content: "\f107"; 1095 | } 1096 | .icon-desktop:before { 1097 | content: "\f108"; 1098 | } 1099 | .icon-laptop:before { 1100 | content: "\f109"; 1101 | } 1102 | .icon-tablet:before { 1103 | content: "\f10a"; 1104 | } 1105 | .icon-mobile-phone:before { 1106 | content: "\f10b"; 1107 | } 1108 | .icon-circle-blank:before { 1109 | content: "\f10c"; 1110 | } 1111 | .icon-quote-left:before { 1112 | content: "\f10d"; 1113 | } 1114 | .icon-quote-right:before { 1115 | content: "\f10e"; 1116 | } 1117 | .icon-spinner:before { 1118 | content: "\f110"; 1119 | } 1120 | .icon-circle:before { 1121 | content: "\f111"; 1122 | } 1123 | .icon-mail-reply:before, 1124 | .icon-reply:before { 1125 | content: "\f112"; 1126 | } 1127 | .icon-github-alt:before { 1128 | content: "\f113"; 1129 | } 1130 | .icon-folder-close-alt:before { 1131 | content: "\f114"; 1132 | } 1133 | .icon-folder-open-alt:before { 1134 | content: "\f115"; 1135 | } 1136 | .icon-expand-alt:before { 1137 | content: "\f116"; 1138 | } 1139 | .icon-collapse-alt:before { 1140 | content: "\f117"; 1141 | } 1142 | .icon-smile:before { 1143 | content: "\f118"; 1144 | } 1145 | .icon-frown:before { 1146 | content: "\f119"; 1147 | } 1148 | .icon-meh:before { 1149 | content: "\f11a"; 1150 | } 1151 | .icon-gamepad:before { 1152 | content: "\f11b"; 1153 | } 1154 | .icon-keyboard:before { 1155 | content: "\f11c"; 1156 | } 1157 | .icon-flag-alt:before { 1158 | content: "\f11d"; 1159 | } 1160 | .icon-flag-checkered:before { 1161 | content: "\f11e"; 1162 | } 1163 | .icon-terminal:before { 1164 | content: "\f120"; 1165 | } 1166 | .icon-code:before { 1167 | content: "\f121"; 1168 | } 1169 | .icon-reply-all:before { 1170 | content: "\f122"; 1171 | } 1172 | .icon-mail-reply-all:before { 1173 | content: "\f122"; 1174 | } 1175 | .icon-star-half-full:before, 1176 | .icon-star-half-empty:before { 1177 | content: "\f123"; 1178 | } 1179 | .icon-location-arrow:before { 1180 | content: "\f124"; 1181 | } 1182 | .icon-crop:before { 1183 | content: "\f125"; 1184 | } 1185 | .icon-code-fork:before { 1186 | content: "\f126"; 1187 | } 1188 | .icon-unlink:before { 1189 | content: "\f127"; 1190 | } 1191 | .icon-question:before { 1192 | content: "\f128"; 1193 | } 1194 | .icon-info:before { 1195 | content: "\f129"; 1196 | } 1197 | .icon-exclamation:before { 1198 | content: "\f12a"; 1199 | } 1200 | .icon-superscript:before { 1201 | content: "\f12b"; 1202 | } 1203 | .icon-subscript:before { 1204 | content: "\f12c"; 1205 | } 1206 | .icon-eraser:before { 1207 | content: "\f12d"; 1208 | } 1209 | .icon-puzzle-piece:before { 1210 | content: "\f12e"; 1211 | } 1212 | .icon-microphone:before { 1213 | content: "\f130"; 1214 | } 1215 | .icon-microphone-off:before { 1216 | content: "\f131"; 1217 | } 1218 | .icon-shield:before { 1219 | content: "\f132"; 1220 | } 1221 | .icon-calendar-empty:before { 1222 | content: "\f133"; 1223 | } 1224 | .icon-fire-extinguisher:before { 1225 | content: "\f134"; 1226 | } 1227 | .icon-rocket:before { 1228 | content: "\f135"; 1229 | } 1230 | .icon-maxcdn:before { 1231 | content: "\f136"; 1232 | } 1233 | .icon-chevron-sign-left:before { 1234 | content: "\f137"; 1235 | } 1236 | .icon-chevron-sign-right:before { 1237 | content: "\f138"; 1238 | } 1239 | .icon-chevron-sign-up:before { 1240 | content: "\f139"; 1241 | } 1242 | .icon-chevron-sign-down:before { 1243 | content: "\f13a"; 1244 | } 1245 | .icon-html5:before { 1246 | content: "\f13b"; 1247 | } 1248 | .icon-css3:before { 1249 | content: "\f13c"; 1250 | } 1251 | .icon-anchor:before { 1252 | content: "\f13d"; 1253 | } 1254 | .icon-unlock-alt:before { 1255 | content: "\f13e"; 1256 | } 1257 | .icon-bullseye:before { 1258 | content: "\f140"; 1259 | } 1260 | .icon-ellipsis-horizontal:before { 1261 | content: "\f141"; 1262 | } 1263 | .icon-ellipsis-vertical:before { 1264 | content: "\f142"; 1265 | } 1266 | .icon-rss-sign:before { 1267 | content: "\f143"; 1268 | } 1269 | .icon-play-sign:before { 1270 | content: "\f144"; 1271 | } 1272 | .icon-ticket:before { 1273 | content: "\f145"; 1274 | } 1275 | .icon-minus-sign-alt:before { 1276 | content: "\f146"; 1277 | } 1278 | .icon-check-minus:before { 1279 | content: "\f147"; 1280 | } 1281 | .icon-level-up:before { 1282 | content: "\f148"; 1283 | } 1284 | .icon-level-down:before { 1285 | content: "\f149"; 1286 | } 1287 | .icon-check-sign:before { 1288 | content: "\f14a"; 1289 | } 1290 | .icon-edit-sign:before { 1291 | content: "\f14b"; 1292 | } 1293 | .icon-external-link-sign:before { 1294 | content: "\f14c"; 1295 | } 1296 | .icon-share-sign:before { 1297 | content: "\f14d"; 1298 | } 1299 | .icon-compass:before { 1300 | content: "\f14e"; 1301 | } 1302 | .icon-collapse:before { 1303 | content: "\f150"; 1304 | } 1305 | .icon-collapse-top:before { 1306 | content: "\f151"; 1307 | } 1308 | .icon-expand:before { 1309 | content: "\f152"; 1310 | } 1311 | .icon-euro:before, 1312 | .icon-eur:before { 1313 | content: "\f153"; 1314 | } 1315 | .icon-gbp:before { 1316 | content: "\f154"; 1317 | } 1318 | .icon-dollar:before, 1319 | .icon-usd:before { 1320 | content: "\f155"; 1321 | } 1322 | .icon-rupee:before, 1323 | .icon-inr:before { 1324 | content: "\f156"; 1325 | } 1326 | .icon-yen:before, 1327 | .icon-jpy:before { 1328 | content: "\f157"; 1329 | } 1330 | .icon-renminbi:before, 1331 | .icon-cny:before { 1332 | content: "\f158"; 1333 | } 1334 | .icon-won:before, 1335 | .icon-krw:before { 1336 | content: "\f159"; 1337 | } 1338 | .icon-bitcoin:before, 1339 | .icon-btc:before { 1340 | content: "\f15a"; 1341 | } 1342 | .icon-file:before { 1343 | content: "\f15b"; 1344 | } 1345 | .icon-file-text:before { 1346 | content: "\f15c"; 1347 | } 1348 | .icon-sort-by-alphabet:before { 1349 | content: "\f15d"; 1350 | } 1351 | .icon-sort-by-alphabet-alt:before { 1352 | content: "\f15e"; 1353 | } 1354 | .icon-sort-by-attributes:before { 1355 | content: "\f160"; 1356 | } 1357 | .icon-sort-by-attributes-alt:before { 1358 | content: "\f161"; 1359 | } 1360 | .icon-sort-by-order:before { 1361 | content: "\f162"; 1362 | } 1363 | .icon-sort-by-order-alt:before { 1364 | content: "\f163"; 1365 | } 1366 | .icon-thumbs-up:before { 1367 | content: "\f164"; 1368 | } 1369 | .icon-thumbs-down:before { 1370 | content: "\f165"; 1371 | } 1372 | .icon-youtube-sign:before { 1373 | content: "\f166"; 1374 | } 1375 | .icon-youtube:before { 1376 | content: "\f167"; 1377 | } 1378 | .icon-xing:before { 1379 | content: "\f168"; 1380 | } 1381 | .icon-xing-sign:before { 1382 | content: "\f169"; 1383 | } 1384 | .icon-youtube-play:before { 1385 | content: "\f16a"; 1386 | } 1387 | .icon-dropbox:before { 1388 | content: "\f16b"; 1389 | } 1390 | .icon-stackexchange:before { 1391 | content: "\f16c"; 1392 | } 1393 | .icon-instagram:before { 1394 | content: "\f16d"; 1395 | } 1396 | .icon-flickr:before { 1397 | content: "\f16e"; 1398 | } 1399 | .icon-adn:before { 1400 | content: "\f170"; 1401 | } 1402 | .icon-bitbucket:before { 1403 | content: "\f171"; 1404 | } 1405 | .icon-bitbucket-sign:before { 1406 | content: "\f172"; 1407 | } 1408 | .icon-tumblr:before { 1409 | content: "\f173"; 1410 | } 1411 | .icon-tumblr-sign:before { 1412 | content: "\f174"; 1413 | } 1414 | .icon-long-arrow-down:before { 1415 | content: "\f175"; 1416 | } 1417 | .icon-long-arrow-up:before { 1418 | content: "\f176"; 1419 | } 1420 | .icon-long-arrow-left:before { 1421 | content: "\f177"; 1422 | } 1423 | .icon-long-arrow-right:before { 1424 | content: "\f178"; 1425 | } 1426 | .icon-apple:before { 1427 | content: "\f179"; 1428 | } 1429 | .icon-windows:before { 1430 | content: "\f17a"; 1431 | } 1432 | .icon-android:before { 1433 | content: "\f17b"; 1434 | } 1435 | .icon-linux:before { 1436 | content: "\f17c"; 1437 | } 1438 | .icon-dribbble:before { 1439 | content: "\f17d"; 1440 | } 1441 | .icon-skype:before { 1442 | content: "\f17e"; 1443 | } 1444 | .icon-foursquare:before { 1445 | content: "\f180"; 1446 | } 1447 | .icon-trello:before { 1448 | content: "\f181"; 1449 | } 1450 | .icon-female:before { 1451 | content: "\f182"; 1452 | } 1453 | .icon-male:before { 1454 | content: "\f183"; 1455 | } 1456 | .icon-gittip:before { 1457 | content: "\f184"; 1458 | } 1459 | .icon-sun:before { 1460 | content: "\f185"; 1461 | } 1462 | .icon-moon:before { 1463 | content: "\f186"; 1464 | } 1465 | .icon-archive:before { 1466 | content: "\f187"; 1467 | } 1468 | .icon-bug:before { 1469 | content: "\f188"; 1470 | } 1471 | .icon-vk:before { 1472 | content: "\f189"; 1473 | } 1474 | .icon-weibo:before { 1475 | content: "\f18a"; 1476 | } 1477 | .icon-renren:before { 1478 | content: "\f18b"; 1479 | } 1480 | -------------------------------------------------------------------------------- /dashboard/assets/stylesheets/jquery.gridster.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2012-08-14 2 | * http://gridster.net/ 3 | * Copyright (c) 2012 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .gridster .preview-holder { 31 | z-index: 1; 32 | position: absolute; 33 | background-color: #fff; 34 | border-color: #fff; 35 | opacity: 0.3; 36 | } 37 | 38 | .gridster .player-revert { 39 | z-index: 10!important; 40 | -webkit-transition: left .3s, top .3s!important; 41 | -moz-transition: left .3s, top .3s!important; 42 | -o-transition: left .3s, top .3s!important; 43 | transition: left .3s, top .3s!important; 44 | } 45 | 46 | .gridster .dragging { 47 | z-index: 10!important; 48 | -webkit-transition: all 0s !important; 49 | -moz-transition: all 0s !important; 50 | -o-transition: all 0s !important; 51 | transition: all 0s !important; 52 | } 53 | 54 | /* Uncomment this if you set helper : "clone" in draggable options */ 55 | /*.gridster .player { 56 | opacity:0; 57 | }*/ -------------------------------------------------------------------------------- /dashboard/config.ru: -------------------------------------------------------------------------------- 1 | require 'dashing' 2 | 3 | configure do 4 | set :auth_token, 'YOUR_AUTH_TOKEN' 5 | 6 | helpers do 7 | def protected! 8 | # Put any authentication code you want in here. 9 | # This method is run before accessing any resource. 10 | end 11 | end 12 | end 13 | 14 | map Sinatra::Application.assets_prefix do 15 | run Sinatra::Application.sprockets 16 | end 17 | 18 | run Sinatra::Application -------------------------------------------------------------------------------- /dashboard/dashboards/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= yield_content(:title) %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | <%= yield %> 21 |
22 | 23 | <% if development? %> 24 |
25 |

Paste the following at the top of <%= params[:dashboard] %>.erb

26 | 27 |
28 | Save this layout 29 | <% end %> 30 | 31 | -------------------------------------------------------------------------------- /dashboard/dashboards/sample.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %>My super sweet dashboard<% end %> 2 |
3 |
    4 |
  • 5 |
    6 |
  • 7 |
8 |
-------------------------------------------------------------------------------- /dashboard/jobs/convergence.rb: -------------------------------------------------------------------------------- 1 | # Populate the graph with some random points 2 | 3 | require "rubygems" 4 | require "json" 5 | require "net/http" 6 | require "uri" 7 | 8 | def get_data 9 | uri = URI.parse("http://localhost:3000/user/1/devices/1/results") 10 | 11 | http = Net::HTTP.new(uri.host,uri.port) 12 | request = Net::HTTP::Get.new(uri.request_uri) 13 | 14 | response=http.request(request) 15 | res=JSON.parse(response.body) 16 | 17 | index = 0 18 | result = [] 19 | res.map do |data| 20 | index = index + 1 21 | result.push({x: index , y: data["temperature"].to_i}) 22 | end 23 | return result 24 | end 25 | 26 | SCHEDULER.every '2s' do 27 | points=get_data 28 | print points 29 | send_event('tempdata', points: points) 30 | end 31 | -------------------------------------------------------------------------------- /dashboard/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This Dashboard doesn't exist. 5 | 17 | 18 | 19 | 20 | 21 |
22 |

Drats! That Dashboard doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | -------------------------------------------------------------------------------- /dashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/designiot-code/83a05d67f5b96616aed4c773b92b5bbfd03249b9/dashboard/public/favicon.ico -------------------------------------------------------------------------------- /dashboard/widgets/clock/clock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Clock extends Dashing.Widget 2 | 3 | ready: -> 4 | setInterval(@startTime, 500) 5 | 6 | startTime: => 7 | today = new Date() 8 | 9 | h = today.getHours() 10 | m = today.getMinutes() 11 | s = today.getSeconds() 12 | m = @formatTime(m) 13 | s = @formatTime(s) 14 | @set('time', h + ":" + m + ":" + s) 15 | @set('date', today.toDateString()) 16 | 17 | formatTime: (i) -> 18 | if i < 10 then "0" + i else i -------------------------------------------------------------------------------- /dashboard/widgets/clock/clock.html: -------------------------------------------------------------------------------- 1 |

2 |

-------------------------------------------------------------------------------- /dashboard/widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-clock styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-clock { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /dashboard/widgets/comments/comments.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Comments extends Dashing.Widget 2 | 3 | @accessor 'quote', -> 4 | "“#{@get('current_comment')?.body}”" 5 | 6 | ready: -> 7 | @currentIndex = 0 8 | @commentElem = $(@node).find('.comment-container') 9 | @nextComment() 10 | @startCarousel() 11 | 12 | onData: (data) -> 13 | @currentIndex = 0 14 | 15 | startCarousel: -> 16 | setInterval(@nextComment, 8000) 17 | 18 | nextComment: => 19 | comments = @get('comments') 20 | if comments 21 | @commentElem.fadeOut => 22 | @currentIndex = (@currentIndex + 1) % comments.length 23 | @set 'current_comment', comments[@currentIndex] 24 | @commentElem.fadeIn() 25 | -------------------------------------------------------------------------------- /dashboard/widgets/comments/comments.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 |

5 |
6 | 7 |

8 | -------------------------------------------------------------------------------- /dashboard/widgets/comments/comments.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #eb9c3c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-comment styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-comments { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .name { 22 | padding-left: 5px; 23 | } 24 | 25 | .comment-container { 26 | display: none; 27 | } 28 | 29 | .more-info { 30 | color: $moreinfo-color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /dashboard/widgets/graph/graph.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Graph extends Dashing.Widget 2 | 3 | @accessor 'current', -> 4 | return @get('displayedValue') if @get('displayedValue') 5 | points = @get('points') 6 | if points 7 | points[points.length - 1].y 8 | 9 | ready: -> 10 | container = $(@node).parent() 11 | # Gross hacks. Let's fix this. 12 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 13 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 14 | @graph = new Rickshaw.Graph( 15 | element: @node 16 | width: width 17 | height: height 18 | renderer: @get("graphtype") 19 | series: [ 20 | { 21 | color: "#fff", 22 | data: [{x:0, y:0}] 23 | } 24 | ] 25 | ) 26 | 27 | @graph.series[0].data = @get('points') if @get('points') 28 | 29 | x_axis = new Rickshaw.Graph.Axis.Time(graph: @graph) 30 | y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) 31 | @graph.render() 32 | 33 | onData: (data) -> 34 | if @graph 35 | @graph.series[0].data = data.points 36 | @graph.render() 37 | -------------------------------------------------------------------------------- /dashboard/widgets/graph/graph.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /dashboard/widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 20px; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 20px; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 20px; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /dashboard/widgets/iframe/iframe.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Iframe extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /dashboard/widgets/iframe/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dashboard/widgets/iframe/iframe.scss: -------------------------------------------------------------------------------- 1 | .widget-iframe { 2 | padding: 3px 0px 0px 0px !important; 3 | 4 | iframe { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dashboard/widgets/image/image.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Image extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /dashboard/widgets/image/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dashboard/widgets/image/image.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #4b4b4b; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-image styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-image { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /dashboard/widgets/list/list.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.List extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /dashboard/widgets/list/list.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
    4 |
  1. 5 | 6 | 7 |
  2. 8 |
9 | 10 |
    11 |
  • 12 | 13 | 14 |
  • 15 |
16 | 17 |

18 |

19 | -------------------------------------------------------------------------------- /dashboard/widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #12b0c5; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $label-color: rgba(255, 255, 255, 0.7); 9 | $moreinfo-color: rgba(255, 255, 255, 0.7); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-list styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-list { 15 | 16 | background-color: $background-color; 17 | vertical-align: top; 18 | 19 | .title { 20 | color: $title-color; 21 | } 22 | 23 | ol, ul { 24 | margin: 0 15px; 25 | text-align: left; 26 | color: $label-color; 27 | } 28 | 29 | ol { 30 | list-style-position: inside; 31 | } 32 | 33 | li { 34 | margin-bottom: 5px; 35 | } 36 | 37 | .list-nostyle { 38 | list-style: none; 39 | } 40 | 41 | .label { 42 | color: $label-color; 43 | } 44 | 45 | .value { 46 | float: right; 47 | margin-left: 12px; 48 | font-weight: 600; 49 | color: $value-color; 50 | } 51 | 52 | .updated-at { 53 | color: rgba(0, 0, 0, 0.3); 54 | } 55 | 56 | .more-info { 57 | color: $moreinfo-color; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /dashboard/widgets/meter/meter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Meter extends Dashing.Widget 2 | 3 | @accessor 'value', Dashing.AnimatedValue 4 | 5 | constructor: -> 6 | super 7 | @observe 'value', (value) -> 8 | $(@node).find(".meter").val(value).trigger('change') 9 | 10 | ready: -> 11 | meter = $(@node).find(".meter") 12 | meter.attr("data-bgcolor", meter.css("background-color")) 13 | meter.attr("data-fgcolor", meter.css("color")) 14 | meter.knob() 15 | -------------------------------------------------------------------------------- /dashboard/widgets/meter/meter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /dashboard/widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #9c4274; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | 9 | $meter-background: darken($background-color, 15%); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: #fff; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | .updated-at { 32 | color: rgba(0, 0, 0, 0.3); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /dashboard/widgets/number/number.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Number extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | @accessor 'difference', -> 5 | if @get('last') 6 | last = parseInt(@get('last')) 7 | current = parseInt(@get('current')) 8 | if last != 0 9 | diff = Math.abs(Math.round((current - last) / last * 100)) 10 | "#{diff}%" 11 | else 12 | "" 13 | 14 | @accessor 'arrow', -> 15 | if @get('last') 16 | if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down' 17 | 18 | onData: (data) -> 19 | if data.status 20 | # clear existing "status-*" classes 21 | $(@get('node')).attr 'class', (i,c) -> 22 | c.replace /\bstatus-\S+/g, '' 23 | # add new class 24 | $(@get('node')).addClass "status-#{data.status}" 25 | -------------------------------------------------------------------------------- /dashboard/widgets/number/number.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | 11 |

12 | -------------------------------------------------------------------------------- /dashboard/widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 30px; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | .updated-at { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /dashboard/widgets/text/text.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Text extends Dashing.Widget 2 | -------------------------------------------------------------------------------- /dashboard/widgets/text/text.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /dashboard/widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | .updated-at { 25 | color: rgba(255, 255, 255, 0.7); 26 | } 27 | 28 | 29 | &.large h3 { 30 | font-size: 65px; 31 | } 32 | } 33 | --------------------------------------------------------------------------------