###### tags: `FM631A`
# Lab 10 更新使用 TDX 運輸資料流通服務
手冊上採用的 [PTX 資料服務](https://ptx.transportdata.tw/PTX/)平台因為政府整合資訊的關係, 在 2022 年底就不再提供服務, 要改成使用 [TDX 運輸資料流通服務](https://tdx.transportdata.tw/), 本文將說明如何修改程式改用新的平台獲取相同的資訊。
## 未註冊用戶的用法
TDX 和 PTX 一樣都可以不註冊使用, 不過會有同一 IP 每日透過程式查詢 50 次的限制, 以下先說明如何使用 TDX 查詢手冊範例中的鐵路班車資訊與特定車站電子看板資訊。
### 修改瀏覽器字串
要免註冊存取 TDX, 程式一定要加上 TDX 認得的瀏覽器名稱, 以 Firefox 為例, 我們可以如下設定 user-agent:
```python
>>> headers = {'user-agent':'Mozilla/5.0'}
```
若是照手冊上設定成 curl 工具的名稱, 是會被 TDX 擋掉的。
你可以在慣用的瀏覽器開啟**網頁開發者工具**, 切換到**主控台**頁次, 執行 `navigator.userAgent` 指令, 即可得到瀏覽器的設定名稱:
![](https://i.imgur.com/oFimolu.png)
實際上不需要輸入完整的內容, 只要前面的**名稱/版本**即可。
### 修改網址
TDX 的 API 網址和 PTX 幾乎一樣, 只要修改小地方即可:
```
https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON
--- ----
| |
| +-+
| |
V V
--- ---------
https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON
```
就可以存取同樣的服務了。實際的 API 網址可在 [TDX 網站](https://tdx.transportdata.tw/api-service/swagger/basic/268fc230-2e04-471b-a728-a726167c1cfc#/TRA/TRAApi_GeneralTrainInfo_2147_1)上查詢。
### 簡易測試--查詢指定班次的起訖站
這裡以 1202 班次為例, 統合以上所說, 查詢這班次火車的起訖站資訊:
```python
>>> import urequests
>>> headers = {'user-agent':'Mozilla/5.0'}
>>> train_url = 'https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON'
>>> train_res = urequests.get(train_url,headers=headers)
>>> j = train_res.json()
>>> print(j[0]['StartingStationName']['Zh_tw'])
北湖
>>> print(j[0]['EndingStationName']['Zh_tw'])
基隆
>>>
```
### 修改範例程式
根據以上, 就可以把原始範例改成這樣:
- lab10_tdx.py
```python
# 無會員:當天次數 50 次
from machine import Pin,PWM
import network
import urequests
import time
import tm1637
import ntptime
# 四位數顯示器
tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17))
# 清空四位數顯示器
tm.write([0, 0, 0, 0])
# 連線至無線網路
sta=network.WLAN(network.STA_IF)
sta.active(True)
sta.connect('無線網路名稱','無線網路密碼')
while not sta.isconnected() :
pass
print('Wi-Fi連線成功')
ntptime.settime()
# 想查詢的車號
s_number = 1202
# 想查詢的車站(1000是台北)
sta_number = 1000
ifttt_url = "IFTTT請求網址"
# 讓網站認為請求是使用瀏覽器發出。因為有些網頁會擋爬蟲程式
headers = {'user-agent':'Mozilla/5.0'}
# 查詢指定火車起點與終點
train_url = ("https://tdx.transportdata.tw/api/basic"
"/v2/Rail/TRA/GeneralTrainInfo/TrainNo/"
+ str(s_number) +"?$format=JSON")
train_res = urequests.get(train_url,headers=headers)
if(train_res.status_code == 200):
pass
else:
print("傳送失敗")
print("錯誤碼:",train_res.status_code)
train_j = train_res.json()
print("\n車號:",s_number)
print(train_j[0]['StartingStationName']['Zh_tw'] \
+" → "+train_j[0]['EndingStationName']['Zh_tw'])
train_res.close()
# 車子第一次出現在時刻表時要提醒使用者
remind = False
# 查詢火車時刻表
time_url = ("https://tdx.transportdata.tw/api/basic"
"/v2/Rail/TRA/LiveBoard/Station/"
+ str(sta_number)+"?$top=20&$format=JSON")
while True:
time_res = urequests.get(time_url,headers=headers)
if(time_res.status_code == 200):
pass
else:
print("傳送失敗")
print("錯誤碼:",time_res.status_code)
time_j = time_res.json()
time_res.close()
# 將車號加入 number列表 中
number = []
for i in range(len(time_j)):
number.append(time_j[i]['TrainNo'])
print("\n即時班車號碼:",number)
# 有查到對應車號, 顯示延遲時間
if(str(s_number) in number):
if(remind == False):
# 蜂鳴器
buzzer_pin = Pin(23,Pin.OUT)
buzzer = PWM(buzzer_pin,freq=0, duty=50)
buzzer.freq(349)
time.sleep(1)
buzzer.freq(294)
time.sleep(1)
buzzer.deinit()
remind = True
ind = number.index(str(s_number))
print("\n表定發車時間:",
time_j[ind]['ScheduledArrivalTime'])
print("延遲時間:",
time_j[ind]['DelayTime'],"分鐘")
tm.number(time_j[ind]['DelayTime'])
res = urequests.get(ifttt_url +
"?value1=" +
str(time_j[ind]['DelayTime']))
if(res.status_code == 200):
pass
else:
print("傳送失敗")
print("錯誤碼:",res.status_code)
res.close()
# 沒有查到對應車號
else:
print("\n目前無"+str(s_number)+"號火車")
TW_sec = time.mktime(time.localtime())+28800
TW = time.localtime(TW_sec)
hour = TW[3]
minu = TW[4]
tm.numbers(hour,minu)
remind = False
time.sleep(300) # 暫停 300 秒
```
## 註冊會員的程式寫法
如果不想受限於每天 50 次查詢, 請先至 [TDX 網站](https://tdx.transportdata.tw/)註冊會員, 註冊後需要一段時間審核, 審核通過會收到通知信件, 即可登入使用。
### 找到金鑰資訊
要以會員身份使用 TDX API, 必須知道你的金鑰資訊, 請在登入後的頁面點選**建立更多金鑰**:
![](https://i.imgur.com/CvRuotJ.png)
往下捲動找到你的預設 API Key, 按一下**編輯**:
![](https://i.imgur.com/NPZsnaR.png)
就可以看到你的金鑰內含的兩項資訊:
![](https://i.imgur.com/qgEezxD.png)
### 取得 Access Token
實際上要以會員身份使用 TDX API, 必須分成以個階段:
1. 程式要利用剛剛取得的 Client Id 和 Client Secret 這兩項資訊向 TDX 索取 Access Token, 這個 Access Token 才是最後使用 API 時的認證, 有效期限是 1 天, 也就是說, 程式必須在 Access Token 失效後重新索取。
2. 利用剛剛取得的 Access Token 當成認證叫用 TDX API
上述過程聽起來就很麻煩, 詳細過程可以參考 [〈TDX運輸資料流通服務API介接範例程式碼說明〉](https://github.com/tdxmotc/SampleCode), 或是[〈「TDX 運輸資料流通服務平台」含 Python 範例程式,PTX 平台的升級版~〉](https://blog.jiatool.com/posts/tdx_python/), 我們已經將整個過程包裝成好用的 [tdx 模組](https://github.com/codemee/tdx), 可以大幅簡化程式。
tdx 模組也已經包含在範例檔中, 使用前請先上傳到控制版上。
### tdx 模組的用法
tdx 模組使用上很簡單, 步驟如下:
1. 以 Client Id 及 Client Secret 建立 TDX 物件:
```python
>>> from tdx import TDX
>>> client_id = '你的 Client Id'
>>> client_secret = '你的 Client Secret'
>>> tdx = TDX(client_id, client_secret)
```
2. 傳入 TDX API 網址呼叫 TDX.get_json() 取得查詢結果轉換後的 JSON 物件:
```python
>>> train_url = 'https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON'
>>> j = tdx.get_json(train_url)
get new access token:
200
access token:eyJhbG...
>>> print(j[0]['StartingStationName']['Zh_tw'])
北湖
>>> print(j[0]['EndingStationName']['Zh_tw'])
基隆
>>>
```
是不是很簡單呢?這個物件會在叫用 API 之前確認 Access Token 的有效時間, 若已經過期, 就會自動重新索取。如果查詢出錯, 會傳回一個這樣格式的物件:
```python
{
'error_code': HTTP 的錯誤碼,
'text': HTTP 回應內容
}
```
### 修改範例程式
有了 tdx 模組, 我們就可以將原本的範例程式改寫, 不管如何使用 TDX API 都沒有問題 (好啦, 其實還是有限制, 每一 IP 每秒 50 次):
- lab10_tdx_member.py
```python
from machine import Pin,PWM
import network
import urequests
import time
import tm1637
import ntptime
from tdx import TDX
client_id = '你的 Client Id'
client_secret = '你的 Client Secret'
# 想查詢的車號
s_number = 4171
# 想查詢的車站(1000是台北)
# 可以使用以下網址查詢
# https://tip.railway.gov.tw/tra-tip-web/tip/tip001/tip111/view
sta_number = 1000
# 四位數顯示器
tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17))
# 清空四位數顯示器
tm.write([0, 0, 0, 0])
# 連線至無線網路
sta=network.WLAN(network.STA_IF)
sta.active(True)
sta.connect('無線網路名稱','無線網路密碼')
while not sta.isconnected() :
pass
print('Wi-Fi連線成功')
# 建立 TDX API 存取物件
tdx = TDX(client_id, client_secret)
ifttt_url = "IFTTT請求網址"
# 查詢指定火車起點與終點
train_url = "https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/" \
+ str(s_number) +"?$format=JSON"
train_j = tdx.get_json(train_url)
if('error_code' in train_j):
print("傳送失敗")
print("錯誤碼:",train_j['error_code'])
print("\n車號:",s_number)
print(train_j[0]['StartingStationName']['Zh_tw'] \
+" → "+train_j[0]['EndingStationName']['Zh_tw'])
# 車子第一次出現在時刻表時要提醒使用者
remind = False
# 查詢火車時刻表
time_url = "https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/" \
+ str(sta_number)+"?$top=20&$format=JSON"
while True:
time_j = tdx.get_json(time_url)
if('error_code' in time_j):
print("傳送失敗")
print("錯誤碼:",time_j['error_code'])
# 將車號加入 number列表 中
number = []
for i in range(len(time_j)):
number.append(time_j[i]['TrainNo'])
print("\n即時班車號碼:",number)
# 有查到對應車號, 顯示延遲時間
if(str(s_number) in number):
if(remind == False):
# 蜂鳴器
buzzer = PWM(Pin(23,Pin.OUT),freq=0, duty=512)
buzzer.freq(349)
time.sleep(1)
buzzer.freq(294)
time.sleep(1)
buzzer.deinit()
remind = True
ind = number.index(str(s_number))
print("\n表定發車時間:",time_j[ind]['ScheduledArrivalTime'])
print("延遲時間:",time_j[ind]['DelayTime'],"分鐘")
tm.number(time_j[ind]['DelayTime'])
res = urequests.get(ifttt_url + "?value1=" + str(time_j[ind]['DelayTime']))
if(res.status_code == 200):
pass
else:
print("傳送失敗")
print("錯誤碼:",res.status_code)
res.close()
# 沒有查到對應車號
else:
print("\n目前無"+str(s_number)+"號火車")
TW_sec = time.mktime(time.localtime())+28800
TW = time.localtime(TW_sec)
hour = TW[3]
minu = TW[4]
tm.numbers(hour,minu)
remind = False
time.sleep(300) # 暫停 300 秒
```