2015年9月21日 星期一

Processing - 開發Android App

相信大家上網Google合併搜尋Processing和Android應該會出現這篇官方文章:Github - Processing

根據小弟我自己的操作後,發現有點卡卡的,後來發現是我自己的Processing版本太久,建議大家趕快更新到Processing 3,icon會變這樣:

下載連結:Processing 3

Step1:設定Sketchbook location

若你是第一次使用Processing可以跳過,若你曾安裝過其他版本,建議修改Sketchbook location,或者將舊資料夾內的資料都刪除。

選擇工具列上的Processing -> Preferences

更新新的路徑


更新後重新開啟,確認資料夾內是不是有這些檔案。


Step2. 增加Android開發模組

點擊右上角有Java字樣下拉式選單。


跳出新的視窗後,選擇Modes的Tab(頁籤),選擇Android Mode,點下右下角的Install後就會開始安裝了。


Step3. 開始開發吧!

切換到Android Mode


貼上以下程式碼:
 int red = 255;  
 int time = 0;  
 int diameter = 50;  
 void setup() {  
  size(400, 400);  
 }  
 void draw() {  
  time = time + 1;     
  red = int(128 * (1 + sin(time * 2 * PI / frameRate / 20)));   
  diameter = int(50 * (1 + sin( time * 2 * PI / frameRate / 5))); // Change the circle's diameter with the time  
  noStroke();         
  fill(red, 0, 0, 50); // Add a 50% transparency to the color  
  ellipse(mouseX, mouseY, diameter, diameter);   
 }  
這裏不解釋程式碼,可以親自到官方找各種教學,或者期待小弟我有新文章~XD

Step4. 燒錄到手機內
* 為什麼不燒錄到模擬器就好?因為很慢,不信的話可以自己試看看。

打開手機的Debug(開發者)模式。
到"設定"內的"開發人員選項",如果沒有的話,請根據各家廠牌的手機開啟此隱藏功能。之後載把"USB偵錯"功能打開。如下圖:

 將USB線兩端各自插上電腦和手機,應該要可以從手機的系統通知欄看到下面訊息。

之後就可以回到Processing IDE(開發環境)將剛剛貼上的程式碼燒錄到Android手機內。
你有兩種方式可以使用,應該都是一樣效果的。

你可以點擊左上角的執行按鈕

或者你可以從工具列上的Sketch -> Run on Device。

Step5. 玩玩看你剛剛的作品





2015年9月18日 星期五

Android - 定位

若在你的APP中有使用到地圖的功能,或者是有需要取得使用者位置,你一定會需要定位的功能。

這篇提供簡單的用法,還有說明一些可能會遇到的問題和解法。

以下是完整的定位功能的程式碼,你可以在你任何想使用的時機呼叫,像是onCreate():

   private void locateUser() {  
     LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); // 取得裝置的定位服務  
     String bestProvider = LocationManager.GPS_PROVIDER; // 指定最佳定位是用 GPS 定位  
     Criteria criteria = new Criteria(); // Criteria 會依照裝置的定位設定依狀況幫你取得裝置位置,見備註1  
     bestProvider = locationManager.getBestProvider(criteria, true);  
     Location location = locationManager.getLastKnownLocation(bestProvider); // 取得最後定位到的位置,見備註2  
     if(location != null){  
       LatLng mylocation = new LatLng(location.getLatitude(), location.getLongitude());  
       Marker userMarker = mMap.addMarker(new MarkerOptions()  
         .position(mylocation)  
         .title("UserLocation"));  
       mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mylocation, 15), 1500, null); // 移動到目前位置  
 //      mMap.animateCamera(CameraUpdateFactory.newLatLngZoom("你的位置-LatLng", "畫面涵括範圍"), "移動到指定位置經過的時間", "callback function");  
     }  
   }  

結果長這樣

