2014年11月13日 星期四

Mac OSX 10.10 Yosemite Eclipse 上機測試無法選擇device問題

最近跟瘋升級到Yosemite

但是今天要測試Android app上機跑的時候卻遇到抓不到裝置的問題(10.9的時候不會這樣啊QAQ)


詳細症狀如下

  1. 接上Android裝置後按下Run as Android Application卻找不到裝置


  2. 在上圖的畫面顯示的情況下拔掉Android裝置再插上去就會出現

  3. 選擇device後按下OK可以正常上機執行

  4. 稍微修改code後再按一下Run as Android Application會發現裝置又消失啦 => 回到第一步loop
    頓時腦中響起「我跳出來啦 我又回去啦 打我啊笨蛋」......  (°Д°#)

解決方式(from Stack Overflow)

  1. 使用adb檢查Android裝置是不是真的有接上Mac

  2. 接著再跑一次Run as Android Application,但是這時候要拖移一下欄位的寬度一切就正常了!!!


  3. 收工(╯°Д°#)╯︵ ┻━┻  

  雖然不知道是Yosemite, Android Plugin還是Eclipse luna的layout問題造成問題,但這問題太扯了!!!


參考資料

2014年10月1日 星期三

Raspberry Pi 樹莓派 無線AP to 有線網路橋接

筆者最近在研究Wi-Fi時發現Pi也可以模擬成AP,因此筆者用閒暇時間整理出如何將Pi變成一台無線基地台

簡介

首先先說明一下筆者所處的網路架構(圖1),圖片從右到左的介紹如下
  i. 首先是modem: 俗稱小烏龜的玩意兒,常見的就是ISP(中華電信, 寬頻業者)給的機器
  ii. 無線基地台(AP): 手機或筆記型電腦能透過Wi-Fi連接上AP後就能上網,很重要的一點是預設要開啟DHCP功能
  iii. 平板、手機、筆電: 透過Wi-Fi上網的機器
  iv. PC: 透過有線網路上網的機器

圖1. 典型網路架構

圖2為筆者目標的網路架構,說明如下
  目標: 
    i. 將有線網路延伸出去,像是AP架在客廳可是房間訊號很差,但卻有有線網路能接的情況
    ii. 另一種玩法就是做封包分析和網路攻擊的時候中間多個橋接器方便很多的
    iii. 由於是橋接的方式,在Pi無線網域下的所有裝置與有線網域下的所有裝置都還是在同一個網段,簡單的說就是網芳還是通的~
  架構:
    將有線網路接到Pi上,再由Pi發出Wi-Fi的訊號給手機、平板等裝置連線


網卡晶片

  筆者使用RTL8188CUS配合Realtek的軟體後可正常使用,但RTL8188SU和RTL8192SU試了好幾次都跑不起來.......

架設前注意事項

  以下的操作建議在Pi上接著螢幕和鍵盤執行,橋接完後AP分給Pi的IP可能會改變,此外有線網卡(eth0)基本上不會再取得IP,即便取得也不穩定,因此完成所有步驟前請直接在Pi上操作

架設步驟

  1. 安裝bridge工具

pi@ ~ $ sudo apt-get install bridge-utils

  2. 檢查網卡晶片組

  若跟筆者一樣是Realtek網卡晶片組請先跳到下方的"修正Realtek網卡在Pi上無法使用hostapd問題"章節安裝Realtek的hostapd,完成後再回到第4步
pi@ ~ $ sudo lsusb

  3. 安裝官方的hostapd

  (Realtek網卡晶片的使用者請勿使用Pi官方版本的hostapd)
pi@ ~ $ sudo apt-get install hostapd

  4. 檢查網卡代號

  此處筆者的有線網卡為eth0,無線網卡為wlan0
pi@ ~ $ ifconfig

  5. 修改無線網卡設定使其eth0和wlan0橋接起來

pi@ ~ $ sudo vi /etc/network/interfaces
auto lo

iface lo inet loopback
#iface eth0 inet dhcp

#allow-hotplug wlan0
#iface wlan0 inet manual
#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
#iface default inet dhcp

auto br0
iface br0 inet dhcp
bridge_ports eth0 wlan0 

  6. 建立hostapd的設定檔(預設可能不會有此設定檔)

pi@ ~ $ sudo vi /etc/hostapd/hostapd.conf
interface=wlan0
driver=rtl871xdrv
bridge=br0
ssid=RPI
channel=6
hw_mode=g
ieee80211n=1
wmm_enabled=1
wpa=2
wpa_passphrase=Raspberry
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
auth_algs=1
macaddr_acl=0 

  7. 重開機

pi@ ~ $ sudo reboot

  8. 測試hostapd是否能正常執行

pi@ ~ $ sudo hostapd -dd /etc/hostapd/hostapd.conf

  9. 編輯hostapd的設定檔

  使其在啟動daemon時載入/etc/hostapd/hostapd.conf的設定
pi@ ~ $ sudo vi /etc/default/hostapd
DAEMON_CONF="/etc/hostapd/hostapd.conf"

  10. 啟動hostapd,接著就能RPi當做AP來使用了~

pi@ ~ $ sudo service hostapd restart

修正Realtek網卡在Pi上無法使用hostapd問題

  1. 安裝Pi官方提供的hostapd,目標只是取得hostapd的daemon而已XD
pi@ ~ $ sudo apt-get install hostapd
  2. 刪除hostapd(不要懷疑,上一步就只是為了daemon檔案而已)
pi@ ~ $ sudo apt-get remove hostapd
  3. 到Realtek的下載頁面下載RTL8188CUS的工具包(在下載頁面搜尋RTL8188CUS即可找到)
    PS: 由於Realtek的下載頁是用js觸發下載事件,為節省麻煩建議直接用其他電腦的瀏覽器下載再使用scp指令(Windows請用WinSCP or FileZilla的sftp)複製到pi

  4. 將RTL8188CUS的工具包從Mac, Linux or Windows傳送到pi的家目錄中(Windows請使用WinSCP或FileZilla)
Shiun@ShiundeMacBook-Pro ~ scp RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911.zip pi@192.168.0.148:/home/pi
  5. 將RTL8188工具包解壓縮
pi@ ~ $ unzip RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911.zip
  6. 移動到hostapd的目錄
pi@ ~ $ cd RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd/
  7. 解壓縮wpa_supplicant_hostapd-0.8_rtw_r7475.20130812.tar.gz
pi@ ~/RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd $ tar -zxvf wpa_supplicant_hostapd-0.8_rtw_r7475.20130812.tar.gz
  8. 進入hostpad的目錄
pi@ ~/RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd $ cd wpa_supplicant_hostapd-0.8_rtw_r7475.20130812/hostapd/
  9. 開始建置hostapd
pi@ ~/RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd/wpa_supplicant_hostapd-0.8_rtw_r7475.20130812/hostapd $ make clean; make
  10. 安裝hostapd到正確的位置
pi@ ~/RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd/wpa_supplicant_hostapd-0.8_rtw_r7475.20130812/hostapd $ sudo make install;
  11. 再複製hostapd的執行檔以方便daemon使用,for Realtek的hostapd到此安裝完成
pi@ ~/RTL8188C_8192C_USB_linux_v4.0.2_9000.20130911/wpa_supplicant_hostapd/wpa_supplicant_hostapd-0.8_rtw_r7475.20130812/hostapd $ sudo cp /usr/local/bin/hostapd* /usr/sbin


這篇寫的好累Orz.......,讀者若有發現任何錯誤請盡量向筆者糾正,謝謝^^
參考資料
  1. Raspberry Pi 的應用 - Wifi 無線基地台

2014年8月17日 星期日

使用Eclipse Luna + Tomcat 7 + Jersey 2架設RESTful service

前言

此篇文章介紹如何使用Jersey架設RESTful web service,使用到的工具如下
  1. Eclipse Luna
  2. Tomcat 7
  3. Jersey 2.11

為避免對Maven或Gradle不熟悉的人感到疑惑,因此此篇文章只針對如何使用Jersey 2架設RESTful service,不使用Maven或Gradle進行管理

架設步驟

1. 開啟Eclipse

2. 建立新的專案File -> New -> Dynamic Web Project


3. 輸入專案名稱JerseySample並選擇Apache Tomcat v7.0作為Target runtime,記得要把Generate web.xml deployment descriptor打勾




4. 將jersey官網下載的jaxrs-ri-2.11.zip解壓縮,接著把所有的jar檔複製到jerseySample/WebContent/WEB-INF/lib下


5. 重新整理JerseySample專案確保lib都有正常載入



6. 修改web.xml讓Tomcat可以正確載入Jersey相關資源
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>JerseySample</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>org.sample.test</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping> 
  
</web-app>

  參數說明
    i. servlet-class
      Jersey 1的servlet-class為
        com.sun.jersey.spi.container.servlet.ServletContainer

      而Jersey 2的servlet-class必須改成(畢竟Sun已經R.I.P.了QAQ........)
        org.glassfish.jersey.servlet.ServletContainer

    ii. jersey.config.server.provider.packages
      Jersey掃描的packet路徑,resource的java檔要放在此package下

    iii. url-pattern
      Resource對應的URL位置,client端必須要照此參數設定才能呼叫到對應的resource

7. 建立package: org.sample.test



8. 建立resource的java檔案: Hello.java



9. 加入以下的程式碼至Hello.java
package test;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class Hello {
    // This method is called if TEXT_PLAIN is request
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String sayPlainTextHello() {
        return "Hello Jersey";
    }

    // This method is called if XML is request
    @GET
    @Produces(MediaType.TEXT_XML)
    public String sayXMLHello() {
        return "" + " Hello Jersey" + "";
    }

    // This method is called if HTML is request
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String sayHtmlHello() {
        return "<html> " + "<title>" + "Hello Jersey" + "</title>"
              + "<body><h1>" 
              + "Hello Jersey"
              + "</h1></body></html></pre>" 
              + " ";
 }
}

10. 將專案在Tomcat上跑起來,並使用瀏覽器檢查http://localhost:8080/JerseySample/api/hello是否正確




11. 使用RESTful client工具檢查xml的request是否能正常使用。只要送出的request包含Accept: text/xml檔頭,Jersey就會自動切換到回傳sayXMLHello函式結果


參考資料
1. REST with Java (JAX-RS) using Jersey - Tutorial (適用Jersey 1)
2. Jersey 2.5 + Maven = easy REST web services

[手工藝玩具] 1入2出 音源切換器

完成以下兩篇的玩具後,筆者的音樂播放器就從PC換成Pi
  Raspberry Pi 樹莓派 啟動I2S DAC or USB DAB 音效卡
  Raspberry Pi 樹莓派 架設ShairPort使用AirPlay播放音樂

但由於最近有不少狀況需要開PC,又PC要輸出聲音的話需要把喇叭從Pi換到PC的耳機座上,
根據以前悲劇的經驗,這樣不是喇叭的耳機頭先壞掉就是PC的耳機座先被插壞.........
因此筆者決定弄一個音源切換器,參考DIY音源切換器[DIY]兩種音源切換器(二入一出)後筆者自製了一個音源切換器

原料如下
  1. 6pin 2段式開關x1
  2. 3.5mm 耳機座x3 (二入一出)
  3. 塑膠盒子,大小為60x40x30mm
  4. 比6pin 2段式開關的pin粗一點點就熱縮套

以上原料大概只有80元可以搞定,接著就是開始對把線焊上2段式開關 -> 塑膠盒鑽4個孔 -> 鎖上耳機座元件 -> 鎖上2段式開關 -> 將耳機座和2段式開關焊上,接著就完成了(詳細內容請直接參考最底下的參考資料)

最後成品如下圖~


補上小小心得
  整天過著寫程式的生活難免會有枯燥乏味的時候,偶而來DIY些小東西來玩也挺有趣的,尤其是對筆者這種拿焊槍次數10根手指頭就能算完的新手,以玩家的角度來做這東西其實挺有趣的

  在焊接的部份以往都有強者學長支援,但這次在沒人支援的情況下在60x40x30mm的盒子裡焊接東西讓筆者苦戰整個下午才搞定,做完其實挺有成就感的

  而實際上這東西在網路上買的到,價格大概3xx到4xx(2014年8月份),不過根據筆者DIY整個東西的時間,其實3xx元已經非常划算了,如果不是以玩家的心情做的話建議還是花錢直接買現成的吧


參考資料
  1. DIY音源切換器
  2. [DIY]兩種音源切換器(二入一出)

2014年7月20日 星期日

Mac OSX 10.9 查詢listening ports

在Linux中可以簡單透過netstat -tulnp列出目前系統listening ports,但OSX 10.9上這招行不通,要改用以下指令才能查詢到目前系統正在listening的ports

Shiun@ShiundeMacBook-Pro ~ sudo lsof -i -P | grep -i "listen"

2014年7月17日 星期四

Mac OSX Apache 啟用/停止 開機啟動

由於筆者最近跑去玩RoR使用Pow作為web server,之前在MAC上開啟的Apache每次都會開機自動啟動,老實說要手動關後才能開Pow真的很煩,因此筆者上網找要怎麼關閉開機啟動Apache

停止開機啟動Apache

  1. 打開瀏覽器並輸入http://127.0.0.1先確認Apache是開啟狀態,若是關閉狀態請先執行以下指令開啟Apache
Shiun@ShiunMac ~ sudo apachectl start
  2. 打開終端機

  3. 輸入以下指令
Shiun@ShiunMac ~ sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist 此時Apache也會被一起關閉


啟用開機啟動Apache

  1. 確認Apache是關閉狀態,若是開啟狀態請先執行以下指令關閉Apache
Shiun@ShiunMac ~ sudo apachectl stop
  2. 打開終端機

  3. 輸入以下指令
Shiun@ShiunMac ~ sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist 此時Apache會跟著一起被啟動

2014年7月14日 星期一

PHP 比對非0字串與整數0時卻回傳true問題

今天強者我朋友遇到PHP比對字串和0時卻發生回傳結果不如預期的問題,因為還挺有趣的所以就筆記下來

以下用簡單的switch case程式碼說明
<?php
$foo = 0;
switch ($foo)
{
    case "str1":
        echo "str1";
        break;
    case "str2":
        echo "str2";
        break;
    default:
        echo "default";
}
?>
Result
str1
由於直覺上0不可能等於"str1"應該要印出"default"才合理,為此筆者翻了一下PHP的文件Comparison Operators,其中有一句話這麼說

If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically. These rules also apply to the switch statement.

簡單翻譯:
   若用"=="比對的是數字和字串時,字串會被轉換為數字再進行比對,此規則也適用於switch-case

因此上述的switch-case程式碼實際比對的流程如下
  1. 由於比對的是整數和字串,因此準備開始轉換字串"str1"為整數
  2. 無法將字串由"str1"轉換為整數,因此PHP將字串"str1"轉換為0
  3. 由於"str1"轉換為0且$foo也為0,因此PHP判斷條件式case "str1"成立印出"str1"到網頁上

同樣的問題可以簡化為
<?php
echo (0 == "str1" ? "true" : "false") . "<br />";
var_dump((int)"str1");
?>
Result
true // 0等於"str1" int(0) // 整數型態的0
顯然地,要比對字串和數字時千萬要小心,不然就是用"==="來進行比對避免出問題
那若我們比對的是0和"1str"呢?
<?php
echo (0 == "1str" ? "true" : "false") . "<br />";
echo (1 == "1str" ? "true" : "false") . "<br />";
var_dump((int)"1str");
?>
Result
false // 0不等於"1str" true // 1等於"1str" int(1) // 整數型態的1 至於為啥會有這樣的結果呢?不要懷疑,就只是PHP的規則如此而已XD

參考資料
1. Comparison Operators

2014年6月19日 星期四

Android 使用Volley發送RESTful request

Android Volley建置一文中筆者建立了Volley的jar檔但卻沒有提說怎麼使用,接下來說明如何使用Volley發送RESTful request吧

流程說明

1. 建立Volley的RequestQueue: 使用者將request放到RequestQueue後Volley會自動將request以非同步方式發送出去

2. 建立JsonObjectRequest, JsonArrayRequest或StringRequest: 依照target的RESTful service規格決定要用JSONObject, JSONArray或String其中之一吧~
註: 筆者採用 使用CodeIgniter架設RESTful Web Service 一文中架設的RESTful web service當做練習對象,其index.php同時提供GET, POST, PUT和DELETE的範例

3. 將第2步中產生的request放到RequestQueue中

4. 等待server的回應並進行處理


實作

1. 建立Android application project



2. 加入volley.jar到專案中

  建置volley.jar可參考Android Volley建置

3. 建立RequestQueue

  此處採用Singleton pattern建立Singleton class,其名稱為ApplicationController,詳細程式碼可參考"參考資料1",以下的程式碼筆者針對Singlegon做簡單的說明
public class ApplicationController extends Application {

    /**
     * Log or request TAG
     */
    public static final String TAG = "VolleyPatterns";

    /**
     * Global request queue for Volley
     */
    private RequestQueue mRequestQueue;

    /**
     * A singleton instance of the application class for easy access in other places
     * Singleton物件,簡單的說就是整個app只會有這一個ApplicationController物件,理想上不會產生第二份ApplicationController物件
     */
    private static ApplicationController sInstance;

    @Override
    public void onCreate() {
        super.onCreate();

        // initialize the singleton
        sInstance = this;
    }

    /**
     * @return ApplicationController singleton instance
     * 要使用ApplicationController一定要先透過此method取得ApplicationController物件
     */
    public static synchronized ApplicationController getInstance() {
        return sInstance;
    }

    /**
     * @return The Volley Request queue, the queue will be created if it is null
     */
    public RequestQueue getRequestQueue() {
        // lazy initialize the request queue, the queue instance will be
        // created when it is accessed for the first time
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    /**
     * Adds the specified request to the global queue, if tag is specified
     * then it is used else Default TAG is used.
     * 
     * @param req
     * @param tag
     */
    public  void addToRequestQueue(Request req, String tag) {
        // set the default tag if tag is empty
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);

        VolleyLog.d("Adding request to queue: %s", req.getUrl());

        getRequestQueue().add(req);
    }

    /**
     * Adds the specified request to the global queue using the Default TAG.
     * 
     * @param req
     * @param tag
     */
    public  void addToRequestQueue(Request req) {
        // set the default tag if tag is empty
        req.setTag(TAG);

        getRequestQueue().add(req);
    }

    /**
     * Cancels all pending requests by the specified TAG, it is important
     * to specify a TAG so that the pending/ongoing requests can be cancelled.
     * 
     * @param tag
     */
    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

4. 修改AndroidManifest.xml

  加入網路存取權限以及開啟app自動啟動ApplicationController
    <!-- 加入網路存取權限 -->
    <uses-permission android:name="android.permission.INTERNET" />
       
    <!-- 加入android:name這項屬性讓ApplicationController能在開啟app後自動建立ApplicationController-->
    <application
        android:name=".ApplicationController"
        android:allowBackup="true"
        .......
    />

5. 建立request

i. GET
private void doGet(){
    // Service的URL
    String url = "http://192.168.11.13/~shiun/ws/service/index";
    // 建立期望收到JsonObject的request
    // 若new JsonObjectRequest的第二個參數為null則代表是GET,反之若為JSONObject則為POST
    JsonObjectRequest request = new JsonObjectRequest(url, null, 
        new Listener<JSONObject>() {
            // 若server回傳HTTP status 200或204則會進入onResponse method 
            // 註:根據官網Issue 57527的討論,最新版應該是2xx都算request success
            @Override
            public void onResponse(JSONObject response) {
                Log.i(tag, response.toString());
            }
        }, new ErrorListener() { // Request失敗處理
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(tag, error.getMessage());
            }
    });
    // 將request放到RequestQueue,Volley會以非同步方式送出request
    ApplicationController.getInstance().addToRequestQueue(request);
}

GET成功~

ii. POST: POST的關鍵在於new JsonObjectRequest時要將POST的資料放到第二個參數中
private void doPost() throws JSONException {
    // Service的URL
    String url = "http://192.168.11.13/~shiun/ws/service/index";
    JSONObject param = new JSONObject();
    param.put("key1", "POST_value1");
    param.put("key2", "POST_value2");
    // 建立期望收到JsonObject的request
    // 因為第二個參數不是null,所以Volley把request當做POST傳送
    JsonObjectRequest request = new JsonObjectRequest(url, param, 
        new Listener<JSONObject>()
        // Request成功
            @Override
            public void onResponse(JSONObject response) {
                Log.i(tag, response.toString());
            }
        }, new ErrorListener() { // Request失敗處理
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(tag, error.getMessage());
            }
    });
    // 將request放到RequestQueue,Volley會以非同步方式送出request
    ApplicationController.getInstance().addToRequestQueue(request);
}

