2026年10月17日 星期六

從活動照片到會動的繪本:AI 生圖與 Google Flow 影片實作

AI 創作實作|照片 × 插畫 × 影片

從活動照片到會動的繪本

把孩子操作機器人的真實活動照片,先轉成溫暖明亮的手繪插畫,再用 Google Flow 生成一段短影片,記錄科技教育活動的另一種方式。

一張活動照片記錄了當下;透過生成式 AI,我們還能把它轉化成具有繪本感的插畫,接著讓畫面中的人物和物件產生細微動作。本次以孩子們圍著 TaleMap 地圖、操作程式機器人的活動為例,實際完成「照片轉插畫」和「插畫轉影片」兩個步驟。

01|活動照片保留真實場景與互動
02|生成插畫轉換畫風,維持主要構圖
03|Flow 影片加入輕柔動作與鏡頭推近

一、從真實活動照片開始

原始照片中,孩子們圍在大型 TaleMap 地圖旁,觀察圖卡並操作白色程式機器人。照片還包含帶領活動的學生、桌面教材,以及右側的教具。生圖時最重要的是先說明哪些內容要保留:人物相對位置、姿勢、服裝、活動用品和現場情境。


二、用提示詞把照片轉成繪本插畫

提示詞可以分成四個部分:指定照片作為參考、列出要保留的內容、描述插畫風格,再補上不希望出現的改動。把構圖要求寫清楚,通常比只寫「變成可愛插畫」更容易得到符合活動紀錄用途的結果。

本次生圖提示詞

請以我上傳的照片為構圖與人物活動參考,將照片轉換成溫暖明亮的日系手繪插畫。保留照片中的主要人物、動作、互動關係、場景配置與重要物件,讓人仍能辨認原本的活動情境。

畫面採用兒童繪本插畫風格,結合水彩暈染與色鉛筆筆觸;人物比例自然可愛,表情生動親切,輪廓以細緻清楚的手繪線條呈現。使用明亮飽和的藍色、湖水綠、橘色與黃色,光線柔和,整體氣氛熱鬧、溫馨、有教育活動紀錄感。背景細節豐富但不凌亂,保留現場的展示物與活動用品。

請勿新增照片中沒有的人物或重要物件;不要改變人物的主要姿勢與相對位置。畫面呈現精緻、完整、適合活動成果展示的插畫,不要加入文字、標題、浮水印或邊框。


生成結果:保留孩子、地圖、機器人與活動互動,並轉為水彩與色鉛筆繪本風格。
觀察:插畫呈現了明亮色彩與手繪筆觸,也保留了地圖、機器人和孩子們操作教具的核心情境。生成式模型仍可能調整人物細節或重新詮釋背景,因此若要精準呈現活動紀錄,應以原照片為參考,並逐項寫明不可變動的元素。

三、把插畫放進 Google Flow 生成影片

完成插畫後,將圖片上傳到 Google Flow,選擇以圖片作為影片起始畫面的功能,再描述希望出現的動作與鏡頭運動。本次設計的是幅度較小的動作:孩子低頭看地圖、按下機器人按鍵,機器人在圖卡上緩慢前進;鏡頭則從全景稍微推近。

  1. 進入 Google Flow,建立新專案。
  2. 選擇可使用圖片作為起始畫面的影片功能(介面名稱與可用選項可能依帳號及模型而異)。
  3. 上傳生成的插畫,設定影片比例、長度與模型。
  4. 貼上動作提示詞,生成後檢查人物、地圖和機器人的連貫性。
  5. 若動作太大或場景變化過多,縮短描述並強調固定構圖,再重新生成。
本次 Flow 影片提示詞

以這張插畫作為影片起始畫面,保留孩子們圍著 TaleMap 操作機器人的構圖、服裝與人物位置。孩子自然地低頭看地圖、輕輕按下機器人按鍵,彼此微笑互動;機器人在地圖上緩慢前進。鏡頭平穩地由全景稍微推近,呈現溫暖明亮的兒童繪本氣氛,水彩與色鉛筆筆觸保持一致。動作自然、幅度小,不增加人物或物件,不改變場景,不出現文字或字幕。

Google Flow 生成的短影片:讓插畫中的孩子、地圖與機器人產生輕柔動態。

四、提示詞怎麼寫,影片會更穩定?

照片或插畫轉影片時,靜態畫面已經提供了人物和場景資訊,提示詞可以把重點放在「誰做什麼、動作多大、鏡頭怎麼移動」。動作越精簡、鏡頭越穩定,越容易維持原畫面。若一次要求多人做不同動作、快速移動鏡頭或大幅改變場景,生成結果就比較容易偏離原圖。

  • 人物動作:低頭看地圖、輕按按鍵、微笑互動。
  • 物件動作:機器人慢慢沿著地圖移動。
  • 鏡頭動作:平穩推近,避免快速旋轉或切換角度。
  • 一致性要求:保留人物位置、服裝、場景與插畫筆觸。
  • 排除事項:不增加人物、不改變場景、不生成字幕或文字。

五、從一張照片延伸出新的活動紀錄

這次實作把真實活動、繪本插畫與短影片連成一個簡單的創作流程。插畫可以用在成果報告、課程簡報或活動回顧;短影片則適合用於社群貼文或課程展示。對教育活動而言,AI 不只是把畫面變得更可愛,也提供了另一種說故事的方法:讓孩子的學習互動,以影像形式再次被看見。

使用提醒:若作品將公開發布,請確認照片中人物的肖像使用同意與活動素材授權。生成畫面可能改變臉部、文字或細節,重要資訊應以原始紀錄核對。Flow 的模型、功能、輸出長度和點數規則可能隨官方更新而調整,操作時請以平台當下顯示為準。

延伸參考:Google Flow 模型與支援功能說明|Google Flow 創作技巧

照片轉插畫與 Flow 影片生成實作|以兒童機器人地圖活動為例

2026年10月16日 星期五

泡泡牆為什麼能從另一台電腦開啟?IP、連接埠與區域網路入門

網際網路入門・Django 泡泡牆實作

泡泡牆為什麼能從另一台電腦開啟?
認識 IP、連接埠與區域網路

老師在一台電腦啟動 Django 泡泡牆,學生卻能用自己的手機或筆電投稿,投影幕也能即時出現新泡泡。這個過程不只是「打開一個網址」,而是裝置在區域網路中找到伺服器,並透過指定連接埠交換資料。

一、先看懂連線中的角色

角色在泡泡牆中的例子工作
用戶端學生手機、筆電、投影瀏覽器提出網頁請求、顯示畫面、送出資料
伺服器執行 Django 的教師電腦接收請求、驗證資料、查詢或更新資料庫、回應結果
區域網路教室 Wi‑Fi 或有線網路讓同一網路中的裝置彼此傳送資料

本例中,學生瀏覽器是用戶端,教師電腦是伺服器。Django 接到投稿後會儲存泡泡,再通知投影頁更新。

二、IP 位址:在網路中找到那台電腦

IP 位址像是裝置在網路上的地址。教室區域網路常見的私有 IPv4 位址會以 192.168.、10. 或 172.16 至 172.31 開頭。例如 Django 主機可能是 192.168.1.100。

在 Windows 主機開啟 CMD,輸入以下指令查詢網路設定:

ipconfig

找到正在使用的 Wi‑Fi 或乙太網路介面,查看「IPv4 位址」。遠端裝置要連線時,使用的就是這個主機 IP。主機若重新連接網路,IP 可能改變,需要重新查詢。

注意:127.0.0.1 是「本機迴路」位址,代表目前正在使用的這台電腦。遠端學生輸入 127.0.0.1 會連回自己的手機或電腦,因此要輸入 Django 主機的區域網路 IP。

三、連接埠:找到電腦裡正確的服務

一台電腦可能同時提供許多網路服務。連接埠(Port)用來區分資料應該交給哪個服務。泡泡牆開發時使用連接埠 8000,所以網址會寫成:

http://192.168.1.100:8000/

其中 192.168.1.100 是主機 IP,8000 是 Django 服務使用的連接埠,最後的斜線代表網站路徑。完整網址可拆成:

網址部分例子說明
通訊協定http://瀏覽器與網站使用 HTTP 溝通
主機位置192.168.1.100要連線的 Django 電腦
連接埠:8000該電腦上的 Django 服務
路徑/projector/要開啟的網站功能

四、為什麼要用 0.0.0.0 啟動?

Django 的開發伺服器預設只接受本機連線。若要讓同一區域網路的其他裝置連線,要讓它監聽主機的網路介面。進入 Django 專案資料夾後,可在 Windows CMD 執行:

set DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,192.168.1.100
set BUBBLE_TOKENS=class-a-2026
set BUBBLE_ADMIN_TOKEN=teacher-admin-2026
python manage.py runserver 0.0.0.0:8000
  • 0.0.0.0:8000:Django 在所有可用網路介面的 8000 埠接收連線。
  • DJANGO_ALLOWED_HOSTS:列出 Django 接受的主機名稱或 IP,請換成自己的主機位址。
  • BUBBLE_TOKENS:學生投稿時使用的活動 token。
  • BUBBLE_ADMIN_TOKEN:管理頁清空泡泡時使用的管理 token,應與投稿 token 不同。

這些 set 指令設定的值只存在於目前 CMD 視窗。關閉視窗後,需重新設定並啟動伺服器。

五、遠端裝置如何連線

學生手機或筆電連上相同 Wi‑Fi 後,在瀏覽器輸入:

  • 投稿頁:http://192.168.1.100:8000/
  • 投影頁:http://192.168.1.100:8000/projector/
  • 管理頁:http://192.168.1.100:8000/manage/

網址中的 IP 請替換成 Django 主機的實際 IPv4 位址。學生在投稿頁填入活動 token;教師若要清空本場泡泡,則到管理頁輸入管理 token。

六、HTTP 請求如何抵達 Django?

當學生按下送出,瀏覽器會向伺服器傳送 HTTP POST 請求。網路設備依照目標 IP 將資料送到教師電腦,作業系統再依連接埠把請求交給 Django。Django 驗證 token 和欄位後回傳結果;投影頁則透過 SSE 接收新泡泡事件。

動作請求/通訊結果
打開投稿頁瀏覽器向 Django 要求 HTML、CSS、JavaScript顯示投稿表單
送出泡泡POST /api/bubbles,附上 JSON 與 tokenDjango 驗證並寫入資料庫
投影端載入舊資料GET /api/state回傳目前泡泡清單
出現新泡泡/api/events 的 SSE 推播投影頁即時更新,不必重新整理

七、Windows 防火牆與網路限制

即使 IP 和網址正確,防火牆仍可能阻擋其他裝置連入。首次執行 Python 時,若 Windows 顯示網路存取提示,應在可信任的教室私人網路中允許通訊。必要時請管理者設定允許 TCP 8000 埠的私人網路入站規則。

部分校園、訪客 Wi‑Fi 會啟用用戶端隔離,阻止同一 Wi‑Fi 的裝置互相連線。若兩台設備都連上網路但仍無法互通,可請網管確認此設定,或改用允許裝置互通的教學網路。

