Welcome to BPI-STEAM documentation!

提示

欢迎来到 BPI-STEAM 的开发指导,本项目托管于 Github BPI-STEAM 开源组织。

注意

此处文档并不会照顾到一般用户的理解,请先具备隔壁 用户文档 的基础。

以下是我们所用的 BPI-BIT 教育开发板。

_images/facade.gif

BPI-BIT 是一款基于 ESP32 高性能芯片且兼容 micro:bit 设计的开源 STEAM 教育产品。

产品介绍

https://img.shields.io/badge/open%20source-bananpi-brightgreen.svg https://img.shields.io/badge/support-webduino-blue.svg

https://webduino.com.cn/site/img/tutorials/zh_cn/detail-03.gif

本产品采用 ESP-WROOM-32 (ESP32)模组作为核心进行设计,拥有 40nm 工艺、使用 Tensilica LX6 双核32位处理器,频率高达 240 MHz,带有 32 个 I/O 引脚,支持 2.4G Wi-Fi 、蓝牙 4.0 以上等多种通信方式,具备 448KB ROM 和 520 KB SRAM 的内存容量,处理速度达 600 DMIPS,搭配 40nm 工艺的超低功耗,是目前市面上最高效能、最稳定以及最通用的产品之一。

它又名 Webduino Bit ,是 Webduino 最新的开发板,除了原本的功能一应俱全 ( Wi-Fi 控制、多装置并联、协同工作… 等 ),更是内置了许多有趣的组件与传感器。

同时 bpi:bit 开源社区还将持续兼容 micro:bit 的大部分配件以及用法。

外观介绍

_images/Interface_CN.jpg

Webduino Bit 开发板长 5 公分宽 5 公分,重量约 10 ~ 12 公克,除了下方 20 Pin 的「金手指接口」,更内置一个 25 颗全彩 LED 灯的矩阵,两个光敏电阻、两个按钮开关、一个温度感应电阻、一个蜂鸣器和一个九轴传感器 ( 三轴加速度、三轴陀螺仪与三轴磁力指南针 ),脚位配置如下:

  • 全彩 LED 矩阵:A10 ( GPIO 4 )
  • 光敏传感器:左上 A0 ( GPIO 36 )、右上 A3 ( GPIO 39 )
  • 按钮开关:按钮 A P5 ( GPIO 35 )、按钮 B P11 ( GPIO 27 )
  • 温度传感器:A6 ( GPIO 34 )
  • 蜂鸣器:P0 ( GPIO 25 )
  • 九轴传感器 MPU-9250:P20 ( GPIO 21 )、P19 ( GPIO 22 )

拓展引脚

_images/goldfinger.jpg

_images/pin-define.jpg

LED 编号

板子按照 5 * 5 排列方式焊接了 25 颗(编号 0 ~ 24 ) 1600 万色全彩 LED (WS2812) ,所有的 LED 的控制仅使用一个引脚 (GPIO 4) 即可控制。

_images/product.jpg

板子正面 LED 序号排布方式如下(5 * 5)

_images/table.png

(将板子正面朝向自己,并结合底盘金手指可知其位置)

版本区别

板子分 1.2 和 1.4 版本等多个版本,版本号标识在板子背面右下角。

_images/version.jpg

安装驱动

连接板子

本产品采用 CH340 / CH341 串口驱动芯片,可以轻松的在 Windows 、 Linux 等系统下自动安装驱动。

将板子通过 MicroUSB 线连接到你的电脑里,以下以 Windows 10 为例。

_images/connect.png

查看驱动

进入 设备管理器 确认串口驱动(Serial)是否安装,进入方法如下。

  • (右键)此电脑 -> 属性 -> 设备管理器
  • 开始菜单 -> (输入)设备管理器
  • 控制面板 -> (搜索)设备管理器

_images/error.png

可以看到 设备显示 USB2.0-Serial ,说明未安装驱动,若此前已安装驱动,可以跳至步骤 5 。

安装驱动

点此获取 Serial CH341 驱动,并按如下说明操作安装驱动

打开下载的 CH341SER.ZIP 压缩包,进入 CH341SER 文件夹,打开 SETUP.EXE,即可看到如下图。

_images/install.png

点击 INSTALL (安装),等待片刻即可完成安装。

确认串口

核对板子是否连接成功

_images/success.png

可以看到原来的 USB2.0-Serial 消失了,取而代之的是 USB-SERIAL CH340(COM3),这意味着你已经成功安装驱动,并且得到板子串口名称为(COM3),你可以通过各种串口工具来查看串口名(COM3)的板子传出的信息。

其他系统

  • 至此板子连接成功,是 Linux 或 Mac 系统则需要你自行 baidu 或 Google 了。

开发 MicroPython 模块

在这里将会教会你如何优雅地设计与开发一个 MicroPython 的 import 模块。

开发 import 模块指导

介绍模块运行机制

开发 Webduino 积木

在这里将会教会你如何优雅地设计与开发一个 Webduino 的 Blockly 积木。

开发 Blockly 积木指导

下面引用一则小故事,阐述一下积木编程的起因。

这是一张关于知识与经验的示意图。

_images/know_exper.png

小孩子都好学,喜欢模仿,从出生就不断接受知识,然后根据自己的想法去拼凑,连成一个整体。

当接触到散乱的积木时,就好像下图这样。

_images/start.png

将编程的代码以积木的方式显现出来,能让我们将头脑中的信息组块,按照有意义的方式形成一个逻辑性、概念性的视野,帮助小孩更好地应用知识进行创作,让其呈现成这样。

_images/result.png