以上的實作過程大致是這樣:
取得裝置定位功能的服務 -> 指定你需要用什麼方式取得定位(GPS、3G網路、WiFi哪一種) -> 取得裝置位置(LatLng 經緯度的資料) -> 使用取得的位置資料做事情:移動到使用者位置、搜尋附近景點、提供推薦路線等。

在最後的地方,當你取得你的裝置位置資料 - location後,你應該要去判斷是不是空值,因為你有可能會取不到資料!原因可能是下面幾個原因:

1. 你的APP沒有要求位置的權限,應該可以在AS的logcat看到以下的訊息:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.idlefox.mymap/com.idlefox.mymap.MainActivity}: java.lang.IllegalArgumentException: invalid provider: null

所以請到AndroidMenifest.xml加入以下權限:
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  

2. 裝置沒有開啟定位功能。

3. 就是抓不到。我之前就是在Sony手機上可以,可是在LG的手機就沒辦法順利抓到,有時要稍微等一下...

4. 還在想...

備註1:
     Criteria criteria = new Criteria(); // Criteria 會依照裝置的定位設定依狀況幫你取得裝置位置,見備註1  
     bestProvider = locationManager.getBestProvider(criteria, true);  

Criteria 是一個很理想定位服務的選擇器,因為定位資料可以透過GPS、網路功能取得,打開你Android手機內建的定位功能就知道。


所以很直覺的就會想說:「那我是不是還要指定取得位置資料要從哪一個方式取得?這樣是不是程式碼要一個一個去檢查,先檢查GPS,如果沒有再看有沒有網路的資料,沒有的話就GG?」
現在可以直接使用Criteria的服務,他會自己幫你選擇裝置內目前精度最好的定位資料。

備註2:
     Location location = locationManager.getLastKnownLocation(bestProvider); // 取得最後定位到的位置,見備註2  

其中你需要注意的地方是,這段程式只會被執行一次,因為你還沒有設定如果位置改變,或者經過多久,你就要重新取得裝置位置的"事件"。

而一般在做定位的APP也只需要在一進入時獲得一次位置資料就好,除非你要提供導航的功能。

github:Android Google Map 定位

Android - Google Map V2


開發工具:Android Studio

因為學弟妹最近好像都會想要在網路上找Android的Google Map怎麼開發,而網路上的好像很多都還是舊的,或者就是使用Javascript V3的教學,但是還是使用V2的好像比較好(一種Android就是要用Android的堅持)。

Step1. 開啟空白專案
因為教學方便,所以建議開啟空白專案,我有一組學弟妹因為直接開啟Google Map的專案,好像就做不下去,可能比較不適合初學者吧。


Step2. 申請Google Map的專案和Key
a. 進入此網址:https://console.developers.google.com/project
b. 建立新專案



c. 進入剛建立的專案首頁,之後選擇API和憑證,再選擇API,選擇Google Map Android API後會跳到下一頁,將API啟用。這一步驟不啟用,即使加入Google Service Key也沒有用。
進入專案首頁

選擇要開啟的API

開啟API,使"啟用API" 變成 "停用API"

d. 新增憑證,也就是幫你的APP註冊,讓你的APP可以辨識到此專案的憑證。可以參考此教學申請

Step3. 在Android Studio加入Google Map,此步驟就可以確認前面的步驟是否都成功。
a. 加入Google Play Service Maps到你的專案
在build.gradle加入下面的程式將google map library加入到專案內
   compile 'com.google.android.gms:play-services-maps:7.8.0'  

完整程式碼如下:
 apply plugin: 'com.android.application'  
 android {  
   compileSdkVersion 22  
   buildToolsVersion "21.1.2"  
   defaultConfig {  
     applicationId "com.idlefox.mymap"  
     minSdkVersion 11  
     targetSdkVersion 22  
     versionCode 1  
     versionName "1.0"  
   }  
   buildTypes {  
     release {  
       minifyEnabled false  
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
     }  
   }  
 }  
 dependencies {  
   compile fileTree(dir: 'libs', include: ['*.jar'])  
   compile 'com.android.support:appcompat-v7:22.1.1'  
   compile 'com.google.android.gms:play-services-maps:7.8.0'  
 }  