八、連線檢查清單

  1. 主機上 Django 是否正在執行,且啟動指令包含 0.0.0.0:8000?
  2. 遠端裝置是否使用主機 IPv4,而不是 127.0.0.1?
  3. 網址是否包含 :8000 連接埠?
  4. DJANGO_ALLOWED_HOSTS 是否包含該 IP?
  5. Windows 防火牆是否允許私人網路連線?
  6. 兩台設備所連的 Wi‑Fi 是否允許用戶端彼此通訊?

九、區域網路與公開網際網路

使用主機私有 IP 的方式,只適合可互相連線的同一區域網路。校外的電腦通常不能直接使用 192.168.x.x 連到教室主機。若需要公開服務,應部署到正式伺服器、設定網域與 HTTPS,並妥善管理密鑰。

安全提醒:python manage.py runserver 是開發伺服器,適合本機開發或受控教室網路示範。不要直接把它公開到網際網路,也不要將活動 token 或管理 token 放在公開文章或程式碼中。

十、課堂延伸問題

  • 為什麼同一台電腦上的 127.0.0.1 和 192.168.1.100 指向不同連線情境?
  • 一台電腦開啟多個網站服務時,如何利用連接埠區分它們?
  • 若主機 IP 改變,學生書籤中的網址會發生什麼事?
  • 若教室網路不允許裝置彼此連線,有哪些可行的部署或網路安排?

從泡泡牆的遠端操作開始,學生可以逐步理解 IP 如何定位主機、連接埠如何定位服務、HTTP 如何交換資料,也能認識防火牆和區域網路規則對網站連線的影響。把這些概念與親手操作連在一起,網際網路就不再只是抽象名詞。

從遠端電腦操作 Django 泡泡牆:區域網路設定教學

DJANGO 泡泡牆・網路實作

從遠端電腦操作 Django 泡泡牆
區域網路設定教學

泡泡牆網站只需在一台電腦啟動 Django,其他電腦便可透過瀏覽器連線投稿或投影。本文以 Windows 主機為例,說明如何在教室的同一個 Wi‑Fi/區域網路內設定與操作。

先了解架構:一台電腦擔任 Django 主機,負責執行網站與保存泡泡資料;其他電腦、平板或手機是用戶端,透過瀏覽器連線。遠端裝置不需要另外安裝 Django。

一、適用情境與準備項目

  • 一台已解壓縮 Django 泡泡牆專案、能執行 Python 的主機。
  • 主機和遠端電腦連接同一個 Wi‑Fi 或區域網路。
  • 主機的區域網路 IP 位址,以及活動投稿 token。

以下假設專案已依照泡泡牆安裝教學完成套件安裝和資料庫遷移。若尚未安裝,請先在專案目錄執行 pip install -r requirements.txt 和 python manage.py migrate。

二、在 Django 主機查詢 IP 位址

步驟 1:開啟命令提示字元

在執行 Django 的 Windows 電腦按下「開始」,搜尋並開啟「命令提示字元」(CMD),輸入:

ipconfig

找到目前正在使用的 Wi‑Fi 或乙太網路介面,查看「IPv4 位址」。以下用 192.168.1.100 作為示例,實際操作時請換成您電腦顯示的 IP。

三、允許區域網路連線並啟動 Django

步驟 2:切換到專案資料夾

在 CMD 用 cd 進入包含 manage.py 的資料夾。例如:

cd C:\Users\學生\Desktop\bubblewall-django

請依照您實際解壓縮的位置修改路徑。

步驟 3:啟用虛擬環境(若有建立)

.venv\Scripts\activate

若命令提示字元前面出現 (.venv),表示虛擬環境已啟用。

步驟 4:設定允許的主機 IP、活動 token 並啟動伺服器

在同一個 CMD 視窗依序執行下列指令。請把 IP 換成主機的 IPv4 位址,也可將 token 換成您要提供給學生的活動通行碼。

set DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,192.168.1.100
set BUBBLE_TOKENS=class-a-2026
python manage.py runserver 0.0.0.0:8000

0.0.0.0:8000 表示 Django 開發伺服器接受主機其他網路介面的連線;若只執行 python manage.py runserver,通常只能由主機本身開啟。

請讓這個 CMD 視窗保持開啟,關閉視窗或停止伺服器後,遠端就無法連線。用 set 設定的環境變數只在目前 CMD 視窗有效。

四、從遠端電腦開啟泡泡牆

在遠端電腦確認已連上與主機相同的 Wi‑Fi,接著打開 Chrome、Edge 或其他瀏覽器,輸入主機 IP 和連接埠:

用途範例網址
學生投稿頁http://192.168.1.100:8000/
投影展示頁http://192.168.1.100:8000/projector/

請將範例網址中的 192.168.1.100 改成主機實際的 IPv4 位址。學生在投稿頁輸入活動 token、想法、隊伍名稱並選擇顏色,送出後投影頁會顯示新泡泡。

五、Windows 防火牆設定

若遠端電腦無法開啟網址,可能是 Windows 防火牆阻擋連線。首次啟動 Python 時若出現防火牆提示,允許 Python 在私人網路通訊。若沒有提示,可在 Windows「防火牆與網路保護」設定中,允許 Python 通過私人網路防火牆。

也可以由管理者建立入站規則,允許 TCP 連接埠 8000 在私人網路使用。請只開放教學場域需要的網路範圍。

六、常見問題排查

現象檢查方式
瀏覽器顯示無法連線確認 Django 主機的 CMD 視窗仍在執行;確認網址 IP 正確、連接埠是 8000,且兩台裝置在相同網路。
出現 DisallowedHost確認 DJANGO_ALLOWED_HOSTS 包含主機的 IPv4 位址;修改後重新啟動 Django。
網頁可開啟,但投稿失敗確認 token 與主機設定的 BUBBLE_TOKENS 相同;檢查文字、隊伍名稱與顏色欄位。
同一 Wi‑Fi 還是互相連不到查看防火牆是否允許私人網路;詢問網管 Wi‑Fi 是否啟用用戶端隔離。部分校園或訪客網路會禁止裝置彼此連線。
主機 IP 改變後網址失效重新執行 ipconfig 查詢 IPv4,並用新的 IP 開啟網址;也要更新允許的主機設定。

七、在外網或校外使用

上述方式適合教室內同一個區域網路使用。若要讓校外網路或不同網路的電腦連線,建議將網站部署到正式伺服器或雲端主機,設定網域名稱與 HTTPS,再妥善管理活動 token。

重要:python manage.py runserver 是 Django 開發用伺服器,不適合直接公開到網際網路。不要只為了遠端連線,就在路由器上將 8000 埠公開轉發到網路。

八、課堂操作流程建議

  1. 教師電腦啟動 Django,並將投影頁投放到教室螢幕。
  2. 教師確認主機 IP,將投稿網址和活動 token 提供給學生。
  3. 學生使用自己的電腦或手機開啟投稿頁,送出文字與顏色。
  4. 全班觀察投影頁即時出現泡泡,討論瀏覽器、HTTP 請求、伺服器、資料庫和 SSE 的分工。

透過這個流程,學生能把「網路上的資料如何傳送」與「多媒體內容如何呈現」連結起來,從實際操作理解網站與互動系統的基本架構。

用 Django 打造即時泡泡牆:從網際網路理論到多媒體實作

網際網路 × 多媒體應用 × Django 實作

用 Django 打造即時泡泡牆
從網際網路理論到多媒體實作

一個泡泡牆看起來像是把文字變成彩色圓泡泡,背後其實串起了瀏覽器、HTTP、JSON、伺服器、資料庫與即時推播。本文以 Django 製作一套可在教室展示的泡泡牆,帶大一新生透過完整作品認識網際網路與多媒體應用的基礎。






一、設計動機

大一新生初次接觸網際網路與多媒體應用時,常會分別學到網頁、伺服器、資料格式、資料庫和視覺設計等名詞,卻不容易看見它們如何合作。泡泡牆把這些概念放進一個能親手操作、能立即看見結果的情境:學生輸入一句想法,按下送出,教室投影畫面就多出一顆彩色泡泡。

因此,這個作品的動機是讓大一新生瞭解網際網路以及多媒體應用的理論基礎。學生可以沿著一則資料的旅程,觀察瀏覽器如何發出請求、Django 如何驗證與處理資料、資料如何存進資料庫,以及伺服器如何把新內容即時送到投影畫面。

二、教學目的

  • 認識用戶端與伺服器的分工,理解瀏覽器如何透過 HTTP 與網站溝通。
  • 分辨 GET 與 POST 的用途,讀懂 200、201、400、401 等狀態碼。
  • 理解 JSON 如何承載文字、顏色、隊伍等結構化資料。
  • 了解 Django URL 路由、View、Model 與 SQLite 資料庫的關係。
  • 體驗 Server-Sent Events(SSE)如何將新泡泡由伺服器推送到投影瀏覽器。
  • 連結多媒體的文字、色彩、動態效果與全螢幕展示,思考科技如何支持課堂互動。
學習成果:完成後,學生不只會看到泡泡牆的畫面,也能說明一顆泡泡如何從投稿表單出發,經過網路傳輸、伺服器處理與資料儲存,最後出現在投影幕上。

三、系統如何運作

學生瀏覽器HTTP POSTDjango API寫入SQLite
投影瀏覽器GET /api/state載入既有泡泡
Django SSE推送新事件投影瀏覽器新增泡泡

學生端將文字、顏色、隊伍名稱組成 JSON,並在 HTTP 標頭附上 Bearer token。伺服器檢查 token 和欄位後,建立資料記錄,回傳 201 Created。同時,SSE 連線會收到新泡泡事件,讓投影畫面無須重新整理就能更新。

介面方法與路徑用途成功/錯誤回應
載入泡泡GET /api/state取得既有泡泡200 OK + JSON
新增泡泡POST /api/bubbles驗證並儲存投稿201 Created;格式錯誤 400;token 無效 401
即時事件GET /api/events維持 SSE 連線、接收新泡泡text/event-stream

四、重要程式解析

1. Model:定義泡泡資料

Django Model 描述一筆泡泡要保存哪些欄位。執行資料庫遷移後,Django 會依此建立資料表。

from django.db import models

class Bubble(models.Model):
    text = models.CharField(max_length=160)
    color = models.CharField(max_length=7)
    team = models.CharField(max_length=40)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        ordering = ['-created_at']

    def as_event(self):
        return {
            'id': self.pk,
            'text': self.text,
            'color': self.color,
            'team': self.team,
            'created_at': self.created_at.isoformat(),
        }

CharField 用來保存文字欄位,auto_now_add=True 會在建立記錄時自動記下時間。as_event() 將 Model 物件轉成適合 JSON 傳輸的 Python 字典。

2. URL 路由:把網址交給對應功能

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home'),
    path('projector/', views.projector, name='projector'),
    path('api/state', views.state, name='state'),
    path('api/bubbles', views.create_bubble, name='create_bubble'),
    path('api/events', views.events, name='events'),
]

路由可以想成網站的分機表:收到不同路徑,Django 就呼叫不同的 View。一般頁面負責回傳 HTML;以 /api/ 開頭的路徑主要交換資料。

3. View:驗證資料、存入資料庫並回傳狀態碼

以下節錄新增泡泡的關鍵邏輯。實際專案也會檢查 JSON 格式、欄位長度與色碼格式。

