2015/11/24

iBeacon 偵測範例 - android client


去年七月在一個講座上第一次聽到 ibeacon 這個名詞,那時候還不是這麼熱門 google 到的第一個就是 estimote 這間公司的產品,不過因為訂購過海關還會被 NCC 攔截...所以後面就沒再繼續追蹤。不過今年跟朋友借到 estimote 的 Beacon,終於可以來測試一下

iBeacon

iBeacon 是 Apple 公司提出的一套可用於室內定位系統的協議,可以使一個智慧型手機或其他裝置在一個 iBeacon 基站的感應範圍內執行相應的命令。
使用 BLE ( Bluetooth Low Energy ) 技術,具有低功耗、低延遲、高射頻等特性
iBeacon ID 長度是 20 bytes,包含以下三個內容:
  • UUID (16 bytes)
  • major number (2 bytes)
  • minor number (2 bytes)
依距離可分為:
  • 最近:幾公分
  • 中距:幾公尺
  • 遠距:大於 10 公尺
兼容設備:
  • 藍牙 4.0 的 IOS 設備( iPhone4s 及以上,iPad 第三代及以上,iPad mini 第一代及以上,iPod Touch 第五代 )
  • Android 4.3 及以上( 如:三星 Galaxy S3 / S4 / S4 Mini, 三星 Galaxy Note 2 / 3, HTC One, Google / LG Nexus 7 2013 version / Nexus 4 / Nexus 5, HTC Butterfly, OnePlus One )

estimote Beacon 規格

  • 32-bit ARM® Cortex M0 CPU 包含加速規、溫度感測器
  • 藍芽 4.0
  • 最遠射程 70 公尺 ( 無障礙、干擾情況下,使用建議抓 40~50 公尺 )

測試範例一

使用官方釋出 estimote-sdk-preview.jar 進行偵測,官方範例可以點此下載
建立一個 BeaconManager 物件,並註冊 Listener

public static void Create(NotificationManager notificationMngr,
  Context context, final Intent i) {
 try {
  currentContext = context;

  // Create a beacon manager
  beaconManager = new BeaconManager(currentContext);

  // 設定搜尋間格時間
  beaconManager.setBackgroundScanPeriod(TimeUnit.SECONDS.toMillis(1),
    0);

  // 偵測 beacon 進入
  beaconManager.setMonitoringListener(new MonitoringListener() {
   // ... close to us.
   @Override
   public void onEnteredRegion(Region region, List beacons) {
    Log.e(ConfigUtil.TAG, "onEnteredRegion");
    Log.e(ConfigUtil.TAG, "beacons: " + beacons);
    
    if(beacons!=null && beacons.size()>0){
        if(beacons.get(0).getProximityUUID().equals(ESTIMOTE_PROXIMITY_UUID)){
         Log.i(ConfigUtil.TAG, "same beacon");
         return;
        }
     ESTIMOTE_PROXIMITY_UUID = beacons.get(0).getProximityUUID();
     Log.i(ConfigUtil.TAG, "ESTIMOTE_PROXIMITY_UUID:"+ESTIMOTE_PROXIMITY_UUID);
     sendUpdateProximityUUID(ESTIMOTE_PROXIMITY_UUID);
       }
   }

   // ... far away from us.
   @Override
   public void onExitedRegion(Region region) {
   }
  });
  
  // 在 beacon 範圍中
  beaconManager.setRangingListener(new BeaconManager.RangingListener() {
       @Override public void onBeaconsDiscovered(Region region, final List beacons) {
        Log.e(ConfigUtil.TAG, "setRangingListener");
        Log.e(ConfigUtil.TAG, "Ranged beacons: " + beacons);
        
       if(beacons!=null && beacons.size()>0){
        if(beacons.get(0).getProximityUUID().equals(ESTIMOTE_PROXIMITY_UUID)){
         Log.i(ConfigUtil.TAG, "same beacon");
         return;
        }
     ESTIMOTE_PROXIMITY_UUID = beacons.get(0).getProximityUUID();
     Log.i(ConfigUtil.TAG, "ESTIMOTE_PROXIMITY_UUID:"+ESTIMOTE_PROXIMITY_UUID);
     sendUpdateProximityUUID(ESTIMOTE_PROXIMITY_UUID);
       }
       }
     });
  
  // 建立連線
  beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
   @Override
   public void onServiceReady() {
    try {
     // 開使偵測
     beaconManager.startRanging(ALL_ESTIMOTE_BEACONS);
     beaconManager.startMonitoring(ALL_ESTIMOTE_BEACONS);
    } catch (Exception e) {
     Log.e(ConfigUtil.TAG, "Exception:"+e);
    }
   }
  });
 } catch (Exception e) {
  Log.e(ConfigUtil.TAG, "Exception:"+e);
 }
}
結束時,記得停止偵測與關閉連線