我的專案畫面

若是出現 Could not find 'com.google.android.gms:play-services-maps:7.8.0',請到SDK Manage 更新下圖中四項內容



b. 新增APP的網路Permission
在AndroidManifest.xml增加以下三行
 <uses-permission android:name="android.permission.INTERNET"/>  
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>  
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

c. 新增APP的位置Permission
在AndroidManifest.xml增加以下兩行
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  

d. 新增Google Map需要的描述
在AndroidManifest.xml增加以下兩項
並且替換下面 "your_google_map_key"
     <meta-data  
       android:name="com.google.android.gms.version"  
       android:value="@integer/google_play_services_version" />  
     <meta-data  
       android:name="com.google.android.maps.v2.API_KEY"  
       android:value="your_google_map_key" />  

AndroidManifest.xml完整程式碼如下:

 <?xml version="1.0" encoding="utf-8"?>  
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
   package="com.idlefox.mymap" >  
   <!-- internet premission-->  
   <uses-permission android:name="android.permission.INTERNET"/>  
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>  
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
   <!-- location premission-->  
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  
   <application  
     android:allowBackup="true"  
     android:icon="@mipmap/ic_launcher"  
     android:label="@string/app_name"  
     android:theme="@style/AppTheme" >  
     <meta-data  
       android:name="com.google.android.gms.version"  
       android:value="@integer/google_play_services_version" />  
     <meta-data  
       android:name="com.google.android.maps.v2.API_KEY"  
       android:value="@string/google_map_key" />  
     <activity  
       android:name=".MainActivity"  
       android:label="@string/app_name" >  
       <intent-filter>  
         <action android:name="android.intent.action.MAIN" />  
         <category android:name="android.intent.category.LAUNCHER" />  
       </intent-filter>  
     </activity>  
   </application>  
 </manifest>  

e. 增加Google Map到你的畫面上
在activity_main.xml加入下列程式碼
   <fragment xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:name="com.google.android.gms.maps.SupportMapFragment"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:id="@+id/map"  
        tools:context=".MapsActivity" />  

完成以上步驟應該就可以將程式燒入你的手機或模擬器內,應該就可以看到下面的畫面,有出現地圖就是成功的。

Step4. 設定你的Google Map
Google Map 的功能大家應該都不陌生,陌生的話就可以打開手機內的Google Map App,以下是一些簡單操作就可以做出來的功能。

     mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();  
     mMap.setMyLocationEnabled(true); // 右上角的定位功能  
     mMap.getUiSettings().setZoomControlsEnabled(true); // 右下角的放大縮小功能  
     mMap.getUiSettings().setCompassEnabled(true); // 左上角的指南針,要兩指旋轉才會出現  
     mMap.getUiSettings().setMapToolbarEnabled(true); // 右下角的導覽及開啟 Google Map功能  
     // 為了讓Google Map的Toolbar得以使用,所以要先建立一個Marker(地標物)  
     mMap.addMarker(new MarkerOptions()  
         .position(new LatLng(10, 10))  
         .title("Hello world"));  

下面就是成果

常見問題:
Q1. 如果地圖是白畫面,連經緯度的線都沒事,是什麼問題?
A1. 通常是Google Map Key 沒有加,若是剛加入,經過一兩分鐘都沒有出現,請確認Key對不對。

Android - Google 新增憑證

新增Debug Key

Debug Key 是用來驗證你使用Android Studio或者Eclipse將Android 程式安裝至手機的Key,通常一台電腦的一個IDE就會對應到一個Key,而為了讓Google知道是誰要使用這個API,就要提供你的Key。(如果是要製作成APK讓其他人可以利用APK安裝,就要製作你專有的Key Store,並且為你的Key Store另外申請憑證)

Step1. 取得 Debug Key
a. 打開終端機(Terminal)

b. 輸入:keytool -list -v -keystore ~/.android/debug.keystore