@require_POST
def create_bubble(request):
    token = request.headers.get('Authorization', '')
    token = token.removeprefix('Bearer ').strip()

    if not token or token not in settings.BUBBLE_TOKENS:
        return JsonResponse(
            {'error': 'unauthorized'}, status=401
        )

    data = json.loads(request.body)
    text = data.get('text', '').strip()
    color = data.get('color', '')
    team = data.get('team', '').strip()

    # 正式程式在此檢查欄位內容與格式
    bubble = Bubble.objects.create(
        text=text, color=color.upper(), team=team
    )
    return JsonResponse(bubble.as_event(), status=201)

Authorization: Bearer ... 是 HTTP 標頭中的通行憑證。驗證失敗回傳 401;資料欄位無效回傳 400;建立成功回傳 201。這些狀態碼是瀏覽器或其他用戶端判斷結果的依據。

安全提醒:教學原型使用環境變數設定 token;正式活動應將預設示範 token 換掉,避免把 token 寫在公開程式碼中。登入表單送出的 token 會由瀏覽器傳送到網站,因此公開部署須使用 HTTPS。

4. SSE:讓投影畫面收到新泡泡

SSE 是由伺服器單向推送資料到瀏覽器的機制。它適合「學生送出、投影端即時顯示」這種更新方向單純的情境。

def events(request):
    q = Queue()
    with _subscribers_lock:
        _subscribers.add(q)

    def stream():
        try:
            yield 'retry: 3000\n\n'
            while True:
                try:
                    payload = q.get(timeout=15)
                    yield f'event: bubble\ndata: {payload}\n\n'
                except Empty:
                    yield ': keep-alive\n\n'
        finally:
            with _subscribers_lock:
                _subscribers.discard(q)

    response = StreamingHttpResponse(
        stream(), content_type='text/event-stream'
    )
    response['Cache-Control'] = 'no-cache'
    response['X-Accel-Buffering'] = 'no'
    return response

投影瀏覽器先呼叫 /api/state 載入舊泡泡,再使用 EventSource 訂閱新事件:

fetch('/api/state')
  .then(response => response.json())
  .then(data => data.bubbles.forEach(addBubble));

const stream = new EventSource('/api/events');
stream.addEventListener('bubble', event => {
  const bubble = JSON.parse(event.data);
  addBubble(bubble);
});

收到資料後,JavaScript 建立泡泡元素、套用顏色,並將文字放入頁面。CSS 負責圓形外觀、陰影、浮動動畫和手機版排版,構成多媒體呈現的一部分。

五、執行步驟

步驟 1:準備環境

先安裝 Python 3.10 以上版本,將 bubblewall-django.zip 解壓縮到一個工作資料夾。開啟 Windows 命令提示字元(CMD),使用 cd 切換到含有 manage.py 的專案目錄。

cd C:\\Users\\學生\\Desktop\\bubblewall-django

步驟 2:建立虛擬環境並安裝 Django

python -m venv .venv
.venv\\Scripts\\activate
pip install -r requirements.txt

看到命令提示字元前面出現 (.venv),表示虛擬環境已啟用。

步驟 3:設定活動 token

原型預設 token 是 demo-token,直接啟動也能測試。若要指定活動用 token,可在同一個 CMD 視窗執行:

set BUBBLE_TOKENS=class-a-2026

此 token 會用來驗證學生的投稿請求。CMD 視窗關閉後,這項設定會消失;正式部署需改用適當的環境變數管理方式。

步驟 4:建立資料表並啟動伺服器

python manage.py migrate
python manage.py runserver

看到 Django 的開發伺服器已啟動後,在同一個 Wi-Fi 網路中,可先用本機網址測試 http://127.0.0.1:8000/。開發伺服器預設只供本機使用;若要讓教室其他設備連線,需設定主機位址、防火牆及允許的網域。

步驟 5:開啟投稿頁與投影頁

  • 學生投稿頁:http://127.0.0.1:8000/
  • 投影展示頁:http://127.0.0.1:8000/projector/

用兩個瀏覽器視窗開啟這兩個頁面。在投稿頁輸入想法、隊伍、選擇顏色和 token,送出後觀察投影頁是否即時出現泡泡。

步驟 6:用 curl 測試 API

開啟另一個 CMD 視窗,以 curl 直接送出 JSON。若將 token 設定為示範值,命令如下:

curl.exe -X POST http://127.0.0.1:8000/api/bubbles ^
  -H "Authorization: Bearer demo-token" ^
  -H "Content-Type: application/json" ^
  -d "{\"text\":\"讓想法一起發光\",\"color\":\"#61D7C5\",\"team\":\"海洋隊\"}"

成功時會回傳泡泡資料及 HTTP 201。把 token 改錯可觀察 401;把色碼改成不符合格式的內容,則可觀察 400。

六、課堂討論與延伸

  1. 如果投稿資料只有文字、沒有顏色,JSON 欄位與表單要如何調整?
  2. 為什麼投影端要先載入既有泡泡,再訂閱 SSE?如果順序相反,可能發生什麼情況?
  3. GET 和 POST 在這個系統中各自負責什麼?為什麼新增資料不使用 GET?
  4. 除了隊伍名稱與文字,還可以加入圖片、表情符號或音效嗎?前端需要做哪些變更?
  5. 目前資料庫記錄會保留。若每次活動要建立獨立泡泡牆,可加入活動代碼或活動模型來分流資料。

七、原型限制與下一步

目前版本使用 SQLite,適合課堂示範和小型單機活動。SSE 訂閱者清單保存在單一 Django 程序的記憶體中,因此多程序、多台主機部署時,事件不會自動跨程序同步;正式擴充時可改用 Redis Pub/Sub 或 Django Channels。正式上線也應使用 HTTPS、妥善管理 token,並搭配正式的 Django 部署伺服器。

這個作品的價值,在於把抽象網路概念變成可觀察的資料流程。學生每送出一顆泡泡,就能看見用戶端、伺服器、資料庫與多媒體畫面如何協同工作,逐步建立對網際網路與互動應用的整體理解。

2026年10月14日 星期三

從資料夾到網站:HTML、CSS、JavaScript 與 Python 網頁伺服器

網站製作入門・從零開始

從資料夾到網站:HTML、CSS、JavaScript 與 Python 網頁伺服器

想做一個自己的網頁,不需要一開始就架設正式主機。先建立網站資料夾、製作首頁,再用 Python 內建的簡易 HTTP Server,就能在自己的電腦上預覽網站。

一、先認識網站資料夾與目錄

網站通常由多個檔案組成,例如網頁、樣式表、圖片和程式。把它們放在同一個專案資料夾,再依用途建立子資料夾,可以讓檔案容易管理。資料夾在命令列中也常稱為「目錄」。

website_demo/
├── index.html       ← 網站首頁
├── css/
│   └── style.css    ← 網頁樣式
├── js/
│   └── main.js      ← 互動程式
└── images/
    └── banner.jpg   ← 圖片

範例先把所有內容放在 website_demo,只建立 index.html 也能開始。日後內容變多,再將 CSS、JavaScript 和圖片分別整理到子目錄。

二、首頁為什麼常叫 index.html?

index.html 是常見的預設首頁檔名。當伺服器收到某個目錄的請求,例如瀏覽器開啟網站根網址時,通常會優先尋找該目錄中的 index.html,因此不必在網址後面輸入檔名。

「首頁」是網站入口頁面的角色;index.html 則是大家常用來命名這個檔案的名稱。若改成 hello.html,瀏覽器就要用完整路徑開啟該檔案。

三、HTML、CSS、JavaScript 各自負責什麼?

技術主要工作可以想成
HTML安排標題、段落、圖片、連結等內容結構房子的結構與房間配置
CSS設定顏色、字型、間距、版面與響應式呈現室內裝潢與外觀
JavaScript處理按鈕、表單、選單等互動行為讓設備動起來的控制系統

三者可以寫在同一個 HTML 檔案,也可以拆成不同檔案。初學時可以先在 HTML 裡寫少量 CSS;網站長大後,再分檔管理,閱讀與維護會更清楚。

四、操作步驟:建立並編輯首頁

  1. 在電腦中建立一個資料夾,例如 website_demo。
  2. 開啟 Notepad++,選擇「檔案」→「新增」。
  3. 輸入以下 HTML 範例。
  4. 選擇「檔案」→「另存新檔」,存入 website_demo,檔名輸入 index.html。確認副檔名是 .html,不要存成 index.html.txt。
