當JavaScript 遇上物聯(lián)網(wǎng)(IoT)
1995年,當工作于 Netscape 的 Brendan Eich著手為Netscape Navigator 2.0 開發(fā)一個稱之為 LiveScript 的腳本語言時,沒有人會想到avascript將在今天的互聯(lián)網(wǎng)軟件開發(fā)中發(fā)揮重要作用。如今,Javascript已經(jīng)在越來越多的領域攻城略地,web工程構建,后端服務器開發(fā),三維圖像,AR,VR等等等。甚至,在近幾年我們也驚喜的發(fā)現(xiàn)已經(jīng)JS可以用來開發(fā)硬件設備。今天,就跟大家聊聊用JS進行簡單物聯(lián)網(wǎng)開發(fā)的心得與體會。
凡是可以用 JavaScript 來寫的應用,最終都會用 JavaScript ——Atwood定律
什么是IOT
我們總說IOT,那到底什么是IOT?IOT是Internet of Things的縮寫,字面翻譯是“物體組成的因特網(wǎng)”,準確的翻譯應該為“物聯(lián)網(wǎng)”。物聯(lián)網(wǎng)(Internet of Things)又稱傳感網(wǎng),簡要講就是互聯(lián)網(wǎng)從人向物的延伸。
其實物聯(lián)網(wǎng)可以從兩個方向進行拆分,即由“物”向“網(wǎng)”,或者是由“網(wǎng)”向“物”。由物向網(wǎng)可以理解為人跨越空間和自身條件的局限對物體進行感知的過程。由“網(wǎng)”向“物”是人跨越空間對物體進行控制的過程。
IoT應用開發(fā)平臺簡介
在IoT應用開發(fā)領域中,大家熟知的開發(fā)平臺主要有如下幾類:
- 嵌入式操作系統(tǒng),包括VxWorks、FreeRTOS、LiteOS等;
 - 極客硬件平臺,包括樹莓派、Arduino等;
 - JavaScript IoT應用開發(fā)平臺,包括Ruff、Tessel、JerryScript、Johnny-Five等。
 
嵌入式操作系統(tǒng),從功能的角度上來說,能夠滿足目前的絕大多數(shù)需求。但是:
- 其入門門檻極高,開發(fā)者想要成為優(yōu)秀的嵌入式開發(fā)工程師,需要學習大量軟硬件知識。相較于軟件行業(yè),嵌入式領域的人才數(shù)量受到了限制。
 - 嵌入式領域在開發(fā)方法上已經(jīng)大幅度落后于整個行業(yè)的發(fā)展。敏捷軟件開發(fā)方法以及精益創(chuàng)業(yè)的理念,受到工具所限,在嵌入式領域極少得到應用,所以該領域在工程方法上發(fā)展緩慢。
 - 這些操作系統(tǒng)的編程概念通常屬于專用領域,所以知識很難在行業(yè)中共享,開發(fā)者在行業(yè)中流動也相對困難,造成的結果是,嵌入式領域對于現(xiàn)代軟件開發(fā)理念的理解也整體上落后于軟件行業(yè)。
 
極客硬件平臺,其初衷是降低開發(fā)門檻,讓更多開發(fā)者得以進入到硬件開發(fā)領域中。但是:
- 它只是在操作方面的入門難度上在努力,而開發(fā)真正困難的部分在編程概念。對于大多數(shù)軟件開發(fā)者而言,難點在于硬件中的編程概念。各種各樣的接口及參數(shù),這是軟件開發(fā)者難于理解和掌握的。
 - 更關鍵的因素是,這些平臺只解決了原型開發(fā)的問題。開發(fā)者即便能夠通過它實現(xiàn)了一個產(chǎn)品原型,也很難將它用到真正的產(chǎn)品中。應用到產(chǎn)品中,往往要重新設計硬件,這些平臺的優(yōu)勢就蕩然無存了。
 - 二者最本質(zhì)的復雜度在于其編程模型,對于軟件開發(fā)者來說,GPIO、I2C之類硬件接口完全是另一種語言,除了要了解接口的編程方法,還要針對每個硬件,閱讀其數(shù)據(jù)手冊,了解參數(shù)細節(jié)。
 