public static void stop() {
 try {
  beaconManager.stopRanging(ALL_ESTIMOTE_BEACONS);
  beaconManager.stopMonitoring(ALL_ESTIMOTE_BEACONS);
  beaconManager.disconnect();
 } catch (Exception e) {
  Log.e(ConfigUtil.TAG, "Exception:"+e);
 }
}
藍芽關閉時,也要記得停止偵測與關閉連線,不然你會看到 XXX 已停止...

public class EstimoteReceiver extends BroadcastReceiver {
 private Intent estimoteServiceIntent;

 // Method called when bluetooth is turned on or off.
 @Override
 public void onReceive(Context context, Intent intent) {
  final String action = intent.getAction();
  if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
   final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
     BluetoothAdapter.ERROR);
   switch (state) {
   case BluetoothAdapter.STATE_TURNING_OFF:
    // When bluetooth is turning off, lets stop estimotes ranging
    if (estimoteServiceIntent != null) {
     //這裡會呼叫上面的 stop()
     context.stopService(estimoteServiceIntent);
     estimoteServiceIntent = null;
    }
    break;
   case BluetoothAdapter.STATE_ON:
    // When bluethooth is turned on, let's start estimotes monitoring
    if (estimoteServiceIntent == null) {
     estimoteServiceIntent = new Intent(context,
       EstimoteService.class);
     context.startService(estimoteServiceIntent);
    }
    break;
   }
  }
 }
}
結論:使用官方釋出 estimote-sdk-preview.jar,只能用 Listener 等待接收事件,測試會出現偵測速度忽快忽慢的問題...

測試範例二

因為官方的 JAR 偵測速度忽快忽慢,所以後面又自己上網找了個 lib 取代官方的 JAR。
使用 Bluetooth LE Library,它已提供 beacon 與一般藍芽設備的辨別方式,可以省掉一些麻煩
一開始先檢查一下藍芽是否開啟

private void checkBluetooth() {
 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 if (mBluetoothAdapter == null) {
     // 如果裝置不支援藍芽
     Toast.makeText(this, "Device doesn't support bluetooth", Toast.LENGTH_SHORT).show();
     return;
 }
          
 // 如果藍芽沒有開啟
 if (!mBluetoothAdapter.isEnabled()) {
     // 發出一個intent去開啟藍芽,
        Intent mIntentOpenBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(mIntentOpenBT, REQUEST_ENABLE_BT);
 }
}
init

private void initServerData() {
 //管理所有藍芽裝置
 mDeviceStore = new BluetoothLeDeviceStore();
 //管理 BluetoothManager/BluetoothManager
 mBluetoothUtils = new BluetoothUtils(this);
 mScanner = new BluetoothLeScanner(mLeScanCallback, mBluetoothUtils);
}
偵測到裝置後的 callback

