2024年9月27日 星期五

React 的動畫函式庫 Framer Motion

最近在網路上看到 Framer Motion 這個基於 React 的動畫函式庫, 雖然我沒學過 React (因為我偏愛 Vue), 但基於好奇, 就透過 ChatGPT 來快速了解其特性 :

問 : 請說明 Framer Motion 是甚麼? 
答 : 
Framer Motion 是一個用於 React 應用程式的動畫庫,提供簡單且強大的 API 來實現各種動畫效果。它是 Framer 團隊開發的,專注於流暢、高效的動畫,並支持許多動態交互,例如過渡效果、彈跳、拖動、滾動等。

Framer Motion 的特色包括:

  1. 簡單易用的 API:使用直觀的屬性(如 animateinitialexit)來輕鬆為元件添加動畫。
  2. 性能優化:內建使用動畫關鍵幀、GPU 加速,確保動畫流暢、性能高效。
  3. 支援複雜的動畫:可以輕鬆地定義多階段動畫、拖動行為和手勢。
  4. 易於整合:可以與 React 框架中的其他元件或第三方庫(如 styled-components)無縫整合。
  5. 動態交互:支持互動性動畫,如當元素被拖動、點擊或滾動時的動畫效果。
可以用 GPU 加速真不賴ㄟ. 

問 : 需要先安裝甚麼執行環境嗎?   
答 : 
所需的環境:
  1. 安裝 Node.js 和 npm。
  2. React 應用程式(如 create-react-app)。
    命令列指令 :
    npx create-react-app my-app
    cd my-app
  3. 安裝 Framer Motion 後,即可在專案中使用它來添加動畫效果。
    npm install framer-motion
問 : 請比較 Framer Motion 與其他競爭之函式庫之優缺點 
答 : 

Framer Motion 在動畫和互動效果方面與其他動畫庫相比有獨特的優勢和挑戰。以下是它與幾個常見的競爭動畫函式庫之比較:

1. Framer Motion

優點:

  • 簡單且直觀的 API:使用者可以通過少量代碼快速建立動畫,API 設計對 React 開發者十分友好。
  • 強大的動態交互功能:支援拖曳(drag)、手勢、滾動動畫等複雜行為,適合現代互動式應用。
  • 自動管理動畫:動畫自動化過程(如布局過渡 layout transition),減少手動處理 DOM 的繁瑣工作。
  • React 專用優化:針對 React 環境進行深度優化,並與 React 16.8+ 的 Hooks 機制無縫集成。
  • 高性能:使用 requestAnimationFrame 和 GPU 加速技術來優化動畫性能,動畫流暢。

缺點:

  • 局限於 React:Framer Motion 是專為 React 應用設計的,因此無法在非 React 框架中使用。
  • 文件與學習曲線:儘管 API 簡單,但某些進階功能(如變換動畫、路徑動畫等)可能需要更多學習與摸索。

2. GSAP (GreenSock Animation Platform)

優點:

  • 功能強大且靈活:GSAP 支援所有類型的動畫效果,從簡單過渡到複雜的時序動畫,並且可用於任何 JavaScript 環境(不限於 React)。
  • 極高的瀏覽器相容性:GSAP 支援各種瀏覽器,包括 IE9+,且在不同的環境中保持一致的表現。
  • 複雜的時序控制:GSAP 提供功能豐富的時間軸(timeline)系統,可以精確控制動畫的執行順序和時間。

缺點:

  • 學習曲線較陡:GSAP 的功能多樣,對初學者來說學習成本較高,尤其是其複雜的 API 和動畫時序系統。
  • 相對於 React 整合性弱:雖然 GSAP 可以用於 React,但它並不像 Framer Motion 那樣針對 React 進行專門的優化,整合上稍微繁瑣。

3. Anime.js

優點:

  • 輕量且靈活:Anime.js 是一個體積小巧的動畫庫,適合實現精簡的動畫效果,尤其是在 DOM 操作和 SVG 路徑動畫上表現優異。
  • 多類型動畫支持:Anime.js 支援動畫對象包括 CSS 屬性、DOM 屬性、SVG、JSON、JavaScript 物件等,非常靈活。

缺點:

  • React 整合性不足:Anime.js 不是專為 React 設計的,與 React 整合時需要額外處理元件生命周期管理,導致開發體驗不如 Framer Motion 流暢。
  • 進階功能較弱:相比 GSAP 和 Framer Motion,Anime.js 在時間軸控制和交互性動畫上功能不如前兩者強大。

4. React Spring