当这个新知识经过理解、练习后,它就会形成一个新组块,存入我们的长期记忆,参与到下一次的组块化中。

准确来说,我们需要的是一个编程概念,一个关于事物与逻辑的组织概念,知道怎么去将各种事物拼接组装成我们想要的事物,积木编程应该引导人们去学会这个概念。

Blockly 是什么?

所谓的 Blockly 积木编程实际上都是指 Google Blockly 的工具,开发者都应该先到这个网站去好好看过一遍,了解一下再进行开发。

可以在此体验 Try Blockly 的积木运行。

_images/blockly_to_code.png

在这里就用一些 Webduino Blockly 的开发示例带领入门 Blockly 开发,这并不是教你如何开发 Webduino Blockly 网站,而是 Blockly 积木。

认识 Webduino Blockly

Blockly 最佳实践语言是 JavaScript 环境,如今配合 HTML5 的飞速发展,不难想象它可以轻松的让人开发出成果,快速产生成就感。

Webduino Blockly 使用的是标准的 Blockly 环境,它所用的 Blockly 生成的是 JavaScript 代码,所以可以直接在浏览器容器上运行看到效果。

在这里 Webduino Blockly 直接导入开发的积木,不需要去部署服务器,也不需要去看如何修改服务器源码,它本身就已经提供了插件开发接口。

提示

开发 Webduino Blockly 积木不需要像 Scratch3 通过修改服务器源码来添加自定义积木代码。

体验 Webduino Blockly

提示

请先具备 HTML5 JavaScript 的编程基础。

不妨先体验一下 Webduino Blockly ,这不同于平时用的教育版,可以作为开发环境所用。

可以在线试玩 在 BPI-BIT 上显示实时图案 的基础示例,当然你也可以看到更多的示例,如下图。

_images/blockly_demo.png

当进入上述示例网站后,点击右上角即可运行程序,此时你注意一下左侧的工具栏。

_images/blockly_list.png

点此 JavaScript 的按钮,你就会看到关于 Blockly 代码的生成。

_images/blockly_code.png

事实上,这就是 Blockly 的本质,积木只是代码的另一种表现形式。

同样的,作为开发者给到用户的也就是这样的一个体验效果,所见即所得,那么将如何做到这个效果呢?

了解 Google Blockly 设计器

这是来自 Google 官方的 Blockly 积木在线设计器,用于设计积木接口外观,包括生成语言的接口。

_images/blockly_developer.png

设计器使用方法参见以下两篇介绍文档,两篇参考资料可选。

可选参考资料。

Webduino Blockly 开发流程

所以我们要如何开发的流程应该尽可能满足如下形式:

  1. 我想做什么功能?
  2. 这功能的积木应该长怎样?
  3. 如何将设计的积木运行起来?
  4. 如何实现积木的功能?
  5. 怎样测试我设计积木?
  6. 别人要如何复现?

基于类似如上的流程,开始制作属于你的积木吧!

了解拓展积木模板

从先前的文档中得知什么是 Google Blockly 设计器,那我们就先从模仿一个通用的模板开始吧。

请先了解一下这个 webduino-blockly-template 示例项目,并下载获得。

拓展积木的基本构成

先认识一下模板目录文件,如下一系列表格,稍后会详细介绍。

webduino-blockly-template
文件路径 介绍与用途
blockly 积木资源文件夹,内容下述
demes 积木示例、设计文件夹,内容下述
blockly.json 积木类型、依赖、实现的定义
itpk-blockly.js 积木的代码生成函数实现
itpk.html 测试原始积木功能 API 页面
itpk.js 经典 JavaScript 接口实现
README.md 积木的使用说明文档
_config.yml 由 Github Page 创建,并提供外链。
webduino-blockly-template\blockly
文件路径 介绍与用途
msg 积木多语言资源文件夹,内容下述
blocks.js 由 积木设计器 自动生成的样式实现
javascript.js 由 积木设计器 自动生成的积木代码生成函数实现
toolbox.xml 由你提供左侧工具列积木样式设计
webduino-blockly-template\blockly\msg
文件路径 介绍与用途
blocks 积木内部的相关的语言文件,内容下述
en.js 定义工具列的 英文 文字变量
zh-hans.js 定义工具列的 简体中文 文字变量
zh-hant.js 定义工具列的 繁体中文 文字变量
webduino-blockly-template\blockly\msg\blocks
文件路径 介绍与用途
en.js 定义积木内部的 英文 文字变量
zh-hans.js 定义积木内部的 简体中文 文字变量
zh-hant.js 定义积木内部的 繁体中文 文字变量
webduino-blockly-template\demos
文件路径 介绍与用途
blockly.xml 积木的使用范例,可导入 webduino blockly
library.xml 积木的设计库,可导回设计器中重新设计

这些基本的定义是便于修改模板积木过程中查阅其确切定义。

拓展积木的使用方法

如果是托管在 Github 上的积木,可以根据 拓展积木的使用说明 可知插件地址外链类似这样:https://bpi-steam.com/webduino-blockly-template/blockly.json 的地址,又或者是这样 https://junhuanchen.github.io/webduino-module-itpk-robot/blockly.json ,请确保在访问的时候,使用 https 访问且内容类似下图的 JSON 效果。

_images/blockly_json.png
拓展积木的模板创建

请尝试使用该项目进行操作,如图点击绿色按钮(Use this template),这将 fork 到你的项目中,此时。

_images/use_template.png

将它配置到你的积木名称和描述,可以随意。

_images/create_template.png

类似这样的效果,注意上述所述的使用方法。

_images/template_example.png