目前為止,諸位會想,IoT行業(yè)對軟件工程師簡直猶如另一個世界,一點都不友好。是的,很多人都是這么想的,于是,有人想用更高級的語言改變這個世界,這其中最為活躍的便是JavaScript社區(qū)。
本節(jié)內(nèi)容參考Ruff CTO 鄭曄的相關文章
JavaScript IoT應用開發(fā)平臺
JavaScript IoT應用開發(fā)平臺,其建設初衷是讓開發(fā)者能夠用JavaScript開發(fā)IoT應用,一方面可以更好地構建抽象,另一方面,可以將比較現(xiàn)代的開發(fā)方式引入到硬件研發(fā)中。JavaScript IoT應用開發(fā)平臺目前主要分為幾大類:
- 在硬件上運行JavaScript,如JerryScript、Espruino等;
 - 提供硬件抽象能力,比如Tessel、Johnny-Five、Cylon.js等;
 - 面向生產(chǎn)的能力,如Ruff。
 
Ruff的優(yōu)勢
Ruff與Arduino相比更貼近網(wǎng)絡,由于Arduino的誕生較早,標準開發(fā)板并沒有網(wǎng)絡通信方式,雖然可以通過擴展的形式添加,但是上手略微復雜。而Ruff天生支持Wifi通信,使用Ruff進行http通信和使用普通nodejs的http通信方式?jīng)]有什么區(qū)別,上手極為簡單。當然,由于Arduino發(fā)布較早,而且一開始就為模塊化開發(fā)設計,感覺Arduino的第三發(fā)模塊相對簡單,就像搭積木一樣一層層安裝即可,而ruff的硬件模塊相對較少,生態(tài)和Arduino相比不是那么成熟完善。
Ruff與樹莓派相比更加貼近物聯(lián)網(wǎng)開發(fā)。樹莓派的本質(zhì)是個濃縮的但是相對完整的操作系統(tǒng),你在樹莓派上可以干任何事情,可以瀏覽網(wǎng)頁,可以編寫web服務端程序,當然也可以直接編寫樹莓派的I/O接口(定時啟動咖啡機、給狗狗喂食。。。)而Ruff的功能相對簡單很多,簡單的可以認為Ruff的功能是單片機,部署各web server還有可能實現(xiàn),但是想訪問瀏覽器等圖形界面就不可能實現(xiàn)了,但是它的核心目標就是針對硬件來編程,功耗更低效率更高??偠灾莾烧咴O計初衷的差異。樹莓派要做電腦,如果太弱了,很多功能做不了,而 Ruff 開發(fā)套件是為了做硬件應用,太強了反而不能體現(xiàn)真實的場景。
Ruff上手
Ruff的入門上手極為簡單。通過官網(wǎng)教程我們可以迅速的使用Javascript寫出個簡單的點亮
- 'use strict';
 - $.ready(function (error) {
 - if (error) {
 - console.log(error);
 - return;
 - }
 - $('#led-r').turnOn(); //點亮小燈
 - });
 - $.end(function () {
 - $('#led-r').turnOff();
 - });
 
