kkbnart's diary

遊びは終わりだ

PC・Android間のBluetooth通信

PCに繋いだセンサ情報をandroidに送信するプログラムを作る機会があったので,そのまとめ.
システムの要件としては以下の様になります.

  • Android側は元々Bluetoothでデータをやり取りする部分があり,そのデータをセンサ情報で置き換えるため通信にはBluetoothを使用
  • PCはVAIOで,元からBluetoothを搭載しているので,それを使用.
  • センサのライブラリがC++のコードで用意されていたので,PC側はC++を使用
  • PC側のソケット通信には最もメジャーであろうSocketライブラリを使用
  • サーバーはPC側で立て,Androidから接続

やったことある人には簡単じゃないかと思われるかもしれませんが,自分は通信まわりのプログラムがとても苦手で,今回も案の定大苦戦を強いられました・・・
全体のおおまかな流れとしては,以下の様になります.

  1. Androidからの接続を受け付けるためのSocketを開く
  2. Androidから接続し,通信用のSocketを開く
  3. 通信用のソケットを通し,センサデータを送信する

通信用のSocketを開くまで

下準備

まずBluetoothのペアリングを行います.この部分に関してはBluetoothドングルを用いた場合など,個別のケースによるので割愛.
次に通信用のCOMポートの設定を行います(これはひょっとしたら必要ないかもしれませんが).今回用いたVAIOPCの場合,Android側でUUIDを指定してソケットを開くとBluetoothの設定から接続先のCOMポートの追加ができました.USBでつなぐ場合は自動的に割り振られると思います(確認はしてないけど).

socket部分のコード

ここまでで下準備は終了.ここからはコードに移ります.
Android側のプログラムに関しては,公式のsampleのBluetoothChatの通信部分を用いました.一部変更点としては,UUIDとしてSPP(SerialPortServiceClass_UUID)を使用し,BluetoothChatService.javaのMY_UUIDを変更します.通信のUUIDに関して詳しく知りたい方はここを参考に.

変更前

// Unique UUID for this application
private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

変更後

// Unique UUID for this application
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

次にPC側です.この部分はSPP通信用Bluetooth Socket Serverを参考にしました.コードは重要な部分のみ抜粋.
接続用のsocketを開き,androidからの接続を待って通信用のsocketを開く部分のコード.

    SOCKET sockRecv;
    SOCKADDR_BTH sockAddrBth = {0};

    sockRecv = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
    if (INVALID_SOCKET == sockRecv) {
        return false;
    }

    sockAddrBth.addressFamily = AF_BTH; // For Bluetooth
    sockAddrBth.port = BT_PORT_ANY;
    if (SOCKET_ERROR == bind(sockRecv, (SOCKADDR *) &sockAddrBth, sizeof(sockAddrBth)))
    {
        closesocket(sockRecv);
        return false;
    }

    int size = sizeof(sockAddrBth);
    getsockname(sockRecv, (SOCKADDR *) &sockAddrBth, &size);

    CSADDR_INFO info = {0};
    info.LocalAddr.lpSockaddr = (LPSOCKADDR) &sockAddrBth;
    info.LocalAddr.iSockaddrLength = sizeof(sockAddrBth);
    info.iSocketType = SOCK_STREAM;
    info.iProtocol = BTHPROTO_RFCOMM;

    WSAQUERYSET set = {0};
    set.dwSize = sizeof(WSAQUERYSET);                              // Must be set to sizeof(WSAQUERYSET)
    set.dwOutputFlags = 0;                                         // Not used
    set.lpszServiceInstanceName = "Server";                        // Recommended.
    set.lpServiceClassId = (LPGUID)&SerialPortServiceClass_UUID;   // !ここのUUID(00001101-0000-1000-8000-00805F9B34FB)はAndroid側とマッチしている必要がある!
    set.lpVersion = NULL;                                          // Not used.
    set.lpszComment = NULL;                                        // Optional.
    set.dwNameSpace = NS_BTH;                                      // Must be NS_BTH.
    set.lpNSProviderId = NULL;                                     // Not required.
    set.lpszContext = NULL;                                        // Not used.
    set.dwNumberOfProtocols = 0;                                   // Not used.
    set.lpafpProtocols = NULL;                                     // Not used.
    set.lpszQueryString = NULL;                                    // not used.
    set.dwNumberOfCsAddrs = 1;                                     // Must be 1.
    set.lpcsaBuffer = &info;                                       // Pointer to a CSADDR_INFO.
    set.lpBlob = NULL;                                             // Optional.

    if (WSASetService(&set, RNRSERVICE_REGISTER, 0) != 0) {
        return false;
    }

    listen(sockRecv, 0);

    SOCKADDR_BTH sab2;
    int ilen = sizeof(sab2);
    // 通信用のsocketを開く.このsocketで通信を行う.
    acceptedSocket = accept(sockRecv, (SOCKADDR *)&sab2, &ilen);
    // 接続用のsocketは必要ないので閉じる
    closesocket(sockRecv);