POST成功~

iii. PUT: PUT的關鍵點在於要new有method版本的JsonObjectRequest
private void doPut() throws JSONException
{
    String url = "http://192.168.11.13/~shiun/ws/service/index";
    JSONObject param = new JSONObject();
    param.put("key1", "PUT_value1");
    param.put("key2", "PUT_value2");
    // 這次的關鍵點在於使用Request.Method.PUT
    JsonObjectRequest request = new JsonObjectRequest(Request.Method.PUT, url, param,
        new Listener() {
            @Override
            public void onResponse(JSONObject response) {
                Log.i(tag, response.toString());
            }
        }, new ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(tag, error.getMessage());
            }
        });
    ApplicationController.getInstance().addToRequestQueue(request);
}

PUT成功~

iv. DELETE: DELETE時只需要把Method換成Request.Method.DELETE並配合service提供的URL即可
private void doDelete() throws JSONException
{
    String url = "http://192.168.11.13/~shiun/ws/service/index/1";
    JsonObjectRequest request = new JsonObjectRequest(Request.Method.DELETE, url, null,
        new Listener() {
            @Override
            public void onResponse(JSONObject response) {
                Log.i(tag, response.toString());
            }
        }, new ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(tag, error.getMessage());
            }
        });
    ApplicationController.getInstance().addToRequestQueue(request);
}