private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
 @Override
 public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
  synchronized(lockObj){
   final BluetoothLeDevice deviceLe = new BluetoothLeDevice(device, rssi, scanRecord, System.currentTimeMillis());
   mDeviceStore.addDevice(deviceLe);
   final EasyObjectCursor c = mDeviceStore.getDeviceCursor();
   for(int i=0; i<=1){
      //是否偵測到同一個beacon
      if(currentBeaconUUID.equals(iBeacon.getUUID())){
       Log.d(ConfigUtil.TAG, "same beacon");
      }else{
       if(tv_main_msg!=null){
        tv_main_msg.setText(iBeacon.getUUID());
       }
       break;
      }
     }else{
      //不是距離最近的beacon
     }
    }else{
     //not beacon device
    }
   }
  }
 }
};
開始搜尋裝置

private void startScan(){
 final boolean mIsBluetoothOn = mBluetoothUtils.isBluetoothOn();
 final boolean mIsBluetoothLePresent = mBluetoothUtils.isBluetoothLeSupported();
 mDeviceStore.clear();
 mBluetoothUtils.askUserToEnableBluetoothIfNeeded();
 if(mIsBluetoothOn && mIsBluetoothLePresent){
  mScanner.scanLeDevice(-1, true);
 }
}
關閉 app 或偵測到藍芽關閉時,記得停止掃描

private void stopScan(){
 mScanner.scanLeDevice(-1, false);
}
結論:使用 Bluetooth LE Library 真的比官方釋出的 JAR 快很多,但離開 beacon 範圍再返回後 update time 跟 distance 更新有時會很慢,或要靠近 beacon 一點...

Android 5.0 使用 estimote 官方釋出 estimote-sdk-preview.jar 藍芽問題

之前測試 android 4.X 的時候沒問題,但到了 Android 5.0 不知道為什麼就出現以下問題... 不過還好換個 thread 就解決了

05-16 09:43:32.181: E/AndroidRuntime(32313): FATAL EXCEPTION: main
05-16 09:43:32.181: E/AndroidRuntime(32313): Process: com.estimote.notificationstest, PID: 32313
05-16 09:43:32.181: E/AndroidRuntime(32313): java.lang.IllegalArgumentException: This cannot be run on UI thread, starting BLE scan can be expensive
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.internal.Preconditions.checkArgument(Preconditions.java:65)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService.checkNotOnUiThread(BeaconService.java:502)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService.access$600(BeaconService.java:61)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.estimote.sdk.service.BeaconService$InternalLeScanCallback.onLeScan(BeaconService.java:490)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.bluetooth.BluetoothAdapter$2.onScanResult(BluetoothAdapter.java:1892)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.bluetooth.le.BluetoothLeScanner$BleScanCallbackWrapper$1.run(BluetoothLeScanner.java:330)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Handler.handleCallback(Handler.java:739)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Handler.dispatchMessage(Handler.java:95)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.os.Looper.loop(Looper.java:211)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at android.app.ActivityThread.main(ActivityThread.java:5321)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at java.lang.reflect.Method.invoke(Native Method)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at java.lang.reflect.Method.invoke(Method.java:372)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1016)
05-16 09:43:32.181: E/AndroidRuntime(32313):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

References

=======================================================================

2016/08/23 補充

※ 目前已知 "Bluetooth-LE-Library---Android" 可能會出現 Beacon UUID 缺少位數的問題,這部分 "Bluetooth-LE-Library---Android" 已經在 2015/7/15 v1.0.0 版 fix 了,舊版的可以到  Bluetooth-LE-Library---Android 下載更新,或是下載附件 IBeaconManufacturerData.java 並覆蓋 "Bluetooth LE Library\src\uk\co\alt236\bluetoothlelib\device\mfdata" 下 IBeaconManufacturerData.java

PS. 範例二中 IBeaconManufacturerData.java 已更新,可直接使用

2015/11/23

如何在 CentOS 安裝 MongoDB

MongoDB 屬於 NoSQL Database 中的文件資料庫這種類型,可以儲存 XML, JSON 或是其他格式的文件資料,這些資料都是 self-describing 的,也就是說文件之間並不需要完全一樣,換句話說,在更新 schema 時,並不需要修改既有的資料。