上图的积木插件地址应为:https://junhuanchen.github.io/webduino-blockly-telecar/blockly.json ,而模板插件地址则是 https://bpi-steam.com/webduino-blockly-template/blockly.json ,记得在 readme 里修改对应提供的地址,将插件托管到 Github 的方法请继续往下看。

拓展积木的托管配置

一般来说,我们会将积木托管在 Github 上,这样就不需要自己额外提供一个网络空间了,所以当我们 fork 项目的时候,会发现创建的模板项目,无法直接使用类似上述的插件外链使用,所以这时候需要你设置 Github Pages 的 Source,如下描述,这是因为它需要转换为该链接,以个人的 Github 为例,如下图是没有设置的情况。

_images/pages_source.png

选择了 Source 的 master branch 分支。

_images/pages_select.png

此时将会产生 Github 的外链,这就是在 Github 上提供积木的地址,积木地址务必提供 https 访问,防止被跳转回 http 。

_images/pages_result.png

使用这个刚生成的地址 https://junhuanchen.github.io/webduino-blockly-template/blockly.json ,在积木载入中确认一遍。

_images/pages_commit.png

注意

注意 fork 的 readme 的内容并没有改变,所以发布的时候,别忘了修改积木地址。

现在,你通过 fork 就已经学会了如何创建一个动态积木,那接下来就从修改一个示例模板开始写积木吧。

修改拓展积木模板

提示

欲读懂内部工作机制,请学习 HTML5ECMAScript 6

我们在前一节中已经掌握了如何创建和使用积木的方法,这节我们将基于标准模板的积木进行开发,请继续往下看。

查看范例积木

进入积木设计器,可以使用国内源 Blockly Developer Tools 如下图。

_images/blockly_tools.png

使用该模板项目 webduino-blockly-template ,并将它的代码通过 download 或 clone 得到它。

_images/demos_library.png

将文件里的 demo/library.xml 导回积木设计器,参考已有积木,重新设计出属于你的积木。

_images/select_library.png

可以看到有如下积木类型。

_images/blockly_list1.png

让我们点开一个 itpk_answer 看看都是如何定义的。

_images/itpk_answer.png

可以看到,左侧是积木编辑工具,右侧则是生成的结果:

  • 积木外观样式预览(Preview)
  • 积木外观定义代码(Block Definition)
  • 代码生成函数桩(Generator stub)

_images/blockly_result.png

现在知道有这些东西就行,回头就会用上,至少知道这些代码都是使用该工具生成的。

设计新的积木

如果我们想做一个新的功能积木,可以建立在已有的基础上,比如参考一个已经存在的积木,将其修改成我们想要的样子,再封装成其他功能,拿查询本机 IP 的积木来举例,如果要在 itpk_ask 的基础上修改的话,那么积木应该长怎样呢?

_images/itpk_ask.png

我们可以参考 itpk_ask 的积木设计,修改它直接变成我们想要的祥子,拖动一个无参数的积木连接块,如下图。

_images/get_input_dummy.png

拖拽出来,放到和其他 Input 一样的地图,并且复制一下字符串对应过来,如下图。

_images/input_contrast.png

经过对比后可以发现 dummy input 不能再结合,所以看到的末尾是无法衔接其他模块的,此时我们将它设计成查 IP 的积木,如下图修改。(移除 value input 并编辑 text itpk_ask 积木块)

_images/itpk_get_ip.png

我们最后来改个名字并保存一下新的积木块,免得弄丢了,如下图。

_images/save_itpk_get_ip.png

现在积木就设计完成了,但我们需要看,它对应的样式代码是怎样的才能放入我们的 Webduino Blockly 当中,所以看 Block Definition 的代码。