c. 若需要你輸入密碼,直接按Enter就好

SHA1就是你要的

Step2. 進入 Google Developer Console 新增憑證
a. 進入Google Developer Console,並且選擇要新增憑證的專案

b. 進入管理憑證的頁面



c. 點擊"新增憑證"

d. 選擇API金鑰


e. 選擇 Android 金鑰


f. 輸入 憑證名稱,辨識用而已,沒有什麼限制

g. 輸入套件名稱
套件名稱就是你APP的Package name,你可以在你專案下的 AndroidManifest.xml 內找到,找到package,後面的值就是你的Package Name


* 備註:有人說也可以看你的app資料夾下的build.gradle檔,裡面會有一個applicationId,後面的值也可以做為你的Package name,若是兩個不一樣,請以build.gradle檔為主


h. 輸入你的SHAI,你可以上面的第一步驟取得。


i. 建立,完成

新增Key Store(待補)

2015年9月15日 星期二

Arduino - RFID 應用及可能會遇到的問題

這篇文章是介紹我在大學最後一個作品時所做出來的成品,還有講解遇到的問題和如何透過軟體的方式來解決。

RFID 一般常見就是運用在門禁卡上,而在我的畢業展 Casink 中,RFID 的白卡是一個籌碼卡的存在,它會紀錄3項資訊:
  1. 使用者ID
  2. 使用者名稱
  3. 卡片內的點數
我所使用的 RFID 白卡本身有其限制,這樣做起來才有挑戰性!
  1. 存取資料的位置有限,原本是16個位置,但有1個位置已經被占用,所以實際可以用的只有15個位置。
  2. 每個位置存的資料也有限,型態是Byte,所以是存0~255的資料。

總結上面要達成的功能和限制,所以我遇到了下面幾個問題:

  • 如果我要讓ID具唯一性,可是一個位置不夠存怎麼辦(使用者超過255個)?
    • 進位
    • 假設我在第7個位置是記0~255,如果到了第256個使用者,我就會在第6個位置記1,第7個位置記1,用簡單的公式就可以得知是第幾個使用者。這樣也可以記錄到6萬多個使用者ID。

(第6個位置的數字*255) + 第7個位置的數字 = 使用者ID

  • 如果我要顯示使用者名稱要怎麼做?
    • 轉換ASCII
    • 每個符號、數字及英文字都可以對應到一個ASCII值,如果是顯示英文名字足以堪用,中文字還找不到方法可以存在卡片內。
    • ASCII 維基 及 對照表
    • 另外還要對Arduino的 Char 和 Byte 的轉換多研究才行。

  • 卡片點數不可能只是0~255吧,這樣應該就不好玩,數字也不人性化,怎麼辦?
    • 進位 及 換算
    • 我們是將紀錄的值乘上500,就是使用者所擁有的點數,使點數變大,可以增加娛樂性。我們一樣是運用1個位置儲存點數,所以點數範圍是 0 ~ 127500。

  • 如果真的遇到不能處理的資料怎麼辦,像是金額超過127500?
    • 透過遊戲規則的限制避免
    • 事先告知玩家最高金額就是127500,看是請他重新新的一輪賭局,或是其他方式彌補。
    • 或者使用更多位置儲存,但只是增加上限,賭局仍有Bug,一樣會有輕易超過上限的問題。

  • 如果我要紀錄更多使用者資料要怎麼做?
    • 透過 ID 來從外地取得更多資料
    • 我們網路上有個資料庫會紀錄每個使用者的 ID 、名稱、系級和最終金額,就是利用ID來取得其他無法存在卡片內的資料。

  • 還要再建立一個伺服器來做為資料庫,好像有點麻煩,有沒有什麼方便的解法?
    • 推薦使用Parse
      • 因為若是Arduino本身具有連上網路的功能,可以不必自己架設伺服器來存取資料,Parse本身提供Arduino的API,而資料庫建置方式也很適合沒有相關背景的人。
      • (尚未實作)
      • Parse
    • 設置專門讀取資料庫的機器
      • 我們是採用Parse及這個方法實作,只要讓Arudino連上桌上型電腦或筆電,就可以讀取電腦內的本地資料。