上記コードのacceptedSocketは保持しておき,通信にも用います.

Androidにデータを送信

ここまでくればあと少し・・・のはずが,ここでまさかのどん詰まり.数時間悩みました・・・

Socketを通してデータを送信するのは簡単で,sendの関数一つの呼び出しで可能です.何故詰まったかと言うと,Android側でデコードしたデータが全然違うという問題が解決しなかったためです.
ちなみに,sendは,send(SOCKET s, const char* buf, int len, int flags)の形式を取り,bufの部分で配列などを変換して送信します.今回はintの配列を送信するため,int * -> char * の変換をしました.

Android側のデコードは他でも使っていて恐らく間違っていない(というか正しかった)と考え,エンコードの際にどうすればよいのかを延々と調べては試すという繰り返しでした・・・
このやり方に関する記事は結構あるのですが,単純に(char *) (intの配列)という風に型変換すればいいと書いてあるものが多く,試しましたがダメ.さらにint * -> byte * -> char * の変換等も色々試しましたが,一向に解決せず.途方に暮れていたところで一筋の光が.

stackoverflowのこの記事によると,htonを噛ませてネットワークバイトオーダーに変換する必要があるとのこと.
最終的には以下のコードで送信できました.arrayが送信する配列,intは送信するデータ数(配列の長さと等価)です.

void btSend(int* array, int len) {
        int* netArray;
        netArray = (int*)malloc(sizeof(int)*len);
        for (int i = 0; i < len; i++) {
            netArray[i] = htonl(array[i]);
        }
        send(acceptedSocket,  (const char *)netArray, sizeof(int)*len, 0);
}

分かってれば一時間もかからずできたものに結局2日もかかってしまい,本当に通信周りの知識をつけないとまずいと感じました・・・

Eclipse Android SDK Content Loader hangs at 0% 対応のbat作ってみた

Eclipse Android SDK Content Loader hangs at 0% の対応

Eclipse Android SDK Content Loader hangs at 0% まとめ - kkbnart's diary

の続きでbatファイルを作ってみた.

bat自体作るのほぼ初めてだったので,簡単なんだけど以外と時間かかった・・・

やることは非常に単純で以下の通り

  1. %USERPROFILE%.androidに移動
  2. ddms.cfgを削除
  3. cacheの中身を削除

実際の処理は以下の通りです.ファイルチェック,出力を多少しているのでちょっと長いです.間違いがあるかもしれないので,使う際は自己責任でお願いします.また間違いに気づいた方は指摘もお願いします.
別ドライブから実行する場合は"cd /d ~"かな?

@echo off

rem This bat file works to resolve Eclipse Android SDK Content Loader hanging problem.

rem Change directory to userprofile.android
cd %USERPROFILE%\.android%
echo %CD%

rem search and delete file "ddms.cfg"
set DEL_FILE="ddms.cfg"
IF EXIST %DEL_FILE% (
    del %DEL_FILE%
    echo Delete file ddms.cfg
) ELSE (
    echo !!CAUTION!!
    echo file does not exist. Please Delete Manually
    GOTO END
)

rem search and delete files in cache
set CACHE_DIR="cache"
IF EXIST %CACHE_DIR% (
    del %CACHE_DIR%\%
    echo Delete files in cache
) ELSE (
    echo !!CAUTION!!
    echo file does not exist. Please Delete Manually
    GOTO END
)

rem finish process
:END
pause
exit

Thanks for Flying Vim

cygwin等のターミナルでvimを終了した後,「Vimを使ってくれてありがとう」(「Thanks for Flying Vim」)が消えない問題に関して.

調べると大体vimrcに

set notitle

と記述すればokと書いてあるが,それでも何故か消えなかった.別の対処法としては,

let &titleold=getcwd()

の様に,titleoldを別の文字で置き換えればok.ただ,自分の場合はかなり特殊な例な気もするので,何か別の原因がある可能性は高い.ちなみにこの場合は開いているディレクトリの場所がタイトルバーに表示される.

cygwinでssh接続

github等の各種サービスにsshで接続する際のまとめ.

まず,SSH キーを生成

:~/.ssh$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/art/.ssh/id_rsa): id_rsa_file_name
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
...

各種サービスにおいて,生成したSSHキー(public)を登録し,秘密鍵パスフレーズ入力を省略するため,ssh-agentを登録.この時の注意点として,cygwinの場合