Blockly.Blocks['itpk_ask_ip'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("问 茉莉 查本机网络 IP ");
    this.appendStatementInput("callback")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(160);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

以上就是你所见到的积木它的定义代码,可以理解为是这段代码描述了积木的样子,也就是说,你也可以不通过 Blockly 设计器来修改积木外观,以及它对应的需要用户实现的桩代码:

Blockly.JavaScript['itpk_ask_ip'] = function(block) {
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  var code = '...;\n';
  return code;
};

这个函数可以为积木提供背后生成的代码环境,例如将 var code = 'var test = 123;\n'; 这样就表示,这个积木块拖拽出来将提供 var test = 123;\n 的代码,也就是所谓的积木生成代码,积木块对应着代码,接下来我们就要将其导入我们的原本的积木当中。

添加新的积木

先前我们制作了一个积木块,我们需要将它添加到我们自己的积木里,先看一下我们想要添加的积木的效果。

_images/blockly_itpk_ask_ip.png

进入 Block Exporter 可以批量选取积木并导出对应代码。

_images/blockly_exporter.png

获取这两段定义内容,然后将它添加到创建的模板积木中,注意结合前一章的目录下的文件描述。

进入 blockly/blocks.js ,在后面添加 Block Definitions 的代码。

Blockly.Blocks['itpk_ask_ip'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("问 茉莉 查本机网络 IP ");
    this.appendStatementInput("callback")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(160);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

进入 blockly/javascript.js 也同样添加 Generator Stubs 的代码。

Blockly.JavaScript['itpk_ask_ip'] = function(block) {
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  var code = '...;\n';
  return code;
};

如果不清楚如何操作的可以看这个提交 添加积木的定义

_images/add_blockly.png

当你添加完了积木的定义,不代表积木可以使用,还需要在 blockly/toolbox.xml 中添加你积木的工具栏位置,否则将无法显示到左侧来,例如下图的效果。

_images/blockly_left_tool.png

那么我们应该如何做呢?先是进入 Workspace Factory 可以模拟设计的积木大致的使用方法,与其他积木进行对接的尝试,还可以导出(Export)积木工具列 toolbox.xml 文件供你参考。

_images/blockly_factory.png

然后拿我们设计的积木出来。

_images/view_toolbox.png

此时通过下图的方式得到 toolbox.xml 文件,然后添加到我们原本的 blockly/toolbox.xml 文件当中。

_images/export_toolbox.png

下载打开它得到 xml 文件代码。

_images/open_toolbox.png

<xml xmlns="http://www.w3.org/1999/xhtml" id="toolbox" style="display: none;">
  <block type="itpk_ask_ip">
    <statement name="callback">
      <block type="text_print">
        <value name="TEXT">
          <block type="itpk_answer"></block>
        </value>
      </block>
    </statement>
  </block>
</xml>

实际上就可以看出积木的对应效果,例如 <block> 标签对应的就是积木块, <statement> 标签对应的就是结合的积木块,所以我们可以在工具栏看到如下效果。

_images/toolbox_result.png

现在将其添加到我们的原本的模板积木当中,与其他存在的积木块作出对比。

以下是原内容:

<category id="catItpk">
  <block type="itpk_ask">
    <value name="question">
      <block type="text">
        <field name="TEXT">今天东莞天气如何?</field>
      </block>
    </value>
  </block>
  <block type="console">
    <value name="console">
      <block type="itpk_answer"></block>
    </value>
  </block>
  <block type="itpk_clear">
  </block>
  <block type="itpk_ask">
  <value name="question">
    <block type="text">
      <field name="TEXT">查 IP </field>
    </block>
  </value>
	<statement name="callback">
	<block type="console">
	<value name="console">
		<block type="itpk_answer"></block>
	</value>
	</block>
	</statement>
  </block>
</category>

在后面添加新内容,变成如下代码。

<category id="catItpk">
  <block type="itpk_ask">
    <value name="question">
      <block type="text">
        <field name="TEXT">今天东莞天气如何?</field>
      </block>
    </value>
  </block>
  <block type="console">
    <value name="console">
      <block type="itpk_answer"></block>
    </value>
  </block>
  <block type="itpk_clear">
  </block>
  <block type="itpk_ask">
  <value name="question">
    <block type="text">
      <field name="TEXT">查 IP </field>
    </block>
  </value>
	<statement name="callback">
	<block type="console">
	<value name="console">
		<block type="itpk_answer"></block>
	</value>
	</block>
	</statement>
  </block>
  <block type="itpk_ask_ip">
    <statement name="callback">
      <block type="console">
        <value name="console">
          <block type="itpk_answer"></block>
        </value>
      </block>
    </statement>
  </block>
</category>

现在,你已经将自己设计的积木添加进去了,此时你还需要最后一步,就可以载入积木看到效果。

在 blockly.json 中添加你的积木类型(types),因为它是一个全新的积木所以需要添加进去,否则不会被载入到动态积木中。

查看 blockly.json 的内容,然后在 types 列中添加 "itpk_ask_ip",

{
  "types": [
    "itpk_ask_ip",
    "itpk_ask",
    "itpk_answer",
    "itpk_clear"
  ],
  "category": "itpk",
  "scripts": [
    "https://cdn.jsdelivr.net/gh/yarrem/stringFormat.js/format.js",
    "itpk-blockly.js",
    "blockly/blocks.js",
    "blockly/javascript.js"
  ],
  "dependencies": [
    "itpk.js"
  ],
  "msg": "blockly/msg",
  "blocksMsg": "blockly/msg/blocks",
  "toolbox": "blockly/toolbox.xml"
}
以下的操作均在浏览器无痕模式下进行。(等你足够了解后,可以先移除积木后刷新再重新载入积木)。

现在你可以提交它,然后等一会提交成功后,可以载入 https://junhuanchen.github.io/webduino-blockly-template/blockly.json 积木查看效果了。

_images/new_toolbox.png

代码生成测试

我们已经能够将自己设计的积木导入积木工具了,但还没有配合积木生成的代码,比如像下面这样,左边是积木,右边是代码。

_images/blockly_gen.png

我们需要知道积木生成代码的在 blockly/javascript.js 中有这样的 Generator Stubs 的代码。

Blockly.JavaScript['itpk_ask_ip'] = function(block) {
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  var code = '...;\n';
  return code;
};

在 Webduino Blockly 中点击 JavaScript 就会这样显示出来,如下图所示。

_images/blockly_code1.png

可以看到,它实际上就对应 var code = '...;\n'; 的返回值,所以我们只需要修改这部分内容。

比如说,将其修改成 var code = 'console.log("hello");\n'; 这就意味着,当你拖出这个代码将会得到 console.log("hello");\n 代码,让我们来试试修改 blockly/javascript.js 。

Blockly.JavaScript['itpk_ask_ip'] = function(block) {
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  var code = 'console.log("hello");\n';
  return code;
};

此时效果图如下,提交后重新载入积木即可看到效果。

_images/blockly_add_code.png

此时你会注意到,明明我们的积木中有 控制台显示 茉莉 的回答 ,但是代码中并没有,那该如何显示出来?看下面代码的 console.log(statements_callback); 的内容,然后继续添加。

Blockly.JavaScript['itpk_ask_ip'] = function(block) {
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  
  console.log(statements_callback);

  var code = 'console.log("hello");\n';
  return code;
};

这时候就意味着 F12 的开发者调试工具的控制台,可以看到如下效果,也就是你所包含的代码内容,掌握了这个方法后,就可以调试积木代码生成的情况了。

_images/blockly_console.png

当你查看代码的时候,它将会生成 控制台显示 茉莉 的回答 的效果,但这个做法只是了解一下现象,并不推荐在这里修改,因为设计器会重新生成代码,所以要减少该部分代码,全部采用外部函数调用的方法,那么我们如果想要轻松的添加自己的积木代码,请参考 itpk-blockly.js 是怎么写的。

举个例子,看 itpk_answer 的积木定义,它调用了 itpk_answer(); 函数。

Blockly.JavaScript['itpk_answer'] = function(block) {
  // TODO: Assemble JavaScript into code variable.
  var code = itpk_answer();
  // TODO: Change ORDER_NONE to the correct strength.
  return [code, Blockly.JavaScript.ORDER_NONE];
};

而这个函数就被实现在 itpk-blockly.js 中,也就是如下代码。

scope.itpk_answer = function () {
  return "webduino.module.RobotItpk.answer()";
}

所以在得到设计器的代码后,只需要将 var code = '...;\n'; 修改成 var code = itpk_answer(); ,极大的减少了设计器的代码变动。

我们继续看另一个例子。

Blockly.JavaScript['itpk_ask'] = function(block) {
  var value_question = Blockly.JavaScript.valueToCode(block, 'question', Blockly.JavaScript.ORDER_ATOMIC);
  var statements_callback = Blockly.JavaScript.statementToCode(block, 'callback');
  // TODO: Assemble JavaScript into code variable.
  var code = '{0};\n'.format(itpk_quick_ask(value_question, statements_callback));
  return code;
};

它就是 itpk_ask 积木的例子,只需要注意到这行代码。

var code = '{0};\n'.format(itpk_quick_ask(value_question, statements_callback));

其他都是设计器自动生成的,解释一下 itpk_quick_ask 的实现过程。

scope.itpk_quick_ask = function (question, callback) {
  return "webduino.module.RobotItpk.quick_ask({0}, function(){\n  {1}})".format(question, callback);
}

在 blockly/itpk-blockly.js 我们看到类似 "{0}-{1}".format(1, 2) 的操作,这个操作是将代码进行格式化,可以帮助生成代码的格式化输入,提供字符串的封装方法,而不是 “1” + “-“ + “2” 的方式,例如下述代码。

scope.itpk_quick_ask = function (question, callback) {
  return "webduino.module.RobotItpk.quick_ask(" + question + ", function(){\n  " + callback + "})";
}

不建议写出以上字符串拼装代码,因为积木设计大量充斥这种字符串相互包含的关系,Code 会显得非常 Ugly ,所以使用这样的方式去开发你的积木对应的代码吧。

添加对应功能

现在积木已经生成,但是我们要如何为如下代码添加对应的功能呢?

scope.itpk_answer = function () {
  return "webduino.module.RobotItpk.answer()";
}

我们继续看它的生成代码,这个实际上就是生成(返回)一段调用 blockly/itpk-blockly.js 函数的 webduino.module.RobotItpk.answer() 代码,所以我们需要在 blockly/itpk-blockly.js 中提供这个函数的实现,也就是如下代码。

RobotItpk.answer = function () {
    return answer.replace("[cqname]", "moli");
}

我们可以在单元测试(unit_test)中得知它的用法。

function unit_test() {
  webduino.module.RobotItpk.ask('东莞天气如何?');
  setTimeout(function(){
    console.log(webduino.module.RobotItpk.answer());
    webduino.module.RobotItpk.ask('高雄天气如何?');
    setTimeout("console.log(webduino.module.RobotItpk.answer())", 1000);
  }, 1000);
}

但是为什么可以链接起来?这需要看 blockly.json 的定义。

{
  "types": [
    "itpk_ask_ip",
    "itpk_ask",
    "itpk_answer",
    "itpk_clear"
  ],
  "category": "itpk",
  "scripts": [
    "https://cdn.jsdelivr.net/gh/yarrem/stringFormat.js/format.js",
    "itpk-blockly.js",
    "blockly/blocks.js",
    "blockly/javascript.js"
  ],
  "dependencies": [
    "itpk.js"
  ],
  "msg": "blockly/msg",
  "blocksMsg": "blockly/msg/blocks",
  "toolbox": "blockly/toolbox.xml"
}

关于依赖的 javascript 代码的使用情况,我们需要将 Blockly 分成两个场景,第一是积木生成代码阶段的,第二是生成代码的时候运行依赖,也就是 scriptsdependencies 两列,如你所见的是 scripts 包含的是 Blockly 积木相关联的实现代码,而 dependencies 则是代码运行的时候依赖的 javascript 代码,例如 itpk.js 代码可以在运行代码的时候实现。

积木功能测试

当我们知道了如何添加自己的功能代码,就要脱离这个 Blockly 生产环境来测试我们的代码了,在开发环境中,分本地和在线测试运行代码,需要注意的是,本地测试的代码具备一定的特殊性,比如没办法依赖 Webduino 中提供的其他功能,例如无法使用 webduino.module 这个模块变量。

本地测试积木功能

所以在本地写经典代码时,一般需要单独测试和移植,例如下面的茉莉机器人 API 的测试代码 itpk.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>茉莉机器人 API 测试</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>

<body>
    <script>
        AskItpk = function (question) {
            $.post("http://i.itpk.cn/api.php", {
                'question': question
            }, function (data) {
                console.log("数据:" + data);
            });
        }
        AskItpk('东莞天气如何?');
    </script>
</body>

</html>
代码无高低,能用就行。

通过这样的方式再转换改写成 itpk.js 的模块结构代码,看如下代码。

+(function (factory) {
    if (typeof exports === 'undefined') {
        factory(webduino || {});
    } else {
        module.exports = factory;
    }
}(function (scope) {
    'use strict';
  
    const url = "https://i.itpk.cn/api.php";
    var answer = "";

    function RobotItpk() {
        Module.call(this);
    }

    RobotItpk.ask = function (question) {
        $.post(url, {
            'question': question
        }, function (respond) {
            // console.log(data);
            answer = respond;
        });
    }
    
    RobotItpk.clear = function () {
        answer = "";
    }
    
    RobotItpk.answer = function () {
        return answer.replace("[cqname]", "moli");
    }

    RobotItpk.quick_ask = function (question, callback) {
        $.post(url, {
            'question': question
        }, function (respond) {
            answer = respond
            callback();
        });
    }

    scope.module.RobotItpk = RobotItpk;

}));

function unit_test() {
  webduino.module.RobotItpk.ask('东莞天气如何?');
  setTimeout(function(){
    console.log(webduino.module.RobotItpk.answer());
    webduino.module.RobotItpk.ask('高雄天气如何?');
    setTimeout("console.log(webduino.module.RobotItpk.answer())", 1000);
  }, 1000);
  
}

// unit_test();

但你也可以直接使用 AMD JS 代码的方式,规范的模块化整合到积木环境中,这就需要你具备一定的现代 JS 代码模块规范的基础,可以先了解 webpack-demo 或配合这个示例项目 webpack-develop-example 来操作,这将有利于你写出规范的专业代码,就像下面这样的代码直接编译运行在浏览器,这将高度符合 Webduino Blockly 的运行环境。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS etc
        module.exports = factory(require('jquery'));
    } else {
        // Browser global variable (root is window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {

    'use strict';

    const url = "https://i.itpk.cn/api.php";
    var answer = "";

    var proto;

    var RobotItpk = function () {

    };

    RobotItpk.prototype = proto = Object.create({
        constructor: {
            value: RobotItpk
        }
    });

    proto.ask = function (question) {
        $.post(url, {
            'question': question
        }, function (respond) {
            console.log(respond);
            answer = respond;
        });
    }

    proto.clear = function () {
        answer = "";
    }

    proto.answer = function () {
        return answer.replace("[cqname]", "moli");
    }

    proto.quick_ask = function (question, callback) {
        $.post(url, {
            'question': question
        }, function (respond) {
            console.log(respond);
            answer = respond
            callback();
        });
    }

    window.RobotItpk = RobotItpk;

}));

