1995年,當工作於 Netscape 的 Brendan Eich著手為Netscape Navigator 2.0 開發一個稱之為 LiveScript 的指令碼語言時,沒有人會想到avascript將在今天的網際網路軟體開發中發揮重要作用。如今,Javascript已經在越來越多的領域攻城略地,web工程構建,後端伺服器開發,三維影象,AR,VR等等等。甚至,在近幾年我們也驚喜的發現已經JS可以用來開發硬體裝置。今天,就跟大家聊聊用JS進行簡單物聯網開發的心得與體會。
凡是可以用 JavaScript 來寫的應用,最終都會用 JavaScript ——Atwood定律
什麼是IOT
我們總說IOT,那到底什麼是IOT?IOT是Internet of Things的縮寫,字面翻譯是“物體組成的因特網”,準確的翻譯應該為“物聯網”。物聯網(Internet of Things)又稱感測網,簡要講就是網際網路從人向物的延伸。
其實物聯網可以從兩個方向進行拆分,即由“物”向“網”,或者是由“網”向“物”。由物向網可以理解為人跨越空間和自身條件的侷限對物體進行感知的過程。由“網”向“物”是人跨越空間對物體進行控制的過程。
IoT應用開發平臺簡介
在IoT應用開發領域中,大家熟知的開發平臺主要有如下幾類:
- 嵌入式作業系統,包括VxWorks、FreeRTOS、LiteOS等;
- 極客硬體平臺,包括樹莓派、Arduino等;
- JavaScript IoT應用開發平臺,包括Ruff、Tessel、JerryScript、Johnny-Five等。
嵌入式作業系統,從功能的角度上來說,能夠滿足目前的絕大多數需求。但是:
- 其入門門檻極高,開發者想要成為優秀的嵌入式開發工程師,需要學習大量軟硬體知識。相較於軟體行業,嵌入式領域的人才數量受到了限制。
- 嵌入式領域在開發方法上已經大幅度落後於整個行業的發展。敏捷軟體開發方法以及精益創業的理念,受到工具所限,在嵌入式領域極少得到應用,所以該領域在工程方法上發展緩慢。
- 這些作業系統的程式設計概念通常屬於專用領域,所以知識很難在行業中共享,開發者在行業中流動也相對困難,造成的結果是,嵌入式領域對於現代軟體開發理念的理解也整體上落後於軟體行業。
極客硬體平臺,其初衷是降低開發門檻,讓更多開發者得以進入到硬體開發領域中。但是:
- 它只是在操作方面的入門難度上在努力,而開發真正困難的部分在程式設計概念。對於大多數軟體開發者而言,難點在於硬體中的程式設計概念。各種各樣的介面及引數,這是軟體開發者難於理解和掌握的。
- 更關鍵的因素是,這些平臺只解決了原型開發的問題。開發者即便能夠通過它實現了一個產品原型,也很難將它用到真正的產品中。應用到產品中,往往要重新設計硬體,這些平臺的優勢就蕩然無存了。
二者最本質的複雜度在於其程式設計模型,對於軟體開發者來說,GPIO、I2C之類硬體介面完全是另一種語言,除了要了解介面的程式設計方法,還要針對每個硬體,閱讀其資料手冊,瞭解引數細節。
目前為止,諸位會想,IoT行業對軟體工程師簡直猶如另一個世界,一點都不友好。是的,很多人都是這麼想的,於是,有人想用更高階的語言改變這個世界,這其中最為活躍的便是JavaScript社群。
本節內容參考Ruff CTO 鄭曄的相關文章
JavaScript IoT應用開發平臺
JavaScript IoT應用開發平臺,其建設初衷是讓開發者能夠用JavaScript開發IoT應用,一方面可以更好地構建抽象,另一方面,可以將比較現代的開發方式引入到硬體研發中。JavaScript IoT應用開發平臺目前主要分為幾大類:
- 在硬體上執行JavaScript,如JerryScript、Espruino等;
- 提供硬體抽象能力,比如Tessel、Johnny-Five、Cylon.js等;
- 面向生產的能力,如Ruff。
Ruff的優勢
Ruff與Arduino相比更貼近網路,由於Arduino的誕生較早,標準開發板並沒有網路通訊方式,雖然可以通過擴充套件的形式新增,但是上手略微複雜。而Ruff天生支援Wifi通訊,使用Ruff進行http通訊和使用普通nodejs的http通訊方式沒有什麼區別,上手極為簡單。當然,由於Arduino釋出較早,而且一開始就為模組化開發設計,感覺Arduino的第三發模組相對簡單,就像搭積木一樣一層層安裝即可,而ruff的硬體模組相對較少,生態和Arduino相比不是那麼成熟完善。
Ruff與樹莓派相比更加貼近物聯網開發。樹莓派的本質是個濃縮的但是相對完整的作業系統,你在樹莓派上可以幹任何事情,可以瀏覽網頁,可以編寫web服務端程式,當然也可以直接編寫樹莓派的I/O介面(定時啟動咖啡機、給狗狗餵食。。。)而Ruff的功能相對簡單很多,簡單的可以認為Ruff的功能是微控制器,部署各web server還有可能實現,但是想訪問瀏覽器等圖形介面就不可能實現了,但是它的核心目標就是針對硬體來程式設計,功耗更低效率更高。總而言之是兩者設計初衷的差異。樹莓派要做電腦,如果太弱了,很多功能做不了,而 Ruff 開發套件是為了做硬體應用,太強了反而不能體現真實的場景。
Ruff上手
Ruff的入門上手極為簡單。通過官網教程我們可以迅速的使用Javascript寫出個簡單的點亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
'use strict'; $.ready(function (error) { if (error) { console.log(error); return; } $('#led-r').turnOn(); //點亮小燈 }); $.end(function () { $('#led-r').turnOff(); }); |
然後連線Ruff自帶wifi熱點。
通過無線網路,使用命令 rap deploy -s 將程式下載到開發板。整個過程極為簡單,自然。不比配置個前端工程複雜太多。
Ruff擁抱Internet
如果ruff只能簡單的開發小燈亮滅這樣的簡單程式,只能作為個玩具。而只用真正接入到廣闊的網際網路中才能實現真正意義上的“物聯網”。後面將介紹如何將ruff接入阿里雲物聯網套件,實現資訊的上報與獲取,實現由“物”向“網”的資訊感知和由“網”向“物”的遠端控制。
阿里雲物聯網套件是阿里雲專門為物聯網領域的開發人員推出的,其目的是幫助開發者搭建安全效能強大的資料通道,方便終端(如感測器、執行器、嵌入式裝置或智慧家電等等)和雲端的雙向通訊。全球多節點部署讓海量裝置全球範圍都可以安全低延時接入阿里雲IoT Hub,安全上提供多重防護保障裝置雲端安全,效能上能夠支撐億級裝置長連線,百萬訊息併發。物聯網套件還提供了一站式託管服務,資料從採集到計算到儲存,使用者無需購買伺服器部署分散式架構,使用者只需在web上配置規則即可實現採集+計算+儲存等全棧服務。總而言之,基於物聯網套件提供的服務,物聯網開發者可以快速搭建穩定可靠的物聯網平臺。
當然各位看官可以訪問阿里雲物聯網套件的產品詳情頁來探索物聯網套件的強大功能,阿里雲 – 物聯網套件 – 產品詳情
從架構圖上我們也可以看出,紅色的訊息釋出通道即為我們前文談到的由“物”向“網”感知過程,而藍色的訂閱訊息服務則是我們前文談到的由“網”向“物”的控制過程。
阿里雲物聯網套件目前沒有官方的JS SDK。但是物聯網套件使用的MQTT協議是通用的物聯網通訊協議,我們可以根據Java的SDK,接入阿里雲產品的通用Openapi。
ruff程式程式碼示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
'use strict'; var fs = require('fs'); var os = require('os') var mqtt = require('mqtt') var productKey = '填寫在阿里雲物聯網套件中申請的productKey' var deviceName = '填寫在阿里雲物聯網套件中申請的deviceName' var deviceSecret = '填寫在阿里雲物聯網套件中申請的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等包,可以自行實現一個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協議,向阿里雲mns服務傳送訊息 $('#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) }) }); |
服務端程式碼示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
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*****' //開通阿里雲物聯網套件生成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裝置接受到的資料為:' + data) } catch(err) { console.log(err) } }, 500) const productKey = '**********' //阿里雲物聯網套件productKey const deviceName = '**********' //阿里雲物聯網套件deviceName const iotClientParams = { ProductKey: productKey, TopicFullName: `/${productKey}/${deviceName}/get`, MessageContent: Base64.encode('turn_on_led'), } //向ruff傳送訊息 setInterval(async ()=>{ try{ var sedRes = await iotClient.request('Pub', iotClientParams) }catch(err){ console.log(err) } }, 2000) |
*附註:服務端程式無需購買阿里雲的ECS,在本地即可測試,物聯網套件和MNS按量服務,非生產環境下幾乎不會產生費用,註冊阿里雲賬號即可使用。以上程式碼示例寫的比較簡單,可以實現簡單的物聯網裝置與服務端程式的雙向通訊,拋磚引玉,期待大家一起實踐起來!
最後,也發一條廣告,阿里雲前端團隊不僅僅是前端!加入我們吧,用手中的JS探索星辰大海!