DELETE成功~


參考資料

1. Asynchronous HTTP Requests in Android Using Volley

2. Android Volley Tutorial - Making HTTP GET, POST, PUT, DELETE Requests

3. Application-element: "Android:name"

2014年6月9日 星期一

CodeIgniter 去除網址中的index.php

自從開始由CI寫PHP後每次都覺得那多餘的index.php很難看,尤其是之前在寫使用CodeIgniter架設RESTful Web Service這篇文章的時候GET, POST, PUT, DELETE的網址竟然是http://127.0.0.1/~shiun/ws/index.php/service/index,中間那index.php真的讓人受不了.........

這次就來把網址美化一下吧

1. 開啟mod_rewrite

Shiun@ShiunMac ~ sudo vi /etc/apache2/httpd.conf
啟動Apache的mod_rewrite模組 LoadModule rewrite_module libexec/apache2/mod_rewrite.so

2. 在CI的根目錄下新增.htaccess並加入以下內容

#IfModule代表module有啟動才會執行IfModule內的內容 <IfModule mod_rewrite.c> #啟動rewrite引擎 RewriteEngine On #這裡指的是CI的位置,也就是CI位於根目錄下的哪個路徑中 RewriteBase /ci/ #改寫規則一: 所有要往/ci/system下的request都導到index.php下 RewriteCond %{REQUEST_URI} ^system.* RewriteRule ^(.*)$ ./index.php?/$1 [L,QSA] #改寫規則二: 所有要往/ci/application下的request都導到index.php下 RewriteCond %{REQUEST_URI} ^application.* RewriteRule ^(.*)$ ./index.php?/$1 [L,QSA] #改寫規則三: 若網址後的參數不為index.php, images, css, js, robots.txt, favicon.ico任何一項 RewriteCond $1 !^(index\.php|images|css|js|robots\.txt|favicon\.ico) #且request的不是一般檔案 RewriteCond %{REQUEST_FILENAME} !-f #且request也不是資料夾 RewriteCond %{REQUEST_FILENAME} !-d #將request參數導到index.php下 RewriteRule ^(.*)$ ./index.php?/$1 [L,QSA] </IfModule> #如果mod_rewrite沒有啟動的話 <IfModule !mod_rewrite.c> # If we don't have mod_rewrite installed, all 404's # can be sent to index.php, and everything works as normal. ErrorDocument 404 /index.php </IfModule>