<!doctype html>
<html lang="zh-Hant">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>我的第一個網站</title>
  <style>
    body {
      font-family: "Microsoft JhengHei", sans-serif;
      max-width: 720px;
      margin: 4rem auto;
      padding: 0 1.25rem;
      color: #24352f;
      background: #f4f8f5;
    }
    h1 { color: #176b52; }
    button { padding: .6rem 1rem; cursor: pointer; }
  </style>
</head>
<body>
  <h1>你好,網站!</h1>
  <p>這是我的第一個 HTML 網頁。</p>
  <button id="helloButton">按我看看</button>
  <p id="message"></p>

  <script>
    document.getElementById("helloButton").addEventListener("click", function () {
      document.getElementById("message").textContent = "JavaScript 讓網頁可以互動!";
    });
  </script>
</body>
</html>

這個範例以 HTML 建立標題、段落與按鈕;<style> 裡的 CSS 負責外觀;<script> 裡的 JavaScript 則在按鈕被點擊時改變文字。

五、在命令提示字元切換目錄

按下 Windows 鍵,輸入 cmd 並開啟「命令提示字元」。使用 cd(change directory)切換目前所在目錄。

cd /d "C:\Users\你的帳號\Documents\website_demo"

請把範例路徑換成自己資料夾的實際位置。Windows 下若目錄在不同磁碟,使用 /d 也會一併切換磁碟。路徑中有空格時,請保留雙引號。

切換後可輸入 dir 查看檔案清單,確認目前目錄內有 index.html。

六、啟動 Python 網頁伺服器:解讀指令

在網站目錄中執行:

python -m http.server 8000
指令部分意思
python啟動 Python 解譯器。
-m要求 Python 以模組方式執行指定模組。
http.serverPython 內建的簡易 HTTP 伺服器模組,用來提供目前目錄中的檔案。
8000伺服器使用的埠號;瀏覽器會透過這個埠連線。

成功啟動時,命令提示字元會顯示類似 Serving HTTP on ... port 8000 的訊息。伺服器啟動期間,這個視窗要保持開啟。

找不到 python 指令時:Windows 有些環境需要使用 py -m http.server 8000。若 8000 埠已被其他程式使用,可以改用其他埠號,例如 python -m http.server 8080,並在瀏覽器改開 8080。

七、用瀏覽器測試網站

  1. 保持命令提示字元中的伺服器執行。
  2. 開啟 Chrome、Edge 或其他瀏覽器。
  3. 在網址列輸入 http://localhost:8000,或 http://127.0.0.1:8000,按 Enter。
  4. 確認畫面出現「你好,網站!」,再按一下按鈕,檢查 JavaScript 互動是否正常。
  5. 修改並儲存 index.html 後,回到瀏覽器重新整理,即可檢視更新結果。

網址中的 localhost 代表目前這台電腦;8000 是伺服器埠號。Python 的簡易伺服器適合本機練習與預覽,並不是完整的正式網站部署方案。

八、停止伺服器與常見檢查

  • 要停止伺服器時,切回命令提示字元視窗,按 Ctrl + C。
  • 若瀏覽器顯示目錄清單或找不到首頁,確認檔名是 index.html,而且命令列目前所在目錄正確。
  • 若瀏覽器無法連線,確認伺服器仍在執行、網址埠號與指令一致,並檢查是否有其他程式占用該埠。
  • 若中文文字亂碼,確認 HTML 的 <head> 中有 <meta charset="utf-8">,並以 UTF-8 編碼儲存檔案。

完成以上步驟後,你已經建立一個由 HTML、CSS 和 JavaScript 組成,並透過 Python 本機伺服器開啟的入門網站。接著可以加入圖片、導覽列、多個網頁,再逐步整理 CSS 與 JavaScript 檔案。

2026年10月12日 星期一

Python Closure 閉包:5 個簡單又經典的範例

PYTHON 入門|函式如何記住資料

Python Closure 閉包:5 個簡單又經典的範例

Closure(閉包)讓內層函式在外層函式結束後,仍能記住並使用外層函式建立的資料。它常用來保存狀態、建立可設定的函式,也能幫助我們理解裝飾器的運作方式。

簡單記法:閉包通常包含外層函式、內層函式,以及內層函式所記住的外層變數。

一、閉包的運作流程

外層函式建立資料和內層函式,接著把內層函式回傳給外部。外層函式結束後,回傳的函式仍保有它需要的外層資料。

呼叫外層函式make_greeter("小明") 建立外層資料name = "小明" 回傳內層函式greet 記住 name 之後呼叫 greet() 外層函式已結束,閉包仍保留所需資料
閉包把內層函式與它需要的外層變數關聯起來,因此外層函式結束後仍可使用。

二、範例 1:閉包記住名字

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


hello = make_greeter("小明")
hello()

執行結果

你好,小明!

程式解說

  1. 呼叫 make_greeter("小明") 時,外層函式的參數 name 取得「小明」。
  2. 內層函式 greet() 使用了外層的 name。
  3. return greet 回傳函式本身,注意這裡沒有加括號;若寫成 return greet(),就會立刻執行它,而不是回傳函式。
  4. 回傳的函式存入 hello。之後呼叫 hello(),它仍能讀取閉包保留的名字。

三、範例 2:建立不同的問候函式

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


greet_ming = make_greeter("小明")
greet_hua = make_greeter("小華")

greet_ming()
greet_hua()

執行結果

你好,小明!
你好,小華!

程式解說

  • 第一次呼叫 make_greeter,建立一個記住「小明」的閉包。
  • 第二次呼叫時,建立另一個記住「小華」的閉包。
  • greet_ming 和 greet_hua 是不同的函式物件,各自保存自己的 name。
  • 因此呼叫其中一個,不會改變另一個閉包記住的資料。

四、範例 3:建立會累加的計數器

def make_counter():
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter = make_counter()

print(counter())
print(counter())
print(counter())

執行結果

1
2
3

程式解說

  • make_counter() 把 count 初始化為 0,並定義內層函式 counter()。
  • 每次呼叫內層函式,count += 1 就把原本記住的值加一,再回傳新值。
  • nonlocal count 告訴 Python:這裡要修改最近一層外部函式中的 count,而不是建立新的區域變數。
  • 如果移除 nonlocal,因為程式在函式內對 count 賦值,Python 會把它視為區域變數;在讀取它之前尚未設定值,會發生 UnboundLocalError。

五、範例 4:各自保存起始值的計數器

def make_counter(start):
    count = start

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter_a = make_counter(0)
counter_b = make_counter(10)

print(counter_a())
print(counter_a())
print(counter_b())

執行結果

1
2
11

程式解說

  • 這個版本讓建立計數器時指定起始值,count = start 把起始值放進閉包。
  • counter_a 記住自己的 count = 0;counter_b 記住自己的 count = 10。
  • 呼叫 counter_a() 兩次,得到 1、2;呼叫 counter_b() 一次,則從 10 加到 11。
  • 每次執行 make_counter() 都會建立一組新的外層環境,所以兩個計數器的狀態互不干擾。

六、範例 5:建立簡單的乘法器

def make_multiplier(number):
    def multiply(value):
        return number * value
    return multiply


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))
print(triple(5))

執行結果

10
15

程式解說

  • make_multiplier(2) 回傳一個記住 number = 2 的函式,存入 double。
  • make_multiplier(3) 回傳另一個記住 number = 3 的函式,存入 triple。
  • 呼叫 double(5) 時,內層函式把 2 與 5 相乘;呼叫 triple(5) 時,則把 3 與 5 相乘。
  • 這種寫法可以建立「已預先設定部分資料」的函式。

七、閉包重點整理

  • 內層函式使用了外層函式的變數,並且被保留下來,就形成閉包。
  • 閉包可以在外層函式結束後,繼續使用外層變數。
  • 每次呼叫外層函式都可以建立一個有獨立狀態的新閉包。
  • 要在內層函式修改外層函式的變數,使用 nonlocal。
  • 閉包常用於保存狀態、建立函式工廠、設定回呼行為,以及實作裝飾器。

學習閉包時,可以先追蹤三件事:外層函式建立了什麼資料、內層函式使用了哪些資料,以及外層函式結束後是哪個回傳函式仍保有這些資料。掌握這個關係,就能看懂許多 Python 程式中的閉包寫法。

Python Decorator 裝飾器:5 個簡單又經典的範例

PYTHON 入門|函式的額外能力

Python Decorator 裝飾器:5 個簡單又經典的範例

裝飾器(Decorator)可以在不改動原函式主要程式碼的情況下,替它加上「執行前處理」或「執行後處理」。常見用途包括顯示訊息、記錄執行時間、檢查權限與保留函式資訊。

先記住一句話:裝飾器接收一個函式,回傳一個包裝函式;呼叫原函式時,就會先經過這層包裝。

一、裝飾器的運作流程

在函式定義上方寫 @my_decorator,Python 會把函式交給裝飾器處理。下面流程圖以「前後訊息」為例:

呼叫 say_hello()使用者呼叫函式 wrapper()裝飾器包裝層 func()原函式內容 回傳結果交回呼叫端 執行前處理執行後處理也可放在 func() 之後
呼叫流程:使用者呼叫包裝後的函式,wrapper 再呼叫原函式,最後把結果交回。

最常見的結構如下。*args 與 **kwargs 讓包裝函式可以接收不同數量的位置參數與關鍵字參數;return 則把原函式的結果傳回去。

def decorator(func):
    def wrapper(*args, **kwargs):
        # 額外處理:執行原函式之前
        result = func(*args, **kwargs)
        # 額外處理:執行原函式之後
        return result
    return wrapper

二、範例 1:在函式前後顯示訊息

def my_decorator(func):
    def wrapper():
        print("開始執行")
        func()
        print("執行結束")
    return wrapper


@my_decorator
def say_hello():
    print("Hello, Python!")


say_hello()

執行結果

開始執行
Hello, Python!
執行結束

程式解說

  1. my_decorator(func) 收到原本的 say_hello 函式。
  2. 裝飾器建立 wrapper,在呼叫原函式前印出「開始執行」,呼叫後再印出「執行結束」。
  3. return wrapper 把包裝函式交回去,因此之後呼叫 say_hello(),實際上執行的是 wrapper。
  4. @my_decorator 是簡寫,等同於 say_hello = my_decorator(say_hello)。

三、範例 2:讓裝飾器支援函式參數

def show_call(func):
    def wrapper(*args, **kwargs):
        print(f"正在呼叫:{func.__name__}")
        return func(*args, **kwargs)
    return wrapper


@show_call
def greet(name):
    print(f"你好,{name}!")


greet("小明")

執行結果

正在呼叫:greet
你好,小明!

程式解說

  • *args 收集位置參數,例如這裡的 "小明";**kwargs 收集像 name="小明" 這種關鍵字參數。
  • func.__name__ 取得原函式名稱,方便記錄目前呼叫哪個函式。
  • func(*args, **kwargs) 再把收到的參數完整傳給原函式。
  • return 會保留原函式的回傳值;若少了它,呼叫端通常會收到 None。

四、範例 3:計算函式執行時間

import time


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()

        print(f"{func.__name__} 執行時間:{end - start:.4f} 秒")
        return result
    return wrapper


@timer
def count_numbers():
    total = sum(range(1_000_000))
    return total


print(count_numbers())

執行結果(時間會因電腦而不同)

count_numbers 執行時間:0.0284 秒
499999500000

程式解說

  • start 記錄函式執行前的時間;原函式完成後用 end 記錄時間。
  • end - start 是執行所花的秒數,:.4f 表示顯示到小數點後四位。
  • 原函式回傳總和,包裝函式將結果存在 result,印出時間後再回傳,因此外層的 print 仍能印出總和。
  • 範例中的時間只是示意;實際數值會受電腦與當時負載影響。

五、範例 4:執行函式前檢查登入狀態

def login_required(func):
    def wrapper(is_logged_in):
        if not is_logged_in:
            print("請先登入")
            return

        return func(is_logged_in)
    return wrapper


@login_required
def view_profile(is_logged_in):
    print("顯示個人資料")


view_profile(False)
view_profile(True)

執行結果

請先登入
顯示個人資料

程式解說

  • wrapper 先檢查 is_logged_in 的值。
  • 若值是 False,印出提示後用 return 結束,不再執行原函式。
  • 若值是 True,程式呼叫 func(is_logged_in),於是顯示個人資料。
  • 這是權限檢查的教學示範;實際網站應由登入系統與伺服器端驗證使用者身分。

六、範例 5:用 wraps 保留原函式資訊

from functools import wraps


def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("執行前")
        return func(*args, **kwargs)
    return wrapper


@my_decorator
def say_hello():
    """向使用者打招呼"""
    print("Hello!")


print(say_hello.__name__)
print(say_hello.__doc__)
say_hello()

執行結果

say_hello
向使用者打招呼
執行前
Hello!

程式解說

  • 經過裝飾後,函式名稱可能被包裝層的名稱 wrapper 取代,文件字串也可能不再指向原函式。
  • from functools import wraps 匯入標準函式庫提供的工具。
  • 把 @wraps(func) 放在 wrapper 定義上方,可以將原函式的名稱、說明文件等資訊保留下來。
  • 這是撰寫一般裝飾器時很好的習慣,尤其當函式會被文件工具、除錯工具或其他程式檢查時。

七、快速整理

  • 核心概念:裝飾器接收函式,並回傳包裝後的函式。
  • 支援參數:多數通用裝飾器使用 *args、**kwargs。
  • 保留回傳值:用 return func(...) 把原函式的結果傳回。
  • 保留函式資訊:使用 functools.wraps。
  • 常見用途:日誌、計時、權限檢查、快取與輸入驗證。

熟悉這幾個例子後,可以試著把「執行前後顯示訊息」改成記錄每次函式呼叫的參數,或替計時裝飾器加上不同函式的測試。從 wrapper 的進入、原函式的執行,到結果回傳,便能掌握裝飾器的基本運作方式。

2026年10月6日 星期二

NAT 是什麼?用「宿舍共用一支對外電話」來理解