var ts = new window.RobotItpk();
ts.ask("nihao");

setTimeout(function () {
    document.write('<h1>' + ts.answer() + '</h1>');
}, 1000);
在线测试积木功能

我们可以在积木的时候里面进行代码的查看,如下图操作。

_images/webduino_into_js_bin.png

如你所见,可以在这里进行代码的运行以及调试。

_images/into_js_bin.png

JS bin 是一款在线编写浏览器 JS 工具,可以在这里进行你的调试于测试,比如我们可以这样做,将 itpk-blockly.js 或 itpk.js 文件代码放入下图所述位置,即可自动运行输出。

_images/js_bin_run.png

上图需要你点开 Console 控制台输出 debug 信息,并且粘贴 itpk.js 代码后拉到低下取消 unit_test() 的注释,从而运行单元测试,默认修改代码自动运行。
添加积木语言

最后积木制作出来后,为了与国际化接轨,我们还需要为积木添加多语言,但这一步需要修改 blocks.js 设计器生成代码,连接多语言变量的定义,所以请确保该积木已经相对稳定后再添加多语言功能。

我们拿内部简体中文的定义来举例子,例如 blockly/msg/zh-hans.js 文件中的内容。

MSG.catItpk = '茉莉 机器人';

这实际上就对应着 toolbox.xml 的首行 <category id="catItpk"> 也就是工具栏的多语言,如下图。

