在微软的makecode编辑器中,我们可以轻松为micro:bit写出这种事件驱动风格的代码:当按钮A被按下的时候打印字符A,当按钮B被按下的时候打印字符B

makecode

生成的代码(javascript)十分简单:

1
2
3
4
5
6
input.onButtonPressed(Button.A, () => {
    basic.showString("A")
})
input.onButtonPressed(Button.B, () => {
    basic.showString("B")
})

当我们使用Python来为micro:bit编程时,却很难写出这种事件驱动风格的代码,如果我们要做到上边类似的事,一般的教程里会让你用while+if/else来做。尽管可以做到类似的事,但思维方式/语义其实并不相同。

Why

makecode将积木映射为JavaScript代码,JavaScript是一个事件驱动型语言,所以在makecode可以很轻松进行事件驱动风格的编程。而Python对事件驱动的关注主要集中在网络编程方面,在Python中做事件驱动风格的编程,并不是那么常见。

要在图形化中,自动生成事件驱动风格的代码,自然就更不易了

我们看到micro:bit官方的bbcmicrobit/PythonEditor中积木化界面(blockly)就不支持:当按钮A被按下的时候打印字符A,当按钮B被按下的时候打印字符B

microbit site

how

由于目前micro:bit的micropython固件并不支持多线程(pyboard倒是有个固件支持_thread),要实现事件驱动,可能只有协程供你选择了。

micro:bit官方对此并没有很好的解决方案

mblock/makeblock的方案倒是十分漂亮,值得我们好好学习(ps:mblock在工程上十分出色,有许多聪明的做法值得学习)

我们先在mblock中来为micro:bit编写事件驱动风格的程序

mblock 积木

接着我们来看看积木generate出的Python代码:

mblock 代码

代码十分规整,规整的好处是从积木到代码的转化将很简单。这种从积木generate出代码的方式,是典型的blockly app风格,尽管长着scratch3.0的样子,关于两者的差异可以参考我此前的文章:Blockly与Scratch3.0的比较分析及选型建议

观察这个代码(注意yeild),如果你熟悉python的协程,应该可以轻松猜到,mblock很聪明地使用协程来做事件驱动风格的编程

至于为何不用async/await,是因为MicroPython是Python 3.4的一个实现,在python3.4中,你不能使用async/await关键字,直到Python在3.5版本才引入关于协程的语法糖async和await

这段代码尽管十分工整,但它并不能运行,它需要一个调度器来调度它们。

经过预处理之后,可运行代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from microbit import *
import music

def on_callback_button_a():
    display.scroll(str('A'), wait=False, loop=False)

def on_coroutine_button_a():
    while True:
        if button_a.is_pressed():
            yield on_callback_button_a()
        else:
            yield

def on_callback_button_b():
    display.scroll(str('B'), wait=False, loop=False)

def on_coroutine_button_b():
    while True:
        if button_b.is_pressed():
            yield on_callback_button_b()
        else:
            yield

def coroutine_start_go(coroutine_v0,coroutine_v1):

    coroutine_v0.send(None)
    coroutine_v1.send(None)
    n = 0
    while True:
       coroutine_v0.send(n)
       coroutine_v1.send(n)

    
coroutine_init0 = on_coroutine_button_a()
coroutine_init1 = on_coroutine_button_b()

coroutine_start_go(coroutine_init0,coroutine_init1)

当然,在进入micro:bit之前,它要连同内核一同被编译为hex文件。

可以看出,mblock在generate出代码之后,还有一个预处理的过程(实际上webduino也干过类似的事,我之前的分析有分析过,他们的做法如出一辙)。

尽管我们可以用一些hack的手段拿到这部分的源码,但我们不打算在此讨论细节(mblock这部分并未开源,出于尊重和规避法律风险的考虑,我们不准备公开他们这部分的代码。大家自己动手去实现吧,按照以上分析,这部分实现起来十分简单,mblock是个很酷的团队,也许之后会开放)

mblock对程序进行预处理的部分使用js来做,如下图,大家把这个转化器当黑盒来看吧

预处理

通过简单的正则,你很快可以写出来:)

参考