優點:

  • 物理模擬動畫:React Spring 以物理模型為基礎,用彈簧物理模擬來構建動畫,提供比線性時間函數更自然的動畫效果。
  • React 整合良好:它也是專為 React 應用設計,與 React 的 Hook 和動畫狀態的管理深度集成,容易控制狀態變化中的動畫。
  • 靈活的 API:可以根據不同場景靈活選擇基於物理的動畫模型或傳統過渡動畫。

缺點:

  • 學習成本:對於需要精確控制的動畫,React Spring 的設置和配置相對複雜,對初學者來說學習成本較高。
  • 缺乏拖動、手勢等功能:相比 Framer Motion,React Spring 對於複雜的互動和手勢控制沒有專門的支援。
Framer Motion 的優勢在於它與 React 的深度整合、易於使用的 API 以及對複雜交互動畫的支持,非常適合需要打造高度互動、動態 UI 的 React 應用。GSAP 則適合需要實現複雜動畫且希望跨平台使用的場景,而 Anime.js 和 React Spring 則適合輕量或物理模擬動畫需求的項目。

總結比較如下表 : 




學 Framer Motion 得先學 React 累累, 似乎輕量的 Anime.js 較合我胃口. 不過我也只是偶然看到透過 AI 了解一下而已, 還真沒時間學, 先記著. 


2024年9月26日 星期四

露天購買 Lolin ESP32 開發板 x 3

今天找到另一款 ESP32 開發板 : LOLIN D32, 此板特點是有鋰電池插座且內建鋰電池充電電路, 當 Micro USB 插上電源時會自動向鋰電池充電, 無電源時則由鋰電池供電, 故可搭配太陽能板在有陽光時由太陽能板供電, 無陽光時則吃鋰電池, 適合用在自供電專案. 





全家取貨付款免運 477 元

買完才發現有更便宜的賣家哈哈 : 


LOLIN 也有 mini 板 :


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 前版本之韌體適用) 網址如下 : 


MicroPython 學習筆記 : 非 ASCII 字元自動轉成 Unicode 問題

這幾天在修改 xtools 模組以便能在 v1.23 版韌體中能正常發送 Line 訊息時, 發現在 MicroPython 中中文字元一律會被轉成 \u 開頭的 Unicode 字元, 例如 : 

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.
>>> v1=100   
>>> v2='您好'    
>>> params={'v1': v1, 'v2': v2}   
>>> ['{}={}'.format(k, v) for k, v in params.items()]    
['v1=100', 'v2=\u60a8\u597d']    

但是我在 PC 的標準 Python 上測試卻不會被轉成 Unicode, 而是顯示原來的中文 :

Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> v1=100   
>>> v2='您好'    
>>> params={'v1': v1, 'v2': v2}   
>>> ['{}={}'.format(k, v) for k, v in params.items()] 
['v1=100', 'v2=您好']      

好奇怪, 詢問 ChatGPT 才知道這其實是 Python 執行環境版本的問題, 不同的 Python 的內建字串顯示函式 repr() 會考量裝置的資源限制 (主要是記憶體) 而有不同的實作 : MicroPython 是在資源有限的嵌入式裝置上運行, 它會將非 ASCII 字元自動轉換為 Unicode 以保字串的輸出能一致; 但在標準 Python 上則因為有豐富的 Unicode 顯示支援會直接顯示這些非 ASCII 字元而無須轉成 Unicode. 總之, 在 MicroPython 和標準 CPython 的 repr() 函數的行為差異與它們的設計目標和資源限制有關。

以下摘錄自 ChatGPT 回覆 : 

問 : 為何標準的 CPython 的 repr() 不轉成 unicode, 而 MicroPython 的卻自動轉換?  

1. MicroPython 的設計考量

  • 資源受限的環境:MicroPython 專為資源有限的嵌入式系統(如 ESP32、ESP8266)設計,內存、存儲等資源相對較少。因此,MicroPython 對字串處理的實現更加簡化。
  • repr() 的簡化處理:在 MicroPython 中,repr() 的行為被簡化,非 ASCII 字符會自動被轉換為 Unicode 轉義序列(\uXXXX),以便在資源受限的環境中正確處理和顯示非 ASCII 字符。這樣做的好處是確保在不同的終端環境中,即使終端不支援中文或其他特殊字符,也能看到唯一標識符來代表這些字符。
  • 一致性與兼容性:通過將所有非 ASCII 字符轉換為 Unicode 編碼,MicroPython 保證在嵌入式系統中的輸出行為更加一致。這樣的設計可以避免一些特殊字符在不同終端或顯示器上產生不預期的行為。

