不需網路就可以自動對時的神奇 ESP8266
Tags: esp8266, esp32, ntptime, thonny
Published: 2022-02-22 03:25:38

在 ESP8266 上使用 MicroPython 時發生了一件神奇的事, 我拿了一個剛剛燒錄好 MicroPython 韌體的 ESP8266 控制板, 居然不需設定 ESP8266 的時間, 就可以取得正確的時間, 例如：

```python
MicroPython v1.17 on 2021-09-02; ESP module with ESP8266

Type "help()" for more information.
>>> import time
>>> time.localtime()
(2022, 2, 22, 9, 31, 0, 1, 53)
>>>
```

你可以看到一連接上 ESP8266, 取得的就是正確時間, 神奇吧？

## Thonny 開發環境的自動對時

不過後來發現, 這是因為我使用的 Thonny 開發環境預設會在連結上控制板時, [自動依照電腦上的時間設定控制板](https://github.com/thonny/thonny/wiki/MicroPython#time-synchronization), 所以控制板不需要校正時間也能取得正確的時間。

這個功能立意良好, 不過若沒有注意, 可能會因為實際部署裝置時並不會跟 Thonny 連接對時, 導致時間錯誤。

### Thonny 的進階設定

如果要取消 Thonny 的自動對時功能, 要修改進階設定檔, 檔案的位置可透過『**工具/開啟 Thonny 資料夾**』開啟資料夾, 然後開啟 **configuration.ini** 檔, 在檔案中找到或是如下建立 ESP8266 專屬的區塊 (ESP32 專屬區塊的名稱就是 'ESP32'), 加上 `sync_time = False` 的設定：

```ini
...
[ESP8266]
port = COM4
used_vidpids = {(6790, 29987)}
sync_time = False
...
```

注意到這個設定必須在 Thonny 關閉時修改才能生效, 重新開啟 Thonny 後, 連結上 ESP8266 控制板, 會先看到 Thonny 提醒你控制板上的時鐘慢了 xxxxx 秒, 這就是因為取消 Thonny 自動對時的關係：

```python
WARNING: Device's real-time clock seems to be off by 698838035 seconds
MicroPython v1.17 on 2021-09-02; ESP module with ESP8266

Type "help()" for more information.
>>>
```

如果取得控制板的時間, 就會看到接近以下尚未對時過的時間：

```python
>>> import time
>>> time.localtime()
(2000, 1, 1, 0, 0, 38, 5, 1)
>>>
```

我們可以讓控制板連上網路, 再利用 `ntptime` 模組透過網路對時：

```python
>>> import network
>>> sta = network.WLAN(network.STA_IF)
>>> sta.active(True)
#7 ets_task(4020ee68, 28, 3fff9ea0, 10)
>>> sta.connect('FLAG-SCHOOL', '12345678')
>>> import ntptime
>>> ntptime.settime()
>>> time.localtime()
(2022, 2, 22, 1, 43, 31, 1, 53)
>>>
```

這樣就可以取得正確的時間了。

## 不連網也能對時？

如果將控制板拔掉後再重新接上, 讓 Thonny 再連結上控制板, 因為剛剛已經斷電了, 所以時間又回到未對時的狀況：

```python
>>> 
Backend terminated or disconnected. Use 'Stop/Restart' to restart.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
WARNING: Device's real-time clock seems to be off by 698838263 seconds
MicroPython v1.17 on 2021-09-02; ESP module with ESP8266

Type "help()" for more information.
>>> import time
>>> time.localtime()
(2000, 1, 1, 0, 0, 19, 5, 1)
```

既然時間不準, 我們就利用網路對時吧：

```python
>>> import ntptime
>>> ntptime.settime()
>>> time.localtime()
(2022, 2, 22, 1, 45, 52, 1, 53)
>>>
```

### ESP8266 的自動連網

時間正確了, 咦, 你有注意到我們沒有連網嗎？沒有連網怎麼可能可以網路對時呢？來確認一下：

```python
>>> import network
>>> sta = network.WLAN(network.STA_IF)
>>> sta.isconnected()
True
>>>
```

咦, 我們已經連上網了！原來 ESP8266 會將網路狀態記錄在快閃記憶體, 重新啟動時會嘗試連上網路, 因此就可以很神奇地進行網路對時。如果我們將網路關閉：

```python
>>> sta.active(False)
>>> ntptime.settime()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ntptime.py", line 34, in settime
  File "ntptime.py", line 20, in time
OSError: -2
>>
```

再想進行網路對時就會發生錯誤, 循錯誤訊息指示, 在 MicroPython 的原始檔中可以看到[這是在向 DNS 伺服器查詢 NTP 伺服器 IP 位址](https://github.com/micropython/micropython/blob/4d9e657f0ee881f4a41093ab89ec91d03613744d/ports/esp8266/modules/ntptime.py#L20)時因為網路不通而發生錯誤。由於網路介面已經關閉, 即使重新啟動控制板, 也不會自動連上無線網路, 當然就無法網路對時：

```python
>>> 
Backend terminated or disconnected. Use 'Stop/Restart' to restart.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
WARNING: Device's real-time clock seems to be off by 698838813 seconds
MicroPython v1.17 on 2021-09-02; ESP module with ESP8266

Type "help()" for more information.
>>> import ntptime
>>> ntptime.settime()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ntptime.py", line 34, in settime
  File "ntptime.py", line 20, in time
OSError: -2
>>>
```

在 ESP32 的 MicroPython 上, 並不會有自動重新連線的問題, 控制板重啟後網路介面都是關閉的。

## 小結

本文說明的雖然是個小問題, 不過常常就是因為這樣的小問題導致耗費大量時間除錯。我個人建議不要太依賴預設的自動行為, 不論 Thonny 是否會幫控制板對時, 若需要正確的時間, 就明確在程式中對時；同樣地, 也明確在程式中連接無線網路, 這樣, 若是無線網路有問題, 也會在連結時看到錯誤訊息, 不會默默不語, 可以及早發現問題。