Wednesday, June 10, 2015

使用Google OAuth2登入並使用雲端試算表

文章攢寫時間︰2015/06/11 13:37

一、前言

自從2015年05月開始,
Google不再提供Client-Login和OAuth的方式提供開發者登入Google帳戶,
如需使用程式登入,
僅能由OAuth2的管道。

在Android裝置上要登入Google使用其服務,
Play Service已提供一個極簡易的方法︰
GoogleAuthUtil.getToken(mActivity, mEmail, mScope);

這種登入的方式雖然很簡便,
但是缺點就是「只能存取自己帳戶底下」的雲端硬碟或個人內容。
如果今天想要把SpreadSheet(試算表)當作雲端資料庫,
讓你的App能隨時來存取資料,
這個方法可能就行不通了。

二、文章開始

前一篇曾使用過Client-Login登入Google的方式來存取SpreadSheet(試算表),
今天則是介紹使用OAuth2的方式。

Google提供了相~當~多~的管道過OAuth2,
這裡僅示例Android我實際用過且work的方式 - 使用P12檔。

步驟1 在Google APIs Console建立一個新專案並做相關設定


同意畫面是顯示給使用者認證的畫面,
商品名稱一定要打上去,
並記得按下方的儲存鈕。
建立新的用戶端ID
選擇使用P12檔的登入方式
此時跳出一個畫面,
把p12檔存起來。
這即將是您登入Google使用的鑰匙。

畫面也同時看到多了一個新的登入連線方式了。

步驟2 將P12檔複製到Android Studio專案底下

在src/main底下點滑鼠右鍵,
建立一個全新的assets資料夾
(此步驟如果已有assets資料夾則無需理會)
將剛才存起來的P12檔存入。

步驟3 匯入需要的jar檔

OAuth2的登入流程相當繁鎖,
但因為Google也提供了google-oauth-java-client套件供我們走OAuth2登入,
省掉開發的時程。

這個專案需要以下的jar檔,
請下載並import。
gdata-core-1.0.jar //spreadsheet會用到
gdata-spreadsheet-3.0.jar //spreadsheet用到
google-http-client-1.18.0-rc.jar //spreadsheet用到
google-http-client-jackson-1.18.00rc.jar //spreadsheet用到
guava-11.0.2.jar //spreadsheet用到
jackson-core-asl-1.9.11.jar //spreadsheet用到

google-api-client-1.18.0-rc.jar //OAuth2會用到
google-oauth-client-1.18.0-rc.jar //OAuth2會用到

步驟4 開始攢寫程式

走OAuth2時,
告知Google我們要存取什麼範圍(SCOPE)的content,
就可由底下的程式範例做登入動作了。
HttpTransport httpTransport = new NetHttpTransport();
                JacksonFactory jsonFactory = new JacksonFactory();

                ArrayList SCOPES = new ArrayList();
                SCOPES.add("https://spreadsheets.google.com/feeds");

                //SerServiceAcountId: clientID value should be similar to @developer.gserviceaccount.com They basically expect the email_address value from the console api credentials
                GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                        .setServiceAccountId("填入剛才新增用戶端ID得到的Mail")
                        .setServiceAccountPrivateKeyFromP12File(getTempPkc12File())
                        .setServiceAccountUser("欲分享SpreadSheet的人的gmail")
                        .setServiceAccountScopes(SCOPES)
                        .build();

                credential.refreshToken();

                String accessToken = credential.getAccessToken();
                Log.i(TAG, "accessToken: "+accessToken);

getTempPkc12File()函式內容如下,
記得更改掉open()裡的P12檔檔名。
        private File getTempPkc12File() throws IOException {
            InputStream pkc12Stream = mContext.getAssets().open("ScalpTest-1a66d0dc35fe.p12");//記得將檔名改成剛才存進Assets資料夾裡的P12檔檔名
            File tempPkc12File = File.createTempFile("P12File", "p12");
            OutputStream tempFileStream = new FileOutputStream(tempPkc12File);

            int read = 0;
            byte[] bytes = new byte[1024];
            while ((read = pkc12Stream.read(bytes)) != -1) {
                tempFileStream.write(bytes, 0, read);
            }
            return tempPkc12File;
        }