3. 重新啟動apache

Shiun@ShiunMac ~ sudo apachectl restart

4. 用新網址瀏覽網頁吧~,少了index.php感覺好看超多~


補充: 由於筆者的根目錄是/~shiun/ws/,因此RewriteBase這行要改成/~shiun/ws/才會動


mod_rewrite補充說明

  由於大多Google到的結果都沒針對mod_rewrite為什麼要這麼寫解釋,害筆者心中總少了啥,因此筆者另外收集並補充以上用到的mod_rewrite規則說明

  1. RewriteCond代表要滿足的條件,若滿足RewriteCond的條件則往下一行繼續執行

  2. 若連續出現數行RewriteCond且RewriteCond最後沒有提示OR, AND之類的文字,則預設為AND比對,例如"改寫規則三"就是連續三個RewriteCond的AND比對

  3. "改寫規則一"中出現的$1為 ^(.*)$ 這個正規表示式的結果,上述三個改寫規則的^(.*)$指的就是domain name之後的所有文字
    例如 aaa.org/index.php?a=b 中的index.php?a=b就是$1

  4. 每個改寫規則最後出現的[L, QSA]中的L代表這條改寫規則結束

  5. [L,QSA]的QSA代表將Query的參數補到網址的最後面
    例如 aaa.org/index.php?a=b&c=d,要是沒有QSA的話rewrite模組會把"?"後所有Query參數砍掉