網際網路入門|IP 位址與路由器

NAT 是什麼?用「宿舍共用一支對外電話」來理解

家裡、教室或辦公室裡,手機、筆電和其他設備常常一起上網。它們各有自己的內部位址,對外卻可能共用同一個公有 IP。負責協調這件事的常見功能,就是 NAT。



先想像宿舍的電話總機

一間宿舍裡住著許多學生,每個人有自己的房號或分機。學生打電話到外面時,不需要每個人都申請一支專屬的對外電話線;大家可以透過宿舍總機,共用同一個對外電話號碼。

學生的分機
各自識別
→
宿舍總機
記下通話對應
→
一個對外電話號碼
外部看到的號碼

當外面有回電時,總機會依照先前記錄的通話資訊,把電話轉到正確的分機。家用路由器執行 NAT 時,也有類似的「對外共用、內部轉送」概念。

把電話比喻換成網路

教室/家中的裝置
💻 筆電 192.168.1.10
📱 手機 192.168.1.11
🖥️ 樹莓派 192.168.1.12
各有自己的私有 IP
⇄
Wi‑Fi 路由器NAT轉換位址
記錄連線與連接埠
⇄
網際網路
☁️ 🌐
網站收到的來源位址
公有 IP:203.0.113.5
回程資料先回到路由器,再依連線紀錄送回發出請求的裝置 ↩

示意用 IP 僅供教學說明。實際網路中的公有 IP 由網路服務供應商或網路管理者配置。

一次上網請求怎麼走?

  1. 筆電以自己的私有 IP(例如 192.168.1.10)向網站提出請求。
  2. 路由器把封包的來源資訊轉換成對外使用的公有 IP,並記錄這筆連線及相關連接埠資訊。
  3. 網站回覆資料時,回覆會先到路由器的公有 IP。
  4. 路由器查詢連線紀錄,把回覆交還給原本提出請求的筆電。

同一時間,手機和樹莓派也可以建立各自的連線。路由器會用連線資訊區分它們,因此多台裝置能透過一個對外 IP 使用網路。

生活比喻與網路名詞對照

宿舍電話例子網路中的對應作用
學生的分機裝置的私有 IP識別內部網路中的裝置
宿舍對外電話號碼路由器的公有 IP讓外部網路找到這個網路出口
宿舍總機路由器的 NAT 功能轉換位址並協調內外網路的資料往返
總機的通話紀錄NAT 轉換表/連線狀態協助把回覆交給正確的內部裝置

NAT 和防火牆一樣嗎?

NAT 的主要工作是轉換網路位址;許多家用路由器也會搭配防火牆規則,限制外部連線。兩者常出現在同一台設備上,但概念不同。NAT 本身不等於完整的安全防護,仍應使用更新的路由器韌體、強密碼及適當的防火牆設定。

補充:教學中常說「多台裝置共用一個公有 IP」,通常指家用路由器常見的 IPv4 NAT/連接埠位址轉換情境。不同網路架構也可能使用 IPv6、電信商級 NAT 或其他配置。

一句話記住:多台裝置使用各自的私有 IP,透過 NAT 共用對外的公有 IP;路由器記下連線資訊,再把回覆送回正確裝置。

MQTT 與 IoT 雙向通訊|Pico 2 W

Pico 2 W × Thonny|第 6 次課程

MQTT 與 IoT 雙向通訊

前一篇已把感測資料整理成 JSON。本篇將資料放進 MQTT 訊息,讓 Pico 2 W 發布感測資料,也訂閱控制主題接收命令。這樣裝置不只會「上傳」,也能從網路接收控制,形成 IoT 的雙向通訊。

一、MQTT 的三個角色

MQTT 使用 Broker(訊息代理伺服器)中轉訊息。Publisher 將訊息發布到某個 Topic;Subscriber 訂閱感興趣的 Topic。Broker 依照訂閱關係轉送訊息,因此 Publisher 通常不需要知道每一個 Subscriber 的網路位置。

Publisher 發布者把訊息送到指定 Topic,例如 Pico 上傳感測資料。
Broker 代理伺服器接收、管理並轉送訊息,是通訊中介。
Subscriber 訂閱者訂閱 Topic 並接收訊息,例如電腦 Dashboard。

同一台 Pico 可以同時扮演 Publisher 與 Subscriber:它發布感測值,也訂閱控制命令。

二、Topic 是訊息的分類路徑

Topic 用來標示訊息的類別或用途。好的命名方式能看出位置、設備與功能,讓多個裝置加入時仍然容易管理。

farm/room01/pico01/telemetry
farm/room01/pico01/status
farm/room01/pico01/control

farm/room02/pico02/telemetry
  • telemetry:感測器或設備定期發布的遙測資料。
  • status:設備目前狀態。
  • control:送往設備的控制命令。

Topic 名稱要在發布端和訂閱端完全一致;斜線分隔的層級也要保持規則一致。

三、Publish:Pico 發布感測資料

發布時要指定 Topic 和 Payload(訊息內容)。Topic 說明這是哪一類訊息,Payload 則放實際資料。本課程沿用上一堂的 JSON 格式,因此接收端可以依照欄位名稱解析資料。

import json

topic_telemetry = b"farm/room01/pico01/telemetry"

# 這些數字只用來示範資料格式
payload = {
    "temperature": 28.3,
    "humidity": 71.2
}

client.publish(
    topic_telemetry,
    json.dumps(payload)
)

範例中的數字只是格式示意,不是實際場域資料。實作時應換成感測器讀值,並確認欄位名稱、型態與單位已事先約定。

四、Subscribe:Pico 訂閱控制命令

Dashboard 或電腦可以把控制命令發布到設備的 control Topic。Pico 訂閱這個 Topic,收到訊息後解析 JSON,再依內容控制 GPIO。

import json
from machine import Pin

led = Pin("LED", Pin.OUT)

def on_message(topic, message):
    print("Topic:", topic)
    print("Message:", message)

    try:
        command = json.loads(message.decode())

        if "relay" in command:
            led.value(1 if command["relay"] else 0)
            print("LED updated:", led.value())

    except (ValueError, KeyError) as error:
        print("Invalid command:", error)

topic_control = b"farm/room01/pico01/control"
client.set_callback(on_message)
client.subscribe(topic_control)

命令可以是 {"relay": true}或 {"relay": false}。實際系統應依設備功能設計命令欄位,並確認收到的內容合法後才執行控制。

五、Pico 連接 Broker 並雙向交換訊息

以下是教學架構範例,假設 Pico 已先連上 Wi-Fi,且韌體已提供相容的 umqtt.simple用戶端。請把 Broker 主機、帳號與密碼替換成教師提供的測試設定。Broker 的網路位置與連線方式依服務而異。

import json
import time
from umqtt.simple import MQTTClient

BROKER = "請填入 MQTT Broker 主機"
CLIENT_ID = b"pico-room01-device01"

TOPIC_TELEMETRY = b"farm/room01/pico01/telemetry"
TOPIC_CONTROL = b"farm/room01/pico01/control"

# 若測試 Broker 不需要帳密,可依用戶端規格省略相關參數
client = MQTTClient(CLIENT_ID, BROKER)

def on_message(topic, message):
    print("Received:", topic, message)
    # 在此解析控制命令並更新 GPIO

client.set_callback(on_message)
client.connect()
client.subscribe(TOPIC_CONTROL)

print("MQTT connected and subscribed")

while True:
    # 示範內容;實作時改成感測器的實際讀值
    telemetry = {
        "temperature": 28.3,
        "humidity": 71.2
    }

    client.publish(
        TOPIC_TELEMETRY,
        json.dumps(telemetry)
    )

    # 檢查是否有訂閱訊息;有訊息時會呼叫 on_message
    client.check_msg()

    time.sleep(5)

set_callback()設定收到訊息時要執行的函式;subscribe()訂閱控制 Topic;publish()發布感測資料;check_msg()檢查是否有新訊息。這個程式每五秒發布一次示範資料,實際量測值需由感測器取得。

套件與 Broker:不同 MicroPython 韌體不一定預先包含 umqtt.simple,需先確認課堂環境的套件安裝方式。此範例使用提示文字作為 Broker 主機,並非可直接連線的服務。

六、MQTT 與 HTTP 如何搭配?

HTTP常見為 Request/Response,適合網頁與 API 的一次性資料交換。
MQTT使用 Publish/Subscribe,適合設備持續發布遙測資料或接收命令。
共同點兩者都在 IP 網路上運作,都必須考慮斷線與錯誤處理。

HTTP 和 MQTT 各有適合的情境,IoT 系統也可以同時使用兩者。例如設備透過 MQTT 傳送即時遙測,而設定頁面或其他服務使用 HTTP API。

七、斷線與安全性

Wi-Fi 或 Broker 可能中斷,設備程式要能顯示連線狀態,並設計清楚的重新連線流程。重新連線後,通常也要重新訂閱控制 Topic。不要在故障時無限快速重試;可採用有限次數與間隔,並讓錯誤訊息容易辨識。

保護 Broker 憑證:不要把真實帳密、Token 或 API Key 放進公開投影片、部落格或 Git 儲存庫。控制 Topic 會影響實體輸出,應使用可信任的 Broker、限制可發布者,並在接收端檢查命令內容。

八、實作任務:完成第一個 MQTT IoT Node

  1. 連上課堂指定 Wi-Fi。
  2. 連接測試 MQTT Broker。
  3. 訂閱設備的 control Topic。
  4. 每五秒發布一筆感測器 JSON 到 telemetry Topic。
  5. 從 Dashboard 或測試用戶端發布控制命令,確認 Pico 收到後能更新 GPIO。
  6. 挑戰:Wi-Fi 或 Broker 斷線後,顯示狀態並完成重新連線與重新訂閱。

測試時,建議同時觀察 Pico 的 Thonny Shell 與 Broker 用戶端畫面,確認每一筆訊息的 Topic、Payload 和方向。

六次課程如何串成完整資料流

  1. 第 1 次:控制硬體,建立 GPIO 基礎。
  2. 第 2 次:連上 Wi-Fi,取得網路資訊。
  3. 第 3 次:建立 Web Server,讓瀏覽器控制 Pico。
  4. 第 4 次:透過 HTTP 與 API 交換資料。
  5. 第 5 次:以 JSON 建立一致的資料格式。
  6. 第 6 次:以 MQTT 發布遙測資料並接收控制命令。

完成這六次課程後,就具備 IoT 專題的基本通訊流程。下一階段可以接續設計 Dashboard 或 IoT 平台,讓資料能被觀察、整理與應用。

Pico 2 W × Thonny|第 6 次課程

MQTT 與 IoT 雙向通訊

前一篇已把感測資料整理成 JSON。本篇將資料放進 MQTT 訊息,讓 Pico 2 W 發布感測資料,也訂閱控制主題接收命令。這樣裝置不只會「上傳」,也能從網路接收控制,形成 IoT 的雙向通訊。

一、MQTT 的三個角色

MQTT 使用 Broker(訊息代理伺服器)中轉訊息。Publisher 將訊息發布到某個 Topic;Subscriber 訂閱感興趣的 Topic。Broker 依照訂閱關係轉送訊息,因此 Publisher 通常不需要知道每一個 Subscriber 的網路位置。