2. CPython 的設計考量

  • 桌面和服務器環境:標準 CPython 針對的是桌面、服務器等更高資源的環境。這些環境中,內存、存儲、顯示器等資源通常不會像嵌入式系統那樣受限,因此 CPython 可以直接顯示 Unicode 字符而不需要轉義。
  • repr() 的行為:在 CPython 中,repr() 的主要目的是提供一個開發者可以識別並重現的物件表示,但它不會自動將所有非 ASCII 字符轉換為 Unicode 編碼。當字串中包含可打印的非 ASCII 字符(如中文),CPython 的 repr() 會直接顯示這些字符,而不是將它們轉換為轉義序列。
  • Python 3 的 Unicode 支持:Python 3 本身對 Unicode 有更好的支持,所有字串都是 Unicode 字符串。CPython 的環境資源豐富,因此可以直接顯示包含非 ASCII 字符的字串,並且這樣做在大多數情況下更符合開發者的期望。

差異總結:

  • MicroPython:由於內存和處理能力受限,對於字串的表示會更保守,因此會將非 ASCII 字符自動轉換為 Unicode 轉義序列。這樣可以保證嵌入式系統上字串的輸出更加一致,並且避免潛在的顯示或處理問題。
  • CPython:資源充裕,並且對於字串處理有強大的 Unicode 支持。因此,當使用 repr() 時,如果字串包含可顯示的非 ASCII 字符(如中文),它會直接顯示這些字符,而不進行轉義。
原來如此, 真是長知識了. 

2024年9月23日 星期一

露天購買 ESP32E 開發板 (4MB Flash) x 3

因為零件箱只找到兩片 4MB 的 ESP32, 為了補庫存上露天找到下面這個賣家, 本來想買一片 179 元的便宜 ESP32-WROOM, 最後還是買了標示品質佳但貴了 50 元的 ESP32E 3 片 : 





全家取貨付款免運 687 元. 


2024-09-28 補充 :

今天拿一片來燒錄 MicroPython v1.23 測試正常 (晶片是 ESP32-D0WD-V3) : 

D:\ESP32>esptool --port COM6 flash_id    
esptool.py v4.6.2
Serial port COM6
Connecting.......
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting......
Detecting chip type... ESP32
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: c0:49:ef:b2:ba:60
Uploading stub...
Running stub...
Stub running...
Manufacturer: 0b
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

D:\ESP32>esptool --chip esp32 --port COM6 erase_flash    
esptool.py v4.6.2
Serial port COM6
Connecting....
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: c0:49:ef:b2:ba:60
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 9.9s
Hard resetting via RTS pin...