參考資料
1. [CodeIgniter]如何去掉 URL 中的 index.php
2. 如何透過.htaccess檔,來使用URL redirect/rewrite功能?
3. .htaccess技巧: URL重写(Rewrite)与重定向(Redirect)

2014年6月8日 星期日

CentOS 6.5 虛擬機器(VM)換網卡後編號錯誤問題(eth0, eth1)

筆者今天在複製VirtualBox中的CentOS機器並給予新網卡號碼後,新的CentOS機器竟然沒自動更新eth0的網卡代號,取而代之的是新增了一個eth1

雖然應該無所謂但是編號越變越多也不是辦法,只好手動進行CentOS的網卡設定,以下筆記如何修改

1. 從VirtualBox的設定值中找到新的網卡MAC位置



2. 修改/etc/udev/rules.d/70-persistent-net.rules下的網卡設定

CentOS偵測到新網卡後自行產生eth1,但此時eth0的網卡已經不存在在機器上了

刪除掉舊的eth0整行設定後,把新的eth1設定中的NAME改為eth0,記得確定address是否為第1步中出現的MAC位置


3. 修改/etc/sysconfig/network-scripts/ifcfg-eth0中的MAC位置

將舊的eth0 MAC位置換為新的MAC位置

4. 重新啟動網路設定,打完收工~

