1. 文档中心
  2. 比卜创造 Micro:bit
  3. Python
  4. 网络

网络

设备可以连在一起,互相收发消息,这就叫网络。许多网络再互联,叫互联网;我们日常说的 Internet,就是「网络的互联网」。

网络编程并不简单,下面这个项目也体现了这一点。但美妙之处在于:它涵盖了你该了解的网络编程常见要素,同时又相当简单、好玩。

先设定场景……

连接

可以把网络想成一层层叠起来。最底层是通信的基础:必须有一条路,让信号从一台设备到另一台。有时用无线电,本例只用两根线。

两台 micro:bit 用鳄鱼夹连接
两台 micro:bit 用鳄鱼夹连接

在这之上,可以搭建网络协议栈的其余各层。

如示意图,蓝色和红色 micro:bit 用鳄鱼夹相连。两台都用引脚 1 输出、引脚 2 输入;一台的输出接另一台的输入。有点像拿电话听筒的方向——一端是麦克风(输入),一端是扬声器(输出)。你对着麦克风说话,对方听筒里就能听到。拿反了就会出怪声!

本例也一样:线必须接对!

信号

协议栈的下一层是信号,往往取决于物理连接的特性。这里就是通过 I/O 引脚在线上传送的开/关数字信号。

还记得可以这样操作 I/O 引脚:

pin1.write_digital(1)  # 打开信号
pin1.write_digital(0)  # 关闭信号
input = pin2.read_digital()  # 读取信号(1 或 0)

下一步是约定如何使用和处理信号——这就需要……

协议

若你有幸见到女王,行为是有规矩的:她到场要鞠躬或屈膝礼,伸手要礼貌握手,先称「Your Majesty」再称「ma'am」……这套规矩叫皇家礼仪。协议就是:在特定情境下大家事先约定怎么行为,以免临场混乱。

女王
女王

计算机网络里同样需要协议,让计算机事先约定如何收发消息。最著名的也许是万维网用的 HTTP(超文本传输协议)。

比计算机更早的一种「发消息协议」是摩尔斯电码:用长短不一的开/关信号表示字符,常听成「嘀」「嗒」。长信号叫划(-),短信号叫点(.)。组合起来就能发字母,例如标准字母表:

.-    A     .---  J     ...   S     .----  1      ----.  9
-...  B     -.-   K     -     T     ..---  2      -----  0
-.-.  C     .-..  L     ..-   U     ...--  3
-..   D     --    M     ...-  V     ....-  4
.     E     -.    N     .--   W     .....  5
..-.  F     ---   O     -..-  X     -....  6
--.   G     .--.  P     -.--  Y     --...  7
....  H     --.-  Q     --..  Z     ---..  8
..    I     .-.   R

按上表,发字母 H 就要连续四次短开信号(四个点 ....);发 L 也是四次,但第二次要长一些(.-..)。

显然时序很重要:要能区分点和划。这也是协议要约定的,这样大家的实现才能互通。本例约定:

  • 持续时间 小于 250 毫秒 的信号为点。
  • 250 毫秒到不足 500 毫秒 为划。
  • 其他时长忽略。
  • 信号间隔 大于 500 毫秒 表示一个字符结束。

于是字母 H 就是四个不超过 250 毫秒的「开」,再跟一段超过 500 毫秒的停顿。

消息

我们终于能构造消息——对人类有意义的内容,这是协议栈的最顶层。

用上面的协议,我可以在物理线上发送这样一串信号:

...././.-../.-../---/.--/---/.-./.-../-..

你能读出是什么吗?

应用

有了协议栈,还需要应用来和它交互——收发消息的界面。HTTP 虽重要,多数人交给浏览器,万维网的底层栈被藏起来了(本该如此)。

那 micro:bit 上该写什么应用?从用户角度该怎么用?

显然,发消息要能输入点和划(我们用按钮 A)。要看已发或刚收到的消息,应能触发滚动显示(用按钮 B)。既然是摩尔斯码,若接了扬声器,输入时还应播放「嘀嗒」作为听觉反馈。

最终程序

完整程序如下,注释较细,便于理解:

from bibo import *
import music


# 摩尔斯码与字符对照表
MORSE_CODE_LOOKUP = {
    ".-": "A",
    "-...": "B",
    "-.-.": "C",
    "-..": "D",
    ".": "E",
    "..-.": "F",
    "--.": "G",
    "....": "H",
    "..": "I",
    ".---": "J",
    "-.-": "K",
    ".-..": "L",
    "--": "M",
    "-.": "N",
    "---": "O",
    ".--.": "P",
    "--.-": "Q",
    ".-.": "R",
    "...": "S",
    "-": "T",
    "..-": "U",
    "...-": "V",
    ".--": "W",
    "-..-": "X",
    "-.--": "Y",
    "--..": "Z",
    ".----": "1",
    "..---": "2",
    "...--": "3",
    "....-": "4",
    ".....": "5",
    "-....": "6",
    "--...": "7",
    "---..": "8",
    "----.": "9",
    "-----": "0"
}


def decode(buffer):
    # 查表解码;没有则返回句号
    return MORSE_CODE_LOOKUP.get(buffer, '.')


# 显示一个点
DOT = Image("00000:"
            "00000:"
            "00900:"
            "00000:"
            "00000:")


# 显示一个划
DASH = Image("00000:"
             "00000:"
             "09990:"
             "00000:"
             "00000:")


# 少于 250ms 为点
DOT_THRESHOLD = 250
# 少于 500ms 为划
DASH_THRESHOLD = 500


buffer = ''           #  incoming 摩尔斯缓冲
message = ''          # 已解码字符
started_to_wait = running_time()


while True:
    waiting = running_time() - started_to_wait
    key_down_time = None
    # 按住 A:发信号、蜂鸣
    while button_a.is_pressed():
        music.pitch(880, 10)
        pin1.write_digital(1)
        if not key_down_time:
            key_down_time = running_time()
    # 引脚 2 收到信号,当作 A 按下
    while pin2.read_digital():
        if not key_down_time:
            key_down_time = running_time()
    key_up_time = running_time()
    pin1.write_digital(0)
    if key_down_time:
        duration = key_up_time - key_down_time
        if duration < DOT_THRESHOLD:
            buffer += '.'
            display.show(DOT)
        elif duration < DASH_THRESHOLD:
            buffer += '-'
            display.show(DASH)
        else:
            pass
        started_to_wait = running_time()
    elif len(buffer) > 0 and waiting > DASH_THRESHOLD:
        character = decode(buffer)
        buffer = ''
        display.show(character)
        message += character
    if button_b.was_pressed():
        display.scroll(message)
        message = ''

你会怎样改进?能否改点、划的定义,让发码更快的人也能用?两台同时发会怎样?可以怎样处理冲突?