MicroBlocks 编程案例: dotPack
文章目录
I also find it fun work in the constrained world of microcontrollers. :-) – John Maloney
前言
dotPack 是英荔教育即将发布的一款可编程像素书包。
我之前在 可编程书包 提到:
可编程书包(led 矩阵屏)带来了完全不同的学习体验:
- 知识以可见的方式呈现出来,这极大提升了可理解性。 编程语言中的许多东西是一种逻辑结构,诸如 for/while/if 控制结构,当学习者使用这些抽象的语法结构时,它在矩阵屏幕上立刻变得清晰可见!于是你就可以对自己的想法进行 debug!
- 可编程书包(led 矩阵)是一种鲜活的媒介,它呈现出图形,因而可以承载和表达个性化的想法,而像素风格使这事儿非常简单。这方面的特质有点像 Scratch 舞台。非常容易激发学习者的个性化表达。
- 完成的作品可以背在肩上作为个性化饰品。
当时, 我通过 hack 一款潮品背包来实现对 Python 编程学习的支持。
dotPack 从一开始就为编程学习而设计, 因此许多方面都出色得多。
我提前拿到了一个 dotPack 样品。尤其喜欢它的高自由度,于是决定深挖它的潜力。
在 MicroBlocks 中编程
dotPack 官方固件支持 Python 和 Scratch 编程,官方主页和文档有清晰描述,我就不赘述了。
本文将分享我近期如何在 MicroBlocks 中对 dotPack 进行编程。
我还将分享一个在 MicroBlocks 中编写的产品级固件,它兼容英荔官方 Python client 的主要 API !
基本思路
就可编程部分来说, dotPack 是一个由 ESP32 驱动的 16x16 NeoPixel 点阵屏。
我最初打算通过刷入 MicroBlocks ESP32 固件来接管它,MicroBlocks 内置了 NeoPixel 驱动,感觉是个很棒的开始。
但一番实验下来,遇到一些问题。
- 使用 ESP32 驱动 NeoPixel 会遇到 “毛刺” 问题: 在大量的 NeoPixel led 中, 偶尔会有一两颗 led 的颜色异常。关于这个问题, Neopixels ESP32 有深入讨论。
- MicroBlocks 内置的 NeoPixel 库,简洁清晰,但缺少一些图形基元(Graphics Primitives), 尤其是与文字显示相关的基元。
以下是一个与"毛刺"问题有关的一个例子, 从视频里可以看出,偶尔会有一些"毛刺"出现。
当时,为了解决这两个问题,我放弃了使用 MicroBlocks 内置的 NeoPixel 库,转而在 MicroBlocks ESP32 虚拟机层面做一些基础工作。由于之前构建MicroBlocks MQTT 库时,有过在 MicroBlocks 虚拟机里工作的经验,这对我来说不难,它只需要一些 C/C++ 的知识。
在 MicroBlocks 虚拟机里编写图形基元
习惯在开始工作之前,逛逛开源社区, 看看有什么项目能帮助我起步,我视 Github 为金矿,运气好的话,时常能在里头挖到宝藏,因而减少的大量工作,有时是按“人月”来计算的!很快就发现了 Adafruit 开源的 NeoMatrix Library , 这正是我寻找的宝藏!
NeoMatrix Library 是对 Adafruit GFX Graphics Library 的简单包装,由于其老到而出色的 API ,驱动 NeoPixel 点阵屏成了举手之劳。
很快就在 MicroBlocks 虚拟机里完成所需的图形基元。 通过包装这些图形基元,在 MicroBlocks 里构建出了 dotPack 库。
如此一来,前头提到的两个问题就都解决了!
现在,在 MicroBlocks 中,我们有了一个体面的 dotPack 驱动库,它有丰富的基本图形基元、支持文字、稳定可靠、没有毛刺!
只要加上想象力,我们就可以实现各种好玩的东西,可以在上边构建细胞自动机: Conway’s Game of Life生命游戏, 以及其他各种很酷的像素艺术。
如果你乐意,甚至可以在上边构建一个旋转的彩色 3D 立方体,只需涉猎一些数学和立体视觉相关知识,就可以在 MicroBlocks 里使用 dotPack 驱动库, 把脑海里的想法实现出来。
我最喜欢在像素屏幕上做的事情之一是制作 “雪崩”。
雪崩(Snow Crash)
you want to try some Snow Crash? –《Snow Crash》
“雪崩” 是 1992 年出版的赛博朋克小说《雪崩》里的一种病毒,这种病毒是元宇宙(Metaverse 这个词正是来自这本小说!)里的一种卡片,卡片上黑白像素胡乱闪烁,就像没有信号的老旧电视机,它能够经由视觉入侵黑客的大脑,因为黑客的大脑善于处理二进制信息 :-)
让我们来看看小说里与雪崩相关的片段:
你想试试“雪崩”吗?… 刚才这话听上去像毒贩在兜售毒品。在现实世界的酒吧里,这是寻常之事,但此地是元宇宙,谁也没办法在元宇宙里卖毒品,因为你不可能只瞅一眼那些妙药就体验到飘飘欲仙的感觉…问题在于,“雪崩”是个电脑术语,指一种系统故障。此类故障通常称为 bug,但“雪崩”却和普通 bug 不同。这种故障出自电脑的底层结构,会对控制显示器电子束的部件造成破坏,令电子束在屏幕上到处乱扫,把完美的像素栅格变成一片飞旋的暴风雪。
让我们在 dotPack 上制作出“雪崩”的效果:
这儿是相应程序
很简单是不是!只需要一点点随机数
配方, 加上操控像素的积木块,把他们搅拌在一起,嘭!“雪崩"出现了!
在 MicroBlocks 为 dotPack 编写固件
固件(Firmware)是一类特定的计算机软件,它为设备的特定硬件提供低级控制… 可能包含设备的基本功能,并且可能为操作系统等更高级别的软件提供硬件抽象服务. 对于不太复杂的设备,固件可以充当设备的完整操作系统,执行所有控制、监控和数据操作功能。 – 维基百科
接下来,我打算在为 dotPack 编写一个固件,使其拥有与英荔官方固件相似的能力,为客户端(无线连接)提供如下 API :
- 支持细粒度的像素控制 (set_pixel)
- 支持字符和滚动文本 (display_char、display_text)
- 支持图像的上传、显示 (display_emoji、 display_char_zh 等大量 API 构建在这个 API 之上)
- 支持动画的上传、显示 (Animation)
- 支持开机启动程序 (图像或动画)
- …
换句话说,我打算在 MicroBlocks 写一个商业产品级别的固件,这意味着我将关注性能和稳定性,由于 MicroBlocks 出色的可理解性,优化问题只是"甜蜜的烦恼”。
由于 dotPack 的现有 Python 客户端开源在 Github 上 ,我们的固件将根据这个 API 反过来编写其实现。
思路
通过阅读 dotPack 的 Python 客户端 源码,可以了解到,dotPack 官方固件与其 Python 客户端采用蓝牙通信来交互。这是大多数教育产品采用的策略(从乐高到 toio 都如此),对于教育产品来说,采用蓝牙通信有很多好处,尤其是考虑到课堂教学。总所周知,学校网络要么连接复杂,要么网速糟糕,通常兼而有之 :-)
我在 MicroBlocks 构建的固件则采用 wifi/websocket 来通信,这要求有稳定的 wifi,对家庭用户和小型教育机构应该可行。不采用蓝牙的原因有 3 个:
- 官方固件已经足够好了,而且未来还会持续改进,似乎没必要再做完全一样的东西。采用 wifi 将获得了更快的传输速度
- MicroBlocks 官方还没有官方的蓝牙库,虽然我为 MicroBlocks 制作第一个蓝牙库,但编译出的固件体积比原来大了一倍,这会导致烧录时间很长。
- websocket 比蓝牙技术栈简单
我决定采用 wifi/websocket。
一旦确定了通信机制,剩下的问题就简单了,我将其视为消息的发送与解释的问题。
让我们采用 Alan Kay/Smalltalk 的面向对象风格(对象之间通信)来看待这个问题
消息:应将计算视为对象的固有功能,可以通过发送消息统一调用它们。 – Dan Ingalls 《Smalltalk 背后的设计原则》
具体思路是这样的,把 dotPack 视为一个(服务)对象,它通过解释它自己理解的消息来调用内部的功能(硬件的功能都包含在我们前头写的 dotPack 库中)。
把 Python 客户端看作另一个对象,它通过给 dotPack 发送消息,请求对方去做一些事情。消息的通道则是 wifi/websocket,当然,我们将来可以轻松将其替换为任何通道,诸如蓝牙,或随便其他什么东西。
让我们看一个具体 API 的实现: 设置背景颜色(set_background)
设置背景颜色(set_background)
Python 客户端一侧:
当用户在 JupyterLab 中输入 pack.set_background('blue')
时,背后发生的事情实际上是通过 wifi/websocket 通道发送了 setBackground,255
, 255 代表颜色值(我们将 RGB 编码为一个数字),更多细节可以查看源码(文末给出了链接)。
固件(MicroBlocks)一侧:
在固件一侧,则是对这个消息做出合理的解释。在此,这个合理的解释是将所有的像素变为蓝色。 如果固件做到了,我们就说它顺利服务了客户端的请求。以下是 MicroBlocks 中的具体代码。
有一处可能令大家困惑: color+0
。color+0
会将字符串转强行转化为数字(类型转化),之所以这样做,是因为消息以文本的形式传输。这个方案并不优雅,我已经把这个问题反馈给了 John,在 MicroBlocks 最新的版本中,他已经为数字和字符串之间的转化提供了体面的解决方案。
值得注意的是,通信的 2 个对象之间传递的消息,并没有某种"正确答案",它们之间如何传递消息,是由你决定的,只要语义清晰,没有分歧即可。它本质上是一个设计问题,重要的是你的想法是什么,并不存在某种最佳的算法技巧。
不需要过早关注技巧,正如不应该过早优化。
计算机教育的一大问题是,把太多时间用来磨练技巧,而没有让一个人去关心更宏大的东西,想法、愿景、理念之类的东西。如果你对大海、远方、冒险、神秘宝藏都没有兴趣,刻苦练习 20 种操舵的技巧又有什么乐趣呢?通常只会让你想赶紧远离这件苦差事。你不会理解路飞的乐趣,黑客的乐趣有时正如海贼的乐趣,冒险精神、对未知事物好奇和向往,比埋头练习技巧重要得多。当你拥有好奇心、兴趣和热情,未知的世界就开始向你敞开。
显示图像
在 MicroBlocks 中编程的乐趣之一是: 你可以理解一切,进而在理解的基础上进一步创造出新东西。
MicroBlocks 提供许多方式帮助你理解新事物,“展开积木定义” 是其中一种强大工具,你可以通过展开别人写的积木块,来理解事物的工作方式.
我想让 dotPack 库支持图片显示,但我对图片在屏幕上的显示原理一无所知,于是我在 MicroBlocks 内置库里闲逛,看看是否有可以学习的库,我猜测 Graphics 分类里可能有我要的东西。果然!我找到一个 BMP 库,它可以用来显示 BMP 格式的图像。
通过展开这个库里的积木的定义,我猜测只需要替换这个积木就能完成我想要的动作: 在 dotPack 的 NeoPixel 屏幕上展示 BMP 图像!
于是我将这个积木替换成:
就完成了所有工作!
现在你可以在 BMP 图像文件(如hello.bmp
) 通过 MicroMlocks 传到板子上(开启显示高级积木
)
然后使用我们修改后的 BMP 积木显示图像了!
显示动图/动画
在固件一侧,我们并没有直接支持 gif 动图格式,而是将动图看作一组 bmp 图像,既然有了处理 bmp 图像的积木,使用它构建动图是很容易的。
这些只是实现的细节问题,在客户端一侧,用户可以使用 Animation 类 加载(load) gif 动图,这个类在内部会做好转化,然后将动图显示在书包上。在用户看起来,就好像 dotPack 已经支持了 gif 动图。
如果你对实现的细节感兴趣,可以分别翻阅 Python 客户端的源码和固件的源码。
其他 API 的实现基本一样。有一处需要注意,即 图像文件的上传,图像文件比一般消息大,为了加快速度,我选择传输字节(bytes), 具体细节,可以查看源码。
开机启动程序
教育即生活 – 约翰·杜威
dotPack 追随杜威的教育理念: 教育即生活。 dotPack 希望学习者的作品能够背在自己肩上,作为一种带有强烈个人风格的艺术作品。
这反过来又给予学习者更强烈的学习动机: 探索和表达自己的风格,并将其融入到生活中。书包陪伴一个学习者的时间很长,它几乎贯穿整个学习生涯里。
开机启动程序能够让编程的成果随时随地呈现。它允许用户指定 Ta 的任何作品作为开机启动项,无论是静态图片,还是我前头展示的“雪崩”这种动图。
你可以背着“雪崩”到处走,像小说里的“乌鸦”(《雪崩》里的反派角色)那样,逮到一个看起来像阿弘(《雪崩》里的主角)一样的黑客,就问:
you want to try some Snow Crash?
大多数操作系统,都支持开机启动项,在硬件裸机中实现开机启动程序相当有趣和简单:
通过以上这些代码(蓝色的自定义积木可以展开成更多细节代码),我们实现了:
- 开机启动项
- 开机时的简单的人机界面,提示用户 wifi 是否连接顺利
- 还包含一个简单的内存优化策略。它能够避免 websocket 服务因内存不足而遗漏给客户端的反馈消息
在 MicroBlocks 里似乎万事可为,重要的是你的想法,你现在可能会跃跃欲试,想在 MicroBlocks 里写一个更加体面的操作系统,事实上,John Maloney 使用 MicroBlocks 为树莓派做过一个操作系统。
优化问题
过早优化是万恶之源 – Donald Knuth 《计算机编程艺术》
在 MicroBlocks 中,你可以看到硬件里发生的一切事情,如果你能够理解一切,那么优化只微不足道的烦恼。
固件分享
以下是我在 MicroBlocks 里为 dotPack 编写的固件: dotPack.ubp
它可以配合使用dotpack_pyclient (版本>=0.3.0
)使用,这是Jupyter notebook 例子
感受
谈谈我在编写固件时的感受。
视频里,左边是 Python 客户端。当 Python 客户端与硬件设备交互时,我们可以实时调试,“看到"硬件内部所发生的事情,也可以在某个时间或交互截面里,仔细观察硬件内部任何细节。右边是 Microblocks 的界面,当客户端请求到来的时候,我们可以实时观察内存余量,以便优化,如 Alan Kay 所说,在这种活性系统里,因为一切变得可理解,优化不过是细枝末节的琐事。我在硬件方面只是门外汉,但这个调试过程中,甚至发现并报告了 MicroBlocks 中的一些 bug.
这是系统对用户的增强。也正是个人计算社区一直努力的方向,在此感谢 John Maloney 在 MicroBlocks 上的出色工作!也感谢 dotPack 团队的努力!
后记
在写这篇文章时(2022.05.25), 我重新测试了 ESP32 NeoPixel,John 最近似乎修复了 ESP32 NeoPixel 的毛刺问题!
如果我在今天开始构建 dotPack 库, 我可能会从 MicroBlocks 内置的 NeoPixel 库开始,这意味着,我无需改写虚拟机,所有一切工作都可以在 MicroBlocks 中完成,相比在 C/C++里编程,当然是要愉快得多的编程体验! 因为你可以实时调试并看见硬件中发生的一切事情。
这是从 MicroBlocks 内置的 NeoPixel 库开始的dotPack库: dotPack-base-NeoPixel.ubl, 一切都在MicroBlocks IDE里完成!
参考
文章作者 种瓜
上次更新 2022-05-25