## 游历 JavaScript IoT 应用开发平台 文/郑晔 物联网(Internet of Things,简称 IoT)时代的脚步声已经越来越响亮,每个程序员都希望跟上时代的步伐,不为时代浪潮所淘汰。面对 IoT 这个纷争初起的领域,程序员们该何去何从?本文将带领诸位进行一次 IoT 应用开发平台的游历之旅,帮助大家了解该领域当今的发展状态,尤其是基于 JavaScript 的 IoT 应用开发平台,为各位搭车 IoT 奠定一些基础。 开启行程之前,我们先明确讨论范围,在行业里,谈到 IoT 开发平台,有人说的是云,如各大云厂商;有人说的是硬件端,如各家硬件厂商;在这里我们将讨论是,硬件端的开发平台,对于大多数软件开发人员而言,这是一个更加陌生的领域。 ### IoT 应用开发平台简介 在 IoT 应用开发领域中,大家熟知的开发平台主要有如下几类: 1. 嵌入式操作系统,包括 VxWorks、FreeRTOS、LiteOS 等; 2. 极客硬件平台,包括树莓派、Arduino 等; 3. JavaScript IoT 应用开发平台,包括 Ruff、Tessel、JerryScript、Johnny-Five 等。 嵌入式操作系统,从功能的角度上来说,能够满足目前的绝大多数需求。但是: 1. 其入门门槛极高,开发者想要成为优秀的嵌入式开发工程师,需要学习大量软硬件知识。相较于软件行业,嵌入式领域的人才数量受到了限制。 2. 嵌入式领域在开发方法上已经大幅度落后于整个行业的发展。敏捷软件开发方法以及精益创业的理念,受到工具所限,在嵌入式领域极少得到应用,所以该领域在工程方法上发展缓慢。 3. 这些操作系统的编程概念通常属于专用领域,所以知识很难在行业中共享,开发者在行业中流动也相对困难,造成的结果是,嵌入式领域对于现代软件开发理念的理解也整体上落后于软件行业。 极客硬件平台,其初衷是降低开发门槛,让更多开发者得以进入到硬件开发领域中。但是: 1. 它只是在操作方面的入门难度上在努力,而开发真正困难的部分在编程概念。对于大多数软件开发者而言,难点在于硬件中的编程概念。各种各样的接口及参数,这是软件开发者难于理解和掌握的。 2. 更关键的因素是,这些平台只解决了原型开发的问题。开发者即便能够通过它实现了一个产品原型,也很难将它用到真正的产品中。应用到产品中,往往要重新设计硬件,这些平台的优势就荡然无存了。 二者最本质的复杂度在于其编程模型,对于软件开发者来说,GPIO、I²C 之类硬件接口完全是另一种语言,除了要了解接口的编程方法,还要针对每个硬件,阅读其数据手册,了解参数细节。 目前为止,诸位会想, IoT 行业对软件工程师简直犹如另一个世界,一点都不友好。是的,很多人都是这么想的,于是,有人想用更高级的语言改变这个世界,这其中最为活跃的便是 JavaScript 社区。 ### JavaScript IoT 应用开发平台 JavaScript IoT 应用开发平台,其建设初衷是让开发者能够用 JavaScript 开发 IoT 应用,一方面可以更好地构建抽象,另一方面,可以将比较现代的开发方式引入到硬件研发中。 JavaScript IoT 应用开发平台目前主要分为几大类: 1. 在硬件上运行 JavaScript ,如 JerryScript、Espruino 等; 2. 提供硬件抽象能力,比如 Tessel、Johnny-Five、Cylon.js 等; 3. 面向生产的能力,如 Ruff。 #### 在硬件上运行 JavaScript 的平台 该类平台主要解决的问题是让硬件平台具有运行 JavaScript 程序的能力,主要是在资源受限的硬件上,比如 MCU(Microcontroller Unit,微控制器,又称单片机),你可以把 MCU 理解成内存很小的芯片,时至今日,谈及单片机,内存通常以 K 为单位。 **Espruino** 网站:https://www.espruino.com Espruino 是将 JavaScript 与硬件开发连接起来的先驱,其设计目标就是能够在单片机上运行 JavaScript 。除了提供引擎之外,Espruino 还提供了一些访问底层设备的程序库,下面是一段代码示例: ``` function toggle() { on = !on; digitalWrite(LED1, on); digitalWrite(LED2, !on); } ``` 其中,digitalWrite 是 Espruino 提供的方法。由于出现的比较早,其程序与今天行业里的主流编程风格有一些差异,偏向于传统的 C 代码风格。 从架构上说,Espruino 做得也差强人意,它将解释器、程序库、底层系统混在了一起,移植起来有一定难度。 **JerryScript** 网站:http://jerryscript.net JerryScript 是三星打造的一款 JavaScript 引擎,它可以运行在 64K 的 MCU上,其相对比较年轻,对于 JavaScript 标准支持得比较好,能够完整支持ECMAScript 5.1。JerryScript 只是一个 JavaScript 引擎,而真正提供设备访问能力是 IoT .js。 **IoT .js** 网站:http:// IoT js.net/ IoT .js 的本意是打造一个类似于 Node.js 的运行时,所以,它提供 Buffer、net、timer 等一些标准模块。当然,最主要的是它提供的设备访问能力,下面是一段示例代码: ``` var i2c = require('i2c'); var wire = new i2c(0x23, {device: '/dev/i2c-1'}); wire.scan(function(err, data) { ... }); ``` 这段代码是一段访问 I²C 接口的代码,如果你不了解硬件接口,这段代码理解起来还是有些难度的。但不难看出,其代码风格已经接近于今天行业里的主流编程风格。 仅仅提供在硬件上运行的 JavaScript 能力是不够的,严格来说,其暴露的依旧是底层的编程接口,面临与传统硬件开发所面对的问题一致,即编程模型无法让软件开发者很好的理解。所以,提供硬件抽象,成为了 IoT 应用开发平台另一个重要的探索方向。 #### 提供硬件抽象的 JavaScript IoT 应用开发平台 该类平台提供软件抽象能力,让更多的软件开发人员使用他们熟悉的语言进入到在 IoT 领域。有了硬件抽象,软件开发者面对不再是 GPIO、I²C 之类的底层接口,而变成了具有开关功能的 LED、能够监测按键按下松开的按钮。这是一个极大的进步,硬件世界的大门对软件开发人员打开了。 **Tessel** 网站:https://tessel.io Tessel 是一个稳定的 IoT 和机器人开发平台,利用 Node.js 所有的程序库创建有用的设备。下面是一段示例代码,定期将声级上报到一个地方。 ``` var tessel = require('tessel'); var ambientlib = require('ambient-attx4'); var WebSocket = require('ws'); var ambient = ambientlib.use(tessel.port['A']); var ws = new WebSocket('ws://awesome-app.com/ambient'); ws.on('open', function () { setInterval(function () { ambient.getSoundLevel(function(err, sdata) { if (err) throw err; sdata.pipe(ws); }) }, 500); }); ``` Tessel 自身除了出品软件,也提供硬件开发板,不过,Tessel 程序也只能运行于 Tessel 开发板上。 **Johnny-Five** 网站:http://johnny-five.io Johnny-Five 是一个 JavaScript 机器人和 IoT 平台,由 Bocoup 公司于2012年发布。下面这段示例代码,让 LED 定期闪烁: ``` var five = require("johnny-five"); var board = new five.Board(); board.on("ready", function() { var led = new five.Led(13); led.blink(500); }); ``` Johnny-Five 不生产开发板,它的程序可以运行于多款开发板上,其缺省支持的是 Ardunio,如果需要其他开发板,可以在 Board 初始化的时候指定,比如下面这段代码就使用了 Edison 开发板。 ``` var Edison = require("edison-io"); var board = new five.Board({ io: new Edison() }); ``` **Cylon.js** 网站:https://cylonjs.com Cylon.js 是一个为机器人、物理计算以及 IoT 而设计的 JavaScript 框架,其目的是让控制机器人和设备变得容易。下面这段示例代码让 LED 每秒闪烁一次: ``` var Cylon = require("cylon"); Cylon.robot({ connections: { arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' } }, devices: { led: { driver: 'led', pin: 13 } }, work: function(my) { every((1).second(), function() { my.led.toggle(); }); } }).start(); ``` 与 Johnny-Five 类似,Cylon.js 也是依赖于别人的开发板。 Tessel、Johnny-Five、Cylon.js 三者有着类似的努力方向,即提供软件抽象,这是很好的做法,也让许多软件开发人员看到了 IoT 的曙光。但是,其基础上的一些问题,决定了他们只能是作为开发者的玩具: 1. 在硬件上运行的能力,这几个平台实际上都是在电脑上运行,然后发送命令控制硬件,也就是用它们开发的应用,需要一个控制端。 2. 更重要的的是,其运行 JavaScript 的基础是 Node.js,这是一种用于电脑上的 JavaScript 运行时,它无法运行于一个资源受限的硬件上,而真实环境中,资源受限硬件才是行业主流。这意味着,这些框架即便未来希望做改造,难度也是很大的。 3. 这些平台虽然在代码级别提供了抽象,但仍然有许多硬件配置的内容,比如 pin、port 之类,对于软件开发人员而言,理解起来还是有门槛的。 由此可见,这些平台的现状只是解决了编程接口的抽象,并没有真正的实现软硬件的隔离。所以,有平台开始了进一步的探索,提供面向生产的能力。 #### 面向生产的 JavaScript IoT 应用开发平台 Ruff 网站:https://ruff.io Ruff 是一个支持 JavaScript 应用开发的物联网操作系统,其目标是打造一个 IoT 版本的 Android。下面是一段 Ruff 示例代码,按下按键,点亮 LED,松开之后,灯熄灭。 ``` 'use strict'; $.ready(function(error) { if (error) { console.log(error); return; } $('#button').on('push', function () { $('#led-r').turnOn(); }); $('#button').on('release', function () { $('#led-r').turnOff(); }); }); $.end(function() { $('#led-r').turnOff(); }); ``` 这段代码已经没有任何与硬件配置相关的代码,完全是应用的逻辑。即只要提供不同的硬件配置,代码就可以运行在不同的硬件上。 事实上,Ruff 也确实做到了这点,它既可以支持像树莓派这样能够运行 Linux 系统的硬件上,也支持像 TM4C1924 这样的 MCU。做到跨硬件,需要从架构设计上有很好的支持,这也是 Ruff 的一大优势。 从 Ruff 提供的特性上看,其企图不止于将引入抽象,更试图将现代软件开发理念带入到 IoT 应用研发之中: 1. 彻底地分离硬件、系统与应用,使三者可以用不同的节奏发布,让 IoT 应用的迭代开发成为可能; 2. 设计测试框架,让开发者可以在开发机上测试应用逻辑,无需部署到真实硬件,大幅度节省了开发调试的时间,从而降低了开发成本; 3. 采用软件包的方式管理各种模块,尤其是驱动,使得模块得以在共享,知识得以流动; 4. 采用命令行的方式进行对相关内容进行管理,比如,板卡、外设驱动、系统升级等等,便于与第三方工具集成。 Ruff 正试图建立一个全新的 IoT 应用开发平台,所以,它支持的硬件数量相对前期发展时间比较长的平台来说,还是相当有限的。但其架构展现的扩展性是足够的,对于开发者而言入门门槛也足够低,如果有更多开发者进入,其未来发展是值得期待的。 ### 衡量 IoT 应用开发平台 从前面一系列介绍,我们已经了解了许多 IoT 应用开发平台,尤其是基于 JavaScript 的 IoT 应用开发平台,作为开发者,我们该如何选择呢?我们不妨梳理出一个 IoT 应用平台的衡量标准,然后,根据实际场景自行选择。 #### 采用 JavaScript 语言 传统嵌入式开发采用 C/C++ 作为主流的程序设计语言,对于现代软件开发而言,这种做法存在一些问题: 1. 缺少自动化内存管理能力,普通程序员经常会犯一些低级错误,造成程序崩溃; 2. 缺少标准库,开发者浪费了大量的时间,在构建基础设施; 3. 缺少可移植标准,每次面对不同的硬件,都需要花费大量时间,让代码在新平台上运行起来; 4. 缺少包管理能力,不同的程序员会反复构建类似的代码,造成行业的浪费; 5. 对于测试缺少内建的支持,测试的编译运行会随着代码规模而不断增长,没有小步开发的基础。 JavaScript 作为行业里唯一一门全栈式开发语言,拥有着广泛的开发人员基础,随着 Node.js 的兴起,配套的基础设施也得到长足的进步,完全可以称之为一门合格的现代开发语言: 1. 支持 GC,开发者无需顾忌内存; 2. 支持面向对象和函数式编程等多种现代编程范式,开发者可以根据需要自行选择; 3. 程序可移植,底层差异由运行时屏蔽; 4. NPM 软件仓库,几十万个软件模块,开发者按需取用,无需重复造轮子; 5. 有多种测试框架,开发者可以很容易在开发环节中进行测试。 作为其他高级语言的开发者,你或许会有疑问,其他语言这些特性似乎也可以支持。还有如下几点让 JavaScript 脱颖而出: 1. 运行时,如 Python、Java 之类的语言,很少有能对 MCU 进行支持的运行时,这使得它们顶多能做到原型级别开发,而无法深入。 2. 流行程度,类似于 Lua 这种本身运行时很小的语言在嵌入式环境中也有应用,也确实有一些项目做到了,比如 NodeMCU,但 Lua 目前属小众语言,其前景取决于行业发展状况。 #### 设计硬件抽象 传统的嵌入式开发平台存在极高的门槛,一个非常重要的原因在于,其系统及应用是一体的,任何一个开发者都需要学习很多的知识,才能成为一个合格的嵌入式开发者。 引入硬件抽象,将实现系统与应用的分离,应用开发者会像使用普通的程序库一样操作硬件,而无需关注底层实现细节,这样可以更好地把注意力放在应用上。 硬件抽象也分为不同的级别: 1. 编程接口,让开发者使用软件抽象,屏蔽底层硬件接口,这是很多提供硬件抽象的平台几乎都能做到的。 2. 硬件配置,将硬件配置进行隔离,让开发者不必关注配置细节,面对做不到这点的平台,开发者还是要了解很多细节。 总的来说,硬件抽象也大幅度降低了门槛,软件开发者可以从应用的角度理解 IoT 应用,无需学习底层细节,从而让更多开发者得以进入到 IoT 研发领域中。从人数上说,软件开发者的数量远大于专攻硬件研发的开发者。降低门槛,将软件开发者引入到一个全新领域所带来的变化,我们已经在移动开发领域看过了一遍。有了好的 IoT 应用开发平台,相信同样的戏码会在 IoT 领域重演。 #### 面向生产 如果只是构建一个原型,其难度与构建一个真实的应用不可同日而语。软件开发者理解这一点并不困难。在 IoT 领域,问题是一样的。真实的 IoT 应用,会面对功耗、价格、性能等诸多问题,这也是在今天,计算资源极大丰富的情况下,在 IoT 领域依然对资源斤斤计较的原因,资源受限的硬件才会大行其道。无论如何,能够运行在资源受限的硬件上,是成为一个真正的 IoT 应用开发平台的前提。 在传统的嵌入式开发中,应用与硬件是紧密耦合在一起的,如果能够实现应用与硬件配置相分离,将带来极大的改变: 1. 应用开发者无需关注硬件如何配置,可以将更多的将注意力放在应用逻辑本身; 2. 硬件具体的配置方式可以在具体的部署时实施。 这样做还会带来一些额外的好处: - 应用与硬件配置分离,让应用的移植成为可能。应用在开发时可以不知道具体实施的硬件,只要在具体交付时,将应用部署在硬件上即可; - 二者的分离实现了研发与生产相分离,双方可以各自独立发展,由此可以实现更好地分工; - 分离让研发和生产可以采用不同的硬件,这会对研发流程带来一些改变,在研发期采用既有硬件进行测试,在完成需求验证之后,再根据情况生产实际的硬件; - 硬件、系统与应用将成为三个独立的概念,可以用不同的节奏发布,迭代开发成为可能。 ### 总结 现如今, IoT 行业主流的开发方式依旧是采用传统的 C/C++ 进行嵌入式开发。但从行业发展的状况不难看出,这种方式已经阻碍了更多人才的进入,更进一步阻碍 IoT 的普及,工具的升级已经迫在眉睫。新近的 JavaScript IoT 应用开发平台已经逐渐展现出其未来的发展前景,但是,所有的 JavaScript IoT 应用开发平台都面临着一个问题:缺乏成熟的行业解决方案。从时间上来说,它们都处于早期,被行业选择和接收需要一个过程。但 IoT 要发展,工具要升级, JavaScript IoT 应用平台是目前最有力的竞争者。 基于此,我们共同游历了当今的 IoT 应用开发平台,尤其是基于 JavaScript 的 IoT 应用开发平台。并了解了 IoT 应用平台的衡量标准,希望这些内容在大家进入 IoT 领域时,会有所帮助。 让我们共同迎接这曙光乍现的 IoT 时代吧!