依照這個網頁的說明 Install MongoDB on Red Hat Enterprise or CentOS Linux,就可以把 mongodb 裝好。

Packages

  1. mongodb-org
    這是自動安裝其他四個 packages 的 metapackage
  2. mongodb-org-server
    包含 mongod daemon、configurations以及 init scripts
  3. mongodb-org-mongos
    包含 mongos daemon
  4. mongodb-org-shell
    包含 mongo shell
  5. mongodb-org-tools
    包含以下的 MongoDB 工具: mongoimport bsondump, mongodump, mongoexport, mongofiles, mongooplog, mongoperf, mongorestore, mongostat, and mongotop

mongod 的 service script 在 /etc/init.d/mongod

設定檔在 /etc/mongod.conf

資料存放在 /var/lib/mongo

log 存放在 /var/log/mongodb

vi /etc/yum.repos.d/mongodb-org-3.0.repo

[mongodb-org-3.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.0/x86_64/
gpgcheck=0
enabled=1

用以下指令安裝並啟動 mongodb

yum install -y mongodb-org
service mongod start

在 /var/log/mongodb/mongod.log 裡面看到這一行,基本上就是把 mongodb 環境安裝好了。

[initandlisten] waiting for connections on port 27017

簡單的 DB 測試

執行 mongo 就會直接連接到 test 這個 database

show dbs 是顯示所有資料庫, show collections 是顯示資料庫中的 collections,相當於關聯式資料庫的 table

> show dbs
local  0.078GB
> show dbs;
local  0.078GB
> show collections;

切換到 mydatabase 資料庫

> use mydatabase;
switched to db mydatabase
> db
mydatabase
  1. insert 新增
    建立一個 post 文件,將 post 放進

    > post = { "title": "今日天氣", "content": "晴朗", "date": new Date()}
    {
     "title" : "今日天氣",
     "content" : "晴朗",
     "date" : ISODate("2015-11-05T08:33:54.761Z")
    }
    > db.blog.insert(post)
    WriteResult({ "nInserted" : 1 })
    
  2. find 查詢
    以 find 取得所有資料

    > db.blog.find()
    { "_id" : ObjectId("563b147db835798ae60f4bae"), "title" : "今日天氣", "content" : "晴朗", "date" : ISODate("2015-11-05T08:33:54.761Z") }
    
  3. update 修改
    先修改剛剛的 post 資料,然後用 update 更新到資料庫中。

    > post.comment = ["Great!"]
    [ "Great!" ]
    > post
    {
     "title" : "今日天氣",
     "content" : "晴朗",
     "date" : ISODate("2015-11-05T08:33:54.761Z"),
     "comment" : [
         "Great!"
     ]
    }
    > db.blog.update({title: "今日天氣"}, post)
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
    > db.blog.find()
    { "_id" : ObjectId("563b147db835798ae60f4bae"), "title" : "今日天氣", "content" : "晴朗", "date" : ISODate("2015-11-05T08:33:54.761Z"), "comment" : [ "Great!" ] }
    
  4. remove 刪除
    以 remove 刪除資料

    > db.blog.remove({title: "今日天氣"});
    WriteResult({ "nRemoved" : 1 })
    > db.blog.find();
    

如何用 R 語言連接 MongoDB

先在 mongodb 重新新增兩筆資料

# mongo
> post = { "title": "今日天氣", "content": "晴朗", "date": new Date()}
> db.blog.insert(post)

> post2 = { "title": "明日天氣", "content": "陰天", "date": new Date()}
> db.blog.insert(post2)

在 R 語言的界面中,依照以下的過程進行測試

  1. 安裝 rmongodb 套件

    > install.packages("rmongodb")
    
  2. 使用 rmongodb library

    > library(rmongodb)
    
  3. 建立 mongodb 連線

    > mongo <- mongo.create(host="192.168.1.11" , db="test", username="", password="")
    > dbns <- mongo.get.database.collections(mongo, db="test")
    
  4. 建立查詢條件,並以 find 查詢資料

    > query <- '{ "title": { "$exists": true }, "content": { "$exists": true } }'
    > 
    > cur <- mongo.find(mongo, dbns, query=query)
    
  5. 將 cursor 結果轉換成 data.frame

     > title <- content <- date <- NULL
     > while (mongo.cursor.next(cur)) {
     +     value <- mongo.cursor.value(cur)
     +     title <- rbind(title, mongo.bson.value(value, 'title'))
     +     content <- rbind(content, mongo.bson.value(value, 'content'))
     +  
     +     date <- rbind(date, mongo.bson.value(value, 'date'))
     + }
     > 
     > posts <- data.frame(title=title, content=content, date=date)
    
     > posts
            title           content       date
     1 隞憭拇除 \xe6\xe6\x9c\x97 1446714342
     2 \xe6\x98憭拇除       \xe9憭\xa9 1446715746
    
  6. 中文顯示有問題
    在產生 data.frame 之前,必須先設定 UTF-8 Encoding

    > Encoding(title)="UTF-8"
    > 
    > Encoding(content)="UTF-8"
    > 
    > posts <- data.frame(title=title, content=content, date=date)
    > 
    > posts
      title content       date
    1 今日天氣    晴朗 1446714342
    2 明日天氣    陰天 1446715746
    

濃縮所有測試過程的指令如下

install.packages("rmongodb")
library(rmongodb)
mongo <- mongo.create(host="192.168.1.11" , db="test", username="", password="")
dbns <- mongo.get.database.collections(mongo, db="test")
query <- '{ "title": { "$exists": true }, "content": { "$exists": true } }'

cur <- mongo.find(mongo, dbns, query=query)

title <- content <- date <- NULL

while (mongo.cursor.next(cur)) {
value <- mongo.cursor.value(cur)
title <- rbind(title, mongo.bson.value(value, 'title'))
content <- rbind(content, mongo.bson.value(value, 'content'))
date <- rbind(date, mongo.bson.value(value, 'date'))
}
Encoding(title)="UTF-8"
Encoding(content)="UTF-8"
posts <- data.frame(title=title, content=content, date=date)
posts

在 rmongodb 寫入中文也會遇到編碼的問題,參考 R實踐之中文編碼轉換,必須先用 iconv 先將中文字串轉碼。

2015/11/16

如何在 CentOS 安裝 Cassandra

Cassandra 屬於 column-family 類型的 NoSQL database,特性是 ring cluster network topology,支援類似 SQL 語法的 CQL,適合儲存依照時間排序且scheme固定的大量資料。以下嘗試在 centos 安裝 cassandra。

單一節點

tar zxvf apache-cassandra-2.2.3-bin.tar.gz -C /usr/share

#啟動
/usr/share/apache-cassandra-2.2.3/bin/cassandra

資料會放在
/usr/share/apache-cassandra-2.2.3/data 這個目錄中

系統紀錄放在
/usr/share/apache-cassandra-2.2.3/logs 這個目錄中

然後參考 cassandra Getting Started 的範例,使用 CQL 測試這個 DB。

// 建立資料庫 mykeyspace
CREATE KEYSPACE mykeyspace
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };

// 使用資料庫
USE mykeyspace;

// 建立 table: users
CREATE TABLE users (
  user_id int PRIMARY KEY,
  fname text,
  lname text
);


// insert 三筆測試資料
INSERT INTO users (user_id,  fname, lname)
  VALUES (1745, 'john', 'smith');
INSERT INTO users (user_id,  fname, lname)
  VALUES (1744, 'john', 'doe');
INSERT INTO users (user_id,  fname, lname)
  VALUES (1746, 'john', 'smith');

// 取得 users 所有資料
SELECT * FROM users;

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1744 |  john |   doe
    1746 |  john | smith

(3 rows)

// 針對 lname 建立 index
CREATE INDEX ON users (lname);

// 條件搜尋
SELECT * FROM users WHERE lname = 'smith';

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1746 |  john | smith

直接用 kill -9 停止此單一節點的 cassandra 的 process。

ps aux | grep cassandra
kill -9 20109

兩個節點

在兩台機器上,都分別先將單一節點的設定方式做好。記得一定要先以單機方式先啟動一次,否則會出現 Token 錯誤的問題。

使用 /usr/share/apache-cassandra-2.2.3/tools/bin/token-generator,計算這兩個節點在 Murmur3Partitioner 時,token 分別要設定為多少?

[root@server bin]# /usr/share/apache-cassandra-2.2.3/tools/bin/token-generator
Token Generator Interactive Mode
--------------------------------

 How many datacenters will participate in this Cassandra cluster? 2
 How many nodes are in datacenter #1? ^C[root@kokola bin]# ./token-generator