[user@CentOSServer ~]$ sudo service network restart


2014年6月4日 星期三

Netbeans 手動加入javadoc的jar擋

由於筆者最近需要用Netbeans寫Java (不能用Eclipse不習慣啊QAQ),又寫Java的時候沒辦法在IDE上即時看doc真的很煩,只好抽時間來研究一下怎麼放含有javadoc的jar檔到Netbeans中

Netbeans即時顯示println的說明

以下以SLF4J為範例說明如何加入javadoc(若讀者沒聽過SLF4J可直接把SLF4J簡單想成println的另一個函式庫)

1. 加入slf4j-api-1.7.7.jar和slf4j-simple-1.7.7.jar到專案中



2. 加入以下用SLF4j寫的簡單程式碼

package test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test.class);
        logger.info("Hello World");
    }
    
}
在寫以上的code過程中就會看到Javadoc not found

3. 加入slf4j-api-1.7.7-javadoc.jar和slf4j-simple-1.7.7-javadoc.jar

  i. 對slf4j-api-1.7.7.jar按右鍵並選擇edit


  ii. 加入slf4j-api-1.7.7-javadoc.jar到Javadoc的欄位中
  
  iii. 同i和ii的步驟將slf4j-simple-1.7.7-javadoc.jar設定為slf4j-simple的javadoc