D:\ESP32>esptool --chip esp32 --port COM6 write_flash -z 0x1000 ESP32_GENERIC-20240602-v1.23.0.bin   
esptool.py v4.6.2
Serial port COM6
Connecting.......
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: c0:49:ef:b2:ba:60
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash will be erased from 0x00001000 to 0x001a8fff...
Compressed 1734240 bytes to 1142447...
Wrote 1734240 bytes (1142447 compressed) at 0x00001000 in 101.2 seconds (effective 137.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32

Type "help()" for more information.

>>> import config  
import xtools   
ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
token=config.LINE_NOTIFY_TOKEN
openai_api_key=config.OPENAI_API_KEY
message='test'
prompt='Who are you'
Connecting to network...
network config: ('192.168.50.3', '255.255.255.0', '192.168.50.1', '192.168.50.1')
>>> 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.'
>>> xtools.ask_gpt(prompt, openai_api_key)      
[object_link_for_thonny=1073573008]'I am an AI language model created by OpenAI, designed to assist with a wide range of questions and tasks. My purpose is to provide information, answer questions, generate text based on prompts, and engage in conversation on various topics. How can I assist you today?'
>>> prompt='你是誰?'   
>>> print(xtools.ask_gpt(prompt, openai_api_key))    
我是一個人工智慧助手,旨在幫助您解答問題和提供資訊。如果您有任何疑問或需要幫助的地方,隨時可以問我!

MicroPython 學習筆記 : 解決 xtools 函式庫在 v1.23 韌體無法傳送 Line 訊息問題

這兩天測試 MicroPython 發現最新版的 v1.23 韌體取消了 ussl 模組改用 tls 模組, 這會讓 "超簡單 Python/MicroPython 物聯網應用" 這本書所提供的好用函式庫 xtools 破功, 導致傳送 Line Notify 訊息的 line_msg() 函式出錯, 這是因為 xtools 會用到另一個 xrequests 模組, 而 xrequests 會用到 ussl 模組之故, 所以只好刷回到 v1.23 之前的韌體才能繼續使用 xtools.  

但是當我使用 v1.19 或 v1.22 版韌體的 MicroPython 串接 OpenAI API 時都會出現 OSError: -40 的系統錯誤, 經查這是舊版 MicroPython 對 SSL/TLS 支援不足所致, 改用 v1.23 版韌體就可順利串接 OpenAI API, 這變成兩難問題, 即退回使用 v1.23 前的版本 xtools 可傳 Line 但無法串接 OpenAI API; 反之, 使用 v1.23 韌體 xtools 無法傳 Line 卻能串接 OpenAI API. 解決之道應該是要要想辦法修改 xtools 函式庫讓它能在 v1.23 版韌體下兩全其美. 

昨天下了一整天雨, 我待在鄉下家測試了一整個下午, 借助 ChatGPT 協助終於搞定此問題, 測試過程紀錄如下. 

首先我將一顆 ESP8266 刷到最新的 v1.23 韌體, 利用 xtools 連上網路後不使用它的 line_msg() 含試發送 Line Notify 訊息, 而是從以前在 Cythone 上測試 Line Notify 時的筆記複製了一個自訂函式 notify() 函式過來, 然後用 MicroPython 內建的 urequest.post() 來發送 Line Notify 的 POST 請求, 參考這篇文章 :


MicroPython v1.23.0 on 2024-06-02; ESP module with ESP8266
Type "help()" for more information.

>>> import urequests  
>>> def line_msg(msg, token):    
    url="https://notify-api.line.me/api/notify"   
    headers={"Authorization": "Bearer " + token}     
    payload={"message": msg}     
    r=urequests.post(url, headers=headers, params=payload)         
    return "訊息發送成功!"   

呼叫 line_msg() 
>>> msg='test'    
>>> token="在此輸入 Line Notify token"   
>>> xtools.line_msg(msg, token)     
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in notify
  File "requests/__init__.py", line 186, in post
TypeError: unexpected keyword argument 'params'    

在 xtools.py 中 line_msg() 是呼叫自訂的 xrequests.py 模組的 post() 發出請求, 所以不會有問題, 此處改用內建的 urequests 會出現問題是因為它的 post() 方法沒有 params 參數, 我將程式與錯誤訊息拿去問 ChatGPT 得知原來 urequests.post() 要用 data 關鍵字傳遞參數而非 params, 順此思路測試下去居然就解決問題了, AI 真是好用啊!  




ChatGPT 答覆如下 : 




原來問題是出在 post() 函式應該用 data 關鍵字傳遞參數, params 是 get() 方法用的. ChatGPT 提供的建議程式碼如下 (其實就是將 params 改成 data 而已) :

def line_msg(msg, token):
    url = "https://notify-api.line.me/api/notify"
    headers = {"Authorization": "Bearer " + token}
    payload = {"message": msg}
    r = urequests.post(url, headers=headers, data=payload)  # 修改為 data
    r.close()  # 記得關閉連線
    return "訊息發送成功!"

但執行仍出現錯誤 "object with buffer protocol required" :

>>> xtools.line_msg(msg, token)    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in notify
  File "requests/__init__.py", line 186, in post
  File "requests/__init__.py", line 125, in request
TypeError: object with buffer protocol required

再次詢問 ChatGPT 才知道原來 data 參數的值必須是 byte 類型 : 




但它建議的解決方法是用 urllib.parse.urlencode() 函式將 payload 字典轉換為 URL 編碼的表單格式, 例如 {"message": "test"} 會轉換為 "message=test" : 

import urequests
import urllib.parse

def line_msg(msg, token):
    url = "https://notify-api.line.me/api/notify"
    headers = {"Authorization": "Bearer " + token, "Content-Type": "application/x-www-form-urlencoded"}    
    # 將 payload 轉換為 application/x-www-form-urlencoded 格式
    payload = {"message": msg}
    encoded_payload = urllib.parse.urlencode(payload)  # 編碼成 URL 格式
    encoded_payload = encoded_payload.encode('utf-8')  # 轉換為 bytes
    # 發送 POST 請求
    r = urequests.post(url, headers=headers, data=encoded_payload)  # 使用編碼後的 payload
    r.close()  # 記得關閉連線    
    return "訊息發送成功!"

其中 encode('utf-8') 會將編碼後的字串轉換為 bytes 類型, 因為 urequests 需要以這種格式發送 POST 資料. 但 ChatGPT 建議的程式無法執行, 因為 MicroPython 沒有支援 urllib 模組啊! 怎麼辦? 
ChatGPT 最後提供一個自訂的小函式 urlencode() 來模擬 urllib.parse.urlencode() 的功能, 它會將 params 字典轉換為 application/x-www-form-urlencoded 格式 (即 'k1=v1&k2=v2& ...' 字串) : 

def urlencode(params):
    # 將字典的鍵值對轉換為 URL 編碼的字串 (k=v) 並以 & 連接多個鍵值對
    kv=['{}={}'.format(k, v) for k, v in params.items()]
    return '&'.join(kv) 

例如 :

>>> params={'a':'1', 'b':'2', 'c':'3'}   
>>> urlencode(params)     
'b=2&c=3&a=1'   

呼叫字串的 encode() 方法即可將 URL 字串轉成 data 參數要求的 bytes 類型 : 

>>> urlencode(params).encode('utf-8')    
b'b=2&c=3&a=1'

參數字典經過這樣編碼為 bytes 類型後便可傳給 data 參數了 : 

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()  # 關閉連線

>>> import urequests   
>>> def urlencode(params):   
    # 將字典的鍵值對轉換為 URL 編碼的字串 (k=v) 並以 & 連接多個鍵值對
    kv=['{}={}'.format(k, v) for k, v in params.items()]
    return '&'.join(kv) 
>>> 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()  # 關閉連線
>>> msg='test'    
>>> token='在此輸入 Line Notify Token'   
>>> xtools.line_msg(token, msg)   
Message has been sent.
>>> xtools.line_msg(token, msg)   
Message has been sent.
>>> msg='哈囉'    
>>> xtools.line_msg(token, msg)   
Message has been sent.

哈哈可以了, 結果如下 :




ChatGPT 真是程式員的好朋友啊! 

接下來用同樣方法改寫傳送貼圖的 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."

測試也 OK :

>>> xtools.line_sticker(token, message, 1, 4)   
'The sticker has been sent.'




但改寫 line_image() 卻不順利, 經詢問 ChatGPT, 原來 LINE Notify API 傳送圖片需要使用 multipart/form-data, 而不是 application/x-www-form-urlencoded, 圖檔須用 imageFile 參數作為文件上傳, 而不能直接使用 URL 編碼處理. ChatGPT 建議的程式碼如下 :

def line_image(token, message, image_path):
    url = "https://notify-api.line.me/api/notify"
    headers = {
        "Authorization": "Bearer " + token
        # 這裡不需要手動設定 Content-Type,urequests 會自動處理 multipart/form-data
        }
    # 打開圖片文件並發送請求
    with open(image_path, 'rb') as image_file:
        # 構造 multipart/form-data 資料
        payload = {"message": message}
        files = {"imageFile": image_file}  # 圖片文件必須在這裡作為二進制數據
        # 使用 urequests.post 發送包含文件的 multipart 請求
        r = urequests.post(url, headers=headers, data=payload, files=files)
        # 判斷是否成功
        if r is not None and r.status_code == 200:
            return "The image has been sent."
        else:
            return "Error! Failed to send the image."

但是呼叫這函式會出現如下錯誤 :

>>> xtools.line_image(token, message, 'kitten.jpg')    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 19, in line_image
  File "requests/__init__.py", line 186, in post
TypeError: unexpected keyword argument 'files'   

ChatGPT 回答錯誤原因如下 :




原來 MicroPython 的 urequests 模組不支援標準 Python 中的 files 參數, ChatGPT 提出建議程式碼改用手動建構 multipart/form-data 請求來解決 :

def line_image(token, message, image_path):
    url = "https://notify-api.line.me/api/notify"
    boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'  # 自訂 boundary
    headers = {
        "Authorization": "Bearer " + token,
        "Content-Type": "multipart/form-data; boundary={}".format(boundary)
       }
    # 打開圖片文件
    with open(image_path, 'rb') as image_file:
        image_data = image_file.read()
    # 構造 multipart/form-data 請求體
    payload = (
        '--{}\r\n'
        'Content-Disposition: form-data; name="message"\r\n\r\n'
        '{}\r\n'
        '--{}\r\n'
        'Content-Disposition: form-data; name="imageFile"; filename="{}"\r\n'
        'Content-Type: image/jpeg\r\n\r\n'
        '{}\r\n'
        '--{}--\r\n'
    ).format(boundary, message, boundary, image_path, image_data.decode('latin-1'), boundary)
    # 發送 POST 請求
    r = urequests.post(url, headers=headers, data=payload)
    # 判斷是否成功
    if r is not None and r.status_code == 200:
        return "The image has been sent."
    else:
        return "Error! Failed to send the image."

但執行結果卻出現如下錯誤:
>>> xtools.line_image(token, message, 'kitten.jpg')    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in line_image
MemoryError: memory allocation failed, allocating 7168 bytes

所以最根本的問題是, ESP32 記憶體不大, 要處理圖片上傳力有未逮, 所以只好放棄. 雖然如此, 但傳送雲端圖片還是可以的, 程式如下 :

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."

我使用的測試圖片網址如下 :


>>> import urequests   
>>> message='test'    
>>> 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.'

測試結果 OK :



露天購買 ESP32-S3 開發板 x 2

最近在測試 MicroPython 傳送 Line Notify 圖片時出現重開機情況, 懷疑是 4MB 的 Flash 記憶體不夠用, 所以今天上露天買了兩片 ESP32-S3, 它具有 16MB Flash, 這樣接下來要串接 OpenAI 也有夠大記憶體可用 : 





新上明取貨付款免運 399*2=798 元. 

2024年9月22日 星期日

2024 年第 38 周記事

周二休孤立的中秋節, 少上一天班感覺很快又到周末了, 本周工作有點忙, 還上了兩堂內訓, 但都沒時間專心聽, 只好等有空看錄影. 最近學習重心轉到大語言模型 API 的串接, 終於肯花 10 美元買了隨用隨付的 OpenAI API (因為新帳戶已經不再贈送 5 美元), 下周剛好有 API 內訓課程用得上. 現在學東西都不急了, 穩紮穩打慢慢學, 我也不靠這些餬口, 況且現下也已經不是追求大富大貴的年紀了, 就只是為了治療射手座的怕無聊毛病而已.

周五晚上帶了 ESP8266/ESP32 開發板回鄉下, 這個周末都在玩 MicroPython 測試, 收穫頗豐, 除順利串接了 OpenAI 模型讓開發板能運用人工智慧外, 也對 xtools 函式庫做了大幅改版. 將近一年沒碰硬體還真是有點生疏了. 動手做還是比較有趣, 不過如能軟硬體都打通更好玩. 

昨天周六早上看天氣不錯就帶了工具再去派出所對面的家族墓園繼續上周未完的整理工作, 先把明堂內裡外都掃乾淨, 擦拭完供桌, 覺得後面的草叢有必要清除, 但這時太陽高照, 熱得我頭昏眼花汗如雨下氣喘如牛, 剩下最後一小塊想說下午有空再過來收尾, 但午後就開始下雨, 一直到今天周日都未停歇, 還好昨天早上有去, 不然本周就沒進度了. 




整理過程中發現後方混泥土牆有裂縫, 且對聯的金字部份有掉漆, 10 月要去請老師看阿蘭添金的日課, 屆時再詢問是否先修繕再添金, 還是添金完再找日子修繕. 

鄉下家的母貓小灰懷第二胎, 本周又在豬舍廁所門口鼓風車上的紙箱裡生下三隻幼崽 : 




現在小灰是六寶媽了, 雖然有了三隻小小喵要顧, 但對第一胎的三隻小喵還是很關心, 昨日下午有一隻從樹上跳上二樓平台, 它就在樹下一直喵, 好像是要叫小喵快點下來. 昨天下午三隻小喵跳下芭樂園外出沒回來, 它就一整個下午在門口對著芭樂園三不五時發出低沉的喵叫呼喚他們回家 : 




到了傍晚時分, 這三隻小喵才冒雨從芭樂田中回來 :





小灰真是個愛孩子的母貓. 

2024年9月21日 星期六

MicroPython 學習筆記 : 用 urequests 串接 OpenAI 大語言模型

這周本來都在測試 OpenAI 與 Gemini 等 LLM, 周四突然想到 ESP8266/ESP32 用 MicroPython 不知道能否串接 OpenAI 等模型, 於是找出開發板, 複習 2022~2023 年鑽研一陣子的 xtools 函式庫, 經過這幾天的把玩, MicroPython 大致回魂了. 

不過卻發現了 xtools 的一個問題, 由於 MicroPython 在 v1.23 版用 tsl 模組替換了 ussl 模組, 使得 xtools 在呼叫 urequests 與 xrequests 的函式時會出現找不到 ussl 的錯誤, 所以我又把韌體刷回 1.19 或 1.22 版, 順便改寫 xtoold 函式庫增加了傳貼圖的 line_sticker() 與傳圖片的 line_image() 函式 (但圖片不知為何傳不出來). 

晚飯後開始來嚐試參考將之前用 requests 串接 OpenAI 模型的函式改寫為 MicroPython 版 :

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}'}
    json={'model': model,
          'messages': [{'role': 'user', 'content': prompt}]} 
    response=xrequests.post(url=url,headers=headers, json=json)
    reply=response.json()
    return reply['choices'][0]['message']['content']

