因為自動飲料機而延畢的那一年(16)
整個飲料機的軟體架構是怎麼實做的呢?
最早我們原本打算要使用STM32F429 Discovery開發板,上頭是ARM Cortex-M4架構的晶片,因為這是我在成大上Jserv嵌入式系統課時使用的教材。
(上 圖是STM32F429開發板)
但後來發現,隨著飲料機的模組變多,控制邏輯越來越複雜,這個板子只個微控制器,要同時控制糖、冰塊、茶湯,用C語言和freeRTOS寫這些東西開發效率會慢到崩潰。
剛好紘銘手上有張intel Galileo 2,我們便就地取材,上頭是的晶片是Intel Quark,而且可以直接讀取SD卡的Linux image,能跑python, nodejs整個就很開心。
上圖是飲料機使用的開發板intel Galileo 2
我們決定用網頁的界面來控制,因為以前寫過網站,所以相對而言會簡單很多,但還是前前後後修改了很多次。一開始決定用python + django來實做,但實際跑起來後發現一些問題,django畢竟是比較厚重的framework,網頁回應的速度有點小頓,而且要接一些realtime的功能顯示飲料機目前狀況比較不方便,後來就決定改用nodejs + express + angularjs來實做,程式比較輕量,網頁載入的速度比較快,而且前後端都是JavaScript,接WebSocket好接多了。
但即使是這樣,實做上還是會遇到幾個問題
- JavaScript的Callback Hell會讓程式碼極難維護,飲料機裡頭有各種事件,例如「感應到杯子了」、「可以加茶了」、「可以加糖了」、「可以加冰塊了」、「冰塊的量到了」,這些事件串起來後會讓callback非常深,後來趕緊學Promise來處理這些問題,我還寫了一篇Promise實做理解筆記。
- 使用MRAA(Intel官方用於操作Intel Galielo 2控制板GPIO的Llibrary)的nodejs界面時,程式剛開始很順利,但執行一段時間後會發生不明原因,無法控制IO的狀況。要是飲料機運行到一半發生這問題,可能會造成茶湯出口無法關閉、茶湯一直流出到整桶茶漏完為止、或是冰塊一直倒出停不下來的失控狀況。剛開始完全摸不著頭緒,單步執行檢查程式流程找不出問題來,官方文件相當簡略,網路上也查不太到該問題,後來懷疑可能是intel官方提供的GPIO控制模組不能夠多次引入,但因為被javascript包起來後看不出來,造成該模組在操作一段時間後可能被Garbage Collection掉,因此決定改變寫法,使用Singleton pattern,確保整個程式環境內的GPIO只有在一開始被引入一次,成功解決這個問題。
- 模組化,飲料機的程式用嘴巴講很簡單,但實做上會遇到很多細節要決定,而且要正確的讓每個模組對應到硬體的零件。實際上包含但不限於以下項目
- 界面
- 顯示可製作的飲料選項、菜單
- 使用WebSocket顯示目前機器工作的狀況,正在製作哪一杯飲料、是否偵測到杯子、正在加茶、加冰塊等
- 顯示目前已下單但尚未製作的飲料清單
- 機器校正相關的設定,包含糖機出糖量的校正、茶湯出口對應的茶湯種類設定、冰塊重量的校正等
- 後端
- jobQueue 負責管理製作飲料的任務清單
- Controller 對應到機器內部各個模組的控制器
- cupSensor 杯子感應器
- iceDispenser 冰塊分配器
- iceGuardServoController 冰塊擋板控制器
- scaleController 冰塊重量感測器
- sugarMachineController 加糖機控制器
- teaController 茶湯控制器
- funnelController 漏斗控制器
- drinkMaker 操作各模組的控制器,完成整個飲料機操作的流程
- boardIO,管理板子上的數位IO和類比IO
- machineStateNotifier 負責告知前端機器目前發生的事件
- config 儲存板子上的各項設定值,包含可製作的菜單,每種菜單對應的原料(糖量、茶湯比例、大杯中杯不同甜度冰塊是否有差別等)、糖機目前的設定值(不同糖機按鈕的單位給糖量、給糖速率等)
另外麻煩的還有把飲料店內的菜單對應到機器的配方,飲料店在做飲料的時候常常會有一些慣例,例如如果加了檸檬,那麼糖就要變兩倍、但如果同時有金桔和檸檬,那麼糖的量只需要變1.5倍、如果鮮奶配上某種茶就要減少糖量,諸如此類奇怪的規則都要慢慢花時間和店家討論釐清。
附上當時開發到一半的界面的操作影片