然后連接Ruff自帶wifi熱點。
通過無線網(wǎng)絡,使用命令 rap deploy -s 將程序下載到開發(fā)板。整個過程極為簡單,自然。不比配置個前端工程復雜太多。
Ruff擁抱Internet
如果ruff只能簡單的開發(fā)小燈亮滅這樣的簡單程序,只能作為個玩具。而只用真正接入到廣闊的互聯(lián)網(wǎng)中才能實現(xiàn)真正意義上的“物聯(lián)網(wǎng)”。后面將介紹如何將ruff接入阿里云物聯(lián)網(wǎng)套件,實現(xiàn)信息的上報與獲取,實現(xiàn)由“物”向“網(wǎng)”的信息感知和由“網(wǎng)”向“物”的遠程控制。
阿里云物聯(lián)網(wǎng)套件是阿里云專門為物聯(lián)網(wǎng)領域的開發(fā)人員推出的,其目的是幫助開發(fā)者搭建安全性能強大的數(shù)據(jù)通道,方便終端(如傳感器、執(zhí)行器、嵌入式設備或智能家電等等)和云端的雙向通信。全球多節(jié)點部署讓海量設備全球范圍都可以安全低延時接入阿里云IoT Hub,安全上提供多重防護保障設備云端安全,性能上能夠支撐億級設備長連接,百萬消息并發(fā)。物聯(lián)網(wǎng)套件還提供了一站式托管服務,數(shù)據(jù)從采集到計算到存儲,用戶無需購買服務器部署分布式架構,用戶只需在web上配置規(guī)則即可實現(xiàn)采集+計算+存儲等全棧服務??偠灾?,基于物聯(lián)網(wǎng)套件提供的服務,物聯(lián)網(wǎng)開發(fā)者可以快速搭建穩(wěn)定可靠的物聯(lián)網(wǎng)平臺。
當然各位看官可以訪問阿里云物聯(lián)網(wǎng)套件的產(chǎn)品詳情頁來探索物聯(lián)網(wǎng)套件的強大功能,阿里云 – 物聯(lián)網(wǎng)套件 – 產(chǎn)品詳情
從架構圖上我們也可以看出,紅色的消息發(fā)布通道即為我們前文談到的由“物”向“網(wǎng)”感知過程,而藍色的訂閱消息服務則是我們前文談到的由“網(wǎng)”向“物”的控制過程。
阿里云物聯(lián)網(wǎng)套件目前沒有官方的JS SDK。但是物聯(lián)網(wǎng)套件使用的MQTT協(xié)議是通用的物聯(lián)網(wǎng)通信協(xié)議,我們可以根據(jù)Java的SDK,接入阿里云產(chǎn)品的通用Openapi。
ruff程序代碼示例:
- 'use strict';
 - var fs = require('fs');
 - var os = require('os')
 - var mqtt = require('mqtt')
 - var productKey = '填寫在阿里云物聯(lián)網(wǎng)套件中申請的productKey'
 - var deviceName = '填寫在阿里云物聯(lián)網(wǎng)套件中申請的deviceName'
 - var deviceSecret = '填寫在阿里云物聯(lián)網(wǎng)套件中申請的deviceSecret'
 - var targetServer = "tcp://" + productKey + ".iot-as-mqtt.cn-shanghai.aliyuncs.com:1883"
 - var port = 1883
 - var host = productKey + '.iot-as-mqtt.cn-shanghai.aliyuncs.com'
 - var clientId = os.hostname();
 - var timestamp = (new Date()).valueOf()
 - var mqttClientId = clientId + "|securemode=3,signmethod=hmacsha1,timestamp=" + timestamp + "|";
 - var mqttUsername = deviceName + "&" + productKey
 - var content = 'clientId' + clientId + 'deviceName' + deviceName + 'productKey' + productKey + 'timestamp' + timestamp
 - // var forge = require('forge')
 - // var hmac = forge.hmac.create();
 - // hmac.start('sha1', deviceSecret);
 - // hmac.update(content);
 - // var mqttPassword = hmac.digest().toHex();
 - // console.log(mqttPassword)
 - // 目前ruff上無法使用crypto等包,可以自行實現(xiàn)一個hmac sha1加密
 - var mqttPassword = '生成的秘文';
 - var puburl = "/" + productKey + "/" + deviceName + "/update"
 - var suburl = "/" + productKey + "/" + deviceName + "/get"
 - var tsl_options = {
 - port: port,
 - host: host,
 - rejectUnauthorized: false,
 - keepalive: 100,
 - clientId: mqttClientId,
 - username: mqttUsername,
 - password: mqttPassword
 - }
 - $.ready(function (error) {
 - if (error) {
 - console.log(error);
 - return;
 - }
 - var mqttClient = mqtt.connect(targetServer, tsl_options)
 - mqttClient.on('connect', function () {
 - console.log('********** Connected **********')
 - //當按鍵被按下時,通過mqtt協(xié)議,向阿里云mns服務發(fā)送消息
 - $('#button').on('push', function () {
 - var data = { ts: (new Date()).valueOf(), deviceName: deviceName }
 - mqttClient.publish(puburl, JSON.stringify(data))
 - console.log(JSON.stringify(data))
 - });
 - //level 0:最多一次的傳輸
 - //level 1:至少一次的傳輸
 - //level 2:只有一次的傳輸
 - mqttClient.subscribe(suburl, {qos:1})
 - //當接受到消息時控制小紅燈亮滅
 - mqttClient.on('message', function (topic, message) {
 - var msg = message.toString()
 - console.log('您接收到的消息為: ' + msg)
 - if(msg=='turn_on_led'){
 - $('#led-r').turnOn();
 - setTimeout(function(){
 - $('#led-r').turnOff();
 - }, 500)
 - }
 - })
 - })
 - mqttClient.on('error', function (error) {
 - console.log(error)
 - })
 - });
 
