2015年9月20日 星期日

透過 Raspberry Pi 讓手機遙控冷氣機



在夏天最不希望遇到的事,就屬於一打開家門,熱烘烘的風就吹到臉上。我一直想要有智慧家電,讓我能再回到家前就打開冷氣機,不過換新的冷氣機實在是太貴了。不如自己動手做,Raspberry Pi 板子還是比新冷氣機便宜。不過現在市面上已經產品可以遙控不同品牌的冷氣機,而且價格在3000 NTD以下,所以要動手做之前還是要考量時間成本。不過老話一句,自己做帶來的成就感是無價的 ??

_______________________________________________________________________________


用Raspberry Pi 去遙控冷氣其實不難,但是如果很多東西都是第一次碰的話,就會花很多時間去評估使用哪種方法。這篇我會列出步驟,還有我曾經遇的問題。這樣想要自己做的人,就可以事先避免浪費時間。

先偷看我做的陽春 iPhone App 介面長怎麼樣。這樣心裡才會有底。


 

很好,只有幾個功能:讀溫濕度、開機、設定溫度。
你一定會想這簡直就是爛爆了,做得這麼殘缺,居然沒有暖氣、除濕這些常見的功能...

不是我不想做,是因為我的Sampo冷氣遙控器是一次送出所有的設定。舉例來說,當我按下溫度下降的按鈕,送出的指令卻是 "冷氣 + 27度 + 風扇上下擺動 + 空氣清淨 "。因為沒有官方文件,我花了一下午還是無法分離各功能的波形。這是我做之前沒有想到的問題,所以最後才做閹割版本。

_______________________________________________________________________________

實作示意圖:

RaspberryPi 版子透過 SSH tunnel的功能連到遠端的AWS server,這個連線會一直存在。透過SSH tunnel,RaspberryPi 就不需要固定IP。當手機要打開冷氣機時,會透過HTTPS連到AWS server,然後server會透過之前的SSH tunnel打開冷氣機。

_______________________________________________________________________________

實作需要的設備:

  • 紅外線模組
    • 可以自己買零件組,但是因為我是第一次使用,為了保險,我從良興買模組
    • 接收模組有LED燈,如果收到訊號就會閃,可以幫助除錯
    • 良興上賣的發射器模組,功率太低,傳送聚集不到兩公尺。後來還是等要自己買零件兜起來(BJT型號為2N3904,價格不到10NTD),可以參考這篇文章 - 自製主動式紅外線遙控發射器
    • 雖然發射模組很爛,但還是建議買一個,可以幫助除錯
    • 接收程式可以下載網路的程式,或是用Linux提供的程式 LIRC。雖然我是使用LIRC,但設定方式很不方便。如果遇到像Sampo這樣的冷氣機,不按照一般LIRC格式,所以會發生錄失敗。這時候就得改用LIRC Raw模式去錄製。
    • 一般設定方式請參考這篇 - Raspberry Pi + LIRC 紅外線遙控實做
  • 固定IP 或是 有網址的伺服器
    • 為了讓手機可以控制冷氣機,需要額外的機器接收手機的指令,接著這台機器再轉送指令到 RaspberryPi 
    • 我是用Amazon AWS 機器。他有免費方案,可以使用
_______________________________________________________________________________


當硬體都設定好了,就差 RaspberryPi 端要打 SSH Tunnel的指令。
先在RaspberryPi 建立 createTunnel.sh,然後替換底下的變數

  1. ADDR_AWS:      server位置
  2. AWS_ID_FILE:  RaspberryPi 連線到AWS server的密碼檔案位置
  3. AWS_UID:          AWS server端的登入ID
  4. PI_UID:               RaspberryPi的登入ID


#!/bin/bash

PORT_PI=19922
PORT_AWS=10022
ADDR_AWS="xxxxxxx.ap-northeast-1.compute.amazonaws.com"
AWS_ID_FILE="/home/pi/bash_shell/EC2Key.pem"
AWS_UID="ec2-user"
PI_UID="pi"

createTunnel(){
        /usr/bin/ssh -f -N -R $PORT_AWS:localhost:22 -L $PORT_PI:$ADDR_AWS:22 $ADDR_AWS -i $AWS_ID_FILE -l $AWS_UID

        if [[ $? -eq 0 ]]; then
                echo Tunnel to AWS created successfully
        else
                echo An error occured creating a tunnel to AWS. Return code: $?
        fi

}

closeTunnel(){
        #get pid of tunnel
        PIDS=`ps aux | grep ssh | grep $PORT_AWS | grep ec2-user | awk {'print $2'}`
        if [[ -n $PIDS ]]; then
                for pid in $PIDS; do
                        #echo $pid
                        sudo kill -9 $pid
                done
        fi
}