Publisher 發布者把訊息送到指定 Topic,例如 Pico 上傳感測資料。
Broker 代理伺服器接收、管理並轉送訊息,是通訊中介。
Subscriber 訂閱者訂閱 Topic 並接收訊息,例如電腦 Dashboard。

同一台 Pico 可以同時扮演 Publisher 與 Subscriber:它發布感測值,也訂閱控制命令。

二、Topic 是訊息的分類路徑

Topic 用來標示訊息的類別或用途。好的命名方式能看出位置、設備與功能,讓多個裝置加入時仍然容易管理。

farm/room01/pico01/telemetry
farm/room01/pico01/status
farm/room01/pico01/control

farm/room02/pico02/telemetry
  • telemetry:感測器或設備定期發布的遙測資料。
  • status:設備目前狀態。
  • control:送往設備的控制命令。

Topic 名稱要在發布端和訂閱端完全一致;斜線分隔的層級也要保持規則一致。

三、Publish:Pico 發布感測資料

發布時要指定 Topic 和 Payload(訊息內容)。Topic 說明這是哪一類訊息,Payload 則放實際資料。本課程沿用上一堂的 JSON 格式,因此接收端可以依照欄位名稱解析資料。

import json

topic_telemetry = b"farm/room01/pico01/telemetry"

# 這些數字只用來示範資料格式
payload = {
    "temperature": 28.3,
    "humidity": 71.2
}

client.publish(
    topic_telemetry,
    json.dumps(payload)
)

範例中的數字只是格式示意,不是實際場域資料。實作時應換成感測器讀值,並確認欄位名稱、型態與單位已事先約定。

四、Subscribe:Pico 訂閱控制命令

Dashboard 或電腦可以把控制命令發布到設備的 control Topic。Pico 訂閱這個 Topic,收到訊息後解析 JSON,再依內容控制 GPIO。

import json
from machine import Pin

led = Pin("LED", Pin.OUT)

def on_message(topic, message):
    print("Topic:", topic)
    print("Message:", message)

    try:
        command = json.loads(message.decode())

        if "relay" in command:
            led.value(1 if command["relay"] else 0)
            print("LED updated:", led.value())

    except (ValueError, KeyError) as error:
        print("Invalid command:", error)

topic_control = b"farm/room01/pico01/control"
client.set_callback(on_message)
client.subscribe(topic_control)

命令可以是 {"relay": true}或 {"relay": false}。實際系統應依設備功能設計命令欄位,並確認收到的內容合法後才執行控制。

五、Pico 連接 Broker 並雙向交換訊息

以下是教學架構範例,假設 Pico 已先連上 Wi-Fi,且韌體已提供相容的 umqtt.simple用戶端。請把 Broker 主機、帳號與密碼替換成教師提供的測試設定。Broker 的網路位置與連線方式依服務而異。

import json
import time
from umqtt.simple import MQTTClient

BROKER = "請填入 MQTT Broker 主機"
CLIENT_ID = b"pico-room01-device01"

TOPIC_TELEMETRY = b"farm/room01/pico01/telemetry"
TOPIC_CONTROL = b"farm/room01/pico01/control"

# 若測試 Broker 不需要帳密,可依用戶端規格省略相關參數
client = MQTTClient(CLIENT_ID, BROKER)

def on_message(topic, message):
    print("Received:", topic, message)
    # 在此解析控制命令並更新 GPIO

client.set_callback(on_message)
client.connect()
client.subscribe(TOPIC_CONTROL)

print("MQTT connected and subscribed")

while True:
    # 示範內容;實作時改成感測器的實際讀值
    telemetry = {
        "temperature": 28.3,
        "humidity": 71.2
    }

    client.publish(
        TOPIC_TELEMETRY,
        json.dumps(telemetry)
    )

    # 檢查是否有訂閱訊息;有訊息時會呼叫 on_message
    client.check_msg()

    time.sleep(5)

set_callback()設定收到訊息時要執行的函式;subscribe()訂閱控制 Topic;publish()發布感測資料;check_msg()檢查是否有新訊息。這個程式每五秒發布一次示範資料,實際量測值需由感測器取得。

套件與 Broker:不同 MicroPython 韌體不一定預先包含 umqtt.simple,需先確認課堂環境的套件安裝方式。此範例使用提示文字作為 Broker 主機,並非可直接連線的服務。

六、MQTT 與 HTTP 如何搭配?

HTTP常見為 Request/Response,適合網頁與 API 的一次性資料交換。
MQTT使用 Publish/Subscribe,適合設備持續發布遙測資料或接收命令。
共同點兩者都在 IP 網路上運作,都必須考慮斷線與錯誤處理。

HTTP 和 MQTT 各有適合的情境,IoT 系統也可以同時使用兩者。例如設備透過 MQTT 傳送即時遙測,而設定頁面或其他服務使用 HTTP API。

七、斷線與安全性

Wi-Fi 或 Broker 可能中斷,設備程式要能顯示連線狀態,並設計清楚的重新連線流程。重新連線後,通常也要重新訂閱控制 Topic。不要在故障時無限快速重試;可採用有限次數與間隔,並讓錯誤訊息容易辨識。

保護 Broker 憑證:不要把真實帳密、Token 或 API Key 放進公開投影片、部落格或 Git 儲存庫。控制 Topic 會影響實體輸出,應使用可信任的 Broker、限制可發布者,並在接收端檢查命令內容。

八、實作任務:完成第一個 MQTT IoT Node

  1. 連上課堂指定 Wi-Fi。
  2. 連接測試 MQTT Broker。
  3. 訂閱設備的 control Topic。
  4. 每五秒發布一筆感測器 JSON 到 telemetry Topic。
  5. 從 Dashboard 或測試用戶端發布控制命令,確認 Pico 收到後能更新 GPIO。
  6. 挑戰:Wi-Fi 或 Broker 斷線後,顯示狀態並完成重新連線與重新訂閱。

測試時,建議同時觀察 Pico 的 Thonny Shell 與 Broker 用戶端畫面,確認每一筆訊息的 Topic、Payload 和方向。

六次課程如何串成完整資料流

  1. 第 1 次:控制硬體,建立 GPIO 基礎。
  2. 第 2 次:連上 Wi-Fi,取得網路資訊。
  3. 第 3 次:建立 Web Server,讓瀏覽器控制 Pico。
  4. 第 4 次:透過 HTTP 與 API 交換資料。
  5. 第 5 次:以 JSON 建立一致的資料格式。
  6. 第 6 次:以 MQTT 發布遙測資料並接收控制命令。

完成這六次課程後,就具備 IoT 專題的基本通訊流程。下一階段可以接續設計 Dashboard 或 IoT 平台,讓資料能被觀察、整理與應用。

JSON 與 IoT 資料格式|Pico 2 W

Pico 2 W × Thonny|第 5 次課程

JSON 與 IoT 資料格式

感測器產生資料,Pico 負責整理與傳送;要讓不同設備、程式和伺服器都能理解同一筆資訊,就需要清楚一致的資料格式。本篇介紹 JSON 的基本結構、Python 字典與 JSON 的轉換,以及 IoT 遙測欄位的設計方法。

作者:施朝斌

一、為什麼 IoT 需要 JSON?

如果只傳送 28.3,71.2,350,接收端雖然看到三個數字,卻不知道它們各自代表溫度、濕度還是光照。JSON 在資料旁附上欄位名稱,讓資料較容易閱讀,也讓程式能依欄位取值。

{
  "temperature": 28.3,
  "humidity": 71.2,
  "light": 350
}

以上數字沿用課程投影片作為格式示例,不代表實際場域量測結果。正式資料應由感測器讀取,並清楚標明單位與資料來源。

二、JSON 的基本結構

JSON 物件使用大括號 {},以「欄位名稱:值」的方式組織資料;陣列使用中括號 [],可以放置多筆項目。常見值的型態包括字串、數字、布林值、物件、陣列,以及 null。

{
  "device": "PICO01",
  "temperature": 28.3,
  "humidity": 71.2,
  "relay": true,
  "location": {
    "room": "A101"
  },
  "alerts": []
}
  • 字串:文字需使用雙引號,例如 "PICO01"。
  • 數字:例如 28.3;不要把數字不必要地存成文字。
  • 布林值:使用小寫的 true 或 false。
  • 物件:可再包含一組欄位,例如 location。
  • 陣列:使用中括號,可表示多筆資料或多個項目。
  • null:表示目前沒有可用值;它不同於字串 "null"或數字 0。

三、Python 字典與 JSON 互相轉換

在 Python 程式中,資料常先以字典整理。json.dumps()把 Python 資料轉成 JSON 文字;json.loads()則把 JSON 文字解析回 Python 資料。

import json

data = {
    "temperature": 28.3,
    "humidity": 71.2,
    "relay": True
}

json_text = json.dumps(data)
print(json_text)

decoded_data = json.loads(json_text)
print(decoded_data["temperature"])

Python 的 True轉成 JSON 後會是小寫的 true;JSON 解析回 Python 後則會成為 True。同樣地,Python 的 None會轉成 JSON 的 null。

兩個常見動詞:dumps把資料「序列化」成文字;loads把文字「解析」成資料。HTTP 傳輸時,送出或收到的通常是文字內容。

四、設計 IoT 遙測資料欄位

一筆可交換的遙測資料,除了感測值,也應讓接收端知道資料來自哪個設備、何時產生,以及每個數字代表什麼。欄位名稱與格式最好固定,後續接入 HTTP API、MQTT 或 Dashboard 時才能共用。

{
  "deviceId": "PICO01",
  "timestamp": null,
  "temperature": 28.3,
  "humidity": 71.2,
  "light": 350,
  "battery": 4.05
}

這些感測值是投影片中的示範數字,不是實際量測結果。timestamp目前設為 null,表示範例沒有提供時間;只有在 Pico 已取得可靠時鐘並採用一致的時間格式後,才應填入時間戳記。每個欄位也應訂定單位,例如溫度使用 °C、電壓使用 V,避免不同設備各自解讀。

設計欄位時,還要決定設備識別方式、欄位拼法、數值型態、單位、取樣時間與缺值表示法。這些規則就是資料格式的一部分。

五、用 Python 建立自己的 JSON 遙測資料

以下以示範值展示資料結構。實作時請把數值換成程式實際讀到的感測器資料;若某欄位目前沒有讀值,使用 None轉成 JSON null,不要假裝已取得測量結果。

import json

telemetry = {
    "deviceId": "PICO01",
    "timestamp": None,
    "temperature": 28.3,
    "humidity": 71.2,
    "relay": False
}

payload = json.dumps(telemetry)
print(payload)

接著可以把 payload放進 HTTP POST 的 Body,或在下一堂課將它作為 MQTT 訊息內容。只要欄位規則維持一致,接收端就能用相同方式解析。

六、錯誤資料也要處理

網路上的資料不一定完整、型態正確或仍然新鮮。接收端應先檢查必需欄位,再確認型態與時間資訊,並適當處理伺服器錯誤。

import json

response_text = '{"temperature": 28.3}'