服務端代碼示例:
- const RPCClient = require('@alicloud/pop-core').RPCClient;
 - const MNSClient = require('@alicloud/mns');
 - const Base64 = require('js-base64').Base64;
 - var iotClient = new RPCClient({
 - accessKeyId: '阿里云accessKeyId',
 - secretAccessKey: '阿里云accessKeySecret',
 - endpoint: 'https://iot.cn-shanghai.aliyuncs.com',
 - apiVersion: '2017-04-20'
 - });
 - var mnsClient = new MNSClient('阿里云賬戶id', {
 - region: 'cn-shanghai',
 - accessKeyId: '阿里云accessKeyId',
 - accessKeySecret: '阿里云accessKeySecret',
 - });
 - const queueName = 'aliyun-iot-xGEDKBE*****' //開通阿里云物聯(lián)網(wǎng)套件生成MNS消息隊列
 - //從隊列中消費消息
 - setInterval(async ()=>{
 - try{
 - var receiveRes = await mnsClient.receiveMessage(queueName)
 - var deleteRes = await mnsClient.deleteMessage(queueName, receiveRes.body.ReceiptHandle);
 - var payload = JSON.parse(Base64.decode(receiveRes.body.MessageBody))
 - var data = Base64.decode(payload.payload)
 - console.log('從IoT設備接受到的數(shù)據(jù)為:' + data)
 - } catch(err) {
 - console.log(err)
 - }
 - }, 500)
 - const productKey = '**********' //阿里云物聯(lián)網(wǎng)套件productKey
 - const deviceName = '**********' //阿里云物聯(lián)網(wǎng)套件deviceName
 - const iotClientParams = {
 - ProductKey: productKey,
 - TopicFullName: `/${productKey}/${deviceName}/get`,
 - MessageContent: Base64.encode('turn_on_led'),
 - }
 - //向ruff發(fā)送消息
 - setInterval(async ()=>{
 - try{
 - var sedRes = await iotClient.request('Pub', iotClientParams)
 - }catch(err){
 - console.log(err)
 - }
 - }, 2000)
 
*附注:服務端程序無需購買阿里云的ECS,在本地即可測試,物聯(lián)網(wǎng)套件和MNS按量服務,非生產(chǎn)環(huán)境下幾乎不會產(chǎn)生費用,注冊阿里云賬號即可使用。以上代碼示例寫的比較簡單,可以實現(xiàn)簡單的物聯(lián)網(wǎng)設備與服務端程序的雙向通信,拋磚引玉,期待大家一起實踐起來!

















 
 
 






 
 
 
 