2015年9月14日 星期一

Arduino - 加入 Library

Step1. 選擇 Sketch -> Import Library -> Add Library


Step2. 選擇要匯入的 Library,一般是Zip或其他壓縮檔,裡面會有 .cpp 和 .h 檔


Step3. 確認 Library有沒有,選擇 Sketch -> Import Library 最下面有沒有剛加入的Library


2015年9月13日 星期日

Arduino - RFID

器材:
  1. Arduino UNO 開發版 *1
  2. RFID RC522 模組
  3. SPI Library (見備註)
  4. MFRC522 Library (見備註)

PIN 腳對應表:
RFID RC522
Arudino UNO
3.3V
3.3V
RST
9
GND
GND
IRQ
MISO
12
MOSI
11
SCK
13
SDA/NSS
10

完整程式碼如下:

 #include <SPI.h>  
 #include <MFRC522.h>  

 #define SS_PIN 10  
 #define RST_PIN 9  
 MFRC522 mfrc522(SS_PIN, RST_PIN);  
 MFRC522::MIFARE_Key key;  

 int block=2;  
 byte blockcontent[16] = {"Hello world"};  
 byte readbackblock[18];  

 void setup() {  
   Serial.begin(9600);  
   SPI.begin();  
   mfrc522.PCD_Init();  
   Serial.println("Scan a MIFARE Classic card");  
   for (byte i = 0; i < 6; i++) {  
     key.keyByte[i] = 0xFF;  
   }  
 }  

 void loop(){  
     mfrc522.PCD_Init();  
     if ( ! mfrc522.PICC_IsNewCardPresent()) {  
       return;  
     }  
     if ( !mfrc522.PICC_ReadCardSerial()) {  
       return;  
     }  
     Serial.println("card selected");  
     readBlock(block, readbackblock);  
     Serial.print("read block: ");  
     for (int j=0 ; j<16 ; j++){  
     Serial.write (readbackblock[j]);  
   }  
   Serial.println("");     
 }

 int writeBlock(int blockNumber, byte arrayAddress[]){  
   int largestModulo4Number=blockNumber/4*4;  
   int trailerBlock=largestModulo4Number+3;  
     if (blockNumber > 2 && (blockNumber+1)%4 == 0){  
       Serial.print(blockNumber);  
       Serial.println(" is a trailer block:");  
       return 2;  
     }  
   Serial.print(blockNumber);  
   Serial.println(" is a data block:");  
   byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));  
   if (status != MFRC522::STATUS_OK) {  
     Serial.print("PCD_Authenticate() failed: ");  
     Serial.println(mfrc522.GetStatusCodeName(status));  
     return 3;//return "3" as error message  
   }  
   status = mfrc522.MIFARE_Write(blockNumber, arrayAddress, 16);  
   if (status != MFRC522::STATUS_OK) {  
     Serial.print("MIFARE_Write() failed: ");  
     Serial.println(mfrc522.GetStatusCodeName(status));  
     return 4;//return "4" as error message  
   }  
   Serial.println("block was written");  
 }  

 int readBlock(int blockNumber, byte arrayAddress[]) {  
   int largestModulo4Number=blockNumber/4*4;  
   int trailerBlock=largestModulo4Number+3;  
   byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));  
   if (status != MFRC522::STATUS_OK) {  
     Serial.print("PCD_Authenticate() failed (read): ");  
     Serial.println(mfrc522.GetStatusCodeName(status));  
     return 3;//return "3" as error message  
   }  
   byte buffersize = 18;//we need to define a variable with the read buffer size, since the MIFARE_Read method below needs a pointer to the variable that contains the size...   
   status = mfrc522.MIFARE_Read(blockNumber, arrayAddress, &buffersize);//&buffersize is a pointer to the buffersize variable; MIFARE_Read requires a pointer instead of just a number  
   if (status != MFRC522::STATUS_OK) {  
     Serial.print("MIFARE_read() failed: ");  
     Serial.println(mfrc522.GetStatusCodeName(status));  
     return 4;  
   }  
   Serial.println("block was read");  
   delay(500);  
 }  

