Imagine, Program, Share

上篇文章里,关于Scratch3.0如何与外部硬件(设备/玩具)通信,我们梳理了社区里的常见做法

在这篇文章中,准备写写我的架构设计,文中会给出架构图,解释这样设计的原因,以及不采用目前主流做法的原因。

设计原则与初衷

编程是伪装成艺术的科学

近来在编程方面,对我影响最深的一本书是《ROS机器人编程实践》。在这本书里,我们看到通过遵循Unix哲学可以构建出何等强大、灵活而简单的系统(Unix哲学在《Unix编程艺术》中被阐述得很精彩)

当然,ROS的简单,是指它在面对所处理的问题时,已尽可能地简单,这个系统本身还是有一定复杂度的。当我们在说《哲学问题》把哲学阐述如此清晰、简单时,其中的简单也是这个意思。你不能指望一本书能把N-S方程的推导过程讲解得和天线宝宝的情节一样简单。如果有作者声称他能,要么他是骗子,要么他是疯子,要么他是伯特兰·罗素

Unix哲学里鼓励如何构建一个系统呢?

我很喜欢zguide(zguide是ZeroMQ的教材,写得几乎和ZeroMQ这个项目一样出色)中的这个阐述

The physics of software is not algorithms, data structures, languages and abstractions. These are just tools we make, use, throw away. The real physics of software is the physics of people–specifically, our limitations when it comes to complexity, and our desire to work together to solve large problems in pieces. This is the science of programming: make building blocks that people can understand and use easily, and people will work together to solve the very largest problems.

作为社会成员的我们,在遇到一个高度复杂的问题时,我们会群策群力,分工合作,将问题拆分为若干个部分,一起解决。这里就体现了编程的科学:创建一组小型的构建模块,让人们易于理解和使用,那么大家就会一起用它来解决问题。

想想你的Unix工具箱中的grep/find/awk/sed组合起来时何等地强大。当一堆的小工具能彼此沟通时,事情就变得有趣了。

和ROS一样,我们鼓励社区的参与,我们想设计一个开放的系统,允许更多的插件加入进来,允许你去连接更多有趣的设备,在这个架构中鼓励人们重用社区的经验,在别人的基础上工作,也方便人们将自己的工作分享给社区。

我们不想构建一个封闭、精致且庞大的系统。

插件系统功能描述

  • 连接scratch3.0(或者任何client)与来自物理世界的各种硬件及开放系统
  • 可以配合scratch3.0官方网站使用。自行架设的scratch3.0网站如果与官方插件系统兼容,那么你也能直接使用这套插件系统。就是说等scratch3.0发布之后,任何采用官方扩展机制的网站,都能使用这套插件系统,而无须hack核心源码
  • 允许你为系统写扩展插件,将任何你能操控的系统接入Scratch3.0中
  • 当然如果你有兴趣,甚至可以把这套系统接入Scratch3.0之外的地方,比如你想把它接入Blockly或者node-red中,插件系统虽然最初是为Scratch3.0而做,但Scratch3.0并不特殊,它仅仅是一个消息订阅者和发布者而已

思路

项目的架构思路主要来自ROS(Robot Operating System)。ROS是个复杂系统,当今世上有趣和复杂机器人大多都运行着ROS,从国际空间站的R2机器人到一些路上自动驾驶的汽车。我觉得ROS系统中处理的很多问题,在使用Scratch驱动外部设备/机器人时也会遇到,尤其当你不甘于只是简单地灌入代码、想做出交互性更好的机器人时(比如Cozmo)。这些问题包括:

  • 传感器的数据发布机制;
  • 服务(service)与动作(action)的区分,以及各自处理的问题;
  • 如何重用消息结构,让一套积木块能将相同的语义传递给不同的硬件,只需调整最末端的驱动代码便可控制天上飞的、地上走的、水里游的。

Cozmo显然就是利用这些概念构建了令人惊叹的操作体验

我最近在构建一个叫ROLS(Robot Operating Lite System)的项目,从名字可以看出,这个项目是想做个轻量级的ROS,目前只开了个坑:ROLS。在github上创建项目的动机常常是,想到了一个好名字,不知道你们有没有这种情况

ROS的作者也在重写这个项目,如他自己在ROS2.0的设计文档中说的其实ROS的核心是一个匿名的发布-订阅中间件系统

本文为Scratch3.0设计的插件系统本质上便是一个匿名的发布-订阅中间件系统,组件之间通过消息彼此沟通。系统中所有的节点都对等,即便是Scratch3.0也不特殊。

至于为这个系统提供更多好用的小工具(诸如roscore、rosrun、rostopic等),我会在ROLS中陆续折腾出来它们的轻量级替代品

