2016年5月7日 星期六

[Raspberry Pi]DHT11溫濕度模組 x Node.js = DHT11 Server


這次要來嘗試自DHT11取得的溫溼度資訊以網頁(Web)的形式公布,也就是HTML Server
最後要突破路由器的限制逆風高灰,讓任何的客戶端都能從網際網路連上伺服器,上面就寫著DHT11讀到的溫濕度資訊

整個架構概念如下:
  1. 將DHT11定時(1分鐘一次)讀出的溫濕度寫到某個檔案(th.out)裡
  2. 開啟一個HTML Server,在客戶端連入時,讀取th.out內的資訊,秀在主頁面上
  3. 為了能穿透路由器,讓客戶端能從網際網路連入,我們需要某種類似DDNS的穿牆服務
OK,戰鬥開始!第一步,將DHT11的讀出值定時寫入特定檔案

從先前的Adafruit提供的python程式,我們已經可以很輕鬆的讀出資訊顯示在Console上,只要再利用Bash Script做點小小加工就可以讓DHT11全天候運轉偵測溫濕度:

開啟新Script
#vim outputTh.sh
設定為可執行
#chmod u+x outputTh.sh

在Script寫下:
#!/bin/bash
while :
do
        date > th.out #先輸出當前時間
        $(./Adafruit_Python_DHT/examples/AdafruitDHT.py 11 4) >> th.out   #輸出DHT11資訊,注意相對路徑
        sleep 60 #在此暫停60秒

done

主要功能是開啟一個無限迴圈,每60秒讀出目前時間以及DHT11資訊,並輸出(覆蓋)th.out

執行此Script!(因為牽涉到底層存取,請給他sudo下去)
#sudo ./outputTh.sh

在此先做個確認,理論上輸出檔案內容應該會長這樣:
20160507 14:14:03
Temp=29.0*C  Humidity=62.0%

最後讓此Script在背景以nohup執行
$nohup sudo ./outputTh.sh &


第二步,設定DHT11 Server(HTML Server)

這裡選擇最好上手的Node.js,這是個整合的很好的伺服器端執行環境,可以把他想像成事件驅動架構,對開發者而言,只要搞懂各事件(Event)的發出時機,並且寫下相對應的處理常式(Event Handler)即可

首先是安裝Node.js
#sudo apt-get install -y nodejs

接著開啟一個Java Script:
#vim server.js

內容寫這樣:

var http = require("http")  '引入http模組

http.createServer(function(request,response){
 response.writeHead(200 , {"Content-type":"text/plain"});
 response.write(fs.readFileSync('./date.out'));
 response.end();

}).listen(8888);

以上Script要這樣解釋:


  1. 程式走到第三行時,會從http模組呼叫createServer函式
  2. createServer()引數1是客戶端連上時要做的回應動作,既然是一連串的動作所以是一個函式而不是變數,就是所謂的回呼啦(Call-back)
  3. 在這裡的回應動作是,讀出th.out的內容在主畫面上輸出
  4. 而createServer()會吐回一個伺服器物件,呼叫此物件的listen(),表示啟動監聽連入需求,這裡使用的監聽埠口為8888

值得一提的是,node.js的任何函式幾乎都有同步/非同步兩個版本,一開始使用了fs.readfile,(註:讀取檔案的非同步版本),結果實際讀取檔案的時機不見得在函式呼叫後,所以可能會在response.end()前都沒讀出buffer,常常以exception收場

所以這裡讀取檔案的函式必須要用同步呼叫(Blocking IO),即為fs.readFileSync()的return值就是讀出的字串

最後以Node.js啟動剛剛寫好的Script
#nodejs server.js

此時來做個小小測試,看看是否能在區域網路內看到網頁,開瀏覽器連上:
http://192.168.1.1:8888


好的,區域網路內連上DHT11 Server沒問題了,最後記得讓DHT11 Server登出後也能在背景執行:
$nohup nodejs server.js &


