[{"data":1,"prerenderedAt":678},["ShallowReactive",2],{"portfolio-\u002Fportfolio\u002Fsmart-home-appliances":3},{"id":4,"title":5,"award":6,"body":7,"cover":662,"description":257,"extension":663,"featured":664,"link":6,"meta":665,"navigation":279,"order":324,"path":666,"pinned":664,"repo":6,"role":667,"seo":668,"stack":669,"stem":674,"summary":675,"year":676,"__hash__":677},"portfolio\u002Fportfolio\u002Fsmart-home-appliances.md","智慧家電控制系統",null,{"type":8,"value":9,"toc":653},"minimark",[10,14,23,26,68,79,83,86,93,150,153,157,172,178,185,251,444,447,474,477,484,490,528,532,538,591,594,613,616,649],[11,12,13],"h2",{"id":13},"專案概述",[15,16,17,18,22],"p",{},"這個專題想解決的是很實際的小麻煩：出門後才想到房間燈沒關、想在回家前先把電腦\n開機、出遠門幾天家裡的盆栽沒人澆水。市面上的智慧家電要整套換掉，於是我決定",[19,20,21],"strong",{},"自\n己把現有的家電接上網路","。",[15,24,25],{},"整個系統分成三個部分：",[27,28,29,48,58],"ul",{},[30,31,32,35,36,39,40,43,44,47],"li",{},[19,33,34],{},"網路","：利用家裡數據機的",[19,37,38],{},"固定 IP","，透過 ",[19,41,42],{},"Port Forwarding"," 把外網請求\n導進我自己切出來的 ",[19,45,46],{},"\u002F24 內網","，讓手機在任何地方都能連回家",[30,49,50,53,54,57],{},[19,51,52],{},"控制器","：每個區域（客廳、我的房間、電腦、澆花）各放一組 ",[19,55,56],{},"Arduino +\nESP8266 Wi-Fi 模組","，用繼電器控制電源開關",[30,59,60,63,64,67],{},[19,61,62],{},"App","：用 ",[19,65,66],{},"MIT App Inventor"," 做手機 App，一個畫面就能看到並切換所有設備\n的狀態",[15,69,70,71,74,75,78],{},"這個專題讓我第一次把",[19,72,73],{},"網路規劃","和",[19,76,77],{},"嵌入式控制","放在同一個系統裡思考，做完之\n後也發現自己對「網路」這一塊特別有興趣。",[11,80,82],{"id":81},"網路架構固定-ip-port-forwarding-24-內網","網路架構：固定 IP + Port Forwarding + \u002F24 內網",[15,84,85],{},"要讓手機從外面控制家裡的設備，第一個問題是「外面怎麼找得到家裡的 Arduino？」\n家用網路的設備都躲在 NAT 後面、只有私有 IP，外網是看不到的。我的做法是：",[15,87,88],{},[89,90],"img",{"alt":91,"src":92},"網路拓樸圖：手機從外網連到數據機的固定公網 IP，依 Port Forwarding 規則轉送到 192.168.10.0\u002F24 內網中各區域的控制器","\u002Fimages\u002Fportfolio\u002Fsmart-home-network.svg",[27,94,95,101,136],{},[30,96,97,100],{},[19,98,99],{},"固定 IP 當入口","：家裡的數據機申請了固定 IP，App 只要記住一個不會變的位\n址，不需要額外架 DDNS",[30,102,103,106,107,111,112,115,116,119,120,123,124,127,128,131,132,135],{},[19,104,105],{},"切出專用的 \u002F24 網段","：在內網規劃一個 ",[108,109,110],"code",{},"192.168.10.0\u002F24"," 給這套系統使用，\nGateway 為 ",[108,113,114],{},".1","，控制器依區域",[19,117,118],{},"固定分配 IP","（",[108,121,122],{},".11"," 客廳、",[108,125,126],{},".12"," 房間、",[108,129,130],{},".13","\n電腦、",[108,133,134],{},".14"," 澆花）。IP 固定是 Port Forwarding 能運作的前提——如果控制器靠\nDHCP 拿到浮動 IP，轉送規則隨時可能指錯設備",[30,137,138,141,142,145,146,149],{},[19,139,140],{},"一個外部 Port 對應一台控制器","：數據機上設定 ",[108,143,144],{},":8081 → .11:80","、\n",[108,147,148],{},":8082 → .12:80","… 以此類推。外部用不同 Port 區分設備，內部每台控制器都只要\n開一個標準的 80 Port，程式可以完全共用",[15,151,152],{},"IP 規劃的好處在後續擴充也看得出來：\u002F24 有 254 個可用位址，新增設備只要挑一個\n沒用過的 IP、在數據機加一條轉送規則，App 多一個按鈕即可，不會動到既有設備。",[11,154,156],{"id":155},"控制器arduino-esp8266-繼電器","控制器：Arduino + ESP8266 + 繼電器",[15,158,159,160,163,164,167,168,171],{},"每個區域的控制器硬體組成都一樣：",[19,161,162],{},"Arduino"," 負責邏輯、",[19,165,166],{},"ESP8266"," 負責 Wi-Fi\n連線與 HTTP、",[19,169,170],{},"繼電器模組","負責實際切換 110V 電源，讓低電壓的控制電路和家電的\n高電壓完全隔開。",[15,173,174],{},[89,175],{"alt":176,"src":177},"控制流程圖：App 發出 HTTP GET 到公網 IP 的指定 Port，數據機 NAT 轉送到內網的 ESP8266，Arduino 解析路徑後切換繼電器，並把目前狀態回傳給 App 更新畫面","\u002Fimages\u002Fportfolio\u002Fsmart-home-control-flow.svg",[15,179,180,181,184],{},"控制介面刻意設計得非常簡單：",[19,182,183],{},"一個動作就是一個 URL 路徑","，回傳值就是目前狀\n態的純文字，App 端不需要解析任何複雜格式。",[186,187,188,204],"table",{},[189,190,191],"thead",{},[192,193,194,198,201],"tr",{},[195,196,197],"th",{},"路徑",[195,199,200],{},"動作",[195,202,203],{},"回傳",[205,206,207,223,238],"tbody",{},[192,208,209,215,218],{},[210,211,212],"td",{},[108,213,214],{},"\u002Fon",[210,216,217],{},"繼電器吸合，設備通電",[210,219,220],{},[108,221,222],{},"living:ON",[192,224,225,230,233],{},[210,226,227],{},[108,228,229],{},"\u002Foff",[210,231,232],{},"繼電器斷開，設備斷電",[210,234,235],{},[108,236,237],{},"living:OFF",[192,239,240,245,248],{},[210,241,242],{},[108,243,244],{},"\u002Fstatus",[210,246,247],{},"只查詢不切換",[210,249,250],{},"目前狀態",[252,253,258],"pre",{"className":254,"code":255,"language":256,"meta":257,"style":257},"language-cpp shiki shiki-themes github-dark","#include \u003CSoftwareSerial.h>\n#include \u003CWiFiEsp.h>\n\nconst int RELAY_PIN = 7;\nSoftwareSerial esp(2, 3);            \u002F\u002F RX, TX：與 ESP8266 溝通\nIPAddress ip(192, 168, 10, 11);      \u002F\u002F 每台控制器燒錄時改成自己的固定 IP\nWiFiEspServer server(80);\nbool isOn = false;\n\nvoid setup() {\n  pinMode(RELAY_PIN, OUTPUT);\n  esp.begin(9600);\n  WiFi.init(&esp);\n  WiFi.config(ip);\n  WiFi.begin(\"HomeIoT\", \"********\");\n  server.begin();\n}\n\nvoid loop() {\n  WiFiEspClient client = server.available();\n  if (!client) return;\n\n  String request = client.readStringUntil('\\r');\n  if (request.indexOf(\"GET \u002Fon\") >= 0)  isOn = true;\n  if (request.indexOf(\"GET \u002Foff\") >= 0) isOn = false;\n  digitalWrite(RELAY_PIN, isOn ? HIGH : LOW);\n\n  client.print(\"HTTP\u002F1.1 200 OK\\r\\nContent-Type: text\u002Fplain\\r\\n\\r\\n\");\n  client.print(isOn ? \"living:ON\" : \"living:OFF\");\n  client.stop();\n}\n","cpp","",[108,259,260,268,274,281,287,293,299,305,311,316,322,328,334,340,346,352,358,364,369,375,381,387,392,398,404,410,416,421,427,433,439],{"__ignoreMap":257},[261,262,265],"span",{"class":263,"line":264},"line",1,[261,266,267],{},"#include \u003CSoftwareSerial.h>\n",[261,269,271],{"class":263,"line":270},2,[261,272,273],{},"#include \u003CWiFiEsp.h>\n",[261,275,277],{"class":263,"line":276},3,[261,278,280],{"emptyLinePlaceholder":279},true,"\n",[261,282,284],{"class":263,"line":283},4,[261,285,286],{},"const int RELAY_PIN = 7;\n",[261,288,290],{"class":263,"line":289},5,[261,291,292],{},"SoftwareSerial esp(2, 3);            \u002F\u002F RX, TX：與 ESP8266 溝通\n",[261,294,296],{"class":263,"line":295},6,[261,297,298],{},"IPAddress ip(192, 168, 10, 11);      \u002F\u002F 每台控制器燒錄時改成自己的固定 IP\n",[261,300,302],{"class":263,"line":301},7,[261,303,304],{},"WiFiEspServer server(80);\n",[261,306,308],{"class":263,"line":307},8,[261,309,310],{},"bool isOn = false;\n",[261,312,314],{"class":263,"line":313},9,[261,315,280],{"emptyLinePlaceholder":279},[261,317,319],{"class":263,"line":318},10,[261,320,321],{},"void setup() {\n",[261,323,325],{"class":263,"line":324},11,[261,326,327],{},"  pinMode(RELAY_PIN, OUTPUT);\n",[261,329,331],{"class":263,"line":330},12,[261,332,333],{},"  esp.begin(9600);\n",[261,335,337],{"class":263,"line":336},13,[261,338,339],{},"  WiFi.init(&esp);\n",[261,341,343],{"class":263,"line":342},14,[261,344,345],{},"  WiFi.config(ip);\n",[261,347,349],{"class":263,"line":348},15,[261,350,351],{},"  WiFi.begin(\"HomeIoT\", \"********\");\n",[261,353,355],{"class":263,"line":354},16,[261,356,357],{},"  server.begin();\n",[261,359,361],{"class":263,"line":360},17,[261,362,363],{},"}\n",[261,365,367],{"class":263,"line":366},18,[261,368,280],{"emptyLinePlaceholder":279},[261,370,372],{"class":263,"line":371},19,[261,373,374],{},"void loop() {\n",[261,376,378],{"class":263,"line":377},20,[261,379,380],{},"  WiFiEspClient client = server.available();\n",[261,382,384],{"class":263,"line":383},21,[261,385,386],{},"  if (!client) return;\n",[261,388,390],{"class":263,"line":389},22,[261,391,280],{"emptyLinePlaceholder":279},[261,393,395],{"class":263,"line":394},23,[261,396,397],{},"  String request = client.readStringUntil('\\r');\n",[261,399,401],{"class":263,"line":400},24,[261,402,403],{},"  if (request.indexOf(\"GET \u002Fon\") >= 0)  isOn = true;\n",[261,405,407],{"class":263,"line":406},25,[261,408,409],{},"  if (request.indexOf(\"GET \u002Foff\") >= 0) isOn = false;\n",[261,411,413],{"class":263,"line":412},26,[261,414,415],{},"  digitalWrite(RELAY_PIN, isOn ? HIGH : LOW);\n",[261,417,419],{"class":263,"line":418},27,[261,420,280],{"emptyLinePlaceholder":279},[261,422,424],{"class":263,"line":423},28,[261,425,426],{},"  client.print(\"HTTP\u002F1.1 200 OK\\r\\nContent-Type: text\u002Fplain\\r\\n\\r\\n\");\n",[261,428,430],{"class":263,"line":429},29,[261,431,432],{},"  client.print(isOn ? \"living:ON\" : \"living:OFF\");\n",[261,434,436],{"class":263,"line":435},30,[261,437,438],{},"  client.stop();\n",[261,440,442],{"class":263,"line":441},31,[261,443,363],{},[15,445,446],{},"三種設備在接線上的差異：",[27,448,449,461],{},[30,450,451,454,455,457,458,460],{},[19,452,453],{},"客廳、我的房間","：繼電器串接在電燈與插座的電源線上，",[108,456,214],{},"、",[108,459,229],{}," 直接對\n應通電與斷電",[30,462,463,466,467,470,471,473],{},[19,464,465],{},"電腦","：電腦不能直接斷電，所以繼電器改成",[19,468,469],{},"並聯在主機板的電源按鈕接腳","\n上。收到 ",[108,472,214],{}," 時讓繼電器吸合約 0.5 秒再放開，模擬「按一下電源鍵」，讓電腦走\n正常的開機流程",[11,475,476],{"id":476},"自動澆花系統",[15,478,479,480,483],{},"澆花控制器在同一套架構上多了一個 ",[19,481,482],{},"DS3231 RTC 時鐘模組","。Arduino 本身沒有可\n靠的時鐘，一斷電時間就歸零；RTC 模組有自己的電池，停電後再復電仍然知道現在幾\n點，排程不會亂掉。",[15,485,486],{},[89,487],{"alt":488,"src":489},"自動澆花流程圖：控制器每秒讀取 RTC 時間，到了設定時間就開啟水泵、澆 N 秒後自動關閉；手機 App 可遠端調整澆花時間、澆水秒數或立即手動澆一次","\u002Fimages\u002Fportfolio\u002Fsmart-home-watering.svg",[27,491,492,498,512,522],{},[30,493,494,497],{},[19,495,496],{},"定時澆花","：控制器每秒讀一次 RTC，到了設定的時間（例如每天 07:00）就讓繼電\n器吸合、啟動小型水泵，澆滿設定秒數後自動關閉",[30,499,500,503,504,507,508,511],{},[19,501,502],{},"遠端調整","：App 可以透過 ",[108,505,506],{},"\u002Fschedule?h=7&m=0"," 修改澆花時間、\n",[108,509,510],{},"\u002Fduration?s=15"," 修改澆水秒數，出門在外也能依天氣調整",[30,513,514,517,518,521],{},[19,515,516],{},"手動澆一次","：",[108,519,520],{},"\u002Fwater"," 立即澆一次，適合天氣特別熱的時候額外補水",[30,523,524,527],{},[19,525,526],{},"安全上限","：不論收到什麼參數，單次澆水時間都有上限，避免參數打錯或網路重\n送請求時，水泵一直開著把陽台淹掉",[11,529,531],{"id":530},"手機-appapp-inventor","手機 App：App Inventor",[15,533,534,535,537],{},"App 用 ",[19,536,66],{}," 開發，整個畫面就是一排依區域分組的按鈕：客廳、我的\n房間、電腦、澆花。",[27,539,540,561,585],{},[30,541,542,545,546,549,550,553,554,557,558],{},[19,543,544],{},"送出指令","：每個按鈕對應一組 URL，點擊時用 ",[108,547,548],{},"Web"," 元件把 ",[108,551,552],{},"Url"," 設為\n",[108,555,556],{},"http:\u002F\u002F\u003C固定IP>:\u003CPort>\u002F\u003C動作>"," 再呼叫 ",[108,559,560],{},"Web.Get",[30,562,563,517,566,569,570,573,574,577,578,580,581,584],{},[19,564,565],{},"同步狀態",[108,567,568],{},"Web.GotText"," 收到控制器回傳的文字後，依 ",[108,571,572],{},"ON"," \u002F ",[108,575,576],{},"OFF"," 把按鈕\n改成對應的顏色與文字；App 開啟時則對每台控制器打一次 ",[108,579,244],{},"，讓畫面顯示的\n永遠是設備的",[19,582,583],{},"實際狀態","，而不是上次按了什麼",[30,586,587,590],{},[19,588,589],{},"連線失敗提示","：控制器沒有回應時（斷電、斷線）跳出提示，而不是讓使用者以為\n已經關掉了",[11,592,593],{"id":593},"成果與收穫",[27,595,596,599,606],{},[30,597,598],{},"完成可以實際在家使用的系統：人在外面就能用手機關掉忘記關的燈、提早把電腦開\n機，盆栽也能按時自動澆水",[30,600,601,602,605],{},"第一次從頭規劃一個網段：",[19,603,604],{},"IP 位址規劃、固定 IP 分配、NAT 與 Port\nForwarding"," 這些原本只在課本上的觀念，在這裡都要自己設定、自己除錯。設定錯\n一條轉送規則、或控制器的 IP 被 DHCP 換掉，App 就連不到設備，這些坑踩過一次\n就記住了，也讓我更確定畢業後想往網路工程的方向走",[30,607,608,609,612],{},"目前最大的問題是安全性：控制器直接把 HTTP 服務開在公網上，沒有加密也沒有身\n分驗證，只要知道 IP 和 Port 就能控制家電。下一步想研究的是讓設備",[19,610,611],{},"不直接對外\n開 Port","，例如手機先 VPN 回家再控制，或改用 MQTT 讓設備主動連到外面的 Broker，\n再加上加密與帳號驗證",[11,614,615],{"id":615},"使用技術",[27,617,618,623,629,635],{},[30,619,620,622],{},[19,621,34],{},"：固定 IP、NAT \u002F Port Forwarding、\u002F24 子網路規劃、靜態 IP 分配",[30,624,625,628],{},[19,626,627],{},"硬體","：Arduino、ESP8266 Wi-Fi 模組、繼電器模組、DS3231 RTC 時鐘模組、小\n型水泵",[30,630,631,634],{},[19,632,633],{},"韌體","：Arduino C\u002FC++（WiFiEsp HTTP Server、繼電器控制、RTC 排程）",[30,636,637,639,640,457,642,457,645,648],{},[19,638,62],{},"：MIT App Inventor（",[108,641,548],{},[108,643,644],{},"Button",[108,646,647],{},"Notifier"," 元件）、Android",[650,651,652],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":257,"searchDepth":270,"depth":270,"links":654},[655,656,657,658,659,660,661],{"id":13,"depth":270,"text":13},{"id":81,"depth":270,"text":82},{"id":155,"depth":270,"text":156},{"id":476,"depth":270,"text":476},{"id":530,"depth":270,"text":531},{"id":593,"depth":270,"text":593},{"id":615,"depth":270,"text":615},"\u002Fimages\u002Fportfolio\u002Fsmart-home-cover.svg","md",false,{},"\u002Fportfolio\u002Fsmart-home-appliances","個人專題 ‧ 網路規劃與硬體整合",{"title":5,"description":257},[162,166,170,670,66,42,671,672,673],"DS3231 RTC","NAT","IP 子網路規劃","IoT","portfolio\u002Fsmart-home-appliances","以家中數據機的固定 IP 為入口，透過 Port Forwarding 連進自己規劃的 \u002F24 內網；每個區域各放一組 Arduino + Wi-Fi 模組控制繼電器，用 App Inventor 做的手機 App 就能從外網開關客廳、房間的電器與電腦，並加上一套可定時、也能遠端調整的自動澆花系統。",2018,"c_gQZTdcUqAOiI6eOJjsvPTD5INmu_TYlbN1e2_duTA",1792959149993]