Blog Cover Image

Inspire you to have New thinking, Walk out your unique Road.

有的時候,你無意間遇到的一些故事,會激發你的靈感,改變你的想法,接下來你會用與之前全然不同的觀念去創造屬於你獨特的故事。

Sign @MinaYu.

[IoT物聯網應用] 智慧路燈管理系統架構與裝置介紹 | 帶您一窺IoT應用結合路燈應用的神秘面貌 | 差點就可一手控制全台26萬盞路燈!

Posted on

感覺一年前就想撰寫這篇文章,當時人還在上一家公司工作。

老實說我蠻喜歡上家公司的工作內容,我真的是蠻希望如果有機會可以繼續做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物聯網運用相關的專案跟產品開發,而對我來說這一年多的工作經驗也是很寶貴的回憶。

那麼下一篇就來撰寫第二專案/產品: 太陽能案場監控平台,來說說關於太陽能場神秘的一幕吧!