今天總結過去幾天的測試結果修改 xtools 函式庫, 主要是修改了 line_msg() 函式, 它原本依賴於自訂的 xrequests 模組的 post() 函式來傳送 Line Notify 訊息, 由於此 post() 函式又依賴於 ussl 模組, 但是 ussl 已經在 MicroPython v1.23 版韌體中被移除, 導致在 v1.23 版中呼叫 line_msg() 函式會出現找不到 ussl 之錯誤.
為了解決 MicroPython v1.23 版移除 ussl 所造成之問題, 我借助 ChatGPT 幫忙修改程式碼, 不再依賴自訂的 xrequests 模組, 而是改用內建 urequests.post() 傳遞以經過 utf-8 編碼之 URL 參數, 測試可順利傳送 Line Notify 訊息, 參考 :
主要修改項目如下 :
1. 新增 urlencode(params) 函式 :
此函式是 ChatGPT 提供, 用來將參數字典轉成 URL 字串, 以便 line_msg() 呼叫 urequests.post() 時傳入 data 參數內 (需用 utf-8 編碼為 bytes 類型).
def urlencode(params):
# 將字典的鍵值對轉換為 URL 編碼的字串 (k=v) 並以 & 連接多個鍵值對
kv=['{}={}'.format(k, v) for k, v in params.items()]
return '&'.join(kv)
2. 修改 line_msg() 函式 :
def line_msg(token, message):
url="https://notify-api.line.me/api/notify"
headers={
"Authorization": "Bearer " + token,
"Content-Type": "application/x-www-form-urlencoded"
}
params={"message": message} # 參數字典
# 呼叫自訂的 URL 編碼函式將字典轉成 URL 字串, 再轉成 utf-8 編碼的 bytes
payload=urlencode(params).encode('utf-8')
# 用編碼後的 payload 傳給 data 參數發送 POST 請求
r=urequests.post(url, headers=headers, data=payload)
if r is not None and r.status_code == 200:
print("Message has been sent.")
else:
print("Error! Failed to send notification message.")
r.close() # 關閉連線
3. 新增了傳送貼圖的 line_sticker() 函式 :
def line_sticker(token, message, stickerPackageId, stickerId):
url="https://notify-api.line.me/api/notify"
headers={ # 加入正確的 Content-Type
"Authorization": "Bearer " + token,
"Content-Type": "application/x-www-form-urlencoded"
}
# 設定正確的 payload
params={
"message": message,
"stickerPackageId": stickerPackageId,
"stickerId": stickerId
}
# 使用自訂的 urlencode 函數將參數編碼,並轉換成 UTF-8 的字節串
payload=urlencode(params).encode('utf-8')
# 發送 POST 請求
r=urequests.post(url, headers=headers, data=payload)
# 判斷是否成功
if r is not None and r.status_code == 200:
return "The sticker has been sent."
else:
return "Error! Failed to send the sticker."
4. 新增傳送雲端圖片的 line_image_url() 函式 :
def line_image_url(token, message, image_url):
# 透過 LINE Notify 發送雲端圖片
url="https://notify-api.line.me/api/notify"
headers={
"Authorization": "Bearer " + token,
"Content-Type": "application/x-www-form-urlencoded"
}
# 構造請求的數據,包含圖片的 URL
params={
"message": message,
"imageFullsize": image_url, # 完整圖片的 URL
"imageThumbnail": image_url # 縮略圖圖片 URL,可與完整圖片相同
}
# 轉成 URL 字串並用 utf-8 編碼為 bytes
payload=urlencode(params).encode('utf-8')
# 發送 POST 請求
r=urequests.post(url, headers=headers, data=payload)
# 判斷是否成功
if r is not None and r.status_code == 200:
return "The image URL has been sent."
else:
return "Error! Failed to send the image URL."
5. 新增 ask_gpt() 函式 :
此函式需要添加匯入 ujson 模組 :
import ujson
def ask_gpt(prompt, api_key, model='gpt-4o-mini'):
url='https://api.openai.com/v1/chat/completions'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
# 建立 data 參數字典
data={
'model': model,
'messages': [{'role': 'user', 'content': prompt}]
}
# 將字典轉成字串後再編碼成 UTF-8
payload=ujson.dumps(data).encode('utf-8')
# 發送 POST 請求
response=urequests.post(url, headers=headers, data=payload)
if response.status_code == 200:
reply=response.json() # 轉成字典
return reply['choices'][0]['message']['content']
else:
return response.json() # 返回錯誤信息
6. 修改 connect_wifi_led() 為 connect_wifi() 函式 :
此函式名稱太長, 所以我把最後面的 led 拿掉, 函式內容不變.
這樣修改後 xtools.py 中只剩下 webhook_post() 函式有用到 xrequests.py 模組的 post() 函式, 因為還未測試其用途, 不知是否能用 urequests.post() 取代, 故仍保留不改, 但把原先的 from xrequests import post 寫法改成 import xrequests :
import xrequests
def webhook_post(url, value):
print("invoking webhook")
r = xrequests.post(url, data=value)
if r is not None and r.status_code == 200:
print("Webhook invoked")
else:
print("Webhook failed")
show_error()
這樣比較清楚這個 post() 函式是來自 xrequests.
7. 修改 config.py :
除了連線 WiFi 基地台的帳密外, 添加了 LINE_NOTIFY_TOKEN 與 OPENAI_API_KEY :
SSID = "TonyS24U"
PASSWORD = "123456"
LINE_NOTIFY_TOKEN="在此填入 Line Notify 權杖"
OPENAI_API_KEY="在此填入 OpenAI 金鑰"
測試結果 :
MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.
>>> import xtools
>>> import config
>>> ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
>>> token=config.LINE_NOTIFY_TOKEN
>>> message='test'
Connecting to network...
network config: ('192.168.192.58', '255.255.255.0', '192.168.192.92', '192.168.192.92')
>>> xtools.line_msg(token, message)
Message has been sent.
>>> xtools.line_sticker(token, message, 1, 4)
'The sticker has been sent.'
>>> image_url='https://cdn.pixabay.com/photo/2024/03/15/17/50/dogs-8635461_1280.jpg'
>>> xtools.line_image_url(token, message, image_url)
'The image URL has been sent.'
結果如下 :
以下是呼叫 ask_gpt() 測試 :
>>> prompt='Who are you'
>>> xtools.ask_gpt(prompt, openai_api_key)
'I am an AI language model created by OpenAI, designed to assist with a wide range of questions and tasks by providing information and generating text based on the input I receive. How can I help you today?'
中文提示詞時傳回值是 \u 開頭的 unicode :
>>> prompt='你是誰?'
>>> xtools.ask_gpt(prompt, openai_api_key)
'\u6211\u662f\u4e00\u4e2a\u4eba\u5de5\u667a\u80fd\u52a9\u624b\uff0c\u65e8\u5728\u56de\u7b54\u95ee\u9898\u3001\u63d0\u4f9b\u4fe1\u606f\u548c\u5e2e\u52a9\u7528\u6237\u89e3\u51b3\u95ee\u9898\u3002\u6709\u4ec0\u9ebc\u6211\u53ef\u4ee5\u5e6b\u52a9\u4f60\u7684\u55ce\uff1f'
用 print() 輸出就會解碼為中文字元 :
>>> print(xtools.ask_gpt(prompt, openai_api_key))
我是ChatGPT,一個由OpenAI開發的人工智慧語言模型。我的目的是幫助回答問題、提供資訊和進行對話。你有什麼想知道的嗎?
Unicode 可直接傳給 Line Notify :
>>> reply=xtools.ask_gpt(prompt, openai_api_key)
>>> xtools.line_msg(token, reply)
Message has been sent.
更新後的 xtools.py (v1.23 韌體適用) 網址不變 :
更新後的函式庫壓縮檔網址不變 :
舊的函式庫壓縮檔 (v1.23 前版本之韌體適用) 網址如下 :
2024-10-05 補充 :
以上針對 v1.23 韌體改版的 xtools 只能用在 ESP32 開發板, 在 ESP8266 板載入 OK, 但執行 line_msg() 等呼叫 urequests.post() 之函式時都會自動 Reset 而無法使用, ESP8266 開發板只能在 v1.23 版之前的韌體上用 xrequests.post() 呼叫 line_msg() 而已. 如果要在 v1.23 韌體上呼叫 line_msg(), line_sticker(), line_image_url() 與 ask_gpt(), 必須使用 ESP32 開發板.
2024-10-08 補充 :
昨天測試 LOLIN D32 ESP32 開發板時發現此板的板上藍色 LED 是 GPIO5, 因此呼叫 connect_wifi() 時 LED 不會閃, 為了適應各開發板 LED 接腳不同, 修改 connect_wifi() 加入一個關鍵字參數 led 預設為 2 :
def connect_wifi(ssid=config.SSID, passwd=config.PASSWORD, led=2, timeout=20):
wifi_led=Pin(led, Pin.OUT, value=1)
sta=network.WLAN(network.STA_IF)
sta.active(True)
start_time=time.time() # 記錄時間判斷是否超時
if not sta.isconnected():
print("Connecting to network...")
sta.connect(ssid, passwd)
while not sta.isconnected():
wifi_led.value(0)
time.sleep_ms(300)
wifi_led.value(1)
time.sleep_ms(300)
# 判斷是否超過timeout秒數
if time.time()-start_time > timeout:
print("Wifi connecting timeout!")
break
if sta.isconnected():
for i in range(25): # 連線成功 : 快閃 5 秒
wifi_led.value(0)
time.sleep_ms(100)
wifi_led.value(1)
time.sleep_ms(100)
print("network config:", sta.ifconfig())
return sta.ifconfig()[0]
若 LED 不是預設 GPIO2, 則呼叫時傳入 led 參數指定即可 :
MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.
>>> import config
>>> import xtools
>>> xtools.connect_wifi(led=5)
Connecting to network...
network config: ('192.168.192.225', '255.255.255.0', '192.168.192.92', '192.168.192.92')
'192.168.192.225'
測試 OK, 已更新 GitHub.