try:
    data = json.loads(response_text)

    temperature = data.get("temperature")
    humidity = data.get("humidity")

    if temperature is None:
        print("Temperature is missing")
    elif not isinstance(temperature, (int, float)):
        print("Temperature has the wrong type")
    else:
        print("Temperature:", temperature)

    if humidity is None:
        print("Humidity is missing")
    else:
        print("Humidity:", humidity)

except ValueError as error:
    print("Invalid JSON:", error)
  • 欄位不存在:使用 get()或明確檢查,避免直接索引而產生 KeyError。
  • 型態錯誤:數字 28.3與文字 "28.3"不是同一種資料。
  • 資料過期:若有可靠時間戳記,可比較資料時間與目前時間;沒有時鐘時不要假裝能判定新舊。
  • 伺服器錯誤:檢查 HTTP 狀態碼,必要時有限次重試並記錄結果。

七、REST API 路徑與資料責任

REST API 常用不同路徑區分資源或功能。例如 /api/telemetry可代表感測資料、/api/status可代表設備狀態、/api/control可代表控制要求、/api/config可代表設定資訊。路徑名稱只是設計示例,實際 API 應依服務規格使用。

把感測資料、設備狀態、控制命令與設定分開,有助於開發者理解每個端點的用途。欄位名稱與格式也要在設備端和伺服器端保持一致。

八、實作任務:定義自己的 Pico 資料格式

  1. 建立一個穩定的 deviceId。
  2. 選擇 2 至 3 個實際存在或課堂指定的感測欄位。
  3. 加入一個設備狀態欄位,例如繼電器開關狀態。
  4. 為每個欄位記錄型態、單位與缺值表示方式。
  5. 使用 json.dumps()轉成 JSON,並在 Shell 檢查輸出。
  6. 依課程提供的測試服務 POST,檢查狀態碼與回應內容。
不要把示範數據當成實測結果:完成實驗紀錄時,註明數值來自範例、模擬器或實際感測器。若來源不同,也要避免混在一起比較或宣稱為場域成效。

本篇重點回顧

  • JSON 以欄位名稱標示資料意義,方便不同設備和程式交換資訊。
  • Python 字典可用 json.dumps()轉為 JSON 文字,用 json.loads()解析回資料。
  • IoT 遙測格式應固定欄位名稱、型態、單位、設備識別與時間規則。
  • 要檢查欄位缺漏、型態不符、資料是否過期及 HTTP 錯誤。
  • 範例數值只示範格式;實際結果應來自感測器或明確標示的測試資料。

下一篇將把一致的 JSON 遙測資料帶入 MQTT,讓 Pico 發布感測訊息,也能訂閱控制命令。

HTTP 與 REST API|Pico 2 W

Pico 2 W × Thonny|第 4 次課程

HTTP 與 REST API

前三次課程讓 Pico 2 W 控制硬體、連上 Wi-Fi,並提供自己的網頁服務。本篇把角色反過來:Pico 成為 HTTP Client,主動向 Internet 上的 API 取得資料,或把感測資料送到遠端服務。

作者:施朝斌

一、從 Web Server 走向 API Client

使用 Pico Web Server 時,手機或電腦的瀏覽器主動連進 Pico;使用遠端 API 時,則是 Pico 主動連到 API Server。API(應用程式介面)提供一組約定好的網址與資料格式,讓程式可以透過網路交換資訊。

HTTP ClientPico 發出要求,等待遠端服務回應。
API Server接收要求、處理資料,再回覆結果。
URL指出要存取的服務或資源位置。
JSON常見的資料格式,能以欄位名稱與值描述資料。

二、GET 與 POST:讀取資料和送出資料

GET 通常用來讀取某個資源,例如查詢天氣或取得感測器資料。POST 通常用來送出資料,例如把 Pico 讀到的感測值傳到伺服器。實際 API 會規定可用的方法、網址、欄位與驗證方式,因此呼叫前要先閱讀服務文件。

  • GET:向 API 詢問資料,常見形式是 GET /api/data。
  • POST:將資料送給 API,常見形式是 POST /api/telemetry。
  • Header:提供內容類型、授權資訊等要求或回應的附加資料。
  • Body:HTTP 要求或回應承載的內容;POST 常把 JSON 放在 Body。

GET 和 POST 是常見用途,不代表每個服務都完全相同。API 文件才是判斷端點、欄位、授權與資料格式的依據。

三、HTTP 狀態碼:先看請求結果

伺服器回應時會提供狀態碼,告訴 Client 這次要求大致如何處理。程式不能只假設回應一定成功,應先檢查狀態碼,再決定要解析回應資料或顯示錯誤。

200 OK要求成功,通常可讀取回傳內容。
201 Created伺服器成功建立了資源。
400 Bad Request要求格式或內容不符合服務規則。
404 Not Found指定的資源或路徑不存在。
500 Server Error伺服器處理時發生錯誤。

狀態碼是診斷線索:例如 404 可先檢查 URL 路徑,400 可檢查送出的欄位與 JSON 格式,5xx 則可能需要稍後重試或聯絡服務提供者。

四、Pico 使用 HTTP POST 傳送資料

投影片以 urequests 示範 HTTP Client。以下資料只是格式示範,不是實際養殖或場域量測值。請將 URL 換成教師提供且允許測試的 API 端點,並依服務文件調整欄位。

import urequests

url = "https://api.example.org/telemetry"

data = {
    "device_id": "pico-demo-01",
    "temperature": 28.3,
    "humidity": 71.2
}

response = None

try:
    response = urequests.post(url, json=data)
    print("Status:", response.status_code)
    print("Response:", response.text)
except Exception as error:
    print("Request failed:", error)
finally:
    if response is not None:
        response.close()

urequests.post(url, json=data)會把 Python 字典轉成 JSON 格式送出。伺服器回應後,程式讀取 status_code與回應文字;不論成功或失敗,都在 finally區塊關閉 Response,釋放網路資源。

端點提醒:api.example.org是示意網址,不能當成真實課程 API 使用。需替換成已確認可用、且允許 POST 測試的服務。不同 MicroPython 韌體可能需要另外安裝或提供 urequests模組。

五、Pico 使用 HTTP GET 讀取資料

GET 的流程相似,只是呼叫方式改為 urequests.get()。若 API 回傳 JSON,可以使用 response.json()將內容轉為 Python 資料結構,再取出需要的欄位。

import urequests

url = "https://api.example.org/data"
response = None

try:
    response = urequests.get(url)
    print("Status:", response.status_code)

    if response.status_code == 200:
        result = response.json()
        print("Returned data:", result)
    else:
        print("Server returned an error")
except Exception as error:
    print("Request failed:", error)
finally:
    if response is not None:
        response.close()

若回應不是合法 JSON,或伺服器回傳錯誤頁面,response.json()可能無法解析。因此先檢查狀態碼,再讀取資料,是比較可靠的順序。

六、一次完整的資料交換流程

  1. 確認 Pico 已連上 Wi-Fi,並能使用目標網路服務。
  2. 閱讀 API 文件,確認 URL、HTTP 方法、必要欄位與授權方式。
  3. 整理感測資料;送出前確認欄位名稱、資料型態與單位。
  4. 發出 GET 或 POST 要求,等待伺服器回應。
  5. 先檢查狀態碼,再解析回應內容。
  6. 關閉 Response,並記錄成功或錯誤訊息。

以 IoT 遙測為例,資料由感測器進入 Pico,程式整理成 JSON,再透過 HTTP POST 傳到 API Server。重點是理解資料如何跨網路移動,而不是記住某一家 API 的名稱。

七、網路程式的三個陷阱

  • Timeout:伺服器可能很慢或暫時沒有回應,程式不應無限等待。
  • Disconnect:Wi-Fi 可能中斷;需要辨認連線失敗,並依課程設計決定是否重新連線。
  • Memory:回應內容可能很大;微控制器記憶體有限,避免不加判斷地讀取大型資料。

建立「逾時、例外處理、有限次重試、關閉 Response」的習慣,能讓網路程式更容易除錯。重試也要有限次數,並避免在服務故障時快速重複送出大量請求。

八、實作任務

  • GET 一個課堂指定、可公開讀取的測試 API。
  • 列印狀態碼,並從 JSON 回應中取出一個指定欄位。
  • 使用測試資料 POST 到教師指定的測試端點。
  • 在 Shell 顯示狀態碼與伺服器回應。
  • 挑戰:遇到連線失敗時,有限次重試並印出清楚的錯誤原因。
API 金鑰與個人資料:若服務要求 Token 或 API Key,不要將它貼在公開投影片、部落格或 Git 儲存庫中。練習應使用教師核准的測試憑證與測試資料。

本篇重點回顧

  • Pico 可以當 HTTP Client,主動向遠端 API 讀取或傳送資料。
  • GET 常用於取得資料,POST 常用於送出資料;實際格式依 API 文件為準。
  • 狀態碼提供伺服器處理結果,應先檢查再解析回應。
  • 使用 JSON 傳遞感測資料時,要留意欄位、型態與單位。
  • 網路程式要處理逾時、斷線、錯誤與記憶體限制,並關閉 Response。

下一篇將把感測資料整理成一致的 JSON 格式,進一步理解資料欄位、時間戳記與錯誤資料處理。

Pico Web Server 與網頁控制

Pico 2 W × Thonny|第 3 次課程

Pico Web Server 與網頁控制

前一堂讓 Pico 2 W 加入 Wi-Fi 網路;這一堂進一步讓它成為 Web Server。手機或電腦用瀏覽器連到 Pico 的 IP,讀取它送出的網頁,再透過網址路徑送出控制要求,讓板載 LED 亮起或熄滅。

作者:施朝斌

一、瀏覽器與 Pico 如何對話?

當瀏覽器開啟一個網址,它會扮演 Client(用戶端),向提供服務的設備送出 HTTP Request(請求)。Pico 扮演 Server(伺服端),接收請求後回傳 HTTP Response(回應),內容可以是 HTML 網頁,也可以是文字或 JSON。

Client手機或電腦上的瀏覽器,提出網頁或控制要求。
Request瀏覽器送出的要求,例如讀取首頁或請求 /on。
Server等待連線、讀取要求並決定如何處理的 Pico 程式。
ResponsePico 回傳的狀態、標頭與網頁內容。

HTTP 通訊通常使用 TCP 連線。簡化來看,伺服器建立 Socket、綁定連接埠並等待連線;瀏覽器連進來後,伺服器讀取 Request、組成 Response、傳送回去,再關閉這次連線。

二、URL 路徑可以代表控制命令

本課程把不同路徑當作簡單命令:瀏覽器要求 /on,Pico 就開啟 LED;要求 /off,Pico 就關閉 LED;要求 /status,則回傳目前 LED 狀態。瀏覽器並不是直接碰觸 GPIO,而是把 HTTP 要求交給 Pico,由 Pico 程式執行控制。

資料流:瀏覽器點選控制連結 → 傳送 HTTP Request → Pico 解析路徑 → 程式控制 LED → Pico 回傳網頁。

三、建立第一個 Pico Web Server

以下範例把前一堂的 Wi-Fi 連線與本堂的 HTTP Server 串在一起。請先將 SSID 和 PASSWORD 換成課堂核准的測試網路資訊,執行程式後從 Shell 記下 Pico 顯示的 IP,再用同一個區域網路內的手機或電腦瀏覽器開啟該位址。

import network
import socket
import time
from machine import Pin