4. 重新寫一次logger.info會發現Netbeans終於跳出javadoc了! (WIndows的Netbeans使用者可透過ctrl + shift + space的快捷鍵叫出javadoc,但OS X上我找不到快捷鍵Orz...)



小知識

  在網路上下載的jar檔常常會看到xxx.jar, xxx-sources.jar, xxx-javadoc.jar,這些jar檔代表不同的意思

種類說明
xxx.jar已將.class檔案們打包好的檔案,xxx.jar也有可能已經包含source和javadoc(例如Android Volley建置出來的jar就包含了javadoc)
xxx-sources.jar包含source的jar檔,在debug時可以看到引用的jar檔的source code
xxx-javadoc.jar包含javadoc的jar檔,使用Netbeans開發時可以即時看到jar內某個class, method, ... etc. 的說明


2014年5月27日 星期二

Raspberry Pi 樹莓派 強化版的解rar工具 unrar-nonfree

相信大家在下載東西的時候有遇過xxx.part1.rar, xxx.part2.rar, ......這種東西吧,而Raspberry本身的unrar對這東西又很無力......,因此筆者打算裝上unrar-nonfree版本

1. 刪除unrar-free (2014年的Raspbian預設沒有安裝,因此不用再刪除)
pi@ShiunPiSnd ~ $ sudo apt-get remove unrar-free
2. 修改apt的source list
pi@ShiunPiSnd ~ $ sudo vi /etc/apt/sources.list
# Source repository to add deb-src http://archive.raspbian.org/raspbian wheezy main contrib non-free rpi
3. 更新apt資料庫
pi@ShiunPiSnd ~ $ sudo apt-get update
4. 準備unrar-nonfree的資料夾,我們準備要從原始碼建置他
pi@ShiunPiSnd ~ $ mkdir unrar-nonfree pi@ShiunPiSnd ~ $ cd unrar-nonfree
5. 安裝相依套件
pi@ShiunPiSnd ~ $ sudo apt-get build-dep unrar-nonfree
6. 下載unrar-nonfree的原始碼並建置成deb檔
pi@ShiunPiSnd ~ $ sudo apt-get source -b unrar-nonfree
7. 安裝unrar-nonfree的deb套件
pi@ShiunPiSnd ~ $ sudo dpkg -i unrar*.deb
8. 接著只要解壓縮part1就可以解開全部part了,另外要輸入密碼也沒有問題~
pi@ShiunPiSnd ~ $ unrar x xxx.part1.rar
參考資料
1. How to install unrar-nonfree? - Raspberry Pi - Stack Exchange

2014年5月26日 星期一

Raspberry Pi 樹莓派 啟動I2S DAC or USB DAB 音效卡

最近筆者的學長幫忙弄了一塊I2S DAC音效卡來玩,因此筆者記錄一下如何在Pi上使用外接音效卡

由於USB或I2S DAC都是使用Linux的ALSA(Advanced Linux Sound Architecture)控制,因此USB DAC的使用者也可以參考此篇文章設定及測試

1. I2S DAC硬體與driver準備(USB DAC使用者直接到下一章節)


筆者的DAC規格
  晶片: PCM5102a
  通道: I2S (下圖左上角Pi預設沒焊排針的8隻腳)

指揮艇組合XD


由於筆者手邊的I2S DAC是採用HiFiBerry的設計,又Raspbian 2014-01-07版本(Linux kernel 3.10.25+以上)已內建HiFiBerry的driver,因此不用再多下載驅動,但需要在啟動時載入driver modules
// 載入I2S DAC需要用到的driver modules pi@ShiunPiSnd ~ $ sudo vi /etc/modules
# 關閉內建DAC省去未來播放音樂還要選擇裝置的麻煩
#snd-bcm2835

# 載入DAC driver module
snd_soc_bcm2708_i2s
bcm2708_dmaengine
snd_soc_pcm5102a
snd_soc_hifiberry_dac

重新啟動Pi讓Linux載入HiFiBerry driver

