[{"data":1,"prerenderedAt":531},["ShallowReactive",2],{"portfolio-\u002Fportfolio\u002Fsmart-tissue-box":3},{"id":4,"title":5,"award":6,"body":7,"cover":516,"description":163,"extension":517,"featured":518,"link":6,"meta":519,"navigation":197,"order":236,"path":520,"pinned":518,"repo":6,"role":521,"seo":522,"stack":523,"stem":527,"summary":528,"year":529,"__hash__":530},"portfolio\u002Fportfolio\u002Fsmart-tissue-box.md","智慧衛生紙盒",null,{"type":8,"value":9,"toc":508},"minimark",[10,14,23,29,49,52,55,66,73,99,102,106,113,119,122,157,333,336,369,373,379,382,414,417,443,446,468,471,504],[11,12,13],"h2",{"id":13},"專案概述",[15,16,17,18,22],"p",{},"這是我大學的畢業專題。出發點很生活化：公共場所（餐廳、辦公室、廁所）的衛生\n紙盒常常要等到",[19,20,21],"strong",{},"已經空了","才被發現，補貨永遠慢一步；同時，衛生紙盒又是一個\n大家每天都會盯著看幾秒的物件，本身就是一個被忽略的「廣告版位」。",[15,24,25,26,28],{},"於是我們做了一個",[19,27,5],{},"，把兩件事合在一起：",[30,31,32,39],"ul",{},[33,34,35,38],"li",{},[19,36,37],{},"感知剩餘量","：在盒蓋內側裝上超音波感測器，量測衛生紙堆的高度，換算成\n「還剩幾 %」，低於門檻時主動提醒補貨",[33,40,41,44,45,48],{},[19,42,43],{},"前方螢幕做廣告","：盒子前面放一塊螢幕，平常輪播廣告與促銷活動，並同時顯示\n目前剩餘量；螢幕上的 App 以 ",[19,46,47],{},"MIT App Inventor"," 開發",[15,50,51],{},"這也是我第一次把「感測器 → 微控制器 → 無線傳輸 → 手機 App」整條鏈路自己串\n起來的經驗。",[11,53,54],{"id":54},"系統架構",[15,56,57,58,61,62,65],{},"整個系統分成兩端：",[19,59,60],{},"衛生紙盒內的硬體端","負責量測與運算，",[19,63,64],{},"前方螢幕上的 App","\n負責呈現與互動，兩者之間以藍牙序列埠溝通。",[15,67,68],{},[69,70],"img",{"alt":71,"src":72},"智慧衛生紙盒系統架構圖：超音波感測器量測衛生紙堆高度，Arduino 換算成百分比後經藍牙送到前方螢幕的 App Inventor App，App 顯示剩餘量、輪播廣告與促銷活動","\u002Fimages\u002Fportfolio\u002Fsmart-tissue-box-architecture.svg",[30,74,75,81,87,93],{},[33,76,77,80],{},[19,78,79],{},"超音波感測器（HC-SR04）","：固定在盒蓋內側、朝下對準衛生紙堆，每隔固定時間\n發射一次超音波並接收回波",[33,82,83,86],{},[19,84,85],{},"Arduino","：讀取回波時間、換算成距離，再換算成剩餘百分比",[33,88,89,92],{},[19,90,91],{},"藍牙模組","：把計算好的百分比以序列埠字串送出，硬體端不需要知道螢幕上要顯示\n什麼",[33,94,95,98],{},[19,96,97],{},"App Inventor App","：接收百分比，更新畫面上的剩餘量，並負責所有廣告、促銷與\n提醒的呈現邏輯",[15,100,101],{},"把「量測」和「呈現」切開的好處是兩邊可以各自修改：要換廣告素材、調整促銷活\n動，只需要改 App；要換感測器或校正數值，只需要改 Arduino 程式。",[11,103,105],{"id":104},"剩餘量量測從超音波到百分比","剩餘量量測：從超音波到百分比",[15,107,108,109,112],{},"這個專題的核心問題是：",[19,110,111],{},"怎麼知道盒子裡還剩多少衛生紙？"," 我們選擇量「高度」\n而不是「重量」，因為衛生紙很輕，秤重感測器在這個重量區間的誤差反而大；而超音波\n感測器便宜、非接觸，裝在盒蓋上也不影響抽取。",[15,114,115],{},[69,116],{"alt":117,"src":118},"超音波量測示意圖：感測器在盒蓋往下量到衛生紙堆頂面的距離 d，再依滿盒與空盒的距離換算成剩餘百分比","\u002Fimages\u002Fportfolio\u002Fsmart-tissue-box-measurement.svg",[15,120,121],{},"換算分成兩步：",[123,124,125,139],"ol",{},[33,126,127,130,131,135,136],{},[19,128,129],{},"回波時間 → 距離","：超音波從發射到接收的時間 ",[132,133,134],"code",{},"t"," 是「來回」的時間，音速約\n每微秒 0.0343 公分，所以距離 ",[132,137,138],{},"d = t × 0.0343 ÷ 2",[33,140,141,144,145,148,149,152,153,156],{},[19,142,143],{},"距離 → 剩餘百分比","：事先校正兩個基準值——滿盒時感測器到頂面的距離\n",[132,146,147],{},"D_full","、空盒時感測器到盒底的距離 ",[132,150,151],{},"D_empty","——目前的剩餘量就是 ",[132,154,155],{},"d"," 落在這\n兩者之間的比例",[158,159,164],"pre",{"className":160,"code":161,"language":162,"meta":163,"style":163},"language-cpp shiki shiki-themes github-dark","const int TRIG_PIN = 9;\nconst int ECHO_PIN = 10;\nconst float D_FULL = 3.0;   \u002F\u002F 滿盒時到頂面的距離（cm），實際依盒子校正\nconst float D_EMPTY = 12.0; \u002F\u002F 空盒時到盒底的距離（cm）\n\nfloat readDistanceCm() {\n  digitalWrite(TRIG_PIN, LOW);\n  delayMicroseconds(2);\n  digitalWrite(TRIG_PIN, HIGH);\n  delayMicroseconds(10);\n  digitalWrite(TRIG_PIN, LOW);\n  long duration = pulseIn(ECHO_PIN, HIGH);\n  return duration * 0.0343 \u002F 2;\n}\n\nvoid loop() {\n  float sum = 0;\n  for (int i = 0; i \u003C 5; i++) {      \u002F\u002F 取多次平均，過濾雜訊\n    sum += readDistanceCm();\n    delay(60);\n  }\n  float d = sum \u002F 5;\n  int percent = (D_EMPTY - d) \u002F (D_EMPTY - D_FULL) * 100;\n  percent = constrain(percent, 0, 100);\n\n  Serial.println(percent);           \u002F\u002F 經藍牙模組送到 App\n  delay(1000);\n}\n","cpp","",[132,165,166,174,180,186,192,199,205,211,217,223,229,234,240,246,252,257,263,269,275,281,287,293,299,305,311,316,322,328],{"__ignoreMap":163},[167,168,171],"span",{"class":169,"line":170},"line",1,[167,172,173],{},"const int TRIG_PIN = 9;\n",[167,175,177],{"class":169,"line":176},2,[167,178,179],{},"const int ECHO_PIN = 10;\n",[167,181,183],{"class":169,"line":182},3,[167,184,185],{},"const float D_FULL = 3.0;   \u002F\u002F 滿盒時到頂面的距離（cm），實際依盒子校正\n",[167,187,189],{"class":169,"line":188},4,[167,190,191],{},"const float D_EMPTY = 12.0; \u002F\u002F 空盒時到盒底的距離（cm）\n",[167,193,195],{"class":169,"line":194},5,[167,196,198],{"emptyLinePlaceholder":197},true,"\n",[167,200,202],{"class":169,"line":201},6,[167,203,204],{},"float readDistanceCm() {\n",[167,206,208],{"class":169,"line":207},7,[167,209,210],{},"  digitalWrite(TRIG_PIN, LOW);\n",[167,212,214],{"class":169,"line":213},8,[167,215,216],{},"  delayMicroseconds(2);\n",[167,218,220],{"class":169,"line":219},9,[167,221,222],{},"  digitalWrite(TRIG_PIN, HIGH);\n",[167,224,226],{"class":169,"line":225},10,[167,227,228],{},"  delayMicroseconds(10);\n",[167,230,232],{"class":169,"line":231},11,[167,233,210],{},[167,235,237],{"class":169,"line":236},12,[167,238,239],{},"  long duration = pulseIn(ECHO_PIN, HIGH);\n",[167,241,243],{"class":169,"line":242},13,[167,244,245],{},"  return duration * 0.0343 \u002F 2;\n",[167,247,249],{"class":169,"line":248},14,[167,250,251],{},"}\n",[167,253,255],{"class":169,"line":254},15,[167,256,198],{"emptyLinePlaceholder":197},[167,258,260],{"class":169,"line":259},16,[167,261,262],{},"void loop() {\n",[167,264,266],{"class":169,"line":265},17,[167,267,268],{},"  float sum = 0;\n",[167,270,272],{"class":169,"line":271},18,[167,273,274],{},"  for (int i = 0; i \u003C 5; i++) {      \u002F\u002F 取多次平均，過濾雜訊\n",[167,276,278],{"class":169,"line":277},19,[167,279,280],{},"    sum += readDistanceCm();\n",[167,282,284],{"class":169,"line":283},20,[167,285,286],{},"    delay(60);\n",[167,288,290],{"class":169,"line":289},21,[167,291,292],{},"  }\n",[167,294,296],{"class":169,"line":295},22,[167,297,298],{},"  float d = sum \u002F 5;\n",[167,300,302],{"class":169,"line":301},23,[167,303,304],{},"  int percent = (D_EMPTY - d) \u002F (D_EMPTY - D_FULL) * 100;\n",[167,306,308],{"class":169,"line":307},24,[167,309,310],{},"  percent = constrain(percent, 0, 100);\n",[167,312,314],{"class":169,"line":313},25,[167,315,198],{"emptyLinePlaceholder":197},[167,317,319],{"class":169,"line":318},26,[167,320,321],{},"  Serial.println(percent);           \u002F\u002F 經藍牙模組送到 App\n",[167,323,325],{"class":169,"line":324},27,[167,326,327],{},"  delay(1000);\n",[167,329,331],{"class":169,"line":330},28,[167,332,251],{},[15,334,335],{},"實作時遇到的幾個實際問題：",[30,337,338,348,357],{},[33,339,340,343,344,347],{},[19,341,342],{},"衛生紙表面不平整","：抽取後頂面會有一張翹起來的紙，單次量測會突然變短。以\n",[19,345,346],{},"多次取樣平均","並限制在 0–100 之間，讓畫面上的數字不會亂跳",[33,349,350,353,354,356],{},[19,351,352],{},"最小量測距離","：HC-SR04 在太近的距離量不準，所以感測器不能直接貼著衛生紙，\n要在盒蓋上預留一點高度，這也是 ",[132,355,147],{}," 不是 0 的原因",[33,358,359,362,363,365,366,368],{},[19,360,361],{},"不同盒子要重新校正","：",[132,364,147],{},"、",[132,367,151],{}," 會隨盒子尺寸改變，所以設計成常\n數集中在程式開頭，換盒子時只需要量兩次距離",[11,370,372],{"id":371},"前方螢幕app-inventor-製作的廣告與促銷畫面","前方螢幕：App Inventor 製作的廣告與促銷畫面",[15,374,375,376,378],{},"盒子前方的螢幕是一台 Android 裝置，上面跑的 App 用 ",[19,377,47],{}," 開發。\nApp Inventor 以拖拉積木（Blocks）的方式寫程式，對我們來說可以把時間花在\n「畫面要呈現什麼」，而不是卡在 Android 開發環境上。",[15,380,381],{},"畫面由上而下分成三塊：",[30,383,384,398,408],{},[33,385,386,389,390,393,394,397],{},[19,387,388],{},"廣告輪播區","：佔畫面最大的版位，用 ",[132,391,392],{},"Clock"," 元件每隔幾秒觸發一次，依序切換\n",[132,395,396],{},"Image"," 的圖片來源，達到輪播效果",[33,399,400,403,404,407],{},[19,401,402],{},"剩餘量區","：顯示目前百分比與進度條；",[132,405,406],{},"BluetoothClient"," 收到 Arduino 送來的數\n字後即時更新",[33,409,410,413],{},[19,411,412],{},"促銷活動區","：顯示限時優惠與折價券 QR Code，讓使用者在抽衛生紙的幾秒鐘內就\n能掃碼帶走優惠",[15,415,416],{},"App 端的主要邏輯（以積木表達的流程）：",[30,418,419,425,431,437],{},[33,420,421,424],{},[132,422,423],{},"Screen.Initialize","：連線到衛生紙盒的藍牙模組，啟動輪播與讀值的計時器",[33,426,427,430],{},[132,428,429],{},"ReadClock.Timer","：若藍牙有資料可讀，就讀出百分比、更新文字與進度條",[33,432,433,436],{},[132,434,435],{},"AdClock.Timer","：輪播索引加一，從廣告清單中換下一張圖",[33,438,439,442],{},[19,440,441],{},"低量提醒","：當百分比低於設定門檻（例如 20%），剩餘量區改成紅色並顯示「請\n補充衛生紙」，讓現場人員一眼就看得到",[11,444,445],{"id":445},"成果與收穫",[30,447,448,451,458,465],{},[33,449,450],{},"完成一個可實際展示的原型：抽取衛生紙時，前方螢幕上的剩餘百分比會跟著下降，\n同時持續輪播廣告與促銷內容",[33,452,453,454,457],{},"第一次完整經歷",[19,455,456],{},"感測器 → 微控制器 → 無線傳輸 → App"," 的端到端整合，也體會到\n硬體資料「先清洗再使用」的重要性——真實世界的感測值永遠比課本上的雜",[33,459,460,461,464],{},"把「量測」和「呈現」拆成兩個獨立的部分、用一個簡單的協定（只傳一個數字）溝\n通，專題後期組員分頭修改 Arduino 和 App 時幾乎不會互相影響，讓我體會到",[19,462,463],{},"職責\n分離、介面越簡單越好","的價值",[33,466,467],{},"還想改進的地方：目前一個紙盒只能對應一台螢幕，而且補貨提醒只有現場看得到。\n如果之後能改用 Wi-Fi 把多個紙盒的剩餘量集中回報到一個後台，清潔人員就能一次\n看到所有位置的狀態，廣告內容也能從後台統一更換",[11,469,470],{"id":470},"使用技術",[30,472,473,479,485,498],{},[33,474,475,478],{},[19,476,477],{},"硬體","：Arduino、HC-SR04 超音波感測器、藍牙序列埠模組",[33,480,481,484],{},[19,482,483],{},"韌體","：Arduino C\u002FC++（回波量測、距離換算、取樣平均）",[33,486,487,490,491,365,493,365,495,497],{},[19,488,489],{},"App","：MIT App Inventor（",[132,492,406],{},[132,494,392],{},[132,496,396],{}," 元件）、Android",[33,499,500,503],{},[19,501,502],{},"概念","：IoT 感測與資料平滑、數位看板（Digital Signage）廣告輪播",[505,506,507],"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":163,"searchDepth":176,"depth":176,"links":509},[510,511,512,513,514,515],{"id":13,"depth":176,"text":13},{"id":54,"depth":176,"text":54},{"id":104,"depth":176,"text":105},{"id":371,"depth":176,"text":372},{"id":445,"depth":176,"text":445},{"id":470,"depth":176,"text":470},"\u002Fimages\u002Fportfolio\u002Fsmart-tissue-box-cover.svg","md",false,{},"\u002Fportfolio\u002Fsmart-tissue-box","大學專題 ‧ 硬體整合與 App 開發",{"title":5,"description":163},[85,524,91,47,525,526],"HC-SR04 超音波感測器","Android","IoT","portfolio\u002Fsmart-tissue-box","大學畢業專題。在衛生紙盒蓋內裝上超音波感測器，即時量測衛生紙堆的高度並換算成剩餘百分比，再透過藍牙送到盒子前方的螢幕；螢幕上以 App Inventor 製作的 App 同時顯示剩餘量、輪播廣告與促銷活動，讓一個日常耗材變成可以感知、也能帶來曝光的小型物聯網裝置。",2018,"CBBJEN-Ixas74_oH1SA0uXqSPMHRq56P7VGN-UR1LWw",1792959149997]