/usr/bin/ssh -p $PORT_PI localhost -i $AWS_ID_FILE -l $AWS_UID ssh $PI_UID@localhost -p $PORT_AWS ls > /dev/null

if [[ $? -ne 0 ]]; then
        echo Creating new tunnel connection to AWS
        closeTunnel
        sleep 5
        createTunnel
fi

當檔案建立完後,就可以執行建立連線。不過因為tunnel會因為無線網路訊號不好,或是server過於忙碌的問題,可能每天會斷線一次,所以需要時常檢查連線是否還在。只要RaspberryPi端透過crontab,每分鐘查詢一次就可以達成。

當tunnel斷線時,可能5分鐘之內都還無法再度連線,所以最少有5個連線需要再等待server回覆。一旦server有能力接受tunnel時,RaspberryPi就會有5個連線,這樣會拖垮效能。解決方式就是使用flock指令,保證只會有一個連線需求。建立 createTunnelLock.sh 來將lock加入。這個script需要指定上面所寫的 createTunnel.sh 的位置,把他填入shell_script的變數內就可以了

!/bin/bash
lock_file=/tmp/lock.create_ssh_tunnel
shell_script=/home/pi/bash_shell/createTunnel.sh

echo "befor critical section"

# lock the file handle; this part will block
(
flock -n 200 || exit 1
$shell_script
) 200>$lock_file # open a file handle; this part will always succeed


echo "after critcal section"

一旦建立SSH tunnel後,server就可以對 RaspberryPi下指令,甚至是重新啟動 RaspberryPi,所以控制冷氣也當然能辦到。剩下的就是使用JSON格式的指令,將開關冷氣的命令從手機傳送到AWS server ,讓server解讀完後,透過SSH tunnel去控制,這個步驟是比較直觀的,我就先不提了。至於HTTPS得設定,AWS有提供硬體解SSL的功能,可以參考文件去設定。當這些都設定完後,就是寫 iOS了,因為UI設計實在是看人喜好,我就不提了。簡單的測試可以先用 XCode的playground 上面執行,對於除錯會有很大的幫助。


2014年9月14日 星期日

[AWS] 到底該選擇那種VM? Paravirtual (PV) 或是 Hardware Virtual Machine (HVM)




如果有仔細看過AMI (Amazon Machine Image)的種類,應該會發現有兩種類似的Image,但是虛擬機器的型態不同,一種是HVM,另一種是PV。到底該挑哪個?


這兩種都是Server Virtualization的技術,可以看這個技術簡介。不過影片內的技術名詞和Amazon用的沒有完全一樣。
或是可以參考從這篇推論HVM是Type 1 hypervisor,而PV是Type 2 hypervisor。

Intel早就砸錢收購Type 1的公司,從AWS文件可以知道原本是PV比HVM好,後來不相上下。

Historically, PV guests had better performance than HVM guests in many cases, but because of enhancements in HVM virtualization and the availability of PV drivers for HVM AMIs, this is no longer true.
現在免費而且高效能的Instance只支援HVM,所以選HVM才行。


但是Spot Instance (竞价型实例)還是有只支援PV的T1 Instance,所以睜大眼睛去看吧。


一鍵在AWS上快速安裝Nginx + MySQL + PHP (透過AWS Marketplace)




如果厭煩了一直要安裝軟體,或是只是想要測試效能。AWS上面提供類似Apple Store或是Google Play的商店,讓你可以直接使用灌好的image。不過可能要收錢,不然就是硬體變差了,所以還是考量自己的需求再決定。
安裝的方式很簡單,進入EC2 Console => Instance => Launch Instance


點選AWS Marketplace,然後在搜尋欄打入"LEMP",接著搜尋。
選擇"LEMP Stack with Sense on Amazon Linux powered by IAANSYS, INC"


注意這個Image不提供t2.micro


接下來就只要一直按下一步就建完了。然後登入Server

ssh ec2-user@ip_地址 -i ~/EC2Key.pem


接著修改Nginx設定檔
mv /etc/nginx/conf.d/phpMyAdmin.conf /etc/nginx/conf.d/nginx.conf
vim /etc/nginx/conf.d/nginx.conf


把 “root /var/www/apps/phpMyAdmin; ”  改為 “root /var/www/html”

並把html檔案放到/var/www/html就大功告成了


2014年9月13日 星期六

將Server移植到Amazon AWS上



AWS服務已經推出很久了,已經有很多教學文,連官網都有詳細的說明。
不過大都散佈在各處,我把自己移植經驗寫下來。