參考 : 


但不管使用 urequests 或 xrequests 的 post() 函式提出請求都出現錯誤, 紀錄如下 : 

>>> import xrequests   
>>> 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}'}
    json={'model': model,
          'messages': [{'role': 'user', 'content': prompt}]} 
    response=xrequests.post(url=url,headers=headers, json=json)
    reply=response.json()
    return reply['choices'][0]['message']['content']
>>> api_key='在此輸入 OpenAI API key'    
>>> prompt='Hello!'    
>>> ask_gpt(prompt, api_key)    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in ask_gpt
  File "xrequests.py", line 124, in post
  File "xrequests.py", line 61, in request
OSError: -40   

查詢 ChatGPT 得知 OSError: -40 通常是指 SSL/TLS 連線錯誤, 有可能是把韌體刷回 v1.23 以前版本之故, 因為 MicroPython v1.23 就是用 TLS 取代 SSL 來解決安全連線問題. 

搜尋谷歌找到下面這篇文章 : 


其範例程式碼如下 :


作者直接使用 urequests 模組的 post() 就能順利地得到回應, 我在想 v1.23 版韌體的 urequests 應該也有修改, 於是把一顆 ESP32 刷回最新版 v1.23 來測試. 不使用 xtools 函式庫, 用基本的 network 連上 WiFi :

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.