如果要使用SpreadSheet API,
這裡提供整份程式碼示例
List<spreadsheetentry> spreadsheets = null;

            SpreadsheetService mSpreadSheetService = new SpreadsheetService("欲存取的資料表名稱");
            mSpreadSheetService.setProtocolVersion(SpreadsheetService.Versions.V3);
            try {
                //2015/05月已停止使用
//                mSpreadSheetService.setUserCredentials("xxx@gmail.com", "密碼");

                HttpTransport httpTransport = new NetHttpTransport();
                JacksonFactory jsonFactory = new JacksonFactory();

                ArrayList SCOPES = new ArrayList();
                SCOPES.add("https://spreadsheets.google.com/feeds");

                //SerServiceAcountId: clientID value should be similar to @developer.gserviceaccount.com They basically expect the email_address value from the console api credentials
                GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                        .setServiceAccountId("填入剛才新增用戶端ID得到的Mail")
                        .setServiceAccountPrivateKeyFromP12File(getTempPkc12File())
                        .setServiceAccountUser("xxx@gmail.com")
                        .setServiceAccountScopes(SCOPES)
                        .build();

                credential.refreshToken();

                String accessToken = credential.getAccessToken();
                Log.i(TAG, "accessToken: "+accessToken);

                mSpreadSheetService.setOAuth2Credentials(credential);

                URL url = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");

                SpreadsheetFeed spreadFeed = mSpreadSheetService.getFeed(url, SpreadsheetFeed.class);
                spreadsheets = spreadFeed.getEntries();

三、結論

以上介紹了使用P12檔過Google OAuth2的方式。
如果是Server則又有各自不同的登入方式,
就交給大家去研究了。

相關文章

1. 官方OAuth2ForDevices
2. 官方OAuth2InstalledApp
3. 官方OAuth2介紹
4. 使用試算表API

取得user-code
curl -d "client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com&scope=https://spreadsheets.google.com/feeds" https://accounts.google.com/o/oauth2/device/code

RESPONSE:
{
  "device_code" : "LDBE-ZXPU4/wyoUadofXRCeslItAym9DJvQAt8fS8mFvlGUk9a5Ix4",
  "user_code" : "LDBE-ZXPU",
  "verification_url" : "https://www.google.com/device",
  "expires_in" : 1800,
  "interval" : 5
}
==============================
取得accessToken
curl -d "client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com&client_secret=yz2SJO7DrYSxEwEYGA6xcjIl&code=LDBE-ZXPU4/wyoUadofXRCeslItAym9DJvQAt8fS8mFvlGUk9a5Ix4&grant_type=http://oauth.net/grant_type/device/1.0" https://www.googleapis.com/oauth2/v3/token


https://accounts.google.com/o/oauth2/auth?
  scope=email%20profile&
  redirect_uri=urn:ietf:wg:oauth:2.0:oob&
  response_type=code&
  client_id=855763605695-nn0a0tv8k83pqq28trtfckfhf8och19t.apps.googleusercontent.com

Tuesday, June 2, 2015

[智能家居]用Arduino官方WiFi Shield的控制LED開關(使用Mac環境)

文章攢寫時間︰2015/06/02 14:03

一、前言

前一篇更新完WiFi Shield的韌體(firmware v1.1.0)後,
我們也因此進入一個天馬行空的世界。
能利用這塊板子做些什麼事呢?

嗯⋯就從微智能家居「控制LED的開關」開始吧~

二、文章開始

步驟1 準備需要的環境

這個專案需要使用到以下的玩具
  • 可連線上網的無線路由器AP
  • Arduino UNO
  • WiFi Shield
  • 330Ω電阻(橙橙棕) x1
  • LED燈泡 x1
  • 跳線 x2
麵包板示意圖
實際連接的樣子

步驟2 下載需要的函式庫Webduino

這個專案需要使用到支援WiFi Shield的Webduino函式庫,
我有針對Webduino Library稍做修改以支援WiFi Shield。

請至GitHub下載並放在
你的Arduino目錄\libraries底下



不熟悉Git的人,
可以到此下載zip壓縮包

步驟3 複製程式碼並引用需要的函式庫


開啟一個全新的Arduino專案,
將下方程式碼全數貼上。
char ssid[] = "換成你的WiFi無線網路名稱";
char pass[] = "換成你的WiFi無線網路密碼";

int status = WL_IDLE_STATUS;
WebServer webserver("", 80);

P(homePage) =
  "<!doctype html>"
  "<html>Arduino微網站</title>"
  "</head><body>"
  "這是微網站首頁。"
  "</body></html>";

P(lightPage) =
  "<!doctype html>"
  "<html>Arduino微網站</title>"
  "</head><body>"
  "這是燈泡控制專頁。"
  "</body></html>";

P(Get_head) = "<h1>收到了GET</h1>";
P(Post_head) = "<h1>收到了POST</h1>";
P(Unknown_head) = "<h1>UNKNOWN request</h1>";

const byte LED_PIN = 9;//LED燈泡插在PIN腳9

/**預設的指令是印岀homePage的文案*/
void defaultCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  server.httpSuccess();
  Serial.println("httpSuccess");
  if (type != WebServer::HEAD)
  {
    server.printP(homePage);
  }
}