SSID = "請填入 Wi-Fi 名稱"
PASSWORD = "請填入 Wi-Fi 密碼"
HTTP_PORT = 80

led = Pin("LED", Pin.OUT)
led.value(0)

# 連接 Wi-Fi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)

for attempt in range(20):
    if wlan.isconnected():
        break
    print("Connecting...", attempt + 1)
    time.sleep(1)

if not wlan.isconnected():
    raise RuntimeError("Wi-Fi connection failed")

ip_address = wlan.ifconfig()[0]
print("Open this address in a browser:", ip_address)

# 建立並啟動 Web Server
address = socket.getaddrinfo("0.0.0.0", HTTP_PORT)[0][-1]
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(address)
server.listen(1)

print("Web Server is ready")

while True:
    client, client_address = server.accept()
    try:
        request = client.recv(1024)
        request_text = request.decode()
        print("Request:", request_text.split("\r\n")[0])

        request_line = request_text.split("\r\n")[0]
        parts = request_line.split(" ")
        path = parts[1] if len(parts) > 1 else "/"

        if path == "/on":
            led.value(1)
        elif path == "/off":
            led.value(0)

        if path not in ("/", "/on", "/off", "/status"):
            status_line = "HTTP/1.0 404 Not Found"
            message = "Page not found"
        else:
            status_line = "HTTP/1.0 200 OK"
            message = "LED is ON" if led.value() else "LED is OFF"

        page = """<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Pico Control</title></head>
<body>
  <h1>Pico 2 W Control Panel</h1>
  <p>Current status: {}</p>
  <p><a href="/on">LED ON</a> | <a href="/off">LED OFF</a> | <a href="/status">Refresh status</a></p>
</body>
</html>""".format(message)

        response = (
            status_line + "\r\n"
            + "Content-Type: text/html; charset=utf-8\r\n"
            + "Connection: close\r\n"
            + "\r\n"
            + page
        )
        client.send(response.encode())

    except Exception as error:
        print("Request handling error:", error)
    finally:
        client.close()

Socket 綁定在 0.0.0.0,表示接受本機各網路介面的連線;HTTP 使用的連接埠是 80。程式在 Shell 印出 IP 後會持續等待瀏覽器連線,所以 Thonny 要保持程式執行中。若按 Stop,Web Server 也會停止。

四、HTML 只是 Pico 傳出的文字

Pico 不會像電腦一樣替你「畫出」網頁;它把 HTML 文字放進 HTTP Response,瀏覽器再負責解析與顯示。範例中的標題、狀態文字和連結都由 HTML 組成。修改標題或按鈕名稱,就能改變瀏覽器看到的控制面板。

這裡使用連結 <a href="/on">與 <a href="/off">,點選後瀏覽器會重新向 Pico 發出新要求。伺服器根據網址路徑執行對應動作,並回傳更新後的狀態。

五、第一次測試與除錯

  1. 確認 Pico 已連上 Wi-Fi,Shell 顯示取得的 IP 位址。
  2. 確認程式已印出 Web Server ready,且仍在執行。
  3. 手機或電腦連到與 Pico 相同、且允許裝置互通的區域網路。
  4. 在瀏覽器輸入 http:// 加上 Pico 的 IP 位址,例如 http://192.168.x.x;實際 IP 以你的程式輸出為準。
  5. 點選 LED ON、LED OFF,觀察板載 LED,並查看 Shell 是否印出 Request。

如果網頁打不開,依序確認:Pico 是否有 IP、Server 是否仍在執行、瀏覽器是否使用正確 IP、裝置是否能互相連線,以及 Request 是否到達 Pico。校園或公共 Wi-Fi 可能啟用用戶端隔離,讓同一 Wi-Fi 的裝置也無法彼此連線;這時可改用經教師同意的測試網路。

六、實作挑戰:做自己的控制面板

  • 將首頁標題改成自己的裝置名稱。
  • 在頁面顯示 Pico 的 IP 與 Wi-Fi 連線狀態。
  • 新增 /status 路徑,顯示 LED 開或關。
  • 加入一個 ADC 或感測器讀值,讓網頁同時呈現狀態與資料。
  • 設計不同顏色的按鈕,或加入定時重新整理狀態的功能。
網路安全:這個入門伺服器沒有登入驗證,也沒有加密,只適合在可信任的教學區域網路中練習。不要將它直接暴露到公共 Internet,也不要在公開程式中留下真實 Wi-Fi 密碼。

本篇重點回顧

  • 瀏覽器是 Client,Pico Web Server 是 Server;兩者透過 HTTP Request 與 Response 溝通。
  • Socket 程式負責等待連線、讀取請求、傳送回應與關閉連線。
  • 網址路徑可對應控制命令,例如 /on與 /off。
  • Pico 傳送 HTML 文字,瀏覽器負責呈現畫面。
  • 除錯時先確認 IP、伺服器狀態、網路互通與 Request 是否到達。

下一篇將從「瀏覽器連進 Pico」走向「Pico 主動向外送資料」,認識 HTTP GET/POST 與 REST API。

Wi-Fi、IP 與網際網路連線|Pico 2 W

Pico 2 W × Thonny|第 2 次課程

Wi-Fi、IP 與網際網路連線

Pico 2 W 連上 Wi-Fi,只代表它加入了無線區域網路。要和其他網路或 Internet 上的服務通訊,還需要網路位址、閘道器與 DNS 等資訊。本篇帶你逐步連線、查看設定,並用明確的順序排查問題。

作者:施朝斌

一、Wi-Fi 不等於 Internet

Wi-Fi 是裝置連接無線基地台(AP)的方式;Internet 則是由許多網路互相連接而成。Pico 成功連上基地台,是通訊的第一步,但基地台或校園網路仍可能受到登入驗證、網路政策或外部連線限制,因此「已連上 Wi-Fi」不一定表示「已能使用 Internet」。

Wi-Fi/AP讓 Pico 以無線方式加入區域網路。
IP 位址標示 Pico 在網路中的位置,讓資料知道要送給哪一部裝置。
Gateway通常是路由器,負責把資料從本地網路轉送到其他網路。
DNS協助把網域名稱查詢成 IP 位址。

可以用「校園地址」來想像:IP 像裝置的門牌,Gateway 像通往外部道路的出口,DNS 則像依照名稱查找地址的服務。

二、讓 Pico 2 W 連上 Wi-Fi

MicroPython 使用 network 模組管理無線網路。以下範例將 Pico 設為站台模式(Station),啟用 Wi-Fi,並使用課堂提供的網路名稱與密碼嘗試連線。

import network
import time

SSID = "請填入 Wi-Fi 名稱"
PASSWORD = "請填入 Wi-Fi 密碼"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)

for attempt in range(20):
    if wlan.isconnected():
        break
    print("Connecting...", attempt + 1)
    time.sleep(1)

if wlan.isconnected():
    print("Connected!")
    print("Network settings:", wlan.ifconfig())
else:
    print("Connection failed. Check SSID, password, and network access.")

network.STA_IF表示 Pico 要連到既有的無線基地台,而不是自行建立基地台。active(True)啟用無線介面;connect()開始連線;isconnected()檢查連線狀態。範例設定最多等待約 20 秒,避免程式一直停在等待迴圈中。

保護網路密碼:請使用教師核准的測試網路或自己的行動熱點。不要把真實 Wi-Fi 密碼寫進公開的程式碼、截圖、作業或部落格;分享程式前,先改成提示文字或讀取本機設定檔。

三、讀懂 ifconfig() 回傳的資料

連線成功後,wlan.ifconfig()會回傳一組網路設定,依序包含 IP 位址、子網路遮罩、Gateway 與 DNS。可以先直接列印,再逐項辨認它們的用途。

if wlan.isconnected():
    ip_address, netmask, gateway, dns = wlan.ifconfig()

    print("IP address:", ip_address)
    print("Netmask:", netmask)
    print("Gateway:", gateway)
    print("DNS:", dns)
  • IP 位址:本例程式會顯示 Pico 實際取得的位址。位址可能因網路不同而改變,不要把課堂範例中的數字當成固定值。
  • Netmask:協助判斷哪些位址屬於同一個區域網路。
  • Gateway:通常是目前網路的路由器位址。
  • DNS:提供網域名稱查詢服務的伺服器位址。

多數教學環境會由 DHCP(動態主機設定協定)自動分配這些資訊。若沒有取得有效的網路設定,可能是連線尚未完成、基地台未提供位址,或該網路需要額外驗證。

四、掃描附近的 Wi-Fi 基地台

掃描可以幫助我們觀察 Pico 附近看得到哪些 AP,以及訊號強度等資訊。掃描只是在查看無線環境,不代表已經連上某個網路。

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

for ap in wlan.scan():
    print(ap)

每筆掃描結果會包含 SSID、基地台識別資訊、頻道、RSSI 訊號強度、安全模式與隱藏狀態等欄位;不同韌體的呈現形式可能略有差異。可比較同一地點不同 AP 的訊號,也可以移動 Pico 後重新掃描,觀察數值變化。

許多 IoT 開發板使用 2.4 GHz Wi-Fi。部署時要確認基地台提供裝置可用的頻段與安全設定;如果連不上,先檢查網路設備設定與課堂環境,不要直接推定是程式錯誤。

五、Pico 上不了網時,依序檢查

  1. 供電與 USB:確認 Pico 正常供電,Thonny 能和開發板互動。
  2. 基地台是否可見:執行掃描,確認目標網路出現在結果中。
  3. SSID 與密碼:檢查大小寫、空格與輸入內容;注意不要將密碼公開。
  4. Wi-Fi 是否已連線:以 wlan.isconnected()確認連線狀態。
  5. 是否取得網路設定:列印 wlan.ifconfig(),檢查 IP、Gateway 與 DNS。
  6. 是否能到達目標服務:依課堂允許的方式測試;校園網路可能限制 Ping 或其他連線方式。

每一步都記錄「成功到哪裡」,比反覆修改程式更容易找出問題。例如掃描看得到 AP、但無法連線,和已連線卻沒有取得 IP,代表問題可能在不同環節。

六、實作任務

  • 連上教師指定或自己建立的測試 Wi-Fi。
  • 列出附近 AP,觀察 SSID 與訊號強度。
  • 印出 Pico 的 IP、Netmask、Gateway 與 DNS。
  • 在程式中加入有限次數的重連嘗試,並顯示成功或失敗訊息。
  • 挑戰:設計一個 Wi-Fi 狀態 LED,讓不同連線狀態有可辨識的燈號。

記錄實驗時,可以保留程式、連線結果與除錯步驟;公開分享前,記得移除 Wi-Fi 密碼及其他帳號資訊。

本篇重點回顧

  • Wi-Fi 連線是加入無線區域網路,不等於一定能連上 Internet。
  • Pico 以 network.WLAN(network.STA_IF)連接既有基地台。
  • ifconfig()提供 IP、Netmask、Gateway 與 DNS 等設定。
  • 依照供電、掃描、帳密、連線狀態、IP 設定與服務可達性的順序除錯。
  • 不要將真實網路密碼放進公開程式碼或教材。

下一篇將從網路連線繼續前進,讓 Pico 2 W 建立 Web Server,並嘗試用瀏覽器與開發板互動。