>>> import network   
>>> import time    
>>> def connect_wifi(ssid, password):  
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)    
    while not wlan.isconnected():
        print('Connecting to WiFi...')
        time.sleep(1)    
    print('Network Config:', wlan.ifconfig())
    
>>> ssid="ASUS-RT-AX3000"    
>>> password="123456"       
>>> connect_wifi(ssid, password)   
True
Connecting to WiFi...
Connecting to WiFi...
Connecting to WiFi...
Network Config: ('192.168.50.48', '255.255.255.0', '192.168.50.1', '192.168.50.1')

連網成功後匯入 urequests, 然後修改上面的 ask_gpt() 函式, 改用 urequests.post() :

>>> import urequests
>>> 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}'}
    json={'model': model,
          'messages': [{'role': 'user', 'content': prompt}]} 
    response=urequests.post(url=url,headers=headers, json=json)
    reply=response.json()
    return reply['choices'][0]['message']['content']

>>> prompt='Who are you?'     
>>> api_key='在此輸入 OpenAI API key'  
>>> ask_gpt(prompt, api_key)  
'I am an AI language model created by OpenAI, designed to assist with a variety of tasks, including answering questions, providing information, and generating text based on prompts. How can I help you today?'

跟它打招呼 : 

>>> prompt='Hello'   
>>> ask_gpt(prompt, api_key)      
'Hello! How can I assist you today?'   

