前言

Smalltalk 爱好者们期待已久的 Making Smalltalk 在上周五举行。Lounsen 提议在来一场 After Party。

我想在 After Party 上分享近期使用 Squeak 做的项目: Squeak 中的 dotPack 模拟器

为了让分享更加充实,我做了些增强,使这个 dotPack 模拟器可以跟 dotPack 实物互动。我喜欢在 MicroBlocks 里为硬件写固件,它是我迄今为止最喜欢的物理计算平台。

打算把分享的话题定为 《Squeak 与 MicroBlocks 的互操作》,这样一来,我不仅能够在分享时讨论 Smalltalk,还能讨论 Smalltalk 的精神后裔: MicroBlocks,它们共享了大量的系统特性和个人计算愿景,值得一提的是 MicroBlocks 的创始者 John Maloney 也是 Squeak 创始团队的成员,John Maloney 在 Squeak 的贡献包括: Smalltalk-to-C 翻译器(可移植性的关键)、第一个 Squeak VM (Mac)、Morphic 用户界面框架…

原定 2 小时的 After Party,持续了 3 个小时,虽然大家都是 Smalltalk 爱好者,依然被 Smalltalk 的灵活和强大所震惊,我们几乎是一边讨论,一边实时编程,在系统中探索碰撞出的新想法,而且几乎总是能够实现突如其来的灵感。

开始

思路

制作出色且可扩展的系统的关键在于设计模块的通信方式,而不是设计其内部属性和行为应该是什么 – Alan Kay

最初打算在 Squeak 中使用串口(serial)来跟 dotPack 硬件通信,后来发现 Squeak 中的串口插件(MacOS)年久失修。于是打算采用网络来通信(dotPack 使用 ESP32,所以能够支持网络)。

两个版本

前后设计了两个版本的通信机制:

  • 基于 HTTP 的互操作
  • 基于 UDP 的互操作

相关的源码都已经放到 Github: DotPack

只需把 DotPack.st 下载下来拖到 Squeak 桌面(确保后缀是.st, MacOS 会把后缀名改为 txt,最好在命令行里检查一下),然后选择 fileIn entire file 即可载入相应的 DotPack 包。

你在 browser 里应该能看到 DotPack。

基于 HTTP 的互操作

服务端

HTTP 是一个服务端和客户端模型,在我们的案例里,MicroBlocks 中的程序充当服务端,Squeak 充当客户端。

我们先来看下 MicroBlocks 里的程序:

.

前边说到 MicroBlocks 是 Smalltalk 的精神后裔,它具有大量 Smalltalk 的典型特征, 诸如 “活性(liveness)“的环境: 你可以实时与环境交互,每一块积木都是活的,点击它将实时得到反馈: 你将看到积木的功能, 如果你用过 Scratch,你对此应该十分熟悉,这种方式支持你以建构主义的方式自由探索系统,整个系统是一个活的文档,它通过响应你来帮助你理解它。一旦你接触过这种编程体验,就再也无法忍受大多数编程环境。硬件领域的编程环境更是令人难以忍受。

计算机应该像乐器,它应该对用户的操作作出即时且一致的响应。想象一下,在吹出一个音符和听到它之间的一秒钟的延迟是多么荒谬!

我喜欢的一个演示是以交互的方式应答 HTTP 请求。你可以先点击 HTTP 服务器请求 积木,对用户的请求做一番探索,等你玩够了,再决定 回应xx至HTTP请求, 对客户端来说一切正常,Ta 完全不知道有一个人手动处理了他的请求!这是一个真正的"人工"智能服务器!

客户端

最初使用 Squeak 内置的 HTTP 客户端(WebClient)跟 MicroBlocks http server 通信,结果遇到一个问题, 我把问题反馈给了 John, John 给出了精彩分析, 记录在此: https://bitbucket.org/john_maloney/smallvm/issues/287/http-server-can-not-work-with-squeak

这个问题通信的双方(Squeak 和 MicroBlocks)都有责任,MicroBlocks 的责任多一些,John 说近期会修复。

后来我用 curl 暂时绕开了这个问题。

为了在 Squeak 中使用 curl, 需要 Squeak 与宿主系统的互操作,让 Squeak 能够调用 shell 命令。CommandShell 是很棒的选择。这是我的意料之外的收获,当我决定用 Squeak 时,我几乎做好了放弃在 Unix/Linux 下积累的经验(我从大二开始用 Linux,差不多十年的经验)。现在这些经验突然又能够在 Squeak 里使用了, Unix/Linux 里有大量实用小工具,一口气都丢了真有些可惜。更加意料之外的是, CommandShell 支持 Unix 管道(pipe), 而且能够用 Squeak 的 inspect 增强它! 这样一来,我不仅没有丢掉 Unix/Linux 工具,还能够随时使用 Squeak 增强它们!

为了在 Squeak 里调用 shell 命令,需要现在 squeak 安装 CommandShell:

1
2
MCMcmUpdater updateFromRepository: 'http://www.squeaksource.com/OSProcess'
MCMcmUpdater updateFromRepository: 'http://www.squeaksource.com/CommandShell'

Squeak 客户端中相关代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pack := DotPack new.
pack stopStepping.
pack clear.

pack withDevice: true deviceAddress: #[192 168 1 8]. "IP Address"
pack sendMessageWith: 'HTTP'. "default"
pack clear.

pack x: 1 y:1 color: Color red.
1 to: 16 do: [:i | pack x: i y:i color: Color red].
1 to: 16 do: [:i | pack x: i y:i color: Color blue].

以下是一个演示视频:

通过在 browser 中探索 DotPack 类,你应该能很快弄懂它的工作方式。

我把视频分享给 John,John 觉得很有意思。他建议我试试他最近设计的 UDP 积木,代码将更简单,响应速度也会更快。

基于 UDP 的互操作

这个版本是在分享开始前的半个小时前做的。我突然想在 16x16 的粗糙"屏幕"上构建出鼠标的幻觉

为了获得足够好的实时性,我决定试一试 UDP。

服务端

MicroBlocks 中的程序

客户端

Squeak 客户端中相关代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pack := DotPack new.
pack stopStepping.
pack clear.

pack withDevice: true deviceAddress: #[192 168 1 8]. "IP Address"
pack sendMessageWith: 'UDP'.
pack clear.

pack x: 1 y:1 color: Color red.
1 to: 16 do: [:i | pack x: i y:i color: Color red].
1 to: 16 do: [:i | pack x: i y:i color: Color blue].

附录

Python client & MicroBlocks UDP server

以下是使用 Python UDP 客户端 与MicroBlocks 的 UDP 服务交互的例子, 运行以下代码将把 dotPack (8,8) 位置的 LED 点亮为红色。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# https://wiki.python.org/moin/UdpCommunication
import socket

ip = "192.168.1.8"
port = 5000
x,y = (8,8)
r,g,b = (255,0,0)
color = r<<16|g<<8|b
message = "setpixel,{},{},{}".format(x,y,color)
sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.sendto(message.encode(), (ip, port))

netcat(nc) client & MicroBlocks UDP server

echo ‘setpixel,8,8,255’ | nc -u 192.168.1.8 5000 -w0