感覺一年前就想撰寫這篇文章,當時人還在上一家公司工作。
老實說我蠻喜歡上家公司的工作內容,我真的是蠻希望如果有機會可以繼續做 IoT 相關的軟體開發,感覺偏向軟硬體結合更有成就感,看著硬體端傳來的機械碼,經過程式加解密後,軟體也能成為人與硬體溝通的橋樑,透過軟體操控硬體設備。
我想應該很少關於路燈
或太陽能
相關的 IoT 應用文章,畢竟其實要搞硬體本身就不是件簡單的事情,而硬體的對象還是十分工業化,規模相當大的硬體設備,動則一塊太陽能板就要幾萬,一盞路燈也要幾萬,在這之中的各種因素又更錯綜複雜,我很慶幸能夠在這樣的機會下工作,雖然之後部門轉型了,沒有在做與 IoT 相關的軟體開發了,而我也離開公司了,但這個難忘的經驗讓我覺得我很喜歡軟硬體結合的知識。
試問,每天走在路上,每天騎車開車經過,有多少人會停下來看路旁的路燈
?如果不是專門施工路燈的技術工程師,也許很少會有人去仔細研究路燈,而在這個部門工作,軟硬體技術也超乎了我原本設想科技也許沒這麼進步的思維,那麼就讓我們來一虧究竟吧!
我在能源管理與系統整合部門工作約一年,接觸到的專案(產品)第一個是智慧路燈管理系統
,第二個是太陽能源管理系統
,這兩個部分的軟體開發期間,讓我一直不斷地學習、挑戰與突破我的個人能力,從軟體開發能力、程式架構規劃能力、會議主持能力、溝通能力一直到硬體機械知識及太陽能領域知識,充分地在這一年的工作期間好好學習。真的是十分想念那樣天天高壓、急迫、忙碌與源源不絕的 deadline 與進度的日子。
這篇會先撰寫關於路燈
的 IoT 應用故事。
智慧路燈管理系統
這個專案是我進公司第一個參與的專案,當時才正開始起步,我當時的感覺是,當時的團隊缺一個有經驗且主導性高的後端工程師,而我就是那個人,比起我的同事入職面試,我很扎實的過了五關,個人作品、履歷一定具備,再來就是英文考試、後端工程師考試、邏輯測驗、人資跟主管交互面試,直到入職半年後偶然和同事們吃飯聊到,才發現原來自己是那個唯一接受很多考試跟面試的人。
即便我當時只是一個一年工作經驗的後端工程師,但我還是能感覺我被賦予厚望。
我以為我才一年的工作經驗,下份工作還可以再找著 Senior 帶我,結果我進公司直接被迫成長為 Senior 帶人,還要主導會議。
但我喜歡挑戰,帶著充滿沒自信的不安感,膽戰心驚的挑戰每一個難關。
智慧路燈管理系統
,就是可以透過網頁查看全部路燈的即時資料,並可以透過網頁遠端控制路燈的明亮度。網頁的首頁是 Google Map 地圖,利用點狀標示出路燈位置,綠點代表路燈正常運作,紅點代表故障,灰點代表不明,可以拉近去點擊觀看路燈即時資料,當點擊時出現該路燈的所在位置、運作狀態、明亮度、溫度與溼度等數值。網頁有帳戶系統提供廠商或使用者登入查看屬自己管轄範圍的路燈,也可以定時間或直接操控路燈的亮度,當時有規劃到實作報修功能,即直接透過網頁選擇故障路燈點選報修,而報修機制會直接通知廠商去做修理、驗收跟修復。
而後端的部分,分為網頁系統後端程式(App/System)
跟 收發資料的閘門程式(Gateway)
,前者就是網頁的後端系統,在伺服器撐起整個網站,負責處理使用者在前端頁面操作的每個動作。而後者則是負責接收、解密來自路燈的即時資料,將使用者於前端網頁執行的控燈命令,進行加密、發送給路燈 IC 卡以達到控制路燈明亮度的功能。
我在剛開始有參與網頁後端
的開發,之後就向主管要求挑戰閘門(Gateway)
的開發,Gateway
是我第一次挑戰開發的部分,對當時的我來說是完全全新且極富挑戰性的程式部分,説Gateway
的範疇等同於開發網頁後端
也不為過,也就是說我從原本第一份工作只參與開發網頁後端
的一部分到第二份工作直接挑戰整個Gateway
的開發,有很大幅度的進步,是頂著高壓的情況下執行,Gateway 一開發到一個部分後,我就直接爆發賀爾蒙失調了 XD
Gateway
的要求是要讓程式從來不間斷的透過MQTT
接收每一筆訊息,每分鐘會有來自全台 26 萬盞路燈傳來的即時訊息,頗具挑戰,跟每秒一萬人次
的網站有著相同或更多等級的難度。
IoT 裝置: 路燈
路燈,就是我們本次的主角,聳立在路上兩旁的路燈,座落於全台每一個地方每一個角落,路燈內有微型電腦,像是 Arduino 或樹莓派的微型電腦,他會感測裝在路燈上的其他裝置,像是溫度計,LED 燈還有其他,然後回傳至 MQTT,接著軟體的部分,再透過訂閱 MQTT 取得路燈傳來的資訊。
不過主管有跟我們說,路燈使用的微型電腦是工業用的,不是像樹莓派那種玩具一樣(wow~~),畢竟是要連續用好幾年加上必須得承受長年日曬風吹雨打。
我們並沒有去直接把路燈剖開來看裡面的機械,只要了解從微型電腦傳來的訊息即可,當然在開發的時候,不是真的拿座落於路旁的路燈作為測試,廠商有幫我們架設了四個只有 LED 跟控制器還有微型電腦的路燈在研究室,其他的模擬路燈訊息,就參考那四盞路燈傳來的訊息,透過寫程式來模擬 26 萬盞路燈的假資料送 MQTT 做開發測試。
這個就是路燈的控制器跟 LED 燈,找相簿找很久,我把敏感資訊都馬賽克了,如果還是不能放再跟我說吧!
照片中的就是提供測試開發的路燈模擬,這是真的路燈只是被拆解了。黑色的就是控制器
,微型電腦所在的地方,而 LED 再靠右後方的地方。
軟體系統架構
接著來介紹整個智慧路燈系統的架構圖,如圖。
程式方面分成兩大塊:
- 網頁: 路燈管理網頁,提供人性化介面供使用者觀看及操作
- 閘門 (Gateway): 透過訂閱 MQTT 接收路燈的即時資料,或者根據網站的操作狀況發送命令操控路燈
網站的部分有下列功能
- 路燈裝置訊息
- 路燈即時資料
- 調光排程
- 報修系統
- 帳號管理
網站的前端透過 api 方式取得資訊,每 10 分鐘會從資料庫取得路燈最新的資訊,而後端會從資料庫中取得資訊。閘門(Gateway)會隨時都監聽(listen)接收來自路燈的資料,並經過解密
,轉換
,處理
及計算
,最後存進資料庫中。
系統架構: 網頁
網頁的首頁就是一張世界地圖,原本規劃如果發展得好就可以往世界各國邁進,從一開始規劃就需要先將往後可能發展的因素都考慮一併規劃(即便並沒有要開發到那種程度 XD)。利用 Google Map 地圖,然後將全部的路燈位置標在正確的位置上,並且用綠點,紅點,灰點之類的的顏色代表路燈的營運狀態。
點選其中一個路燈,左方會跳出該路燈的基本資訊,像是位置(x,y軸)
,名稱
,所在地區
,廠商
,還有即時資訊,比方說溫度
,亮度
,流明值
。上方的選單 Navbar 可以進入其他功能,像是用列表方式查看某地區的路燈,設定對路燈調光的排程,以及觀看民眾回報的路燈維修議題。
對了你可能會想問,怎麼知道每個路燈的確切位置並顯示在網頁上?
其實就是每個路燈資訊都會附上該路燈的x, y軸
以標示他的位置,然後再由前端去整合在 google map 上。
由於本人是實作後端的部分,所以只介紹後端網站的實作,接下來介紹後端如何設計。
語言採用 Python 實作整個後端部分,考量到後端工程師有剛出社會的新鮮人加上自身經驗也沒有很足夠,所以第一個專案就先採用標準格式 MVC
架構來實作後端程式的開發。但是這個MVC
的架構也不算真的 MVC,因為就以前實作的經驗,MVC 的 C(Controller)應該是會和前端黏在一起,但近幾年軟體開發比較偏向前後端分離,所以C
的部分就會由 api
的功能代替。
Controller
如圖,Controller 的部分就是 api 接口,接收前端的 Request 然後再將處理好的 Response 回傳至前端。再來是 View 的部分,我們可以接 View 解釋成處理資料
,將從 Controller(api)接收的 Request 拆解,然後決定要去哪一個
Table 取資料或者是要組成怎麼樣的資料回傳給 api 層。
api web service 是用 Flask 套件
View
- 拆解 Request 挾帶的資訊
- 根據 Request 資訊決定要做什麼處理
- 於哪個資料表(Table)取得資訊
- 計算資訊或處理資訊使其成為前端接受的 Json 格式
- 回傳於 api 層準備回傳前端
Model
Model 可以理解為對資料庫做的任何操作: Create, Read, Update, Delete.
關於資料庫的操作是使用 SQLAlchemy 套件 ORM 的觀念去實作 Model 的部分,所以資料庫的 Schema 會由 Python 先透過 SQLAlchemy 建立好 Table,欄位跟關聯,然後對資料庫做管理。
簡單來說後端的設計十分中規中矩,而每個功能像上圖所呈現,有自己的 api,自己的處理程式及自己對資料庫操作的程式。
那舉一個例子的話就會是:
Controller(api) | View | Model
接收前端的Request,要求路燈的資料 -> 去到即時資訊的部分取到路燈資料 -> 從資料庫取得資料並回傳給View
壓成json,回傳給前端 <- 處理取得的資料,使其成為前端方便讀取的格式 <-
系統架構: 閘門 Gateway
再來介紹 Gateway 部分,Gateway 的部分算是我後端生涯中第一次嘗試非網站服務相關的後端程式,由於新奇加上挑戰難度高,於是主動向主管要求想要勇敢挑戰與嘗試。
Gateway 的開發難度可能比後端網站還高一點,所以獨立出來不和web service
擺在一起,他是獨立的一包程式。
由啟動MQTT
並訂閱頻道最為主程式的功能,一切的功能的驅動都由 MQTT 接收資料,或者由網頁端下命令控制路燈為主。
(由於當時還沒規劃客戶端如何控制路燈這部分,專案就先被結(ㄧㄠ)束(ㄓㄢ ˇ)了,所以我沒辦法實際描述控燈是怎麼做到的)
先看收集資料的部分
Gateway 接收資料
如圖,主程式執行後,就訂閱路燈傳遞訊息的頻道 topic(/com/device/msg/*),接著就是只要接收到任何路燈的訊息,就對訊息做
- Decode (拆解)
- Transfer (轉換): 這個部分是將路燈收進來的值轉換成人比較看得懂的數據,例如
溫度:34
- Process: 就是處理轉換過的數值,可能是先存入資料庫保存原始的
raw data
,或是做像是告警方面的功能 - Calculate: 針對接收到的路燈訊息做計算,在存回資料庫
什麼是 MQTT?
這邊介紹一下 MQTT
全名: Message Queuing Telemetry Transport (MQTT),是一種通訊協定,很像是 HTTP,但專門為硬體效能低下的遠端裝置以及網路狀況糟糕的情況下而設計的發布/訂閱型訊息協定。
那通訊的方式就是兩種
- 發布 (Publish)
- 訂閱 (Subsribe)
圖解,一樣可參考 Gateway 的部分,通常會有一個儲存訊息的 MQTT Broker (在途中咖啡色三角錐的部分)。路燈的微型電腦會將感應器接收到的資訊組成訊息,然後透過網路發布至特定的 頻道 (/topic/*
),接著接收訊息的軟體/程式訂閱該頻道(/topic/*
)就能夠接收來自該頻道的訊息。
頻道的概念?
假設我今天有三種感測器: 路燈,太陽能板跟冷氣機,那我分別將這三個機械要傳的資料都傳到特定的頻道:
- 路燈:
/light/msg/*
- 太陽能:
/sun/msg/*
- 冷氣機:
/air/msg/*
如此一來如果我只想要路燈的資訊,那程式只要訂閱 /light/msg/*
就能接收到路燈相關的資訊
所以假設目前有三盞路燈有名稱跟他們的訊息頻道就會是:
- 路燈 ST0001:
/light/msg/st0001/*
- 路燈 ST0002:
/light/msg/st0002/*
- 路燈 ST0003:
/light/msg/st0003/*
那今天想要對路燈傳送命令或管理訊息呢?
- 路燈 ST0001:
/light/command/st0001/*
- 路燈 ST0002:
/light/command/st0002/*
- 路燈 ST0003:
/light/command/st0003/*
以上的頻道名稱都是虛構的。
訊息能夠決定要儲存在 Broker 還是不存,遺失就遺失了。
訂閱就像是訂閱 youtube 頻道一樣,今天訂閱了 CNN News,那只要有更新就會向我推播 CNN News 的新內容。
也就是說假設今天有十個電腦都訂閱了路燈的頻道 /light/msg/*
,那這十部電腦都能接收到來自/light/msg/*
的新訊息。
所以這種通訊模式很適合與 IoT 設備通訊,他就像傳簡訊一樣一來一往,等客戶端或伺服器端有接收到相關訊息才會做出反應,而不是像 HTTP 一樣是立刻要回覆訊息。而其中要點就是可能會有遺失訊息的問題。
解密: IoT 傳來的訊息?
有幾種,看當初寫進微型電腦的程式如何,有些比較先進,那可能就是將機械碼寫的讓人看得懂,例如:
Json格式
{
"device": {
"name": "light0001",
"place": {
"x": 112,
"y": 31
}
},
"data": {
"degree": 34,
"light": 46,
"dimming": 30
}
}
這種的是 json 格式,收進來 python 可以直接轉 dictionary 去做處理
那另外一種是
String
0010101101010101 || --- 01001
好啦,我不會寫 XD,反正就是你要根據機械的規格書上去判斷前四碼代表什麼,中間六碼代表什麼,然後去解析。
如何撰寫 IoT 裝置訊息處理的工作不在於我們身上,所以通常這個部分是由硬體工程師去做撰寫,然後我們要和廠商或工程師取得規格書或者確認裝置會用什麼格式傳至 MQTT。
解決的問題
接收資料的閘門,感覺起來似乎沒有想像中困難,其實當初困難的點主要是:
- 初次接觸 MQTT: 由於對 MQTT 的不理解,所以判斷實作 Gateway 是一個需要花費比較多時間研究的功能開發
- 不同的路燈廠商: 台灣這麼大,路燈不可能只由一個廠商製作及維護,那在不同廠商的 IoT 訊息設計會不同,所以也要製作針對不同廠商的路燈訊息處理程式
- 效能(處理時間): 收集即時資訊意味著不能有任何卡住或遺失資料的可能性,要想想不斷地接收 26 萬盞路燈每分鐘不斷發送的即時訊息,意味著不允許程式有過長的當機時間跟處理卡住的時間。但由於當時的程式能力加上專案的急迫性,所以並沒有使用類似 Thread 或是 Async 的方式加快處理效能
我當初也是第一次接觸 MQTT,對於資訊網路本來就比較弱的我,確實當時花了不少時間在理解這一塊知識,從架設 Broker 到理解訊息跟如何開發,都花了不少時間,因為他的邏輯跟以往實作的程式都有點不同。
再來花比較多時間是和廠商確認裝置傳來的訊息,還有撰寫不同廠商的路燈訊息。
這個部分是用不同的頻道名稱去區分,然後由後端的程式來判斷要派給哪個處理程式去處理:
/ACompany/light/msg/*
/BCompany/light/msg/*
以此類推,然後再後端程式接收的部分,依據頻道名稱派給不同的處理器。
最後就是效能問題,當初的後端底子比較弱,所以都還是用一個 Thread 去撰寫程式,也就是說只要其中一步卡住了,後面的訊息就會卡住,消化不完。
Gateway 發送命令
接著要來看看如何控燈,起初規劃時,只能夠控制路燈的亮度(但好像也沒有其他部分可以控制了)。猶如上部分所述,由於還沒規劃從什麼地方下命令控燈,專案就被消失了,所以並沒辦法確切描述控燈的來源。
不過當初稍微發想,可能會經由兩個部分去驅動命令
- 從資料庫讀取調燈的排程: 網頁部分有規劃一個排程(Schedule)的部分,就是可以提供使用者根據需求去預定在某個期間將燈光亮度調整至一定的數值,接著在下個期間再將燈光亮度調至其他亮度。
- 由前端網站即時調整,透過後端網站發送 MQTT 給閘門(Gateway): 總之就是只要前端收到使用者有任何即時調光的需求,就透過後端看去如何將動作傳至 Gateway
再來,接收到需要調光的命令後,Gateway 部分要做的是
- 收到需要調光的路燈裝置及指定數據
- 轉換訊息: 轉換數據跟參數成路燈看得懂的格式
- 加密: 可以理解成將訊息壓成機械碼跟 MQTT 接受的訊息格式
- 發送(Publish): 發送至路燈
- 驗證機制: 命令發送後,必須檢查剛剛發送的命令有沒有成功調光了
發送命令的部分會比接收訊息的部分稍微複雜一點點
解決的問題
- MQTT 訊息規格
- 其他奇怪的 Bugs: 像是一次要對一萬盞以上的路燈做控燈,透過 MQTT 一次發送大量訊息,會因為訊息過長而發送失敗,那要如何處理
- 如何驗證路燈調光成功
先來看看當初卡住的 MQTT 訊息問題,這也是一門學問。
對特定路燈將亮度調為 30
topic
/light/command/ST001
payload
{
"dimming": 30
}
看起來一切都沒問題,但是每次發送給路燈就是不成功,來回嘗試幾次後,終於鼓起勇氣詢問廠商,約了時間詢問問題。
原來那些空格逗號之類的在 Python 來說就是稀鬆平常的事情,但是在硬體內卻容不了一粒沙。
依照上面的例子,payload 必須得長這樣才能調光成功:
{"dimming":30}
一點空格換行都不行,必須緊黏在一起。如果對象是前端,前端就是依據參數取值,對象是後端,也是依據參數取值,可是對象是 IoT 裝置,所以硬體工程師描述每一個空格跟標點符號都很重要,在機械的世界中,他們是依據字串位置來切割訊息。
也就是說
{"dimming":30}
|-------| |-|
元素 數值
分段是 by 字數,一段一段去切然後轉換意思,所以如果這時候多空一個格,可能就造成數值抓不到,而報錯。
那你規格書幹嘛分行的這麼美!!
再來就是 MQTT 訊息長短的問題,畢竟我們怎麼可能一次只調一盞燈,當然是一塊地區的路燈一起條燈光。那當初在設計調光的時候,是期望透過 MQTT 接收來自後端的調光指令,如果一次條一萬盞燈,那 MQTT 的 Payload 就會是一萬盞燈的訊息長度,傳給閘門 Gateway,Gateway 再把它切成一萬個訊息分別派送。
而當初就是發內含一萬盞路燈的
- 名稱
- id
- 數值
頻道是 /light/command/set/dimming/
結果不知道為什麼閘門訂閱頻道後卻沒有接收到任何的調光命令。
後來在主管的協助下,發現問題點在於傳出去的 MQTT 封包,發現含一萬盞路燈訊息的 Payload 被截掉只剩下一半,所以導致訊息無效無法傳出去。
怎麼會這樣呢?
再更深入去追尋後,是發現我們的程式設計有問題,
def process():
data = get_devices()
payload = encode(data)
save(payload)
publish(config.topic, payload)
做了一個程式,是接收到路燈資訊後,對訊息加密成要寄出去的 payload json 格式,然後會將每次調光的命令資訊存進資料庫(save),接著最後就將一萬盞燈透過 MQTT publish 出去。
乍看之下沒什麼問題,問題就在於,由於這個程式一 publish 訊息完就直接結束,會讓一萬盞燈的巨大 payload 來不及全寄出去,程式就 ending 了,所以訊息才會出現一半之說。
後來將 save 和 publish 兩個動作互換,先確保命令
有確實透過 MQTT publish 出去後,執行下一個存入資料庫的動作,這麼一來訊息就能完整送出。
這個問題好像是卡了兩週吧 XD
使用到的技術?
路燈專案比較像是中規中矩讓初階者可以好好熟悉基本的程式概念,所以用到的技術大部分都是 Python 一些必備的套件跟規劃模式,然後學著規劃程式架構跟資料庫設計。
在製作後端網站的部分,有幾個除了規劃 MVC 網站架構之外,像是第一次自己設計資料庫,還有練習怎麼讓後端跟前端牽動起來等基礎但重要的技術。
在規劃帳號管理的功能中,初次學習到了AAA 協議來規劃實作帳號功能。
然後我自己嘗試用 Decorator 的概念做了一個 Error Handle 例外處理的函式,可以在每個函式上使用裝飾器,統一管理當例外出現時可以怎麼處理,大大精簡了程式碼的品質。
再來是多練習 SQLAlchemy 資料庫管理套件。
在製作 Gateway 時的技術能力用往上升了好幾倍,除了接觸到 MQTT 這種新的傳輸協議之外,我首次嘗試從函式轉為物件導向且套用設計模式的概念來撰寫程式。這是我過去第一年完全無法理解也做不到的事情。
我想學會物件導向,就是我在這個專案最大的成長。
為什麼專案沒做成?
總之初次接觸 IoT 的專案就很有趣,有趣的在於 IoT 的設備都掌握於手,還有一堆其他奇奇怪怪的問題跟來自機械端很未知的知識等待我們的挖掘。
但至於為什麼最後沒做成,這就是很可惜的事情了,沒做成的原因不是由於技術克服不了而是最複雜的人
的問題。
既然是專案/產品,就必須有買貨人
為什麼這麼說呢?
因為其實不同地區的路燈維修或管理早就會有很多來自地方的建設公司或施工公司去提前得標,其幕後的內幕很多。所以面對公開招募標案的流程或活動,我們早就比地方的公司來晚個幾步,再加上對於品質的要求,硬體設備跟軟體設備需要更優良的規格去製作,但是哪個買貨人會希望用大量的資金去買呢?大部分都希望能用最低成本及還過得去的品質去做這件事舊好。
因此到後來業務沒有標到案子,這個專案/產品也就暫時完結了。
公司政策
在我工作的那一年,可能由於新冠肺炎的流行,加上公司內部鬥爭跟轉型,從集團的別的部分調來很多主管空降,整個公司的政策都轉個 180 度,原先可能偏向是要實作軟硬體結合以達到方便管理,但為了疫情重大虧損,需要賺錢,所以將路燈的營運部門直接砍掉,IoT 設備部門也解散被移轉的七零八落。
而這部分的決策轉彎跟公司部門架構的拆解,也造成我後來就離開公司的主要原因,因為畢竟軟體部門被轉為數據維運部門,再也沒有軟體開發的需求,就沒有軟體工程師的存在了,是吧?
我還是很慶幸自己能夠參與 IoT 物聯網運用相關的專案跟產品開發,而對我來說這一年多的工作經驗也是很寶貴的回憶。
那麼下一篇就來撰寫第二專案/產品: 太陽能案場監控平台,來說說關於太陽能場神秘的一幕吧!