2024年9月25日 星期三

MicroPython 學習筆記 : xtools 模組改版備忘錄

今天總結過去幾天的測試結果修改 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. 

沒有留言 :