2. 檢查Pi是否有抓到DAC


顯然的Pi有抓到HiFiBerry DAC,且內建的DAC也關掉了 (若讀者使用USB DAC在此處也會看到)

3. 播放音樂測試

pi@ShiunPiSnd ~ $ aplay xxx.wav
收工~

(2014/05/26 update) 筆者試著播放/usr/share/sounds/alsa下的音樂卻發生錯誤,但播放自己的音樂卻沒問題,若讀者發現無法播放/usr/share/sounds/alsa下的音樂也請不用擔心

參考資料
1. HiFiBerry

2014年5月25日 星期日

Raspberry Pi 樹莓派 利用Samba架設NAS (Linux based OS適用)

今天就來開始打造以Pi + Samba為基底的NAS吧!

首先要準備的是
  1. 樹莓派
  2. USB Y型線
  3. USB外接硬碟 (小抱怨: 現在USB2.0的外接硬碟超難找是怎樣,害我還要找USB3.0的Y型線找個半天)
          配件由左至右: USB3.0 Y型線、AC轉USB轉接頭、樹莓派、USB外接硬碟


接著把Y型線的供電端接上AC轉USB轉接頭、標準USB頭接上Pi、另一頭(USB micro-B)接上USB外接硬碟

由於樹莓派USB端也可以進行供電因此這種接法是可行的,但這種接法不會經過保險絲(micro USB端供電才有1A的保險絲)所以還是要注意一下安全性

以下開始架設NAS

1. 格式化硬碟

建議用ext4格式就好,反正未來資料都是走網路傳輸,特地搞個fat32, exfat或ntfs還不如用Linux支援度較好的ext4
  i. 檢查硬碟是否有抓到,筆者的Pi抓到的位置是/dev/sda1

  ii. 重建硬碟分割


  上圖中由上到下的紅框說明如下
// 用fdisk處理硬碟分割 pi@ShiunPiSnd ~ $ sudo fdisk /dev/sda // 列出/dev/sda下的硬碟分割資訊 Command (m for help): p // /dev/sda下有個分割叫做/dev/sda1 Device Boot start End Blocks Id System /dev/sda1 2048 1953521663 976759808 7 HPFS/NTFS/exFAT // 刪除第一個分割 Command (m for help): d Selected partition: 1 // 新增第一個分割 Command (m for help): n // 剩下全部用預設值即可 ...... // 寫入新的硬碟分割資訊到硬碟中 Command (m for help): w

  使用 fdisl - l 檢查System欄位是否為Linux,是的話就ok啦~


  iii. 格式化硬碟
// 使用mkfs.ext4格式化硬碟 pi@ShiunPiSnd ~ $ sudo mkfs.ext4 /dev/sda1 

  iv. 檢查格式化結果
// 使用parted指令檢查 pi@ShiunPiSnd ~ $ sudo parted

// 輸入q離開parted (parted) q
  v. 修改分割的label
// 將分割的label設定為UDisk pi@ShiunPiSnd ~ $ sudo e2label /dev/sda1 UDisk
以上步驟完成後可以直接重新開機讓Pi自己去把UDisk掛載到/media/UDisk下

2. 安裝samba

// 安裝samba套件 pi@ShiunPiSnd ~ $ sudo apt-get install samba

3. 設定為需要帳號密碼才能登入

// 編輯samba設定檔 pi@ShiunPiSnd ~ $ sudo vi /etc/samba/smb.conf
# "security = user" is always a good idea. This will require a Unix account # in this server for every user accessing the server. See # /usr/share/doc/samba-doc/htmldocs/Samba3-HOWTO/ServerType.html # in the samba-doc package for details. security = user

4. 在smb.conf的最底下加入要分享的目錄 

[Share] comment = Share_Folder path = /media/UDisk browseable = Yes writable = Yes public = No 以下為參數解釋:
參數 說明
comment = Share_Folder#就只是個comment XDDDDD
path = /media/UDisk#設定要分享的目錄位置
browseable = Yes#可以直接從Finder(OS X) or 檔案總管(Windows)看到
writable = Yes#開啟寫入權限
public = No#不允許訪客瀏覽此目錄

5. 設定samba使用者帳號 

#新增pi帳號為samba使用者 pi@ShiunPiSnd ~ $ sudo pdbedit -a pi new password: retype new password: #列出samba的使用者 pi@ShiunPiSnd ~ $ sudo pdbedit -L pi:1000:

6. 用手邊的電腦測試看看能不能連上吧~






註: Windows使用者請直接在檔案總管的網址列上打\\ip 也可以存取samba,例如在網址列上打以下的字串 \\192.168.11.14


參考資料

1. Raspberry Pi 安裝心得、教學、簡介