Token Generator Interactive Mode
--------------------------------

 How many datacenters will participate in this Cassandra cluster? 1
 How many nodes are in datacenter #1? 2

DC #1:
  Node #1:  -9223372036854775808
  Node #2:                     0

然後分別編輯兩台機器上的 /usr/share/apache-cassandra-2.2.3/conf/cassandra.yaml

把

- seeds: "127.0.0.1"

改為

- seeds: "192.168.1.11,192.168.1.12"

把

listen_address: localhost

分別改為各自的 IP: 192.168.1.11,192.168.1.12

listen_address: 192.168.1.11

在 192.168.1.11 增加一行

initial_token: -9223372036854775808

在 192.168.1.12 則改為

initial_token: 0

然後分別啟動兩台機器的 cassandra。

就可以在第二台機器,用 CQL 取得剛剛在單一節點時建立的 mykeyspace

[root@server bin]# ./cqlsh
Connected to Test Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 2.2.3 | CQL spec 3.3.1 | Native protocol v4]
Use HELP for help.
cqlsh> use mykeyspace ;
/usr/share/apache-cassandra-2.2.3/bin/../lib/cassandra-driver-internal-only-2.7.2.zip/cassandra-driver-2.7.2/cassandra/cluster.py:3331: DeprecationWarning: ResponseFuture.result timeout argument is deprecated. Specify the request timeout via Session.execute[_async].
cqlsh:mykeyspace> 
cqlsh:mykeyspace> select * from users;

 user_id | fname | lname
---------+-------+-------
    1745 |  john | smith
    1744 |  john |   doe
    1746 |  john | smith

(3 rows)

可以用 nodetool 查看 cluster 的狀態

[root@server bin]# ./nodetool -h localhost status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address       Load       Tokens       Owns    Host ID                               Rack
UN  192.168.1.11  68.09 KB   256          ?       53b41601-c145-4c1a-81d1-9b5f8b09ac73  rack1
UN  192.168.1.12  207.75 KB  256          ?       66642998-9179-4b06-b341-e654dc8e8ca6  rack1

service script

以下為 service script,儲存這個檔案到 /etc/init.d/cassandra,chmod 755 後就可以用 service cassandra start 啟動 cassandra。

#!/bin/bash
# chkconfig: 2345 99 01
# description: Cassandra

. /etc/rc.d/init.d/functions

CASSANDRA_HOME=/usr/share/apache-cassandra-2.2.3/
CASSANDRA_BIN=$CASSANDRA_HOME/bin/cassandra
CASSANDRA_NODETOOL=$CASSANDRA_HOME/bin/nodetool
CASSANDRA_LOG=$CASSANDRA_HOME/logs/cassandra.log
CASSANDRA_PID=/var/run/cassandra.pid
CASSANDRA_LOCK=/var/lock/subsys/cassandra
PROGRAM="cassandra"

if [ ! -f $CASSANDRA_BIN ]; then
  echo "File not found: $CASSANDRA_BIN"
  exit 1
fi

RETVAL=0