/**當收到燈泡控制的指令時*/
void lightCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
{
  server.httpSuccess();
  Serial.println("httpSuccess");
  if (type != WebServer::HEAD)
  {
    //檢查是傳來的是GET或是POST
    switch (type)
    {
      case WebServer::GET:
        server.printP(Get_head);
        server.println(" ");
        break;
      case WebServer::POST:
        server.printP(Post_head);
        server.println(" ");
        parsePost(server);
        break;
      default:
        server.printP(Unknown_head);
    }
    server.printP(lightPage);
  }
}

/**解析傳來的POST指令*/
void parsePost(WebServer &server) {
  Serial.println("parsePost");

  char key[16];
  char value[16];
  while (server.readPOSTparam(key, 16, value, 16)) {

    //比對字元使用的是strcmp,當回傳值為0時代表字元符合
    if (strcmp(key, "light") == 0) { 
      server.print("燈光已經");
      if (strcmp(value, "on") == 0) {
        digitalWrite(LED_PIN, HIGH);
        server.print("打開。");
      } else {
        digitalWrite(LED_PIN, LOW);
        server.print("關閉。");
      }
    }
  }
}

void setup() {
  pinMode(LED_PIN, OUTPUT);//初始化LED燈
  
  Serial.begin(9600);//初始化序列埠至9600

  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while (true);
  }

  // attempt to connect to Wifi network:
  while ( status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);

    // wait 5 seconds for connection:
    delay(5000);
  }

  // 設定當外部連線連入Arduino時,相對應的處理函式
  webserver.setDefaultCommand(&defaultCmd);   // 處理「首頁」的請求
  webserver.addCommand("light.html", &lightCmd);  // 處理「燈泡控制頁」的請求
  webserver.begin();

  // you're connected now, so print out the status:
  printWifiStatus();
}

void loop() {
  webserver.processConnection();
}

/**印岀連線的WiFi狀態*/
void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

步驟4 引用函式庫

這個專案需要用到WiFi、SPI和上面所述的Webduino共三個函式庫,
請在Arduino IDE工具列中使用[草稿碼]→[include Library]來引用。

※剛才下載到libraries目錄底下的Webduino岀現在Contributed libraries裡了(見下圖)

步驟5 編譯並啟動

引用完所需要的函式庫以後,
就可以驗證並上傳程式至Arduino UNO裡了。
啟動序列埠監控視窗(Shift + Command + M),
等待連線的ip位址陸出。

步驟6 使用CocoaRestClient對WiFi Shield發岀POST連線

因為範例裡是使用POST的方式去發岀key為light和value為on/off的參數去控制LED燈,
推薦使用CocoaRestClient這套軟體去發岀POST請求。


接著就可以等著看結果囉!


三、注意事項
Arduino官方有提到WiFi Shield的以下數位PIN腳已被使用,
串接LED燈時請不要再使用這些腳位了。

數位PIN腳4 被用來當作SD卡的Slave Select(控制訊號)了。
數位PIN腳7 被用來當作WiFi Shield和Arduino之間的握手協定(handshake)了。
數位PIN腳10 被用來當作WiFi的Slave Select(控制訊號)了。
數位PIN腳11, 12, 13被用來當作Arduino溝通的序列埠了。

所以我們只能用數位PIN腳 3, 5, 6, 8, 9來控制LED燈。

四、結論

我們透過HTTP的POST方式將參數丟給Arduino WiFi Shield,
當然我們也就可以用GET來丟參數。
但因為Webduino這套第3方函式庫對於POST有較佳的程式支援,
理所當然就使用POST來傳輸以控制LED燈的開關嚕!

在成功使用WiFi控制LED燈後,
接下來聰明的你還想玩些什麼呢?(笑)

相關連結

1. Arduino官方網站
2. 更新WiFi Shield firmware