_images/multilingual_toolbox.png

接着改积木的多语言需要绑定到设计器代码中,例如 blockly/msg/blocks/zh-hans.js 中提供的变量。

Blockly.Msg.itpk_clear = "清理回答";
Blockly.Msg.itpk_answer = " 茉莉 的回答";
Blockly.Msg.itpk_ask = "问 茉莉 ";

但这些变量还需要添加到 blockly/blocks.js 替换字符串常量,如下代码。

Blockly.Blocks['itpk_clear'] = {
  init: function() {
    this.appendDummyInput()
        .appendField(Blockly.Msg.itpk_clear);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(315);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Blockly.Blocks['itpk_answer'] = {
  init: function() {
    this.appendDummyInput()
        .appendField(Blockly.Msg.itpk_answer);
    this.setOutput(true, null);
    this.setColour(315);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Blockly.Blocks['itpk_ask'] = {
  init: function() {
    this.appendValueInput("question")
        .setCheck("String")
        .appendField(Blockly.Msg.itpk_ask);
    this.appendStatementInput("callback")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(315);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Blockly.Blocks['itpk_ask_ip'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("问 茉莉 查本机网络 IP ");
    this.appendStatementInput("callback")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(160);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

可以将 itpk_ask 和 itpk_ask_ip 的定义代码做对比,实际上就是将 .appendField("字符串"); ,替换成字符串变量 .appendField(Blockly.Msg.字符串); ,修改后出来的效果如下图。(可以看到 itpk_ask_ip 并没有被汉化,还处于简体中文的字符串常量)

_images/multilingual_blockly.png

积木使用示例
分享示例前,请务必先载入积木后再导入示例。

最后要如何将你的积木插件分享给其他人,除了给积木插件地址,还需要处理一下自己的 readme 帮助文件,以及在里面提供一些使用案例,如下图可以导出你的示例代码。

_images/output_usage_demo.png

示例文件一般推荐保存积木目录下的 demos/blockly.xml ,除了这样做还可以在线分享链接,如下图指示。

_images/output_link.png

但是,但凡内部案例涉及到的外部积木插件,都必须要提前导入,否则将显示一个积木黑块表示不可用。

提示

由于上述开发文档已经建立起来,接下来我们将会推出一系列积木的开发实例文档,例如如何开发语音识别与合成、OCR 文字、无线通信、组件调试等等有用的积木,此外还会做一些应用积木,例如无线遥控车、无线遥控无人机、智能家居管理、各类云功能接入等等趣味应用积木的开发过程文档。

附文:两类积木机制与设计思路

本文会在此讲点积木编程的哲学,如果你不喜欢,可以跳过该章节,然后从实际出发,如我们现在所看到的积木有两种体系,一种是 Scratch 体系,另一种则是 Blockly 体系,下面单独介绍这两种积木设计。

Scratch 积木体系

Scratch 是麻省理工学院的“终身幼儿园团队”(Lifelong Kindergarten Group)开发的图形化编程工具,主要面对青少年开放。

现在大多都在谈 2019 年后正式发布的 Scratch3 版本了,如果你对开发它有兴趣可以看看这个的项目 添加 Scratch3 自定义积木 ,本文暂不对此深入阐述。

在线试试 Codelab Scratch3 编程吧,这由我们的开源合作伙伴提供。

_images/scratch3.png
Blockly 积木体系

2012 年 6 月,Google 发布了完全可视化的编程语言 Google Blockly,类似 MIT 的儿童编程语言 Scratch , 你可以通过类似玩乐高玩具的方式用一块块图形对象构建出应用程序。每个图形对象都是代码块,你可以将它们拼接起来,创造出简单功能,然后将一个个简单功能 组合起来,构建出一个程序。整个过程只需要鼠标的拖曳,不需要键盘敲击。

_images/blockly.png
说说 Scratch 与 Blockly 的机制区别

虽然我们在用 Blockly 来开发积木的,但我们也应当学习 Scratch 的设计理念和运行机制,这将有利于我们更好的开发适合教育的编程积木。

首先 Scratch 已经拥有了一个自己专属的运行环境(虚拟机),在这个环境上,运行自己所定义的积木,早在 2.0 时期使用的是 Adobe 的 Flash AIR 环境,现在还可以看到它的影子,但随着 3.0 的推出,已经全面改用 JavaScript(React) 了,也就是拥有了良好的浏览器跨平台性。

这点和 Webduino Blockly 是基本一致的,都是基于 JavaScript 的环境,但也存在很大的区别,比如说 Scratch3 并不鼓励直接使用代码编程,本质上还是积木调用接口编程。(但 Scratch3 也有 Blockly 接口)。

对 Scratch3 来说,所谓的积木只是某一类功能的调用接口,可以理解为某一类插槽,在定义的时候,就是为了将积木绑定到某一个函数、模块上,直接将其功能调起,所以 Scratch3 的积木设计大多都是单例设计,意味着,一个积木对应一个功能。

而 Blockly 并非如此设计,举例来说 Webduino Blockly 是一种将 Blockly 积木运行在 JavaScript 的环境上的网站,它将执行由 Blockly 的积木将生成对应的 JavaScript 代码。

这意味着,Blockly 积木块本身只是代码的映射,并非是某一个可以调用的功能,这个区别你应该可以体验出来。

不同的运行机制会导致什么现象呢?

首先 Scratch3 可以直接点击积木运行查看效果,而 Blockly 需要点击运行才能查看效果,因为需要所有积木生成代码后才能运行查看整体效果,但技术是会发展变革的,并非 Blockly 做不到,而是它的哲学就是 The web-based visual programming editor 也就是只做一件事:“可视化编辑,生成代码”。

积木的设计理念

直接运行积木的设计为开发者带来的理念就是,开发者设计的每一个积木都是期望可以直接运行的,也就跳过了定义和配置的环节,每一次都是全新的开始,这对于程序来说,积木就是单例,不可抽象,所点即所得。

从程序设计的角度上讲,这并不能成就一个大型程序,只能作为一般的小程序来体验,但 Scratch3 本就是一个面向少儿编程的工具,不需要对它有过多的要求。

而 Blockly 的设计理念并非如此,设计思路复杂且巧妙,主要强调的是 积木 就是 代码,只不过是在研究代码的组织形式,所谓的拼接积木,实际上就是在组织不同的代码块,如果我们把每一段功能代码当作是一份积木,那么产生的结果就是 Blockly 将会拆分积木中的代码,因为它可以尽可能的抽象积木块供大家自由组合。

积木的教育结果

经过 Scratch3 和 Blockly 的机制,从编程教育这个角度来看, Scratch3 的设计显得有些狭隘了,因为它并不鼓励你学会代码编程,反观 Blockly ,它本来就是代码的另一种表现形式,如果你学会了组织 Blockly 积木,实际上也就学会了如何组织代码,转移到写代码这件事,实际上就很自然而然的了。

与其说谁更好,倒不如相互学习,Scratch3 作为一款编程教育产品将是非常棒的教具,而 Blockly 则并非是一个教育玩具,双方都有各自的优势和可取之处,发展将会越来越好。

我们知道编程不是积木,但积木可以映射出一种现象,这种现象也许是你在任何语言编程(甚至是生活)中都会遇到的问题,那就是如何组织许多小部件形成一件完整的作品。

最后,编程也好,积木也罢,都是指引我们学习事物本质的工具,而非停留在表面现象,只会使用,而不知其原理。

金手指拓展板设计

稍后补充,不好意思,此处是备选目录。

_images/footer.png

IoT 平台规划

项目文件已经托管到 Github,不过尚未完成,目前计划是写一部分文档,再写一部分代码去完成文档中的功能,一步一步慢慢来

项目地址

https://github.com/Walkline80/IoT-Platform-Web

受到 帅大叔 之前玩的使用贝壳物联接入天猫精灵,再通过天猫精灵语音控制设备的启发,准备自己实现一下这个过程,不过不是通过第三方的物联网平台。

这里的规划先不包括接入天猫精灵,因为只要平台建立起来,要接入是很简单的事情,其次,要自己实现物联网平台,理论上也并不困难(自我催眠中)

接入流程

  1. 首先用户登录平台,注册账号并登录
  2. 用户在平台上创建虚拟设备
  3. 在设备开发中,使用虚拟设备提供的参数,使硬件设备和虚拟设备两者之间建立联系
  4. 两者使用 http 轮训的方式交换信息,是交换信息,因为这里并不存在消息推送或者事件触发
  5. 平台下发指令,包括控制设备动作以及获取设备状态
  6. 是否需要设备主动上报数据呢?

提示

所谓下发指令并不是从平台直接下达指令到设备让设备去执行,而是给虚拟设备标记一个期望值,比如期望设备做出 power on 的动作,或者期望得到设备当前的 power state,当硬件设备在轮训期间得到期望值会主动执行动作或者上报状态等。

用户注册

开始设想是使用微博账号单点登录,省去管理账号的麻烦,不过考虑到对接天猫精灵的时候就是 OAuth2 授权,在授权的时候再调用一次 OAuth2 授权有点太复杂了,暂时先 pass

也可以使用手机号加验证码的方式登录,但是短信验证码服务是要收费的。。。。。

所以暂时决定还是使用传统的注册账号加手工验证的方式开通账号

用户表设计
  • 使用邮箱账号作为用户名,注册时检查格式
  • 昵称默认使用邮箱前缀,用户可以自行修改
  • 密码使用 md5 加密,除了使用字典应该是不能破解的,网上有破解 md5 的网站,看介绍也是用搜集来的 md5 码做对比假装破解了
  • 手机号前期不用录入,可以作为第二用户名,或者等短信验证码免费了可以用验证码直接登录
  • 每个用户分配一个 uuid,作为对接天猫精灵时的身份认定
  • 用户组,确定用户权限
  • 启用状态,由管理员以手工方式启用,代表注册成功
  • 登录ip,应该没啥用
  • 注册日期,也没啥用
字段设计
name type length
email varchar 20
nickname varchar 20
passwd varchar 32
mobile varchar 11
uuid varchar 36
group int
enabled int
ip varchar 15
date datetime
用户操作记录

这里用来记录用户所有的操作,包括:登入登出、添加修改虚拟设备、修改个人信息等

但是不包括虚拟设备与硬件设备交互操作的记录 还是应该包括与硬件设备交互的操作记录的~

操作记录表设计

需要记录的操作应该包括

  • 用户登入、登出等
  • 虚拟设备添加、删除等
  • 平台发送的控制命令等
  • 平台接收的数据等
  • 来自其它平台,比如天猫精灵的控制命令等
字段设计
name type length
uuid varchar 36
op_type int
operation varchar 200
ip varchar 15
date datetime
op_type 数据记录
name memo
Login 用户登入、登出等操作
Device 添加、删除设备等操作
Control 发送控制命令等操作
Data 接收设备数据等操作
Remote 接收来自远端命令等操作,如来自天猫精灵
创建设备

用户登录平台以后第一件事当然是新建一个虚拟设备,硬件设备使用虚拟设备提供的 keysecret 与平台进行对接绑定,

虚拟设备表设计
  • key 和 secret 还没想好用什么方法生成
  • device_name,手动指定设备名称
  • 想好了再补充。。。。。
  • 生成日期

以下是对接天猫精灵平台需要的参数,如果不对接可以不用填

  • device_id,自动生成
  • device_type,天猫平台支持的设备类型,从下拉列表选择
  • device_zone,设备摆放位置,从 这里 获取最新的,并从下拉列表选择(也可以不填,可以事后从 天猫精灵 app 中选择)
  • device_brand,设备品牌(可以不填,使用系统默认)
  • device_modal,设备型号(不知道用途,也可以不填)
  • device_icon,设备图片 url(可以不填,使用系统默认)

例如按以下设置:

"deviceId" => "1234567890",
"deviceName" => "智能开关",
"deviceType" => "switch",
"zone" => "办公室",
"brand" => "Walkline Hardware",
"modal" => "wkhw_one_switch",
"icon" => "https://walkline.wang/logo.png",

则显示内容如图:

_images/device_list.png

此例中的两个 开关 字样是对应 deviceTypeswitch,是不能自己指定的
字段设计

暂时是这些

name type length
key varchar 36
secret varchar 36
device_id varchar
device_name varchar 20
device_type varchar 20
device_zone varchar 20
device_brand varchar 30
device_modal varchar 30
device_icon varchar 100
date datetime

更新日志

Change log
  • 2019.06.13: 补上记录用户操作记录的功能了,完成了用户登录功能
昨天把 PHP 的 MySQL 扩展从 mysql 换成 mysqli 了,各种不适应不明白不会用,到今天终于晃过神来了,还算顺手,啥时候 mysqli 也废弃了再去尝试 pdo
  • 2019.06.11: 完成了用户登录页面、用户注册页面和相应脚本文件以及 API 程序,忘记加上记录用户操作记录了。。。。。

Indices and tables