网络

可以将设备连接在一起以相互发送和接收消息。这称为网络。互连网络的网络称为互联网。互联网是所有互联网的互联网。

联网很困难,这反映在下面描述的程序中。然而,这个项目的美妙之处在于它包含了您需要了解的网络编程的所有常见方面。它也非常简单和有趣。

但首先,让我们设置场景……

联系

将网络想象为一系列层。最底层是通信最基本的方面:需要某种方式让信号从一个设备传到另一个设备。有时这是通过无线电连接完成的,但在本例中,我们将简单地使用两根电线。

../_images/network.png

正是在此基础上,我们才能构建网络堆栈中的所有其他层 。

如图所示,蓝色和红色 micro:bits 通过鳄鱼线连接。两者都使用引脚 1 作为输出,引脚 2 作为输入。一台设备的输出连接到另一台设备的输入。这有点像知道在哪一边拿着电话听筒——一端有一个麦克风(输入端),另一端有一个扬声器(输出端)。您通过麦克风录制的声音会从其他人的扬声器中播放出来。如果你拿错了手机,你会得到奇怪的结果!

在这种情况下完全相同:您必须正确连接电线!

信号

网络堆栈中的下一层是信号。这通常取决于连接的特性。在我们的示例中,它只是通过 IO 引脚沿电线发送的数字开和关信号。

如果您还记得的话,可以像这样使用 IO 引脚:

pin1.write_digital(1)  # switch the signal on
pin1.write_digital(0)  # switch the signal off
input = pin2.read_digital()  # read the value of the signal (either 1 or 0)

下一步涉及描述如何使用和处理信号。为此,我们需要一个……

协议

如果你见过女王,就会对你应该如何表现抱有期望。例如,当她到达时,你可以鞠躬或行屈膝礼,如果她礼貌地伸出手握手,称她为“陛下”,然后称她为“女士”等等。这套规则被称为皇家协议。协议解释了在特定情况下(例如会见女王)如何表现。协议是预先定义的,以确保每个人在特定情况出现之前都了解正在发生的事情。

../_images/queen.jpg

正是出于这个原因,我们定义并使用了通过计算机网络进行消息通信的协议。计算机需要事先同意如何发送和接收消息。也许最著名的协议是万维网使用的超文本传输​​协议 (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 毫秒的暂停(表示字符结束)。

信息

我们终于到了可以构建信息的阶段——一个对我们人类真正有意义的信息。这是我们网络堆栈的最顶层。

使用上面定义的协议,我可以将以下信号序列通过物理线路发送到另一个 micro:bit:

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

你能弄清楚它说的是什么吗?

应用

拥有网络堆栈非常好,但您还需要一种与之交互的方式 - 某种形式的应用程序来发送和接收消息。虽然 HTTP 很有趣,但大多数人不知道它并让他们的网络浏览器处理它 - 万维网的底层网络堆栈是隐藏的(应该是)。

那么,我们应该为 BBC micro:bit 编写什么样的应用程序呢?从用户的角度来看,它应该如何工作?

显然,要发送消息,您应该能够输入点和破折号(我们可以为此使用按钮 A)。如果我们想查看我们发送或刚刚收到的消息,我们应该能够触发它在显示屏上滚动(我们可以使用按钮 B)。最后,这是摩尔斯电码,如果连接了扬声器,我们应该能够在用户输入消息时播放哔哔声作为一种听觉反馈形式。

最终结果

这是该程序,它的所有荣耀都带有大量注释,因此您可以看到发生了什么:

from microbit import *
import music


# A lookup table of morse codes and associated characters.
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):
    # Attempts to get the buffer of Morse code data from the lookup table. If
    # it's not there, just return a full stop.
    return MORSE_CODE_LOOKUP.get(buffer, '.')


# How to display a single dot.
DOT = Image("00000:"
            "00000:"
            "00900:"
            "00000:"
            "00000:")


# How to display a single dash.
DASH = Image("00000:"
             "00000:"
             "09990:"
             "00000:"
             "00000:")


# To create a DOT you need to hold the button for less than 250ms.
DOT_THRESHOLD = 250
# To create a DASH you need to hold the button for less than 500ms.
DASH_THRESHOLD = 500


# Holds the incoming Morse signals.
buffer = ''
# Holds the translated Morse as characters.
message = ''
# The time from which the device has been waiting for the next keypress.
started_to_wait = running_time()


# Put the device in a loop to wait for and react to key presses.
while True:
    # Work out how long the device has been waiting for a keypress.
    waiting = running_time() - started_to_wait
    # Reset the timestamp for the key_down_time.
    key_down_time = None
    # If button_a is held down, then...
    while button_a.is_pressed():
        # Play a beep - this is Morse code y'know ;-)
        music.pitch(880, 10)
        # Set pin1 (output) to "on"
        pin1.write_digital(1)
        # ...and if there's not a key_down_time then set it to now!
        if not key_down_time:
            key_down_time = running_time()
    # Alternatively, if pin2 (input) is getting a signal, pretend it's a
    # button_a key press...
    while pin2.read_digital():
        if not key_down_time:
            key_down_time = running_time()
    # Get the current time and call it key_up_time.
    key_up_time = running_time()
    # Set pin1 (output) to "off"
    pin1.write_digital(0)
    # If there's a key_down_time (created when button_a was first pressed
    # down).
    if key_down_time:
        # ... then work out for how long it was pressed.
        duration = key_up_time - key_down_time
        # If the duration is less than the max length for a "dot" press...
        if duration < DOT_THRESHOLD:
            # ... then add a dot to the buffer containing incoming Morse codes
            # and display a dot on the display.
            buffer += '.'
            display.show(DOT)
        # Else, if the duration is less than the max length for a "dash"
        # press... (but longer than that for a DOT ~ handled above)
        elif duration < DASH_THRESHOLD:
            # ... then add a dash to the buffer and display a dash.
            buffer += '-'
            display.show(DASH)
        # Otherwise, any other sort of keypress duration is ignored (this isn't
        # needed, but added for "understandability").
        else:
            pass
        # The button press has been handled, so reset the time from which the
        # device is starting to wait for a  button press.
        started_to_wait = running_time()
    # Otherwise, there hasn't been a button_a press during this cycle of the
    # loop, so check there's not been a pause to indicate an end of the
    # incoming Morse code character. The pause must be longer than a DASH
    # code's duration.
    elif len(buffer) > 0 and waiting > DASH_THRESHOLD:
        # There is a buffer and it's reached the end of a code so...
        # Decode the incoming buffer.
        character = decode(buffer)
        # Reset the buffer to empty.
        buffer = ''
        # Show the decoded character.
        display.show(character)
        # Add the character to the message.
        message += character
    # Finally, if button_b was pressed while all the above was going on...
    if button_b.was_pressed():
        # ... display the message,
        display.scroll(message)
        # then reset it to empty (ready for a new message).
        message = ''

你会如何改进它?您能否更改点和破折号的定义,以便快速的摩尔斯电码用户可以使用它?如果两个设备同时发送会发生什么?你会怎么做来处理这种情况?