但提示詞不能用英文以外的語言, 因為 MicroPython 只支援英文 : (見下面補充)

>>> prompt='嗨'     
>>> ask_gpt(prompt, api_key)    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in ask_gpt
KeyError: choices

真不錯, 這樣一來跑 MicroPython 的開發板也能藉助 AI 增強功能了. 

補充 :

我拿一顆 ESP8266 刷 v1.23 版韌體如法炮製卻不行 : 

>>> ask_gpt(prompt, api_key)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in ask_gpt
  File "requests/__init__.py", line 186, in post
  File "requests/__init__.py", line 95, in request
OSError: -40

詢問 ChatGPT 答覆為 ESP8266 的硬體與其 MicroPython 堆疊對 SSL/TSL 的支援有限. 所以要用 MicroPython 作 AI 應用必須採用 ESP32, 一般 4MB Flash 的就可以, 需要處理音訊或圖片則可用 16MB Flash 的 ESP32. 


2024-09-26 補充 : 

今天將上面以中文詢問出現錯誤的訊息 ChatGPT, 原來原因不是 MicroPython 不支援中文, 而是跟傳 Line Notify 訊息出錯原因一樣, 使用 urequests.post() 時應該將 payload 轉成字串後呼叫 encode() 以 utf-8 編碼為 bytes 類型用 data 參數傳送 (可見 MicroaPython 的 urequests 與 CPython 的 requests 模組不同, requests 用 json 參數傳遞字典沒問題, 但 xrequests 不行, 需用 data 參數) :

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()  # 返回錯誤信息