start() {
  if [ -f $CASSANDRA_PID ] && checkpid `cat $CASSANDRA_PID`; then
    echo "Cassandra is already running."
    exit 0
  fi
  echo -n $"Starting $PROGRAM: "
  daemon $CASSANDRA_BIN -p $CASSANDRA_PID >> $CASSANDRA_LOG 2>&1
  usleep 500000
  RETVAL=$?
  if [ $RETVAL -eq 0 ]; then
    touch $CASSANDRA_LOCK
    echo_success
  else
    echo_failure
  fi
  echo
  return $RETVAL
}

stop() {
  if [ ! -f $CASSANDRA_PID ]; then
    echo "Cassandra is already stopped."
    exit 0
  fi
  echo -n $"Stopping $PROGRAM: "
  $CASSANDRA_NODETOOL -h 127.0.0.1 decommission
  if kill `cat $CASSANDRA_PID`; then
    RETVAL=0
    rm -f $CASSANDRA_LOCK
    echo_success
  else
    RETVAL=1
    echo_failure
  fi
  echo
  [ $RETVAL = 0 ]
}

status_fn() {
  if [ -f $CASSANDRA_PID ] && checkpid `cat $CASSANDRA_PID`; then
    echo "Cassandra is running."
    exit 0
  else
    echo "Cassandra is stopped."
    exit 1
  fi
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status_fn
    ;;
  restart)
    stop
    start
    ;;
  *)
    echo $"Usage: $PROGRAM {start|stop|restart|status}"
    RETVAL=3
esac

exit $RETVAL

如何用 R 語言連接 cassandra 資料庫

以下簡述如何使用 R 語言連接 cassandra 資料庫。

  1. 調整 cassandra 設定,將 rpc server 打開,並重新啟動 cassandra

    修改 /usr/share/apache-cassandra-2.2.3/conf/cassandra.yaml,將 rpc server 打開

     start_rpc: true
    
  2. 下載 cassandra-jdbc library
    到 cassandra-jdbc 網頁,下載 cassandra-jdbc-2.1.1.jar 以及 cassandra-jdbc dependencies,將所有 jar 檔都放在要執行 R 語言的電腦的一個資料夾中。

  3. 在 R 語言,安裝 RJDBC 套件

     install.packages("RJDBC")
    
  4. R 語言的範例程式

    • 載入 RJDBC library

      > library(RJDBC)
      Loading required package: DBI
      Loading required package: rJava
      
    • 載入 Cassandra JDBC driver,以及相關 jars,我是放在 D:/git/system/pkg/cassandra/cassandra-jdbc/libs/ 這個目錄中,要注意目錄最後面的 libs 後面一定要加上 /

      > cassdrv <- JDBC("org.apache.cassandra.cql.jdbc.CassandraDriver", list.files("D:/git/system/pkg/cassandra/cassandra-jdbc/libs/",pattern="jar$",full.names=T))
      
    • 連接到 cassandra

      > casscon <- dbConnect(cassdrv, "jdbc:cassandra://192.168.1.11:9160/mykeyspace")
      
    • 對此 cassandra connection 執行 CQL

      > res <- dbGetQuery(casscon, "select * from users limit 10")
      
    • 取得 CQL 結果

      > res
      user_id fname lname
      1    1745  john smith
      2    1744  john   doe
      3    1746  john smith
      
  5. 錯誤訊息
    如果在連接 cassandra 時,發生 SQLNonTransientConnectionException,就表示 cassandra server 並沒有打開 rpc server 連接的設定。

     > casscon <- dbConnect(cassdrv, "jdbc:cassandra://192.168.1.11:9160/mykeyspace")
     Error in .jcall(drv@jdrv, "Ljava/sql/Connection;", "connect", as.character(url)[1],  : 
       java.sql.SQLNonTransientConnectionException: org.apache.thrift.transport.TTransportException: Cannot write to null outputStream
    

References

[研究] Apache Cassandra 2.0.3 安裝 (CentOS 6.5 x64)

設定 Cassandra cluster 的 token

cassandra token calculator