我移植的是APP的backend server,Server除了Query Data外,還兼負責壓縮儲存圖片。
所以需要做下面幾件事:

  1. 啟動一台Server (使用AWS EC2服務)
  2. 透過終端機登陸Server, 並安裝Apache + MySQL + PHP
  3. 啟動SQL伺服器 (使用AWS RDS)
  4. 將MySQL檔案上傳到AWS RDS
    • mysql --host  "AWS RDS的DNS"  -u  "使用者名稱" -p < ~/backup.sql
    • DNS長得像 “xxxx.ccwhkkoksnnz.ap-northeast-1.rds.amazonaws.com”
  5. 上傳PHP code到Server
  6. 將備份圖片上傳到雲端空間 (使用AWS S3服務)
  7. 將圖片透過PHP上傳到雲端空間
流程相當長,大家還是耐心的做下去吧~


2014年9月4日 星期四

[PHP] 將圖片上傳到Google Cloud Storage



透過terminal用gsutil上傳圖片並不是難事,只要透過網頁取得OAuth2 Key後,就可以上傳了。
不過當我想在Apache+PHP 的環境下使用 gsutil ,就會遇到權限問題。

譬如在PHP下想要上傳檔案
<?php
    exec("gsutil cp /tmp/test.txt    gs://bucket/");
?>

會得到底下的錯誤
 **OSError: [Errno 13] Permission denied: '/var/www/.config'**

如果用暴力法修改權限(chmod 777 /var/www/),還是會遇到gsutil報錯
 **AccessDeniedException: 403 Insufficient Permission
 **CommandException: 1 files/objects could not be transferred.

比較好的方式還是用google提供的Google API Client Library for PHP
可惜目前還是beta版,範例很少,
這邊附上我試出的結果
1. 先申請Server Account


接著按"Generate new P12 key"

2. 把這個檔案放到Server

3. 接著修改下面的sample code
   這個範例在上傳同時,順便會把檔案權限設為Public Read

Sample Code:
https://github.com/Brandon-Lin/google-cloud-storage/blob/master/upload_file_using_service_account.php




將Sever移植到Google Compute Engine


貼上如何將LAMP (Linux Apache MySQL PHP) Server 移植到Google Compute Engine (GCE).
這個server是用來壓縮上傳圖片還有處理SQL Query.


在這之前還是先準備信用卡,大部分的服務都需要信用卡。

1. 設定Google Cloud Compute Engine(GCE),可以參考底下這兩篇
    不過Google只支援PHP 5.4,所以OS選Debian比較保險
    (Google的教學影片也是用Debian)

初探 Google Cloud Platform - 1. Create instance

初探 Google Cloud Platform - 2. How to login and manage instance


2. 在VM Instance上面安裝Apache2, MySQL, 和PHP
     記得不需要安裝PHPMyAdmin,因為SQL DB並是安裝在另一台SQL Server上

3. 取消Apache Directory Listing功能 (網頁看不到目錄)
sudo /usr/sbin/a2dismod autoindex
sudo service apache2 restart

4. 把PHP code搬到GCE的VM Instance
    4-a: 建立網路硬碟,從左方選單點選"Storage browser"
         

    4-b: 點選"Add bucket"

    4-c: 填入Bucket名稱 (類似資料夾名稱)

    4-c: code搬到Google Cloud Storage (GCS)
gsutil -m cp <你的PHP Code> gs://你的資料夾

    4-d: code從GCS搬到VM Instance
(在VM console上打底下的指令)
gsutil -m cp gs://<你的PHP Code> /var/www

    5. 將MySQL上傳
         5-1: 匯出SQL file
  mysqldump --databases "你的DB名稱" -u "用戶名稱" -p"<密碼>" --hex-blob --default-character-set=utf8 | sed 's/ENGINE=MyISAM/ENGINE=InnoDB/g' >  備份名稱.sql



         5-2: 把SQL檔案搬到Bucket

         5-3: 建立DB


                   按"New Instance"


                    填入DB名稱


         5-3: 匯入SQL,選擇"Import"



        5-4:  將SQL IP 記下

        5-5: 加入可以存取DB的IP

       5-6: 修改MySQL DB連線位置
$dsn = "mysql:host=173.194.2xx.xxx;dbname=$db_name";
        $dbh = new PDO($dsn$db_user$db_password

                        array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));


6. 使用舊Server上的PHPMyAdmin
vim /etc/phpmyadmin/config.inc.php

/* Server parameters */
$cfg['Servers'][$i]['host'] = '173.194.2xx.xxx';
//$cfg['Servers'][$i]['connect_type'] = 'tcp';
//$cfg['Servers'][$i]['compress'] = false;

接著用舊Server上的PHPMyAdmin查看是否能連線到Google Cloud SQL




這樣就完成設定了~