架构图

scratch3.0插件系统

我们自上往下看。

首先看Scratch3.0部分。在Scratch3.0中,我们需要写一个js插件,这个插件就像特洛伊木马,作为我们的内应,之后它将负责与外部沟通。如果你对这部分不熟悉,应该先去看看官方文档:Scratch 3.0 Extensions Specification

这部分是Scratch3.0的原生扩展机制。目前官方已经完成了好几个扩展,有兴趣的话,可以自行阅读源码。其中wedo2的扩展很值得一读。

你在Scratch3.0中写js扩展时,除了要定义出积木块的样式之外,还需要定义出opcode(语义)和它的具体实现,这部分将在vm中执行,我们参考了wedo2的机制,采用 socketio来传递消息(当然你也可以直接使用websocket)。

插件系统中有一个websocket server(这个架构很像jupyter),与vm进行双向通信。

websocket server采用zeromq来sub/pub消息

而每个硬件被程序代理,代理程序也采用zeromq来sub/pub消息

系统看去有些啰嗦,比如代理程序似乎是不必要的。如果你为scratch3.0写过插件,你可能会想,我们为何不把websocket server视为vm的延伸,如果vm的概念延伸到物理机器上,不就具备了和硬件交互的能力!(已经从浏览器蔓延到了系统进程中)那么为何还需要多一层硬件代理。原因是为了构建一个匿名的发布-订阅系统,以便解耦和重用。

具体实现

上图是一张语言无关的架构图,你可以用任何你喜欢的语言去实现它,下边说说我们目前自己的实现(这部分由@izuo和我一起实现):

在少儿编程领域,microbit,scratch,Cozmo是我最喜欢的三个项目,所以我们第一步先做了microbit的scratch插件 ,下一步计划是把cozmo也带入scratch中

相比于js,我更偏好python,所以在具体实现上,我没有选择electron,而是使用python来构建,之后使用pyinstaller打包分发到各个平台 (目前我们完成了mac系统和win10系统的打包,更多的环境还在陆续添加,因为scratch3.0正式发布还远,所以平台兼容这块,倒也不急,但会考虑服务于国内教育环境(比如对windows 32位系统的兼容))

why and why not

原本想回答一些why的问题,这些问题我和@izuo讨论了许多,但一时半会可能也不好说清,天色已晚,今天写几个对why not的思考,就该睡了。

why not Web Bluetooth

Web Bluetooth个很棒的方案,我们目前正在这块做一些实验,Web Bluetooth让vm可以直接与硬件交互!

这个方案另一个好处是对移动端友好,安卓中你甚至只需要浏览器,就能控制硬件,ios中,浏览器目前不支持 Web Bluetooth,你需要包装成app。apple正在成为过去的微软

不过并非所有人都喜欢蓝牙,也不是所有场景都适合蓝牙,如果我们想把cozmo和microbit接入scratch,我们就做不到(microbit通过一些方式可能能做到,不过会有一些其他问题)

要把bb8接入,倒是做得到的,bb8支持ble(Web Bluetooth需要ble)

此外值得一提的是,来自国内的mCookie用到了Web Bluetooth的策略.此外有趣的是他们用了NW.js而不是electron

why not chrome serial

这也是个有趣方案,我们也在关注和实验

这个方案对chrome的版本要求比Web Bluetooth低,对老机器支持比较好,不过需要安装浏览器插件

为何不在websocket server中直接操控硬件

除了前头解释过的原因之外,还有一个异步执行的问题,这个问题我在jupyter的架构中讨论过,有兴趣的话可以翻翻那篇文章。jupyter和我们遇到相似的问题,这部分的架构也基本一样

采用基于消息的架构,对社区有什么好处

最坏情况下(比如大家没有好的协同习惯),社区依然能够共享大多的经验,因为架构强行使用消息来通信,你可以看到任何组件是如何收发消息的(通过源码或是类似rostopic的工具)。而在最好的情况下,如果大家都共用消息结构,引入一个新的硬件,你通常只需要需要修改最底层驱动的几行代码

这种架构的灵活性,ROS已经向我们充分显示了。当然设计出色且通用的消息体又是另一个话题了

为何要做成一个开放的系统,而不是发布一个针对某个具体产品的软件

我不喜欢封闭的系统

我们处在一个封闭、占地为王、屁股决定脑袋的世界。open source、重用、fork…这些概念在软件行业之外的领域,受到猛烈抨击。如果你在开源社区之外去做这些事,不只是政治不正确,你还可能吃官司。关于这些话题,到处都是屁股决定脑袋的言论

我们喜欢scratch社区的口号:

Imagine, Program, Share