9.4. 建立链路¶
上一页介绍的链路层大部分是自动的,但有一处需要 Python 脚本介入:告诉摄像头要加入哪个网络。在这一步成功之前,本节其余部分介绍的网络功能都无法工作。
9.4.1. network 模块¶
network 模块将摄像头的网络硬件暴露给 Python。具体提供哪一组接口取决于具体的板子:许多摄像头有一块无线芯片并暴露一个 WLAN 类(取名自 Wireless Local Area Network,无线局域网);某些板子还有一个内置的以太网端口并暴露一个 LAN 类(取名自 Local Area Network,局域网,即有线版本)。两者的使用模式相同,但有一个重要区别:无线接口必须被告知要加入哪个网络,而以太网会接收线缆上的任何信号。
9.4.2. Wi-Fi 流程¶
加入一个 Wi-Fi 网络分三步:构造接口、将其启用、请求它用密码连接到一个指定名称的网络。接口会在后台与接入点协商;一次 isconnected() 调用会在链路建立完成时给出报告:
import network
import time
wlan = network.WLAN(network.WLAN.IF_STA)
wlan.active(True)
wlan.connect("my-network", "my-password")
while not wlan.isconnected():
time.sleep_ms(100)
print("link up")
参数 IF_STA 选择 station(站点)模式——摄像头加入由其他人托管的网络。相反的模式 IF_AP 让摄像头托管它自己的小型网络,供其他设备加入;这对配置界面和现场设置很有用,但不是常见情形。
一旦 isconnected() 返回 True,摄像头就在网络上了。上层为完成自身设置所需的其他一切,都在链路建立期间自动完成了;后面的页面会逐一详述这些环节。
9.4.3. 可能出现的问题¶
在这一步会出现几种实际的失败模式。
网络名称或密码错误。 连接尝试会静默地重试,直到应用放弃。给等待包裹一个超时,使上面的循环不会永远阻塞:
start = time.ticks_ms() while not wlan.isconnected(): if time.ticks_diff(time.ticks_ms(), start) > 10000: raise OSError("Wi-Fi did not come up in 10 s") time.sleep_ms(100)
超出范围。 摄像头与接入点必须足够靠近,使信号强到足以维持一条链路。
status()返回一个代码,指示链路未建立的原因;scan()返回无线电能看到的网络列表,这是在connect无法成功时需要运行的诊断。接入点要求的不只是密码。 开放网络(无密码)和常见的密码保护网络都可由上面所示的
connect处理。工作场所和学校里更大的网络有时会使用一种不同的方案,要求摄像头向一个单独的登录服务器进行身份验证;这些需要给connect提供额外的参数。完整内容请参阅 class WLAN —— 控制内置 WiFi 接口。
9.4.4. 保持连接¶
建立链路只是问题的一半。保持连接是另一半——接入点会重启、摄像头会漫游到范围之外、DHCP 租约会过期、无线电固件偶尔会卡住。一台要在网络上运行数月的摄像头必须注意到这些情况并自行恢复。
检测的模式是每次主循环迭代调用一次 isconnected(),并在它返回 False 时作出反应。当连接已经断开而无线电尚未察觉时,isconnected() 可能会短暂地撒谎——在链路 "本应" 处于正常状态时却失败的套接字发送,是应用判断连接已断的另一个证据。当两者不一致时,status() 是更权威的来源。
重连的模式是 disconnect(),紧接着用相同的凭据 connect(),等待部分像初次连接那样包裹一个超时。在两次尝试之间进行退避——一秒、两秒、四秒,逐次翻倍直到大约一分钟——使长时间的中断不会反复冲击接入点,也不会把无线电的功耗预算耗费在空转循环上:
import network
import time
_BACKOFF_S = (1, 2, 4, 8, 16, 32, 60)
def reconnect(wlan, ssid, password):
for delay in _BACKOFF_S:
wlan.disconnect()
wlan.connect(ssid, password)
deadline = time.ticks_add(time.ticks_ms(), 10_000)
while not wlan.isconnected():
if time.ticks_diff(deadline, time.ticks_ms()) < 0:
break
time.sleep_ms(100)
if wlan.isconnected():
return True
time.sleep(delay)
return False
当那个辅助函数持续返回 False 时,无线电固件本身可能已经卡死。最后的手段是给无线电断电重启:active(False)、短暂暂停、active(True)、从头重新连接。这能让无线电固件回到一个已知状态,代价是额外几秒钟的停机时间:
def radio_power_cycle(wlan, ssid, password):
wlan.active(False)
time.sleep(1)
wlan.active(True)
return reconnect(wlan, ssid, password)
一台已经脱网数分钟的摄像头是应用必须看到的一个真实故障。恢复代码应当把这一状态暴露出来——在一个主循环会检查的标志中把网络标记为不健康,并让应用在该标志未清除期间跳过它本会进行的网络发送——这样长时间的中断就不会让应用因等待永远不会写入的套接字而停滞。
9.4.5. 以太网(如果存在)¶
带有内置以太网的板子使用相同的模式,但没有 connect 这一步。LAN 接口用 active() 启用,而一旦插入线缆,接口便可使用:
import network
lan = network.LAN()
lan.active(True)
print("link up")
在这之后,无论是哪个接口把摄像头接入了网络,本节其余页面都以相同的方式适用。上层并不关心其下方的链路是 Wi-Fi 还是以太网——"已连接" 就是 "已连接"。
有关完整的 WLAN 和 LAN 参考,包括这里未能容纳的配置选项,请参阅 network --- 网络配置。