Step1. 貼上程式碼

Step2. 燒入程式
如果你把上面的程式碼整個貼上,你會發現無法執行,因為你並沒有SPI和MFRC522的Library,所以第一步驟要先從Library加入,可以參考此連結。

Step3. 插上RFID模組, PIN腳請參考上方PIN腳對應表

Step4. 測試
打開Serial Monitor 如果成功,會照順序出現以下畫面:

 表示RFID已經初始化成功

Hello World就是上方程式碼預設寫入的內容,後面方框是沒有資料的意思。

Step5. 了解程式碼
 #include <SPI.h>  
 #include <MFRC522.h>  

 #define SS_PIN 10  
 #define RST_PIN 9  
 MFRC522 mfrc522(SS_PIN, RST_PIN);  
 MFRC522::MIFARE_Key key;
以上都僅僅是定義 RFID 開發需要用到的工具,還有對應的PIN

int block=2;  
 byte blockcontent[16] = {"Hello world"};  
 byte readbackblock[18]; 
block 可以理解成開鎖的位置,一般市面上買到的白卡都會在2的位置,若是要正確讀取或者寫值,就是要輸入正確的block位置。

bloclcontent 是一個長度為16的 byte 陣列,紀錄的要寫入 RFID 白卡的內容,因為是教學需要,所以在一開始就預設好,學習之後可以在loop()內任意修改。

readbackblock 是存著讀取 RFID 卡後存資料的變數。

void setup() {  
   Serial.begin(9600);  
   SPI.begin();  
   mfrc522.PCD_Init();  
   Serial.println("Scan a MIFARE Classic card");  
   for (byte i = 0; i < 6; i++) {  
     key.keyByte[i] = 0xFF;  
   }  
 }  
setup() 內就是照做就好。
目的是為了讓 RFID 可以正常使用。
Serial所做的事情是為了讓操作的人可以瞭解現在執行的狀況。

     mfrc522.PCD_Init();  
     if ( ! mfrc522.PICC_IsNewCardPresent()) {  
       return;  
     }  
     if ( !mfrc522.PICC_ReadCardSerial()) {  
       return;  
     }  
在loop()內的一開始就要加入上面的動作,而且一定要放在loop內,是為了讓使用者可以一直讀取卡片。

若將 mfrc522.PCD_Init() 放在 setup()內,就只能讀取一次卡片。

下面兩個執行的動作照順序是:判斷是不是另外一張卡片;卡片是否可以被讀取。之後可以你可以在這兩個function的{}加入其他動作,像是Serial.print()告知使用者發生什麼樣的錯誤,導致無法讀取卡片。

     writeBlock(block, blockcontent);
     readBlock(block, readbackblock);  
     Serial.print("read block: ");  
     for (int j=0 ; j<16 ; j++){  
         Serial.write (readbackblock[j]);  
     }  

writeBlock() 是將 blockcontent 的內容寫入卡片內。這是因為是示範用,所以直接寫在讀取的動作前面,正常是不可能這樣使用的,而且通常寫入需要一段時間,想像你在7-11使用悠遊卡也不是馬上就好。

readBlock() 是讀取的動作,而讀取到的資料就放在 readbackblock 內。

而讀取後就是要顯示,一般分為兩種,這裡是把所有資料給顯示出來,常用的方式就用 for 迴圈,注意:這裏要用Serial.write()才會正常把"Hello world"給顯示出來,你可以稍微試試看使用Serial.print(),看看會有什麼不一樣的結果顯示,Tip : 可以朝 ASCII 思考。

備註:
連結:在Arduino 內加入 Library 
下載:MFRC522 Library