第三步,先別管DHT11 Server了,你聽過ngrok嗎?看看他們官網的圖示,他們就是有辦法讓你的伺服器暴露在路由器之外,

首先是安裝,在apt-get上沒有此套件,我們直接下載執行檔解壓縮即可

下載:
$wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
解壓縮:
$unzip ngrok.zip

取得!接著的動作十分簡單,可以想像這個軟體做的動作就是左耳進右耳出,把封包轉送出/ngrok伺服器,啟動ngrok,引數打入:

$ngrok http 8888

意義就是以http協定轉接DHT11 Server監聽埠口(port:8888),嘿!逼哩八啦他還給你一個很神秘的網址,把他貼上瀏覽器就知道………哇!DHT11 Server走入人群了!


用手機從3G網路測試看看…嗯…沒問題!
以後回家前就可以先知道家裡溫濕度如何了(那又怎樣




2016年1月19日 星期二

[Raspberry Pi] Ad Hoc


Ad Hoc,拉丁原文有"將就的、隨意的"的語意,在Wifi協定中代表"無線隨意網路",也就是不需要中央基地台,通訊端之間即能夠構成的網絡

相較於Ad Hoc模式,Infrastructure就是日常生活中熟悉的中心輻射式網路拓樸,網路內通訊統一透過中央基地台分發

在Linux下設定Ad Hoc模式的步驟非常簡單,只要改/etc/network/interface內設定再重開裝置即可!
參考:http://spin.atomicobject.com/2013/04/22/raspberry-pi-wireless-communication/

步驟:

  1. $sudo vim /etc/network/interface #改網路設定
  2. wlan0設定如下
    1. iface wlan0 inet static   #靜態IP
    2. address 192.168.1.1      #設定IP
    3. netmask 255.255.255.0    #設定子網路遮罩
    4. wireless-channel 1  #訊號通道
    5. wireless-essid RPiAdHocNetwork #暴露在可用的Wifi清單中的名稱
    6. wireless-mode ad-hoc #模式:ad-hoc
  3. 存檔
  4. $sudo ifdown --force wlan0  #先將wlan0關閉(強制下架)
  5. $sudo ifup wlan0 #再將wlan0重新打開,套用方才設定的設定值

如此一來就可以了,果真隨意簡單

原先以為通訊上不用經過HUB或許傳輸上會變快,殊不知ad hoc是比Infrastructure更為古老的架構,許多針對Wifi傳輸最佳化的方法可能都不支援,而且還帶有一些毛病,參考此篇:Limitations of Ad Hoc Mode Wireless Networking


實際用iperf測速,使用Ad Hoc相較於Infrastructure,傳輸速度完全沒有提昇,該是嘗試Wifi Direct看看





2016年1月10日 星期日

[.NET] Memory Leaks


前陣子在工作上遇到了"OutOfMemory"的例外,深感驚訝,以為在GC環境下,這種像是慢性病的東西可以從此與我絕緣,真要下手查BUG時,突然理解到,關於GC,關於記憶體分配模型,我理解的太模糊,才會感覺每一行指令好像都值得懷疑似的(最後發現是TextBox內容控制機制有問題)

.NET的記憶體分配模型:


  • 堆疊(Stack):
    • 何時被分配?
      • 進入函式,配置實值物件,或宣告實例參考時
      • 依序連續分配,像是盤子一層層疊起來所以稱為Stack
    • 什麼東西會被分配?
      • 實值型別(Value Type)物件
        • 註:可以從物件是否繼承自System.ValueType來確定是否為實值型別
        • 例如:Int , Float , 使用Structure宣告的物件
      • 參考型別(Reference Type)參考(Reference),可稱為Handle,概念上可以想成指標
    • 何時被回收?
      • 當函式退出(Return)時,按照分配順序一層層移除,後進先出
      • 註:所以若在此函式範圍做了 Object a = new Object,在退出此函式時參考a會被沒收,此時跟a鏈結的物件,就成了無主遊魂,很快地會被牛頭馬面GC(Garbage Collector)回收,帶到陰曹地府等待投胎轉世(輪迴?)

  • 託管堆積(Managed Heap):
    • 何時被分配?
      • 隨時隨地,在你使用New配置物件時
    • 什麼東西會被分配?
      • 參考型別物件,可稱為Instance
        • 註:String是參考型別物件,分配在堆積上,只是其屬性為不可修改(Immutable)
    • 何時會被回收?
      • 當GC掃描到此Instance並無任何參考指向他,便會標記起來,接著自動回收

  • 非託管堆積(Unmanaged Heap):
    • 何謂非託管?
      • 就是非在CLR上執行的程式,不受.NET管理,好比你用C語言基於Standard Library寫出來的程式
    • 何時被分配?
      • 隨時隨地,在你使用New/malloc配置物件時
    • 什麼東西會被分配?
      • 任何你用New/malloc配置的東西
    • 何時會被回收?
      • 必須手動明確(Explicit)地呼叫Delete/Free,此物件所佔據的記憶體空間才會被釋放

可能的記憶體洩漏原因:

  • 有問題的非託管程式碼
    • 物件被分配(New/malloc)出來了,但忘記寫下Delete(Free),典型的錯誤
    • 最可怕的是第三方供應商的函式庫有問題,你沒程式碼他又不想理你
  • 託管程式碼:
    • 對於IDisposable物件,使用過後未呼叫Dispose()
      • 非託管程式碼或是運用到獨佔資源(Resource)的物件可能會實作此介面,作為提供呼叫端明確呼叫釋放時機的可能性
      • 例如:Filestream、SqlConnection、Usercontrol
      • 需要手動控制釋放時機的物件才實做IDisposable(程式結束時釋放的那種不算)
      • 對於所有IDisposable物件,盡量使用Using,減少人為失誤造成的困擾
    • 物件已失去參考,但隸屬的EventHandler仍與活動中的Event連結
      • 由於成員函式隱含實例作為參考,可能會使得EventHandler對應的物件無法被釋放(這點相當有趣,物件成員函式預設至少有一引數為自己)
    • 其他,自己應用上的失誤
      • Queue:Enqueue沒有Dequeue的快,結果就是一直累積
      • 傾盡CPU全力,狂寫檔案(StreamWriter.Write()),緩衝區被玩爆(這種問題很快會爆發,好查)
      • 記憶體控制機制設計有問題

分析記憶體洩漏的好工具,perfmon.exe

    • 心得:
      • 帶出的數據非常完整,而且可以設定固定採集時間長度,記錄在不同的採集檔內,比起自己在程式碼內寫LOG,用這工具比較能確保實驗在固定的資料搜集條件下進行
    • 抓漏心法:
      • 找出可能的嫌疑點,開perfmon設定長度N分鐘週期M秒採樣,時間到後觀察增長趨勢
      • 沒改善,表示找錯方向,改動其他嫌疑點,重複上面過程,再看一遍
    • 詳細使用方法:
    • 非常好的評論:


參考資料:
經常性的字串處理你該用StringBuilder
Using using as grantee (even for the filestream):
Close and Dispose
Heap、Stack、Reference Type、Value Type、Boxing、Unboxing

2016年1月4日 星期一

[Raspberry PI] PWM


PWM,Pulse Width Modulation,以數位方式控制高低電位週期比例來達到模擬類比電壓的效果,比方說控制馬達速度、LED燈亮度...

值得一提的是,因為是模擬的方式所以PWM訊號頻率其實也會出現在輸出訊號的頻譜裏,當然也要輸出裝置頻寬夠高才能夠被察覺.

樹莓派身為一塊萬用開發版當然有辦法輸出PWM,目前查到有下面幾個方法:


  • RPi.GPIO
    • 指令下達方式:Python
    • PWM生成方式:軟體模擬
    • 安裝
        • #sudo apt-get install python-dev python-rpi.gpio
    • 用法:
        • #sudo python #進入Python
        • >>import RPi.GPIO as GPIO
        • >>GPIO.setmode(GPIO.BCM)
        • >>GPIO.setup(18, GPIO.OUT)    #設定GPIO18為輸出腳
        • >>pwm = GPIO.PWM(18, 500)   #建立PWM實例,在GPIO18輸出,頻率為500Hz
        • >>pwm.start(100) #開始輸出DUTY=100%的脈波
    • 心得:
      • 用在伺服馬達(Servo)上會感覺到顫振,確實是用軟體方式來輸出,持續輸出脈寬的重複性不佳,大概只適合做LED燈控制等人機應用.
      • 軟體輸出方式大概是以系統內建計時器,去計時控制輸出腳位為高位/低位,由於Linux並非即時系統,各行程(process)無法保證被定時呼喚,因此即便系統計時器是可靠的,在上位做控制的行程也沒辦法總是在計時到達的當下去處理.



  • RPIO
    • 指令下達方式:Python
    • PWM生成方式:半軟體模擬?(文件上寫著透過DMA通道,不太清楚背後機制)
    • 安裝
        • #sudo apt-get install python-setuptools
        • #sudo easy_install -U RPIO
    • 用法:
        • #sudo python #進Python
        • >> from RPIO import PWM
        • >> servo = PWM.Servo()           #建立一個Servo實例,預設週期為50Hz
        • >> servo.set_servo(18,1500)    #在GPIO18上持續輸出脈寬為1500us的脈波
        • >> servo.set_servo(19,10000)  #在GPIO19上持續輸出脈寬為10000us的脈波
    • 心得:
        • 發現如果將其中一隻Pin輸出關閉,所有輸出都會被關閉,不知道是不是BUG?
        • 可以多通道輸出(但不曉得通道代表什麼意思),比方說
          • >> servo0 = PWM.Servo(0)
          • >> servo1 = PWM.Servo(1)
          • 如此就建立了兩個Servo物件對應到不同的DMA通道,但操作起來仍發現會相互干擾
        • 顫陣比GPIO小,但仍能感受到.


  • pigpio
    • 指令下達方式:pipe 、c-interface、python
    • PWM生成方式:軟體模擬(p指令)、硬體輸出(hp指令)

      • #git clone https://github.com/joan2937/pigpio #跟上最新版
      • cd PIGPIO
      • make
      • sudo make install

    • 用法(pipe方式):

      • #sudo pigpiod #喚起掌管輸出入的專用daemon
      • #pigs  p 19 255 #在GPIO19上輸出DUTY 100%的PWM信號(軟體模擬輸出)
      • #pigs hp 18 100 500000#在GPIO18上輸出頻率為100Hz DUTY為50%的PWM信號(硬體直接輸出)

    • 心得:
      • "硬"PWM,直接用樹莓派BCM2835晶片內建兩個底層PWM輸出單元生成PWM信號,直接以IC輸出信號重複性一定是最穩定的,也就是脈寬不會有忽大忽小的問題
        • pigpio只是作為寫入晶片上特定registor的API罷了
      • BCM2835只有兩個PWM通道(Channel),甚至在Pi2上只有一隻引腳被接到介面IO上,而Raspberry PI B+則有GPIO18,GPIO19可用.
      • 操作起來真的是顫陣最低的(滿意)!
      • 另外要說pigpio的shell指令很豐富,初期拿來測試硬體相當順手,文件也建立的很完整(滿意)!
      • 各型Raspberry PI GPIO表:http://elinux.org/RPi_BCM2835_GPIOs
從BCM2835文件可以發現這塊晶片頂多只能輸出兩個PWM信號,真要再多的話就要以通訊方式(I2C UART)送出命令透過PWM IC代為輸出穩定的PWM信號了,類似透過這樣的晶片

後記:
即便是用了Hardware PWM,功率電路之間的相互干擾也是另一令人頭痛的因素....尚待努力


其他參考:
BCM2835硬體Sheet