透過NB-IoT電信模組取得NTP網路時間,以Python為例
我在「透過NB-IoT電信模組發送MQTT訊息」說到有些 NB-IoT 模組只提供最基本的 TCP/UDP 封包傳輸指令,不提供 MQTT 等應用協定的傳輸指令。大部份 NB-IoT 模組的也沒有 NTP 指令 (查詢網路時間) 。
本文說明在這種情形之下,取得網路時間的經驗。
大多數 IoT 裝置並未內建計時元件,它們不知道今夕何夕。但是實務上,我們又希望透過 NB-IoT 之類的電信模組,將裝置「當時」的狀態資訊回傳到控制中心。簡單說就是裝置回報的狀態資料要包含年、月、日、時、分、秒。所以,我們送出資料前,必須先查詢「現在時間」。
使用 NB-IoT 電信模組時,查詢時間的可行方法通常是以下兩種之一。
- NB-IoT 模組提供查詢 4G/LTE 基地台目前時間的指令。
- 沒有查詢時間的指令,但有 UDP 封包傳輸指令。
正常來說, NB-IoT 模組接上 4G/LTE 基地台的電信網路後,可以直接向基地台詢問目前時間。所以大部份 NB-IoT 模組不提供 NTP 協定的 AT 指令。
查詢時間的 AT 指令似乎是共通的。以 u-blox 的 SARA-N/SARA-R4系列,和 SIMCOM 的 SIM7000系列 為例,它們查詢時間的 AT 指令都是 AT+CCLK?
,Python範例如下。
serial_port.write(b'AT+CCLK?')
resp = serial_port.read()
# +CCLK: "14/07/01,15:00:00+01"
# OK
回傳的時間格式是 “年/月/日,時:分:秒+時區修正”。「年」是西元年後二碼。「時」是24小時制,1~24。時區修正視時區而定,可能是加幾小時,也可能是減幾小時。台灣的時區是加八小時(+08)。也有些模組總是回傳 UTC 標準時間 (+00)。
但我個人經驗,這個指令有時候查不到正確的時間。這和 NB-IoT 模組有關。如果碰到這種情形,那就自己透過 UDP 傳輸指令查 NTP 網路時間吧。
實作 NTP 客戶端查詢網路時間的範例,請看「Python NTP Client example」。主要內容如下:
def NTP_get_seconds(host="pool.ntp.org", port=123):
'''
@return seconds (UTC time)
'''
unix_epoch = 2208988800 # 1970-01-01 00:00:00
# ntp packet: at least 48 bytes
# head = 0x1B => 00,011,011 (li = 0, vn = 3, mode = 3)
ntp_packet = b'\x1B' + b'\0' * 47
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(ntp_packet, (host, port))
data, _ = client.recvfrom(48)
seconds = struct.unpack("!12I", data)[10] - unix_epoch
return seconds
其實 NTP 客戶端的查詢指令很簡單,就是一個 48 bytes 的資料封包,第一個 bytes 是 0x1B ,接著 47 個 bytes 都填 0 。然後用 UDP 協定把封包送到 NTP 伺服器。
回傳的資料格式, Python 可以用 unpack()
方法解析。它會得到一個基於西元元年的時間秒數。這和 Python 時間函數慣用的 unix 紀元時間不一樣。所以我寫的這個函數最後會減去 unix 紀元起始秒數,把它變成基於 unix 紀元的時間秒數。
中間的三行 socket 函數方法,請自行替換成 NB-IoT 模組的 UDP 傳輸 AT 指令。