ssh-agent -s
ssh-add -l

ではなく,

eval `ssh-agent -s`
ssh-add -l

とすること.cygwinの設定し直し等で度々引っかかるので,備忘録として書きました.ここではメモ程度のため,理由等の詳細は,http://stackoverflow.com/questions/17846529/could-not-open-a-connection-to-your-authentication-agent を参照

Eclipse Android SDK Content Loader hangs at 0% まとめ

EclipseAndroid開発をしている人なら経験があろう,Eclipse起動時にContents Loader が0%で止まってしまい,作業ができなくなってしまう悪名高きバグ.あのときのイライラ感と言ったら・・・
あまりにも頻繁に起こるので,対処法をまとめてみた.

  1. Run Eclipse -Clean
    一番簡単な対処法.プラグイン追加後などに,起動オプションとして"-clean"を指定する.調べると大抵出て来るけど,自分自身はこれで解決したことが一回もない.

  2. adb kill-server, adb start-server
    Android Debug Bridgeの再起動コマンド.上同様自分はこれで解決したことなし.

  3. delete all files in .projects
    かなり有効な解決手段.workspace.metadata.plugins\org.eclipse.core.resources.projectsのフォルダ内の全てのファイルを削除し,eclipseを再起動するとほぼ確実に解決する.しかし,その代償としてworkspace以外のディレクトリにあるプロジェクトの参照が消えるので,削除する前に.projects内のファイルのバックアップを取っておき,eclipse再起動後にファイルを元に戻す必要がある.
    この方法は長い間使ってきたが,確実に解決するけど面倒臭い

  4. delete cache, ddms.cfg
    最近見つけた対処法.%USERPROFILE%.android内にあるcacheフォルダの中身とddms.cfgファイルを削除し,再起動.非常にスマートな解決法で,この方法が一番おすすめ.stack overflowでも賞賛のコメントが大量についており,素晴らしい対処法であることが伺える.

総括
とりあえず4の対処法でok.それでだめなら3.
これで皆が救われることを祈る.

参考: http://stackoverflow.com/questions/13489141/eclipse-hangs-at-the-android-sdk-content-loader

Windows8が再起動できなくなった時の対応

先日のこと,自分の使っているASUSのノートPCで,HD容量が大分圧迫されていると警告が出たので,ディスククリーンアップを実行.この際,windows updateにもチェックを入れたのだが,処理が一行に終了しないため,キャンセルして再起動.すると・・・

windowsを修復できないとの文字が・・・トラブルシューティング画面が出てきてしまい,色々策を講じるも起動できない状態に・・・

こんなこと初めてだったので,パニック状態.とりあえずデータをサルベージしなければと思い,ググってみてそれらしい方法が見つかった.どうやら,windows8のインストールディスクから起動し,外付けのHDにデータをコピーすればよさそうと分かった(実はここで既に勘違いをしていたのだが).しかし,このPCには内蔵のDVDドライブがないので,USBの物をつなぎ,そこから起動することに.

再度問題発生.Biosの設定変える必要があり,Bootの優先順位でUSBのDVDドライブを一番にしなければらないが,その方法が分からない.

まぁここら辺は常識っちゃ常識だけど,PCごとにBIOS違ったりするから手順も違うことを知らなかったので.ここで結構悩んだ後,いい動画が見つかったため解決.
https://www.youtube.com/watch?v=X3GFZm9at6g

これでやっとインストールディスクから立ち上げられる・・・!ここまでで数時間かかったので,なんかやり遂げた感.そして次のコピーの段階へ.どうやらプリインストールのWindows(WindowsPE)でコマンドプロンプトを立ち上げ,そこでxcopyのコマンドで丸ごとドライブの中身をコピーすれば良さそうと分かった.

ここでプロンプトを立ち上げて気づく.これってわざわざインストールディスクから起動しなくてもできたのでは・・・実はこのPCは最初からでwin8が入っていたためか,初めからコマンドプロンプトは立ち上げられたのだった・・・ここら辺に気づかないのは本当に情けない・・・恐らく,最初から入っていない場合は必要な場合もあるのかな・・・

本当に時間の無駄でした.まぁちょっとここら辺の知識ついたかな・・・まぁその後無事データを回収して,なんとか事なきを得たものの,PCはほとんど初期化してしまったため,OfficeやらEclipseやらは入れ直しになり,今現在も修復中でございます.

ちなみになぜ起動しなくなったかの原因は不明だけど,どうやらディスククリーンアップでwindowsアップデートはチェックをつけない方がいいらしい.今度からは気を付けよ・・・ga