測試結果中英文都 OK :

>>> prompt='Hello'    
>>> reply=xtools.ask_gpt(prompt, openai_api_key)    
>>> print(reply)   
Hello! How can I assist you today?   
>>> prompt='嗨'    
>>> reply=xtools.ask_gpt(prompt, openai_api_key)   
>>> print(reply)     
嗨!你好!有什么我可以帮助你的吗?

注意! 此處如果提示詞是中文, ask_gpt() 的回應預設會用簡體中文的 unicode 字串表示, 需用 print() 輸出才會解碼為中文, 例如 :

>>> 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(xtools.ask_gpt(prompt, openai_api_key))    
我是ChatGPT,一個由OpenAI開發的人工智慧語言模型。我的目的是幫助回答問題、提供資訊和進行對話。你有什麼想知道的嗎?

此函式已新增到 xtools.py 函式庫裡, 參考 :


MicroPython 學習筆記 : ESP8266/ESP32 網路存取測試 (五)

在陳會安老師寫的 "超簡單 Python/MicroPython 物聯網應用" 這本書中提供了一個非常好用的 xtools 模組, 呼叫裡面的 line_msg() 即可傳送 Line Notify 訊息. 但是 line_msg() 只能傳送文字訊息, 無法傳送貼圖與圖片. 

今天參考之前在 PC 上傳送 Line 貼圖的函式, 改寫為可在 MicroPython on ESP8266/ESP32 上執行的函式 line_sticker(), 參考 :

#  Python 學習筆記 : Line Notify 訊息推播 (二) : 貼圖

from xrequests import post 

def line_sticker(token, message, stickerPackageId, stickerId):    
    url="https://notify-api.line.me/api/notify"   
    headers={"Authorization": "Bearer " + token}     
    payload={"message": message,      
             "stickerPackageId" : stickerPackageId,      
             "stickerId": stickerId}   
    r=post(url, headers=headers, params=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."

其中 post() 是 xrequests 模組的函式, 須在前面匯入此函式. 注意, 此處要使用可傳參數的 xrequests 模組而非 MicroPython 內建的 urequests 模組, 因為它無法傳遞參數. 

傳送圖片的函式 line_image() 則是參考下面這篇改寫 :


同樣也是要先匯入 xrequests.post() 函式 : 

from xrequests import post 

def line_image(token, message, image):    
    url="https://notify-api.line.me/api/notify"
    headers={"Authorization": "Bearer " + token}
    payload={"message": message, 
             "imageFile" : open(image, 'rb')}    
    r=post(url, headers=headers, params=payload)
    if r is not None and r.status_code == 200:    
        return "The image has been sent."      
    else:     
        return "Error! Failed to send the image."
 
我把上面兩個函式添加到 xtools.py 模組中後測試可送出貼圖 :  

>>> import xtools    
>>> import config  
>>> ip=xtools.connect_wifi_led(config.SSID, config.PASSWORD)  
network config: ('192.168.192.86', '255.255.255.0', '192.168.192.92', '192.168.192.92')  
>>> token=config.LINE_NOTIFY_TOKEN   
>>> message='test'    
>>> xtools.line_sticker(token, message, 1, 4)      
'The sticker has been sent.'
>>> xtools.line_sticker(token, message, 1, 3)   
'The sticker has been sent.'
>>> xtools.line_sticker(token, message, 1, 13)    
'The sticker has been sent.'

結果如下 :




接下來測試傳送圖片, 我先將一張 16KB 大小的圖片 kitten.jpg 上傳到開發板根目錄下  




然後將之前在 PC 上測試的函式修改為如下的 MicroPython 版函式 line_image(), 參考 :


from xrequests import post

def line_image(token, message, image):    
    url="https://notify-api.line.me/api/notify"
    headers={"Authorization": "Bearer " + token}
    payload={"message": message, 
             "imageFile" : open(image, 'rb')}    
    r=post(url, headers=headers, params=payload)
    if r is not None and r.status_code == 200:    
        return "The image has been sent."      
    else:     
        return "Error! Failed to send the image."

此處同樣是要用到 xrequests.post() 送出 POST 請求. 將其添加到 xtools.py 中測試, 結果只有訊息有傳, 圖片沒傳, 而且很常當機須重啟 (不論是 ESP8266/ESP32 皆如此)  :




不知是不是 4MB 記憶體太小所致 (溢位造成重新啟動)? 等有 16MB 的 ESP32 時再來測試, line_image() 就暫且還是放在 xtools.py 裡面吧! 我已更新 GitHub 上的 xtools.py 檔案與 lib.zip :