2016/06/28

[java?]doReturn()は複数書けるし、ArgumentCaptorは複数取得している

まだまだテスト中だ。
とにかく、Java自体に自信がないので、テストパターンを多く作りすぎているような気がする。
まあいいや(仕様変更が来ないことを祈ろう)。

 

テストするメソッドで、別のメソッドを呼んでいると扱いが面倒なので、spy()していちいちdoReturn()とかdoNothing()とか書いている。
しばらくやっていると、同じ引数で1回目と2回目で違う値を返さないといけないパターンが発生。
まあ、レスポンス解析だからありがちだと思う。

どうやったらいいか調べていたのだが、単にdoReturn()をずらずらつなげればよいみたいだ。

doReturn(new byte[] {1, 2, 3}).doReturn(new byte[] {4, 5, 6}).when(xx).read(anyInt());

ほほう。

 

あと、呼び出した疑似関数の引数をArgumentCaptorで保持する方法も知った。

ArgumentCaptor<byte[]> writeCaptor = ArgumentCaptor.forClass(byte[].class);
doNothing().when(xxx).write(writeCaptor.capture());

//テスト実施後
byte[] captorData = writeCaptor.getValue();
assertThat(Arrays.equals(captorData, COMPARE_DATA), is(true));

assertThat()で配列の比較方法がよくわからんので、もうArrays.equals()にしてしまったが、まあいいや。

 

この方法だと、captorDataには最後に呼び出したものが残るようですな。
複数回はできるのだろうか?
できるのだ。
java - Can Mockito capture arguments of a method called multiple times? - Stack Overflow

さすがだねぇ。
と思って試すが、なんか動かない。。。

ああ、書いてあるのが、ArgumentCaptorの生成とverify()が並んで書いてあったから、そういう書き方なのかと思ったけど、verify()はいつも通りテスト実施後に呼べばいいだけか。

ArgumentCaptor<byte[]> writeCaptor = ArgumentCaptor.forClass(byte[].class);
doNothing().when(xxx).write(writeCaptor.capture());

//テスト実施後
verify(xxx, times(4)).write(writeCaptor.capture());
List<byte[]> captorData = writeCaptor.getAllValues();
assertThat(Arrays.equals(captorData.get(2), COMPARE_DATA), is(true));

つまり、Captorは動いていて、取得方法だけ変えればよいというわけだ。
verify()は呼ばないとだめなようです。
times()できっちりじゃなくても、atLeast()でいけるみたいだ。

2016/06/27

[android]カバレッジを取りたい

Android Studio 2.1.2 + Nexus5で、カバレッジを取りたい。
いまの環境(2016/06/27)で、なるべく手間を掛けず、LCOVみたいなカバレッジ結果が見たいのだ。

Local Unit Testsの場合は、Project Explorer...という名前じゃないかもしれないが、左側に出てくるツリーでTestクラス名を右クリックすると「with Coverage」というメニュー項目があるからわかった。

では、instrumented Unit Testsの場合は?


検索すると、jacocoというものを使う人と、使わない人がいる。
なんとなく、使わなくてもよくなっていそうなので、この人のを参考にした。

baroqueworksdevの日記: コードカバレッジを使って、視覚的/統計的にテスト状況を確認する

terminalからgradlewコマンドを実行せずとも、Android Studioの右上くらいにある「Gradle」をクリックするとツリーが出てきて、その中に「verification/createDebugCoverageReport」みたいなのがあるから、それをダブルクリックすると同じ効果があるようだ。

 

で、そもそもこれで何が実行されるのかがわかっていなかったけど、Instrumented Unit Tests側のフォルダ(androidTestと書いてある方)のテストが実行されるようだ。

ただ、実機側のログに「java.lang.ClassNotFoundException: org.jacoco.agent.~~」や「TestRunner: Failed to generate emma coverage. 」と出ているので、jacocoを明示的に指定しなくてもよくなっただけかも。

あと、テストがちゃんと動かせてないのかも。。。
でも、reportフォルダの中にはそれらしいものが生成されているから、まったくダメというわけでもないだろう。

 

この辺りが役立った。
BDS pluginの生成するソースでは、Handlerを使っているのだ。
AndroidのInstrumented Unit Testsを試す - mnatsu31’s blog

 

あと解決できてないのが、同じく生成されているServiceだ。
これが動いてくれないと、何もできないに等しいのだ。。。

でも、下回りを全部動かすテストって、もうUnit Testじゃないよねぇ。
そうそう、そもそもは、単にアプリを動かしたときのカバレッジがほしかっただけなのだ。

初心を忘れていましたね。


しかし、勝手にカバレッジを取ってくれるような方法は見つけられてない。
gccで-coverageを付けてビルドしたアプリを動かしたときのようなやつだ。

正常系はボタンとか操作して動かすことでカバレッジを取っておいて、それで動いていない箇所だけLocal Unit Testsでやろうか、と思っていたのだ。

 

JavaやAndroidだから、そういうのもできるのかも、と思ったけど、やり方がわからんだけか。。。
なんか「最新の科学で解決しました」みたいなイメージでいるから、よくわからないけどやってくれそうな気がしたのだよ。

[android]Instrumented Unit Testsじゃないとandroid.jarは例外/デフォルト値を返す

こんなテストコードを書いた。

@Test
public void testTest() throws Exception {
    SparseArray<String> strMap = new SparseArray<>();
    strMap.put(0x00, "moji");
    String err1 = strMap.get(0x00, "momo");
    System.out.println(err1);
}

実行すると、nullになった。。。
newしてるのにnullか。


よくわからずに書いている箇所があるが、build.gradleのreturnDefaultValuesもそうだ。
これをコメントアウトすると、実行するとエラーになる。

java.lang.RuntimeException: Method put in android.util.SparseArray not mocked

これだ。
実機を動かさずに行うUnitTestではandroid.jarが空実装だから、例外になるようだ。
これを、例外を出さずにデフォルト値を返すようにするのが、returnDefaultValuesというわけだ。

 

SparseArrayは特にAndroid実機の機能を使っているわけでは無いのだろうけど、これを解決させるのに悩むくらいだったら、実機で動かしてしまった方が楽そうだ。

そういう、Mockなどを使わずに動かしてテストもしたいなら、Instrumented Unit Testsの方になるようだ。


Local Unit Testsを始めるときと同じように、書いてあるとおりにやると動いた。
テストを右クリックで生成するときに、どのフォルダに格納するか聞いてくるが、あれの先が「androidTest」になるのだな。

そして、build.gradleも「testCompile」じゃなくて「androidTestCompile」になる。
returnDefaultValuesはtrueのままでよいようだ。

実行すると、アプリが実機に転送されて実行し、ちゃんとassertThat()なんかも判定している。

 

android.jarのところも含めてMockにしてメソッドを動かしていくならLocal Unit Tests、android.jarはそのまま流して動かしたいならInstrumented Test Unitsがよいかな。

[ble]iOSはPPCPを読まない

私はiOSはもうやっていないのだけど、BLE Periphearlを作るとiOSのことも考えることがある。
Connection Intervalがどうなっていたか、忘れてしまったのだ。

 

Bluetooth Accessory Design Guidelines for Apple Products

2016/06/27の時点でも、R7が最新のようだ。
これはこれでよいのだけど、探している途中で見つけたこちらが気になった。

BLE settings for max data transfer speed to iOS - Nordic Developer Zone

質問は、通信が遅いんだけどどうしたらいいの?なのだが、回答に「Apple does not read the PPCP value you set」とあるのだ。
iOS(というか、Apple製品なんだろうけど)はPPCPを読まないんだ!

 

さらっと書いてあるけど、Connection IntervalはPPCPに書いておけば手間が省けていいや、くらいに思っていたので、ちょっと衝撃だ。

GAP Connection Parameter Update

S110だと、接続が確立した後でsd_ble_gap_conn_param_update()を呼ぶだけでよいようだ。

[android][memo]targetSdkVersion取得とExceptionの並列

タイトルに書くほどのネタじゃないけど、書き残しておかないと忘れそうなので、小さい話を2つメモとして残しておく。

  1. 自アプリのtargetSdkVersionを取得する
  2. Exceptionは並列に書ける

3つたまったら記事にしようと待っていたけど、たまる日が来ないかもしれないので2つにしてしまった。


1. 自アプリのtargetSdkVersionを取得する

自アプリのtargetSdkVersionを知りたかった。
アプリの情報だから簡単に取ってこれるのかと思ったけど、

  • PackageManager取得
  • Contextから自アプリのパッケージ名取得
  • PackageManagerと自アプリパッケージ名からApplicationInfo取得
  • ApplicationInfo#targetSdkVersionを取得

とやらねばならんかった。

書いてしまえば大したことじゃないのだけど、なかなかたどり着けなかったのだよ。

 

最初はBuild.VERSION.SDK_INTでやってたんだけど、これは端末が持つ情報だから、アプリに関係なく同じバージョンが取れるのでした。


2. Exceptionは並列に書ける

stackoverflowのこちらを読み、そのままコピーしたソースを使っていた。
しかし、それをFindBugsにかけると「例外がスローされないのに例外をキャッチしています」と言われてしまった。

記事では「Exception」だけで囲んでいたけども、

  • NoSuchMethodException
  • IllegalAccessException
  • InvocationTargetException

があり得るらしい(Android Studioが出してくれた)。
最初のがgetMethod()で、残り2つはinvoke()だ。

分けるのも面倒なので、こう書いた。
(gattを別変数にコピーしているのは意味無さそうなので消したけど、いいよね?)

try {
    Method localMethod = gatt.getClass().getMethod("refresh");
    if (localMethod != null) {
        return (Boolean) localMethod.invoke(gatt);
    }
} catch (NoSuchMethodException e) {
   Log.e(...);
} catch (IllegalAccessException e) {
   Log.e(...);
} catch (InvocationTargetException e) {
    Log.e(...);
}

そしたら今度はAndroid Studioが2つめ以降のExceptionに文句があるようだった。
JDK7とか書いてあるが、何を言っているのかわからなかったので、提案されるまま修正させてみた。

try {
    Method localMethod = gatt.getClass().getMethod("refresh");
    if (localMethod != null) {
        return (Boolean) localMethod.invoke(gatt);
    }
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
    Log.e(...);
}

へー!
そんなことできるんだ。

これか。
複数の例外型のキャッチと型チェックが改善された例外再スロー
例外とはいえ、型が違うのに型の方をORできるなんて、不思議な感じがするな。

2016/06/26

[java]assertThat()はどのimportだ?

前回、assertThat()を書いたが、importに何も書かないとこうなる。

image

まあ、何かimportしてくれるのだろう、と思ってクリックすると、こうなる。

image

わ、私には選べない!


試したが、一番上の「MatcherAssert.assertThat」でよさそうだ。

import static org.hamcrest.MatcherAssert.assertThat;

ちなみに、is()だとこんなの。

image

これも「CoreMatchers.is」でよいみたい。

ただ、その下のIs.isを選んでもエラーにならないから、継承関係にあるのかな?

 

CoreMatchersクラスはこうだ。

public class CoreMatchers

Isクラスはこうだった。

public class Is<T> extends BaseMatcher<T>

BaseMatcherは、こう。

public abstract class BaseMatcher<T> implements Matcher<T>

 

MatcherAssert#assertThat()は、こう。

public static void assertThat(T actual, Matcher matcher)

Matcherクラスは、こうだ。

public interface Matcher<T> extends SelfDescribing

Matcherの血を引いているものであれば、assertThat()の第2引数にできるということか?
でも、CoreMatcherはMatcherの流れではないぞ。

 

などと間の抜けたことを思っていたが、is()はメソッドだから、戻り値だけでよいのだ。
CoreMatchers#is()は、こうだ。

public static <T> org.hamcrest.Matcher<T> is(T value)

上で「Matcherクラス」と書いてるものは、packageがorg.hamcrestだから、それだけのことだ。


さて、メソッド的な流れはなんとなくわかったというか、Android Studioの一番上を選んでおけばよいということがわかった。

気になったのは、org.hamcrestだ。
orgというからには、なんかの団体のはず。

Hamcrest

大きい画像はないのだが、拡大してみるとハムと果物がお皿に載ったような写真だ。
そしてハムが階段状に重ねられている上を、ナイフっぽいサーフィンボードのようなものの上に載った人が滑っているような画像だった。

 

なので、HamcrestのHamは、食べるハムなのだろう。
Java生まれで他の言語にも移植された、というようなことが書かれている。

よくわからないけど、ありがたく使おう。

[java]メソッド名は漢字でもよいのだ

Android Studioの指示に従ってテストメソッドを作ると、あたまに「test」を付けたメソッド名を作ってくれる。

method() → testMethod()

だから、Javaのユニットテストは、1つのテストメソッドに全パターンを書いてしまうのかな?と思ったけど、別にそういう決まりはないだろう。
setUp()などもあるから、テストしたい最小の内容をメソッドにするのがよいんじゃなかろうか。
テスト数も稼げる行ったテスト項目数とメソッド数が一致するしね。

 

ただ、テストメソッドをたくさん作るとなると、メソッド名もその数だけいることになる。
Cでやるときは、めんどくさくてメソッド名の後ろに「1」「2」なんて付けてたのだけど、間に追加したいときに「1a」なんてやってるので、もうちょっとましな名前ルールにしたい。

そういえば、誰かに聞いたのだが、確かJavaはメソッド名に漢字が使えるとか何とか。
UTF-8の場合だけとかかもしれないけど、そういうのを聞いたことがある。
そのときは、へー、くらいで流していたが、そういえばその人も、普通はやらないけどテストメソッドだったらやる、とか言ってた気がする。

@Test
public void testMethod名前が思いつかん() throws Exception {
    assertThat(10, is(not(3)));
}

おー、いけた!

 

そういえば、C#なんかも日本語が使えると聞いた気がする。
まあ、日本語のメソッドを呼び出すような処理は書きたくないけど、UnitTestだと自動的に呼んでくれるから、確かにテストならありかな、と思えてきた。

2016/06/25

[java]Unit Testは意地になっていかんね

別にJavaだからとかではなく、Unit Testを始めると、うまく通らない箇所があるとどうも意地になって攻略するというか、解決するというか、対処しようとしてしまう。

 

今やっていて、うまくいかないのが、BLEのスキャン開始
Mockitoを使いながら進めていくのだけど、今はL.111でNullPointerExceptionが発生して悩んでる。

こんなところを通しても意味がないので、仕事だったら「実動作で確認」で逃げるんだけど、今はお遊びでやっているので、何とかしたくなってしまうのだ。

このL.111って、分割するとこうのはず。

ScanSettings.Builder builder = new ScanSettings.Builder();
ScanSettings.Builder builder2 =builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
ScanSettings settings = builder2.build();

これで実行すると、builder2がnullになった。
でも、ステップインさせてもsetScanMode()が実行されていないから、nullを返したというよりも、処理が行われなかったのでnullにしているような感じを受けた。

さっきも、普通に動かすときは問題ないのに、テストで動かすとnewのくせにnullを返すところがあったのですよ。
だから、classを読込んでいないとか、なんかバージョンが合わないとか、そんな変な理由じゃなかろうか。

 

悔しいが、今日は時間切れだ。

[android]AARのテストをしてみよう!

ぼちぼち、Androidのテストをせねばならぬ。
TwoSpinnersでいろいろやっていたのは、あれは結合テスト的な、実際に動かすテストだったのだけど、ユニットテストのやり方も知っておきたい。

Android Studio 2.1.2でやってみよう。


Android Studioでプロジェクトを新規作成し、Activityだけを持つアプリを作った。
そしてそこに、New ModuelesでAndroid Libraryを追加。
com.hiro99ma.blogpost.myaarというのがそれだ。

デフォルトでExampleUnitTestというものがあるので、それを動かしてみよう。
ツリーから右クリックし、'Run ExampleUnitTest'を選択。

image

エラーだ。

Process finished with exit code 1
Class not found: "com.hiro99ma.blogpost.myaar.ExampleUnitTest"Empty test suite.

あるのに!
AARだからダメなのかと思ったが、appの方も同じだった。
何かしないとダメらしい。


Building Local Unit Tests | Android Developers

今回やろうとしているのは、Local Unit Testということになる。
書いてあることは、

  • build.gradleのdependenciesにjunit(とオプションでmockito)を追加
  • テストclassを作る
  • Mockのことを飛ばすと、もうRun Local Unit Tests

手順が違うように見えないが。。。

あ、「Sync Projectしろ」とあるな。
ツールバーのアイコンをクリックして、同じようにRunさせると、今度は動いた!

image

Syncしてなかっただけ、ということか。
でも、Syncが必要なときはだいたいメッセージが出ていたと思うが。。。
まあいい。


ちなみに、テスト内容は「assertEquals(4, 2 + 2);」と単純なものだ。
最初に期待値を書いて、次に評価対象を書く。
逆でも結果としては変わらないと思うけど、たとえば第1引数を5にして失敗させると、こうなる。

image

ExpectedとActualが出力されるので、やっぱり期待値と評価対象は合わせておいた方がよいだろう。
私はGoogleTestでそれを知らず、全部引数を逆に書いてがっかりしたことがあるのでね。。。


では、自分でclassを作って、それのテストまでやってみよう。

public class MyAar {
    private Context mContext;

    public MyAar(Context context) {
        mContext = context;
    }

    public String getString(int resId) {
        return mContext.getString(resId);
    }
}

エディタ側の方で、クラス名にカーソルを当てていると電球マークが出てくるので、マウスでクリックしてメニューを開く。
Alt+Enterでもよいかも。
「Create Test」があるので、選択。

image

Android 2.1.2では、こういう画面が出てきた。

image

setUp, tearDown、あとはgetString()にチェックしてOK。
そうすると、Choose Destination Directoryダイアログが出てくる。

image

2つ候補が出てくるが、開いたときは下の方がフォーカスされていた。
上の方はApplicationTest.javaが既にあって、これはテストのメイン処理みたいなものじゃなかろうか。
だから、デフォルトのままでよかろう。
そうすると、MyAarTestというクラスを作ってくれた。

public class MyAarTest {

    @Before
    public void setUp() throws Exception {

    }

    @After
    public void tearDown() throws Exception {

    }

    @Test
    public void testGetString() throws Exception {

    }
}

まあ、空っぽですな。

じゃあテストを書いてみようか、というところで、Contextをどうやって持ってきてよいかということになる。

ここでまた、Android Developerのサイトに戻ろう。


Mock Android dependencies

Mockitoというのを使うと、いいらしい。
伊藤さんのMockか、と思ったが、モヒートらしい。。。

サイトではbuild.gradleのdependenciesに追加しているが、こういうのはGUIからでもできたはず、とProject StructureのDependenciesで「mockito-core」を検索してみた。

image

betaねぇ。。。
まあいいや、それにしてみよう。
build.gradleも見ておく。

testCompile 'junit:junit:4.12'
androidTestCompile 'org.mockito:mockito-core:2.0.71-beta'

追加されたのだけど、最初からあるjunitとちょっと違うな。
まあ、それもよしとしよう。

サイトをまねして@Mockと書いたが、classの前に「@RunWit(MockitoJUnitRunner.class)」の記載もいるようだ。
が、それを追加してもまだだめで、MockitoJUnitRunner.classすらも怒られている。
importを追加しても、そんなの使ってないよ、という感じだ。

ここで、build.gradleのandroidTestCompileをtestCompileに変更すると、直った。
ちっ。


そんなこんなで書いたのが、こういうテストclass。

@RunWith(MockitoJUnitRunner.class)
public class MyAarTest {
    MyAar mAar;

    @Mock
    Context mMockContext;

    @Before
    public void setUp() throws Exception {
        mAar = new MyAar(mMockContext);
    }

    @After
    public void tearDown() throws Exception {
        mAar = null;
    }

    @Test
    public void testGetString() throws Exception {
        Mockito.when(mMockContext.getString(R.string.app_name)).thenReturn("Hello");
        String str = mAar.getString(R.string.app_name);
        assertThat("Hellos", is(str));  //★第1引数がExpectedのつもりで書いてる
    }
}

最後のassertThat()は、サイトのをまねした。
失敗させないと、動いているかどうかわからんので、Mockの方は"Hello"、期待値は"Hellos"としてみた。

結果は、

image

ああ、ちゃんと失敗しているね。

ん?
Expectedが"Hello"になってる。。。
引数の意味がassertEqual()とは逆なのか。

Assert (JUnit API)
第1引数がactualで、第2引数がMatcherというものらしい。

なんというか、言葉をしゃべるように、

assertThat(実際の値, is(期待値))
  →「実際の値 is 期待値」

ということかしらね。
まあ、私も期待値を後ろに書きたい方だからよいのだけど、他のassertと順番が逆なのは嫌だな。

こちらに、assertThat()じゃないと回避できないというか不便というか、そういう問題があるという話が載っていた。
assertEquals()は古い。assertThat()+Matcherを使う。 : Java好き

特にこだわりが無いなら、assertThat()にしておけば困らないのかな。
ただ、見た目が「assertTrue()」に似てるので、そこは気をつけねば。。。

2016/06/23

[android]SharedPreferenceのedit()は取得してから使おう

EditViewを使った。
ユーザに入力してもらうのだけど、基本的には最後に入力した文字列を使うことになるだろうから、覚えておきたい。

そうなると、SharedPreferencesが登場する。
使い方はそんなに難しくないし、ネットでも説明がよく出てくる。

 

で、保存するときにちょっとだけだからと、こうした。

SharedPreferences sp = getPreferences(MODE_PRIVATE);
sp.edit().putString(KEY, "もじもじ");
sp.edit().commit();

だめだった。
例外などは発生しないけど、保存してくれない。。。

 

いろいろやった末、こうすればよかった。

SharedPreferences sp = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor ed = sp.edit();
ed.putString(KEY, "もじもじ");
ed.commit();

あらー、edit()って内部で持ってる何かのインスタンスを返すだけとかじゃないんだ。
APIの説明を読むと「Create a new Editor」と書いてあるので、これは取得というよりも生成のAPIなんですな。

名前がcreateEditor()とかだったらよかったのに!と思うのは私だけか。。。

[java]JavaDocの見た目を変えたい

お仕事でJavaというかAndroidを使っている(慣れない)。
まずはインターフェースを決めようと言うことで、JavaDocを書いている。

ルール通りに書いて、Android Studioの「Tools > Generate JavaDoc」にすると、-encoding UTF-8などを指定すれば、そこそこちゃんとしたAPI仕様書になってくれる。

でも・・・何か物足りない。
そうだ、いつもAndroid Developerで見ているあれよりもさっぱりしすぎているのだ。
あそこまでいかなくてもいいので、ちょっと見栄えを変えてみたくなった。
ほら、中身は大したことなくても、見栄えでごまかされることってあるじゃないか。


いろいろありそうだったけど、Oracleで最近の記事なので、これをやってみた。
Colorful Javadoc (Brewing tests with CAFE BABE)

 

まず、デフォルトで作った場合。
GitHubに置いているやつで、特にドキュメント整備などしてないが、まあ見栄えの例だからいいだろう。

image

 

作ったJavaDocフォルダの中にあるstylesheet.cssをリネームして、stylesheet_orig.cssにする。

次に、記事中にある「Download」からどれか選んでダウンロードする。
まずは一番上の青で試そう。
stylesheet_blue.cssがダウンロードされる。

これをstylesheet.cssにリネームして、stylesheet_orig.cssと同じ場所に置く。

image

ぱっと見た目はわからないけど、少し青になったのだ。

 

緑は、違いがわかりやすいな。

image

 

紫も、しっとりしててよいかも。

image

 

これ以外にも、JavaDocのStyleSheetを作るようなサイトがあったから、使えるのかもしれん。

 

実はフォントを変えたかったので探し始めたのだが、あまりそちらの方は出てこなかった。
CSSの中を見たけど、たぶんDejavuってのがフォントだろう。
書き換える箇所が多そうだし、どのフォントがよいかもよくわからんので、私は考えるのをやめた。。。

2016/06/22

[androidstudio].idea/misc.xmlのlanguageLevelが毎回変わる

Android Studio 2.1.2をWindows10 64bitで使っているのだが、プロジェクトを立ち上げるたびなのか何なのかわからないけど、.idea/misc.xmlのlangeageLevelがいつも変わっている。

<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8"

だったり、

<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7"

だったり。

困っているわけではないのだが、なんだか気持ちが悪い。
gitで管理しているから見えているだけなので、外せばよいのかもしれないが、そもそもこんな項目って変化するものじゃないと思うのだけどなぁ。

 

java - Android .idea/misc.xml's languageLevel tag keeps changing JDKs - Stack Overflow
これがそうなのだけど、回答は「gitignoreで管理から外す」なので、すっきりしない。

が、今回はもうそれでいいや。。。


今回は、こちらのgitignoreをまねしようと思う。
A .gitignore for use in Android Studio

 

あとは、お客さんにプロジェクトファイルをリリースするとき、どういう環境を作るか考えんといかんなぁ。
理屈からすると、gitで管理しているものをすべて渡してしまえばよいはずだが、はてさて。

2016/06/21

[android?]@NonNullはそんなに強くない?

API仕様が決められてるAndroidのライブラリを作っている。
引数にnullは受け付けないやつだったので「これは@NonNullにしとけばいいだろう」と、何も考えずに@NonNullを付けておいた。

Android Studioも、@NonNullを付けておいてnullチェックのif文を書くと「それは常にfalseだよ」と教えてくれるので、取っ払った。
いやいや、新しい言語は楽でいいねぇ。

 

が、考えてみると、nullかどうかなんて動的に変わるものだから、静的にnullかどうかわかるのは実装で直接書いたときだけじゃなかろうか?
実行しているときに、経路によってnullになってしまうこともあるだろう。

そういうとき、どうなるんだ?


どうもならなかった。。。。
使うところで、NullPointerExceptionした。

そっか、アノテーションって、ほんとに注釈なんだねぇ。。。
そのままなのだけど、それを、それを我々は受け入れねばならぬのだ!

 

Android Studioで@NonNullのパラメータをnullチェックすると表示される、

image

これはちょっと言いすぎってことですな。

個人的には、NULLチェックはあまり好きではないのだが、API仕様があるからしようがないですわ。

[java]ByteBufferに手こずる

JavaにByteBufferなるものがあることを知り、byte[]でわざわざ受信データが来るたびにnewし直して、System.arraycopy()で連結して、という手間が省けそうだと思って使ってみた。

 

が、何か、何かピンとこないのだ。
なんでだろう?

 

たぶん、get()してもput()してもposition()が動くからじゃなかろうか。
ポインタが1つしかないから、自分で「今は書込むだけ」とか「今は読込むだけ」とか決めておくか、別変数で位置を管理するなどしないといかんだろう。

今の使い方だと、受信データが来たらput()で追加していき、読む方はarray()で配列を取ってきて、読込み済みの位置を自分で管理、なのかな。

 

あと、array()したときに取得できるのがcapacityの分までなので、limit()のところまで切り取りたいときには、新たにbyte[]でほしい分だけnewした後にSystem.arraycopy()とかでコピーすることになりそうだ。
get()だと、positionが動いてしまうから、書込みメインで使うとそうなるんじゃなかろうか。

 

戻り値としてByteBufferを返すときには、positionを0に、limitをデータの終わりまでにしていたのだが、こういうときにflip()が使えるんだな(ネットで調べた)。
今は、

bb.position(0);
bb.limit(bbArray.length);

みたいにしていたのだ。
でも、メソッドでやらなくてもいいなら、こっちの方がわかりやすい気がするけど、うーん、どうなんだろう?


ひとまず、書込みと読込を並行して行うような使い方はしない、ということさえわかればなんとかなりそうだ。

ただ、こういうのに向いたclassがあるんじゃなかろうか、という気がしなくもない。
それを探し続ける時間もないから今回はByteBufferにするけど、環境が優雅になったらなったで悩みが出てくるものですな。

2016/06/16

[android]ライブラリBuildConfig.DEBUGがtrueになってくれない

ライブラリを作っている。
デバッグビルドの時はログを出して、リリースビルドの時は出したくない。
できれば、リリースビルドの時にはコード自体削ってほしいのだけど、出したいところもあるので全部削りたいわけでもない。

 

とりあえず、自分でログ出力するclassを作って、BuildConfig.DEBUGをif文で見るようにした。
したのだが、ログが出ないのだ。
デバッガで止めても、falseになっている。
けれども、Android StudioでCtrl押しながらクリックしてBuildConfigに飛ぶと、trueになっているっぽい。

だいたい、falseのときは

public static final boolean DEBUG = false;

でわかりやすいのだけど、trueのときは

public static final boolean DEBUG = Boolean.parseBoolean("true");

になっている。
直接trueって書いてくれれば、もう少し安心できるのに。。。

 

ライブラリのBuildConfigは、強制でfalseになる、という話が多いので、たぶんそうなのだろう。
でも、Build Variantで変更できるので、なんかねぇ、納得しづらいねぇ。。。

 

こちらの方が、対処を書いてくださってる。
[Android] BuildConfig.DEBUGがtrueにならない: Java知識ゼロ人間の生活

これを見ると、アプリのdebug/releaseで切り分けられることになるんじゃないかしら。
つまり、「compile()」を使うと、Build Variantの設定は無視され、デフォルトの'release'が使われる、ということか。

それなら、まあまだ納得がいくな。

 

だったら、一番よいのは、関数はcompile()のままで、後ろのconfigurationをライブラリのBuild Variantによって設定する、とかなんだろうけど、うーん、よくわからん。

[esp8266]DeepSleepとIO16

そのうち試そう、と思いつつ、ESP8266の省電力機能は試していない。
今回も試さないのだけど、いつか試すときのために、と思ってIO16とRSTをショートさせている。

たとえば、ドキュメントのLow Power Solutionを見ると、Deep-sleepの項目には、

image

などと書かれている。

EXT_RSTBはESP8266の呼び名。
External reset signalで、WROOM-02ではRSTピンになる。

じゃあIO16は何なのかというと、ESP8266ではXPD_DCDCピンになっている。
Datasheetだと、Deep-Sleep Wakeup兼GPIO16だそうだ。

 

Technical Referenceによると、GPIO16は他のGPIOピンと違い、RTCモジュールと関連づけられているそうだ。
内部でプルダウンしているとかは書いてあるのだが、それ以上の記述は見つけきらんかった。

WakeupとRSTが接続されていることになるから、Deep-Sleepから起き上がったらIO16がLowになって自動的にリセットされる、という流れだろう。
起き上がる、と書いたが、RTCモジュールとつながっていることと、system_deep_sleep()というAPIが引数に時間をとることから、時間によってWakeupさせるのであればIO16との接続がいるのだろうけど、ボタン操作とかの割込みでWakeupするのであれば直接RSTをたたき落とせばよいだけなのだろう。

なので、もしかしたらGPIO16とRSTを接続しなくてもDeep-Sleepは使えるんじゃないだろうか。。。と推測だけしているのだが、試していない。


ESPr Door Sensor - スイッチサイエンス
これのWikiに、開閉したらリセットがかかると書いてあったので、たぶん起動時にWiFi接続して送信したら、Deep-Sleepするのだろうと思う。
その後でWakeupするトリガは、IO16ではなくて、直接RSTをLOに落とすんじゃなかろうか、という気がしている。

回路図ではRSTにIO16もつながっているけど、スイッチらしきものもつながっているから、うーん、自信がないね。
こういう回路を、さらっと読めるようになりたいのだ。

2016/06/15

[android]BLE接続までに時間がかかることがあるが、解決せず

しばしば、connectGatt()を呼んでからonConnectionStateChange()までにやたらと時間がかかることがあるのだ。
10秒とはいかないが、そのくらい待つことがある。
かと思えば、さっさとつながることもある。

nRF Master Controlアプリではそういうばらつきがほとんどないので、私のアプリがよくないのだと思う。


logcatを見比べていくと、私の方ではこのログが出ていたのだが、nRF Master Controlアプリには出ていなかった。

D/BluetoothLeScanner: could not find callback wrapper

connectGatt()より後に出ているのだが、別にこれが何度も呼ばれているわけでもないし、待っている間も何かログが出ているわけではない。
なので、疑わしいのはこれか、という程度。

 

ソースファイルはこの辺だろう
wrapperがnullだから、というくらいしかわからない。
Log.d()程度だし、あんまり関係ないような気がする。

 

気になるので出ている原因を調べたところ、私が悪かった。
BluetoothLeScannerでスキャンし、ボタンを押して接続させている。
接続するときにはスキャンを停止させる。
それだけならよかったのだが、ダイアログが消える処理のところにもスキャン停止を行っていた。
このログは、2回目にstopScan()を呼んで、もうコールバック関数の登録が消されたあとだから出力された、ということのようだった。

 

2重呼びを解消して安定した!ように見えたのだが、まだまだじゃった。。。


それ以外で違うログは、これの出力されるタイミングが早いか遅いかくらいしかない。

--------- beginning of system

私のアプリはconnectGatt()を呼んだ後で、nRF Master Controlアプリはずいぶん前だ。

でも、これって時間やプロセス名が出てないログで、これの前に空いているスペースの数も違うので、何か深いところで出している感じがする。


じゃあ、もしかしたら接続時ではなくて切断で足りていないものがあるのかもしれない。

D/BluetoothGatt: refresh()

私の方には、このログが出ていない。
が、BluetoothGatt#refresh()なんてAPIは引っかからないぞ?
・・・あ、これ@hideだ。
えー、リフレクションして呼んでるの??

 

refresh()で検索すると、キャッシュを使わずにサービス検索してほしい場合に呼び出しているようだった。
How to programmatically force bluetooth low energy service discovery on Android without using cache - Stack Overflow
それだと、別にキャッシュは使ってもらって構わないなぁ。
むしろ、キャッシュ使ってもいいから早く接続完了させておくれ、というくらいだ。
まあ、サービス走査は接続した後だけどね。。。

書いてあったメソッドをそのままコピーして呼び出してみたが、うん、早くはならなかったよ。


わからんかった。
悔しいが、保留だ。
だれか教えてくだされ~~~

2016/06/14

[java]CountDownLatchは再利用しない

BLEの非同期処理を、CountDownLatchで同期にしようとしている。
が、1回目はうまく行くのだが、2回目は結果がうまく返らない。
ログを出してみた問頃、同期待ちにならず、するっと抜けている。

 

CountDownLatch (Java Platform SE 6)
カウントはリセットすることができない、とのことだ。
リセットしたい場合はCyclicBarrierを検討しなさいとのこと。

EffectiveJava第2版の項目69を読むと、CyclicBarrierは使用頻度が低いらしい。
理由はわからんが、じゃあCountDownLatchを使うたびにnewすればいいだろう。


ここまでのソースだ。
hirokuma/AndroidTwoSpinners at a869bfa3af06cbdc19a618ca1877fd8efc568632
何かもう、最初のTwoSpinnersの面影がなくなってきつつあるな。。。

CountDownLatchで待ったりカウントダウンしたりさせているのは、この辺
writeだと、ここで待たせて、ここでカウントダウンしている。
readやDescriptorの設定も似たようなものだ。

 

さて、非同期で返ってきた値を同期で参照しようとすると、間にグローバル変数というか、共通で読むことができるメモリがいる。
今のところ、それはBundleを使っている。
何でかというと、元がそういう感じだったからだ。

何だかよくわからないが、便利そうだ。
ただ、よくわからんのでちょっと怖い。。。

JavaなのかAndroidなのか、とにかく、同じようなことをやるのにいろいろ手段があるように感じてしまうのだ。
そして、それはたぶん同じではないので、使い分けがいるのだろうと思う。

が、1パターンを知ってそれで動いてしまうと、そこで立ち止まってしまうのよねぇ。
かといって、全部知ってから実装するなんてのは、まあ無理だ。
そこら辺をどうやって調べるなり、安全性を確保するなりするのかが、わからずに困っているところだな。

2016/06/13

[android]Handlerの変数でLintの警告が出る

Hnadlerの変数で、Lintの警告が出ている。

 

これだと何も言われない。
private Handler mHandler = new Handler();

なのに、こうすると、なにやら警告が出てくる。
TwoSpinnersで、BLE用に作ったサービスからのコールバックを受け取るここですな。
private Handler mHandler = new Handler() {};

 

このHandlerクラスをstaticにしないとメモリリークするかもしれんぞ、というような内容だ。
じゃあ、static変数にすればいいや、とstaticを頭に付けたのだが、変わらない。

そういう話ではないということ?
ここに至って、ようやくJavaについて調べることにした。

 

classの中に書くclassには、いくつか種類がある。
この呼び名だが、なんかいろいろある。。。
今のところオラクルのドキュメントが正になる気がするので、そこから見ていこう。


まず、大きなくくりとしてNested Classesと呼ぶようだ。
staticがついたものをStatic Nested Classes、staticがついていないものをInner Classesとしてある。
ちなみに、Nested Classesの外側になるのは、Outer Classesだと思うが、文章ではenclosing classとも書いているから、そっちかもしれん。

「Nested classesは2つのカテゴリに分けられる」とあるから、この2つがそのカテゴリになる。
こう書きつつも「Non-static nested classes(inner classes)」と括弧で書いているから、何だかわかりづらい。
用語を作ったのなら、もう力で押し切ってほしいのだが。。。

 

Static Nested Classesは1種類だけだが、Inner Classesの方は別パターンが2種類ある。
1つがLocal Classes、もう1つがAnonymous Classes

そして、Static Nested Classes、Inner Classesと同列に「Shadowing」「Serialization」という項目がある。

「Shadowing」は、引数と、Nested Classesのフィールドと、Enclosing Classのフィールドに同じ名前のものがあった場合のサンプルソースが載っている。
そういう場合、Nested Classの中からはEnclosing Classのフィールドは触れませんよ、というのが、隠されている(shadow)という意味なのかな。

「Serialization」は、Inner Classesをシリアライゼーションするときは注意せんといかんですよ、ということを言っているようだ。
今回のHandlerの話を検索すると、コンパイラが自動的にメソッドを作るなどという言葉が出てきていたので、これが関係しているのかもしれん。
the moon at dawn: handleleakがうざったい。

 

というわけで、こうだ。

  • Nested Classes
    • Static Nested Classes
    • Inner Classes
    • Local Classes(Inner Classesの別バージョン)
    • Anonymous Classes(Inner Classesの別バージョン)

別バージョンというか、special kinds of inner classesとあるから、派生したパターンとかかもしれん。


うーん。

staticが付いている方は、そこそこ独立性が強いから"Nested"として、staticが付いていない方はEnclosing Classに取り込まれているところが強いから"Inner"としたのかしら。

そして、これに日本語の説明が加わると、どの用語がどれなのかわからんごとなってくる。

ここら辺が、どういう名前で呼ばれているのかの参考になりそう。
匿名クラスとかローカルクラスとか - 日々常々

 

中身を省略したLocal ClassesとAnonymous Classsesだが、上記リンクに説明があるのでそちらを読んだ方がよいだろう。
でも、せっかくなのでオラクルの方を眺めておく。

  • Local Classes
    • ブロックに書いたclass
    • メソッドの中に書くのが普通かね
    • (個人の感想)みんな「使わない」って書いてるから、使わないんだろう
  • Anonymous Classses
    • classの宣言と具体化を同時にできる
    • Local Classesと似ているけど、彼らには名前がない
    • (個人の感想)Anonymous ClasssesのためにLocal Classesという仕様が必要だった、ということか

というところまで見ていって、ようやく最初に戻る。

なんでHandlerの変数にstaticを付けてもダメかというと、あれはAnonymous Classsesだから、カテゴリとしてInner Classesになるからだ。
Lintが言いたいのは「Static Nested Classesにしようよ」だから、やり方を変えないといけないのだな。

 

まあ、しょせんLintの警告だから、問題がなければスルーしてよいのだ。
だけど、安全に書く方法があって、特にそれで問題がないなら、変更しようかね。

[android][ble]位置情報サービスも有効にしないとスキャン結果が戻らない

先週書いたAndroidのBLEアプリサンプルを動かした。
が、なぜかBLE Peripheralのスキャンをしても画面に表示されない。
エラーなどは出ていないのだが、onScanResult()がコールバックされないので、画面に出てこないのだ。

じゃあスキャン自体動いていないのかと思ったが、Logcatの方にはデバイス名が表示されるし、設定アプリのBluetoothを開くとデバイス名が出てくる。
アプリ情報を見ても、位置情報は許可している。
Blueoothを入り切りしたり、電源を入れ直したりしたが変わらない。
もう1台あるAndroid端末に同じアプリを入れると、こっちはスキャンされる。

なんじゃこりゃ?と思ったが、Android端末自体の「位置情報サービス」が有効になっていなかった。
有効にしたら、動いた。

image


BLEが無効だったら確認するし、アプリの位置情報許可がなければ確認するのだけど、位置情報サービスは考えていなかった。

こちらが詳しい。
Android 6.0でBLEデバイスのスキャン結果を受け取るには位置情報モードをオンにする必要がある - Qiita

うん、こういう動きだった。
設定アプリでスキャンできたのは、たぶんPEERS_MAC_ADDRESSパーミッションがついているからじゃなかろうか。

 

v6.0.0_r1のソースだと、ここら辺
これをまねして、TwoSpinnersに追加したのがこちら
getInt()しているから、たぶんSQLiteから直接設定値を持ってきているのだろう。

Intentで設定画面に飛ぶようにしたのだが、アプリを起動していきなりジャンプすると何が起きたかわからないので、Toastを表示させるようにしている。

また、onResume()したときにもう一度チェックするようにした。
無効な場合は、ボタンを押せなくしておけばよいだろう、という作りだ。

 

今回は、hasScanResultPermission()から真ん中だけ抜き取ったけど、全部まねするか、hasPeersMacAddressPermissionだけ外すとか、そういう方がよいかも。
うちのNexus5はAndroid6.0.1だけど、全部のバージョンで位置情報サービスが必要というわけでもないようだからだ。

技適の検索 (2)

日本で使えるnRF52832の安いモジュールはまだ出ないのだろうか。。。

海外で新しいBLEモジュールが発売されても、日本の技術基準適合証明を通るまでは買いづらい。
nRF52のDKなんか安いのだけど、そういう理由で買っていない。
電波暗室を設置してまでやるつもりはないので、ソフトだけやる私のような人は出遅れてしまう。

 

さっきGoogle検索すると、まだ太陽誘電さんしかなさそうだ。
名前のルールはわからんが、おそらく4文字目が「G」なのがnRF51822やnRF51422で、「H」がnRF52832なのだろう。

DigiKeyやMouserでは売られているのだが、技適はどうなってるのだろうか。

総務省で検索したが、たぶんこれだろう。

image

名前はこうなっている。

  • モジュール単体:EYSHCNZXZ
  • 評価ボード:EBSHCNZXZ
  • 評価キット:EKSHCNZXZ

こういう名前単位で取得するのか、モジュールだけ取得してボードやキットはそれを使うから取得しなくてよいのかがよくわかってない。
「設計認証」だから、同じ設計であればよい、とかだろうか。。。

ともかく、EBSHやEKSHでは出てこず、EYSHだけが出てきた。
でも「EYSHCNZXZ」だと出てこないので、なかなか検索キーワードが難しい。

 

じゃあこれを買うか?というと、うーん、ちょっとお高いですな。
まあ、一般向けじゃないし、数多く売れるわけでもないから仕方ないのでしょう。

2016/06/11

[android][ble]BLE接続する (3) - 最終回

3回目。
今回はCharacteristicのアクセスを見ていく。

AndroidでのBLEアクセスについて調べる、というよりも、Bluetooth Developer Studioで生成されたAndroid Clientのソースをなるべく流用して楽にBLEアクセスするアプリを作るのを目的としているので、そこら辺はご容赦を。

なるべく、既存のアプリを機械的に変更するだけでBLE対応できるようにしたいと思っている。
まあ、まだちゃんとしたAndroid/BLEアプリを作ったことがないので、何でもかんでもはできないだろうが、勉強だからよいのだ。


はじめる前につらつらと考えていたのだが、BLE接続してしまえば、アプリによってはUIなどなくても勝手にRead/Writeできるはずだ。

接続も、ペアリングしていればUI無しでよいのかもしれない。
ペアリング無しの機器であれば、最初からUI無しでも接続できるだろうが、さすがにそれはちょっと怖い。
基本的には人見知りで、知り合いになった後であれば気楽に話せる、というのがよいだろう。

 

ここは、もうちょっと慣れてからだな。


話を戻そう。

BDSサンプルでは、PeripheralControlActivityで値の読み書きをしている。
こんな画面だ。

image

ベースはこちらだが、ReadとWriteだけだと足りないので、Write without Response、Notification、Indicationを追加している。
nRF51822のソース
Nordic pluginsをそのまま使うと、UINT8_ARRAYの書込みでHardFaultが発生するのを忘れていてはまってしまった。。。

 

PeripheralControlActivityでは、こんなしくみになっている。

  • Write
    1. 該当するTextViewに16進数を書込んで、ボタンを押す
    2. PeripheralControlActivityのonWrite()が呼ばれる
      1. TextViewが持つandroid:tagを取得
        • tagにはサービスUUIDとキャラクタリスティックUUIDが載っている
          例:gatt_op_B7B851C6D7B847B38F93773756B46F9A_B7B80010D7B847B38F93773756B46F9A
      2. tagを分解して、サービスUUIDとキャラクタリスティックUUIDを取得
      3. 取得したUUID達から元になったUI部品を検索(EditTextになるみたい)
      4. 検索できたEditTextから文字列を取得
      5. 取得したUUID達からCharacteristicPropertiesを取得
        • CharacteristicPropertiesは自作classで、キャラクタリスティックが書き込めるかどうかなどの情報を持たせている・・・のだが、これは自動的取得するのではなくonCreate()のときに1つずつ設定している。
        • nRF Master Controlアプリは相手の情報を知らなくてもやっているから、自動でできるんじゃないの?
      6. 書き込み可能かチェック
      7. EditTextに書込まれている文字列が16進数として妥当かチェック
      8. BleAdapterService#writeCharacteristic()で書込む
    3. 書込が終わったら、handleMessage()が呼ばれる
      1. GATT_CHARACTERISTIC_WRITTENイベント

こんなもんだろう。
Characteristicの属性チェックを省けば、以下があればよいことになる。

  • BleAdapterService
  • サービスUUID
  • キャラクタリスティックUUID
  • 書込みたい値

このUUIDだが、ハイフンを付けた文字列のようだ。
"0000XXXX-0000-1000-8000-00805f9b34fb"みたいなやつ。
BleAdapterServiceにUUIDの定義はあるのだが、そちらはハイフン無しの16進数だ。
変換は、Utility.normaliseUUID()でできる。

できるけど、ハイフン無しの形式で持つ理由がないと思う。
使ってないし。
それならいっそのこと、最初からハイフン付きで生成した方がよさそうだ。

 

Readについても、流れは同じ。
結果はコールバックで戻ってくるので、テスト先で処理したい場合は、そこに流すしくみが必要になる。

Notification/Indicationもだいたい同じだが、こちらは先にCCCDに書込みをして有効にしておかねばならない。
今のところ、nRF51822でNotification/Indicationするしくみを作っていないので、今回はここまで。


今回までのAndroidソース
BDSのAndroid Clientプラグインソース

そう、とうとうプラグインの動作を変更するところに手を染めてしまった。
といっても、128bit UUIDをハイフン区切りにするだけなのだが。
テンプレートは、ログを出す部分をどうするか迷いつつ、BleAdapterServiceのgetService()をpublicにした。
Activityから使いたいとなると、別パッケージになっても使えた方が便利だからだ。

 

やることはまだあるのだけど、読み書きについては流れがわかったつもりなので、シリーズは今回で終わりにしよう。

[bds]Characteristic設定を128/16bit設定は伊達じゃない

image

この赤枠で囲んだところの話だ。
UUIDの表示を16bitにするか128bitにするかなのだが、Pluginによるソース生成にも影響を及ぼしていた。

 

Android Clientの場合、名前として「サービスUUID_キャラクタリスティックUUID」という文字列を作ることがしばしばあるのだが、そのキャラクタリスティックUUIDが16bitで出力される。
しかし、内部では128bit前提になっているため、いろいろ一致しないでnullになったりしていたのだ。

 

Nordic Serverの場合も似たようなもので、service_init()で16bitとして処理しようとするためか、サービスのUUIDは128bitで、キャラクタリスティックのUUIDは16bitにしようとしてしまい、変なことになる。
何というか、Generic Access ServiceのUUIDをベースにした128bit UUIDになっているようだ。

 

不具合やん!と思ったのだが、ちゃんと登録した企業であれば16bit UUIDが使えるからね。。。
128bit UUIDで使いたいなら128bitで、16bit UUIDで使いたいなら16bitで、ということだ。
見た目だけじゃないってことですな。

[android][ble]BLE接続する (2)

前回はBLE接続しただけで終わった。

しかし、あっさり「BLE接続」と書いているが、どこまでシーケンスが進んだら「成功」と見なしているのだろうか?
それに、このconnect()は同期で結果が返ってきているのだけど、だいたいこういうのは非同期でやるものではなかろうか?

疑問が尽きないので、connect()が何をしているのか見てみよう。


BleAdapterService.java#L212

やっているのは、これ。

  1. BLEアドレスからBluetoothDeviceを取得(メンバ変数…じゃなくてフィールド)
  2. BluetoothDevice#connectGatt()

引数にcallbackとか着いているから、これは接続要求を出しただけだろう。
それならば、納得だ。

 

コールバックになっているmGattCallbackが、大きい。
L71~L170まである。
中で持っているのは、これら。

  • onConnectionStateChange()
  • onServiceDiscovered()
  • onCharacteristicRead()
  • onCharacteristicWrite()
  • onCharacteristicChanged()
  • onDescriptorWrite()
  • onReadRemoteRssi()

なんとなく名前でわかるようにしてあるところが偉いな。

Changedは状態変化通知、特にonCharacteristicChanged()はNotification/Indicationだろう。
Read, Writeは要求した結果が返ってくるだろう。Write Without ResponseでもonCharacteristicWrite()は呼ばれたと思う。

 

というわけで、connectGatt()したらonConnectionStateChange()が呼ばれるはず。
L80でサービスを見に行くようにしてあるのだろう。
そしたら、onServicesDiscovered()が呼ばれるはずだ。

さて、ではこれがどうやってActivityと会話しているかというと、たぶんMessage.obtain()というやつだろう。
前回、よくわからないままコピーしたものがいくつかあるのだが、ActivityにHandlerを追加しているのだ。

Handlerは、スレッド間通信のしくみらしい。
Handlerをnewした方がサーバになって、メッセージをobtain()したときだけhandleMessage()が実行される、ということだろうか。

ちょっと話が脱線するが、Androidアプリは同一プロセスで動いているんじゃなかったっけ?
スレッド間通信といっても、同じプロセス内だったら関数コールでもなんでもいいんじゃなかろうか。。。
mutexとか、そういうのをJavaがうまいこと隠蔽しているのかも。

ともかく、ログを入れて試したところでは認識通りのようだ。
connect()を呼ぶと、handleMessage()のGATT_SERVICES_DISCOVEREDまで呼ばれて終わった。

 

元のBDSサンプルでは、数字を打ち込むTextViewとWriteやReadのButtonが並んでいる画面が表示される。
接続するまでは非アクティブで、接続するとアクティブになる。
そういった処理をこのhandleMessage()でやるのだな。

わかった気がする。

 

では、次回はCharacteristicのアクセスについて見ていこう。
BDSサンプルだと、UUIDをxml側に書いているから、そこをどうするか考えないとねぇ。


コールバックされる内容と、Activityへの通知方法がわかったので、なんとなくだが理解の半分くらいまで届いたんじゃなかろうか、という気がしている。

この「気がしている」という感覚が大切で、わからんわからんと思っているだけだと、けっこうつらいのよね。。。
長いことこういうお仕事してるけど、いつかは「わかった!」という瞬間が来るはず、という自信が支えているところはあるねぇ(偽りの自信かもしれんが)。

2016/06/10

[android][ble]BLE接続する (1)

ようやくここまで来たか。。。

 

ベースとしているBluetooth Developer StudioのAndroid Clientプラグインでは、一覧を表示するActivityとBLE操作をするActivityが別になっている。
その際、BLE操作するActivityに渡しているのは、以下。

  • デバイス名
  • デバイスアドレス

これだけなのだ。
BluetoothDeviceの中身をまるまる渡すようなことをしているのかと思ったが、そうではないのだな。

そして、接続時にやっているのは、これ。

BleAdapterService mBluetoothLeService;
mBluetoothLeService = ((BleAdapterService.LocalBinder)service).getService();
mBluetoothLeService.connect(mDeviceAddress);

まとめて書いたけど、実際はServiceConnectionというサービスをnewしてoverrideしたonServiceConnected()の中でmBluetoothLeServiceを取得して、接続ボタンを押したonConnect()の中でBleAdapterServiceのconnect()を呼んでいる。

このconnect()はプラグインが提供しているBleAdapterServiceクラスが持っている。
他にもBleAdapterServiceは便利そうな関数を持っているから、これを使うとよいのだろう。


考えることはいろいろあるが、まずは接続だけしてみる。

 

よくわからないが、connect()にはBleAdapterServiceがいるので、関係するものをBDSサンプルから持ってきた。

ActivityのonCreate()あたりでbindService()するようだ。
AndroidのServiceは、バックグラウンドで動いてくれるしくみらしい。
はじめ方としてstartService()とbindService()の2種類あるらしいが、元のまねをしてbindService()にする。

bindService()するのに、ServiceConnectionのインスタンスがいる。
これもよくわからんが、ServiceConnectionのインスタンスを作るときにoverrideするonServiceConnected()でBleAdapterServiceを取ってきている。

 

これだけ書いて動かしたのだが、onServiceConnected()が呼ばれない。。。
さんざん悩んだが、単にAndroidManifest.xmlに<service>を書いてやらんといかん、というだけだった。
エディタで「<service」くらいまで書くと、候補で勝手にクラス名まで選んでくれた。
そこまでできるんだったら、サービスの定義が入ってないのを指摘してくれたらよかったのに(ログは見てないけど)。


ともかく、これでmBluetoothLeServiceに中身が入った。
あとは、mBluetoothLeService.connect()にデバイスアドレスを付けて呼ぶだけだ。
前回作ったコールバック関数というかListenerではBluetoothDeviceを渡しているので、問題なく呼べる。

うん、接続できた。
スニファで見ても、Advertisingが止まった。

ここまでのソースは、こちら

 

うーん、BLE接続できたのに、感動が薄い。
単にBLE機器を接続しただけだからと言われればそれまでだが、一覧を出して選択するまでで感情を使い切ってしまったのだろう。

加工したい人向けにはこういうやり方でよいとして、BLE Peripheralの選択くらいまではOSが吸収してくれんかのぅ。
せっかく設定アプリがあるのだから、接続した後だけやりたいのだが。


と、ごちゃごちゃ書いたが、BLE接続だけしておしまい、というわけにはいかんだろう。
Characteristicへのアクセスもあるし、Notification/Indicationの取得もある。
それに、よくわかってないServiceもある。

 

Android開発に終わりはないのだ!というわけで、次はCharacteristicなどを見ていきましょうかね。

2016/06/09

[android]DialogFragmentから元のActivityに結果を返したい

くっ、BLE接続の前にもう1段階あった。。。

まあ、ダイアログ側でBLE接続をしてもよいのだけど、とにかくダイアログで行った結果を呼び出し元のActivityに返したい。

 

検索すると、いろいろ出てきはするのだけど、だいたい「やりたいことに対して実装が多いので簡略化する方法を考えた」というパターンが多い。

onActivityResult()なんかで返せるのかと思っていたのだが、そういう時代ではないみたいだ。


現状のソースはこちら

 

Activity側では、ダイアログをこの順番で表示させている。

  1. DialogFragmentをextendsしたクラスをnewする
  2. そいつをshow(getFragmentManager(), "xxx")する

DialogFragmentの説明を見ると、呼び出し元のActivityでイベントハンドラを作っておいて、DialogFragmentの方はgetActivity()したものをキャストしてイベントハンドラを呼び出している。

キャストするとちょっとガチガチすぎるので、interfaceとか作っておけばよいのかな。
あれ、でもgetActivity()で取ってこれるのは単なるActivityだ。
それがinterfaceをimplementsしているとかわかってくれないんじゃないか?

・・・エラーにならない。
こんなのでいいんだ。
なんか、Javaってこういうダウンキャストに対しては厳しいというイメージだったのだけど、不意に見せる優しさなのかね。
こういうギャップにぐっと来るのかもしれない。


BluetoothAdapterを渡すためにメソッドを追加したのだけど、どうもsetArguments()で渡す方がよいらしい。
Y.A.M の 雑記帳: Android Fragment で setArguments() してるサンプルが多いのはなぜ?

ほほう。
サンプルを見ると、DialogFragmentをextendsした方にnewInstance()を作って、その引数に入れてもらっているようだ。
そしてsetArguments()するところは呼ぶ人からは隠している。
よし、まねしよう。

と思ったのだが、私が渡したいのはBluetoothAdapterだ。
オブジェクトのようなものは、Serializableなどになっていないといけないみたいだ。
使いたいのはBluetoothLeScannerだから、それが得られるようなものであればなんでもよいのだけど。。。

 

しかし、getActivity()の存在を知ったので、そこからgetSystemService()してgetAdapter()すればよいことがわかるのだった。
つまり、今の時点では引数はいらないということだ。

 

今日までのソースは、こちら。
次回こそ、次回こそはBLE接続のはず。。。

https://github.com/hirokuma/AndroidTwoSpinners/tree/618c641d9b6c6b925dc9853b2b416749aa6b3726/app/src/main/java/com/blogpost/hiro99ma

[cygwin]ldでIs a directoryがしばしば出るが、ジャンクションのせいかも

Windows10 Pro 64bitにCygwin 64bit版をインストールしている。
以前、32bitにしてうまく動かなかったので、64bit版にしたのだ。

リンクのldで、「cannot open output file」で「Is a directory」がしばしば出る。
-o は「ディレクトリ/オブジェクト名」という構成。
動かし方は、シェルスクリプトの中でmakeを呼んで、makeの中でgccしている。

毎回出るならともかく、エラーの頻度が高いとはいえ発生しないときもあるのだ。
別にgccでビルドしているだけで、特殊なことはしていないのに。。。

 

 

特殊なことはしていない、と書いたが、そういえばジャンクションしているフォルダだった。
CドライブがSSDでDドライブがHDDで、本体は大きいのでDドライブに置いているのだが、いろいろな都合でCドライブで処理をしたかったため、ジャンクションを張ったのだ。

 

もしや、とDドライブの方で実行すると、エラーも出ずに終わった。
まだ1回しかやっていないので、はっきりとは言えないけれども、ちょっとあやしいかもしれんね。

[nrf51]デフォルトでBLEのNotificationを有効にすることができない場合がある

地味な記事なのにアクセス数がそこそこ多いので、もしかしたらAndroidのNotificationで検索に引っかかってしまうことがあるのかも。
ごめんなさい、そっちじゃないです!

 

BDSのサンプルでBleAdapterServiceというものがあるのだが、そこにsetNotificationsState()はあってもgetする方が無かった。

CCCDってReadできるのだけど、なんでsetだけ別APIになってるのだろう?
readDescriptor()がないのかと思ったが、そうでもない
めんどうだっただけかもしれない。

 

そこでPeripheralの方がどうなっているか気になった。
CCCDのデフォルト値ってどうなってるんだろう?

Is there any to set CCCD's default value to be 1(i.e. enabling the notification of an attribute)? - Nordic Developer Zone

デフォルト値は設定できないんだ!
へー。
Bondingしている場合はCCCDの値を保持する仕様になっているから、できるらしい。

そして、S110の用語で「system attribute」はCCCD値のことを表しているんだと。
へー!
以前わからないままになっていたのが、ここでわかるとは。。。

2016/06/08

[android]ダイアログ上のラジオボタンの何番目が選択されているのか?

BLE接続しようとしたのだが、もう1つ手前があった。
今回はラジオボタンで選択したあと、接続ボタンを押してBLE接続させようとしている。
ダイアログ上のラジオボタンの何番目が選択されているのかを知らねばならぬのだ。


これが前回までのソース。
PeripheralSelectDialogFragment.java
選択されている情報を取得したいのは56行目

 

ラジオボタンを選択するためにタップしたときは、OnItemClick()を通るのだろう。
そこの引数にpositionがあるから、それを保持するようにしておく、ということもできるだろう。

でも、そこの中を見る限りではListViewっぽいので、ListViewをfindViewById()で取ってきてgetCheckedItemPosition()を使う、ということもできそうな気がする。

この2つしか思いつかなかったので、やってみよう。


タップした位置を覚えておく場合は、onItemClick()からmOnCheckboxClickListener.onClick()が呼ばれるようだ。

ああ、これがAlertDialogにsetOnItemSelectedListener()したら呼ばれるやつか。
・・・呼ばれんやん。
呼ばれているのはsetSingleChoiceItems()でoverrideしたonClick()だけだ。
ソースは、前のdismiss()する手前のところだ。
つまり、第2引数がpositionだ。

int型のprivate変数でも用意して、onClick()の第2引数を覚えるようにしておき、setPositiveButton()のonClick()で使うようにすればいけるだろう。

 

次は、ListViewそのものを取ってくる方法か。
dismiss()とかで見ているAlertParamsクラスもListViewを使っているけど、dialog.mListLayoutなんか使って取得しているから私のところでは無理な気がするのよねぇ。

あ、AlertDialogにgetListView()なんてものがある。
onClick()の引数はDialogInterfaceだから、

ListView lv = ((AlertDialog)dialog).getListView();

でListViewが取ってこれるようだ。
lv.getCheckedItemPosition()とすると、positionと同じものが取れているように見える。
「見える」としているのは、確信が持てないからだ。
BLE端末が2つしか無いのだけど、今のところはこれでうまくいっている。


今回までのコミットはこちら。
PeripheralSelectDialogFragment.java

次こそ、次こそはBLE接続を。。。

2016/06/07

[android]AlertDialogをsetAdapter()しても閉じたくない

前回の続き。

ダイアログに表示される項目をラジオボタンにすることはできたのだが、AlertDialog.BuilderにsetAdapter()するとタップしてダイアログが閉じてしまう。
これをsetSingleChoiceItems()でやると、閉じなかった。

実装を見てみよう。


setSingleChoiceItems()は、こんなやつ
setAdapter()は、こんなやつ
違いは、p.mCheckedItemとp.mIsSingleChoiceだ。
p.mCheckedItemはどのアイテムを選択状態にして開始するかだから、たぶん関係ないだろう。

となると、p.mIsSingleChoiceか。
AlertControllerのここがあやしいな。
どうたどったらここに着くのかわからないが、BuilderのpはprivateだからmIsSingleChoiceを設定することはできないだろう。

見た感じ、悪さをしそうにも見えないから、もうこれでいいんじゃないかね。
まっとうにやるなら、同じようにonItemClick()なんかをやってやるんだろうけど、これはcreateListView()の中だから、ListViewの生成からやってやることになるのか。。。

よし、やめよう。
1つだけ選択したいしたいという気持ちは、setSingleChoiceItems()も私も一緒だから、大丈夫なはずだ!

 

今のところ、ここまで。
https://github.com/hirokuma/AndroidTwoSpinners/tree/72528714e76ac02881104ee74c0b046ecb1934a9

ようやく次回は、BLE機器の接続になるかな。

[android]私のAndroid Studioは2.1.2じゃなかったのか??

Android Studio 2.1.2のstable版が出たのでアップデートしますか?というようなダイアログが出たので、はいはいと進めた。

が、私は今まで2.1.2を使っていたはずなのだけど、違ったのだろうか?
ダイアログには「2.1.2」と出ていたから、てっきりそう思っていたのだが。。。
こちらは、アップデート後のバージョンだ。

image

前が無いので、どうにもわからん。。。
アップデートしたらしたで、プロジェクトを開いたらGradle Pluginのバージョンが古いのでアップデートしますか?と聞かれるし。

 

もしかして、β版もアップデート対象にしていたのでは、と思ったが、そうでもなさそうだ。

image

 

 

大丈夫なのだろうとは思うが、ときどきビルドできないくらい内容が変わることがあるから、せめてビルドできているときのバージョンは把握しておかんといかんだろう。

ほら、私もAndroidのお仕事もちょっとくらい取ってこれるようになろうとしているではないか(気付かないと思うが、しているのだ)。
組み込み側はやるけど、スマホ側ができないので、ちょっと損している気がするのだ。
もしスケジュールに余裕があるなら「どっちもいけますよ(iOSはやらないけど)!」と、仕様を把握する期間を減らして安くできるという点をアピールできるんじゃ無かろうか、と目論んでいるのだ。

組み込み側は、だいたいコンパイル環境などは決め決めでやるのでソースから同じバイナリはできるのだけど、スマホ側ってなんとなく不安だ。
ダウンロードしてくるものがあると、それがダウンロードできなくなったらどうしよう、とか、バージョンが変わってしまったらどうしよう、とか、そういう心配が出てきてしまう。

まあ、スマホのアプリって、組み込み機器みたいに同じバージョンが長生きすることも少ないだろうし、組み込み機器だってDFU載せてバージョンアップしやすくなっていることを考えると、ビルドできる間はバージョンアップし続ける必要がある、ということになるのだろうか。。。

おそるべし、IT業界!

2016/06/06

[android]ダイアログがリストだが、ラジオボタンには自分でせんといかんのか

前回は、ここまでだった。

image

確か、私の目論見では、ここでラジオボタンタイプの表示が出てきて、どれかを選択してボタンを押す、だったと思う。

別にこれでも目的は果たせるのだが、せっかく目標を立てているのだから、解決した胃ではないか。


たぶん、BaseAdapterをextendsしているクラスで、getLayoutInflater().infrate()でandroid.R.layout.simple_list_item_1を渡していて、このsimple_list_item_1が持っているのがTextViewだから、ラジオボタンじゃなくて文字だけになってるんじゃないだろうかね。

ソースだと、ここ
L.114もTextViewなのだけど、これは代入するだけなので、元がラジオボタンになってくれなくては。

ダイアログを作るとき、「setSingleChoiceItems()」を使っているけど、実は自分でAdapterを指定してるから、意味をなしていない気がする。。。
ネットでsetSingleChoiceItems()を使った例を見るとラジオボタンになっていたからね。

 

じゃあ、私もここで、simple_list_item_single_choiceをレイアウトとして使えばよいのではなかろうか。

image

おー。
出た出た。
見栄えがこれでよいのかどうかは別として、期待通りだ。
setSingleChoiceItems()も、setAdapter()に変更して、影響がなかった。

 

が、アイテムをタップするとダイアログが閉じてしまう。。。
setCancelable(false)も効果ない。
setOnItemSelectedListener()しても効果ないというか呼ばれない。

あ、setAdapter()からsetSingleChoiceItems()に戻したら閉じなくなった。
なんか、まっとうな修正方法ではないという感じだけは伝わってくるのだけど、回避方法がわからん。

[hw]ZEROPLUSロジアナのカーソルのつかみ方

すごく今さら感が強いのだが、数年使ってようやくZEROPLUSロジアナのカーソルを期待通りにつかむ方法がわかった。

カーソルと呼んでいるのは、このAとかBとかだ。
ツール上はBarと書いてあるので、A BarとかB Barが正式名称だろう。

image

 

画面に見えているときは、マウスカーソルを持っていくと手の形になるので、つかんで移動させて、離せばそこに設定される。

image

ただ、いつも画面の中にあるわけはなく、スクロールして画面外にある場合は、端っこにちょろっと見えている。

image

こういう状態でも、同じようにマウスカーソルで動かすのだが、どのカーソルが持ち上がるのかがよくわからなかったのだ。

最近気付いたのは、マウスカーソルの位置をちょっと下にずらしたりすると、つかむ予定のカーソル名が出てくるようだ、ということ。
これは、Bをつかめる場合。

image

ただ、ここで私がAを使いたかった場合にどうしたらよいのかがわからなかった。
だから、スクロールしてAが表示されるようにして、がんばってドラッグして移動させていたのだ。

 

しかし先ほど気付いたのだが、グレーの濃い部分をダブルクリックするとダイアログが表示されるのだ。

image

これで、Aを選択すると・・・・

image

おお、Aがアクティブになった!
やるやん、私!!


と思ったら、右クリックして「Place」を選ぶと、好きなカーソルを右クリックした場所に配置させることができた。

image

・・・。
Go ToとAdd Barは見てたんだけどねぇ。。。

ツールはいろいろ使ってみないとダメですな。

2016/06/05

[esp8266]NFCIDをFirebaseにPOST

せっかくFirebaseにPOSTすることができるようになったので、前回作ったNFCIDを取得してTCPで送信するやつをFirebaseにPOSTするようにした。

hirokuma/esp8266_rcs620s at ce246cc89cd207f4f3cc2ddde72f8f36a417f66e

image

あまりちゃんと作っていないので、コンソールにはログがいろいろ出てしまうし、RC-S620/Sをうまいことやってないのでエラーが出てしまう。
InListPassiveTargetでカードがなかったときの処理がよくないのよねー。

また、Firebaseからのレスポンスを待っていないので、それまでに送信してしまうとあまりよろしくないようだ。
POSTの結果はあまり大きなサイズではないので、全部受信をためてから解析してもよいかもしれない。

 

せっかくなのでESP8266のDeep Sleepも使ってみようかと思ったが、復帰させるトリガを持っていないのでやめた。
Initiatorだと搬送波をガンガン出すことになるし、カードが近づいたらDeep Sleepから復帰させたいとしてもかなり時間がかかると思うので、この使い方だとDeep Sleepさせても仕方なかろう。

Deep Sleepの解除は割込みかタイマだったと思うので、あまり応答性がよくなくてよいか、定期処理のときに使うのがよいのかな?

2016/06/04

[android]FirebaseのNotificationを受けるアプリも作りたいが、うまくいかん

うまくいかんシリーズだ。

 

ESP8266からPOSTしたら、たぶんNotificationしてAndroidアプリで受け付けることができるんだろう。

よくわからんなりに、Firebaseのコンソール画面から左側の「Notifications」を選び、Androidを選択。
そして、適当に項目を埋めた。
そのときにアプリのパッケージ名がいるようなので、同じ名前で作る。
親切にも、Android Studioでどうしたらいいかのガイドが出てくるので、従う。

  • ダウンロードしたJSONファイルをドラッグ&ドロップ
  • build.gradleを、プロジェクト直下とapp内の両方を書いてあるとおりに変更

が、送ってこないね。。。

こちらを見ると、「firebase-core:9.0.2」も追加するようなのだけど、見つからないと怒られる。
Add Firebase to your Android Project  |  Firebase
「firebase」でライブラリを検索するといくつか出てくるのだが、「firebase-core」だと出てこない。。。

 

あ、やりたいことによって追加するライブラリが違うんだ。
https://firebase.google.com/docs/android/setup#available_libraries

と思ったが、そもそも候補に出てこないのだ。

 

ん?

image

2.2 preview?
でも、これはInstant Runだけの話だよなぁ。
Google Play services 9.0.2以降がインストールされていればよいっぽいのだが。

あら、今気付いたが、インストールしているGoogle Play servicesのバージョンは「30」だ。

よくわからんが、Standaloneの方のSDK Managerを使ってインストールして再起動すると、エラーが出なくなった。
まあいいや。

compile 'com.google.firebase:firebase-messaging:9.0.2'

を追加した。
コンソール画面で出てくる説明は、全体としての説明で、ここのサービスについては必要なライブラリを組み込むことになるようだ。
まあ、1行だけなんだけどね。

 

が、Notificationは飛んでこない。
JSONファイルは読んでいるようなのだが、signature not valid、なんて言われている。

うーん。。。

android - GoogleSignatureVerifier signature not valid message (not using the google maps api) - Stack Overflow

「バグで偽のログが出てるけど、動作には影響しないので修正するまで我慢して」と言っているのかな?


アプリを新しく作って、build.gradleに埋め込んでおけば通知バーに何か出てくるのかと思ったけど、そんなに甘いものじゃないのかな?

Receive and handle notifications

フォアグラウンドで動くときは何もいらないかと思っていたけど、何か追加しなさいと言っているな。。。

FirebaseMessagingServiceをextendsして、onMessageReceived()をoverrideしてログを出すようにしてみたが、呼ばれんな。


あれ、Nexus5で動かなかったのでNexus7でやっていたのだが、いま見たらNexus5の方にはNotificationが出ている。

image

Nexus5の方は、まだFirebaseMessagingServiceなどを追加する前だ。
じゃあ、別に今のままでもよいということか?
ううーん。。。


送信して1時間ほど放置したが、届かなかったので、Nexus5を再起動させた。
そのおかげかどうかわからないが、送信したら届くようになった。

・・・あら、来ない。
次に送ったら、来たけど、受け取れてないのもあるな。
うーん。。。
本体がスリープ状態(LCD消灯)してるけど、電源ボタンを押すと通知が来ているのよね。

通知が受け取れると言うことは、アプリは別にこのままでよいということなのか?
わっからーん。

[esp8266]FirebaseからincludeSubDomainsが返ってくるが、そういうものだ

前回、ESP8266からFirebaseへPOSTした。
200OKは返ってくるのだけど、JSONデータが載っていない。

HTTP/1.1 200 OK
Content-Length: 31
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache
Strict-Transport-Security: max-age=31556926; includeSubDomains; preload

 

まだなにか足りないのだろうか?
なんとなく、Content-Typeにcharsetを追加して見たのだが同じだった。
うーむ。


curlに--verboseを付けるとヘッダも見えるということで、やってみた。

 

$ curl --verbose -X POST -d ~~~
(中略)
< HTTP/1.1 200 OK
< Content-Length: 31
< Content-Type: application/json; charset=utf-8
< Cache-Control: no-cache
< Strict-Transport-Security: max-age=31556926; includeSubDomains; preload
<
* STATE: PERFORM => DONE handle 0x600057850; line 1955 (connection #0)
* multi_done
* Connection #0 to host xxxx.firebaseio.com left intact
{"name":"-KJONbf8kDuFbF5AOcVs"}

同じやん。

順番を見ると、このincludeSubDomainsのあとでユニークIDが送られてきている。
つまり、私が初回のTCP受信ですぐに切断しているのがだめなのだな。

----------
HTTP/1.1 200 OK
Content-Length: 31
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache
Strict-Transport-Security: max-age=31556926; includeSubDomains; preload


----------
{"name":"ふにふに"}
----------

はいはい、私が悪うございました。

ちゃんとContent-Length分を取得するまでは受信を止めてはいけないということだ。
かといって、データが全部来るとも限らないからタイムアウトの設定もいるだろうな。

この辺りをArduinoでやると楽なのかもしれん。
ESP8266のNon OS向けHTTPクライアントライブラリがあるといいのだけどね。
まあ、POSTするだけだったら、失敗しても気にしないということにして受信は読み捨ててしまうというのもありだとは思う。

[esp8266]TLS/SSLの接続はできるが送信後に失敗したのは、私が悪かった

FirebaseのデータをPOSTするやり方がわかったので、あとはESP8266でやるだけだ。
TLS/SSLのサンプルをまねしてやってみよう。
NONOS SDK v1.5.3だ。


client handshake start.
client handshake ok!

うん、接続はうまくいっている。
が、espconn_secure_send()して、送信完了コールバックの後くらいにESP8266がエラーログを吐いている。

client's data invalid protocol
Error: SSL error 3

うーん。
ESP8266でたまに困るのだが、ESP8266自体がログを出すけれども、それが何なのかよくわからないことがしばしばある。
今回も「SSL error 3」と情報は出ているのだが、3が何なのかよくわからん。


ESP8266のTLS/SSLライブラリはaxTLS Embedded SSLのはずだから、ここを見ることにする。

「invalid protocol」で検索したが、出てくるのは「invalid protocol message」の方だった。
ということは、メッセージ自体はaxTLSではなく、アプリ側で出力させているということか。
一応、ライブラリの方も見ておこう。

$ find ./ -name "*.a" | xargs strings -f | grep  "invalid protocol"
./libssl.a: client's data invalid protocol
./libssl.a: server's data invalid protocol
./libssl.a: invalid protocol message

あー、これじゃわからんではないか。

 

では、もう片方のメッセージ「SSL error」で検索しよう。
こちらは、tls1.cで、ssl_display_error()に渡されたエラー値をマイナスしているだけだ。
SSL_OKなどと比較していることからすると、エラー値の3はこれか。

#define SSL_CLOSE_NOTIFY                        -3

クローズされたっていう通知で、その理由を示すものではないのね。。。


サーバに切断されて、その理由が「invalid protocol」ということか。
Handshakeはうまくいっているから、証明書とかの問題ではないと思うのだが。。。
まさか、TLSのバージョンとかか?
でも、それだとHandshakeがうまくいかないと思う。

一応Wiresharkで見ることにした。
Windowsだと見えないので、VirtualBox上で確認。。。
ネットの情報を見ながらやってみたが、そんなに悪くない気がする。

curlに、--tlsv1.1を付けても成功するし、--tlsv1.0でもいけた。
Cipher SuiteもESP8266で対応しているTLS_RSA_WITH_AES_256_CBC_SHAを指定したが、ちゃんと動いた。

curl --tlsv1.0 --ciphers AES256-SHA -X POST -d '{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json?auth=ごにょごにょ'

まあ、Handshakeできてるからねぇ。


ここまでやっておいてなんだが、JSONとしてPOSTしているつもりなのだが、ヘッダが足りないとか、単純なところでサーバがはじいているだけじゃなかろうか?

あ、Content-Lengthを指定してないや。。。

 

POST /rest/saving-data/test.json?auth=ごにょごにょ HTTP/1.1
Host: xxxx.firebaseio.com
Accept: */*
Content-Type: application/json
Content-Length: 68

{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}

はい、OKです。。。


今回の反省

  • もっと自分を疑ってよい!

 

以上でございます。

まあ、ESP8266がSSLエラー3を出してきたときはサーバ側で切断されているから、送信しているフォーマットが間違っていないかどうかを確認するといいんじゃないの、というところかね。

 

ソースは、こんなのです。
hirokuma/esp8266_firebase_post: ESP8266 NONSDK v1.5.3 Firebase POST

Firebaseをcurlであとちょっとだけ試す

あとちょっとだけ、ね。

 

認証有りにしたいのだ。

authentication - Firebase server side auth with HTTP POST request using the firebase secret - Stack Overflow

 

ああ、jsonの後ろに「?auth=認証シークレットID」を付けるだけか。

うん、できた。


しかしこれ、全部のセンサー側が同じ認証シークレットIDでよいものだろうか?
1つ解析されてしまったらおしまいではないか。
クエリー文字列って、平文で渡されそうな気がするので心配だ。

 

まずは、クエリー文字列はどうやって渡すのか調べておこう。

GET /index.html?query=xxx HTTP/1.1\r\n
Host:www.yahoo.co.jp\r\n
....

HTTPの場合は、普通に載せればいいようだ。
FirebaseはHTTPSだから。。。どうなるんだ?

SSLでHTTPメッセージはどの部分が暗号化されるの? - QA@IT
ヘッダも暗号化されるらしい。
セッション層の暗号化だから、それより上側にあるアプリケーション層のHTTPは暗号化されるということか。

よかったよかった。

2016/06/03

Firebaseをcurlでもう少しだけ試す

つらつらと考えていたが、こっち側(センサ側)はデータをアップするだけだろう。
だから、クエリーの方法はサーバのことをやる気分になったときでいいや。

なので、もう少しだけデータのアップ方法を見ておこう。
私にとってサーバを立てるというのは、それはもう大ごとなので、簡単にやってもらえるのであればもうちょっと使ってみたいのだ。


Saving Data - Firebase

  • PUT
    • データの置き換え
  • PATCH
    • データの一部更新
  • POST
    • リストやデータの追加
    • クライアントは「messages/users/<unique-id>/<data>」のようなユニークIDを生成する
  • DELETE
    • 消す

 

PUTは、前回試したやつだ。
PATCHは、updateとあるから、一部だけ変えるのだろう。
DELETEも試したので、消すだけだとわかる。
問題はPOSTだ。

リストを保存する例だけ見ると、PUTがPOSTになっただけだ。
しかし、戻ってくるデータにユニークIDが含まれている。


やってみよう。

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLSkVZbAkcX4hMjxEd"}

image

うん。
PUTしたときはtestの下にtest1があったけど、これはユニークIDが挟まっているな。
push IDと書いてあるけど、どう使えばよいのだ?
素直に考えると、階層が深くなるだけなのだが。。。

$ curl -X POST -d '{"test2":
{"name":"yoko","date":"11/22"}}' 'https://xxxx.firebaseio.com/rest/saving-data/-KJLSkVZbAkcX4hMjxEd/test.json'

{"name":"-KJLUx5Rr_yCiIadZVLi"}

image

ああ・・・そうなるのか・・・。
そうなるよな。

では、何も考えずに同じPOSTを2回やってみよう。

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLVqUQNYqMzWKrwGp_"}
$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLVyLX0T7_y-8dMvBM"}

image

まあ、ユニークIDは毎回発行されるから、こうなるわな。


このIDは、時間情報があるわけでも無いと思うので、サーバ上には別々の場所に保存されてありがたいのだが、到着した順番はわからんだろう。

となると、どういう風に使う目的なのだ?
JavaScriptでは、これをpush()というAPIでやるらしいから、何かPUSH系のメッセージという扱いのように見えるのだが。

 

今頃知ったのだが、このFirebaseって今年のGoogle I/Oで発表されたばかりなんだな。
新しいドキュメントはGoogleで、前のドキュメントは買収前ということか。
Google I/O 2016 で「Firebase」の新バージョンが発表!プッシュ通知機能を iOS アプリで使ってみた | Developers.IO
ということで、プッシュ通知というのも売りとなる機能らしい。

iOSのNotificationとか出てくるから、これはスマホにとってのプッシュ通知用ということか。
そこは、もう少しスマホ側のアプリに慣れてからにしよう。


".sv"というキーを使うと、サーバ側の値が使えるらしい。
今はtimestampだけのようだが、これはありがたい!

 

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03",".sv":"timestamp"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLZhC49FhjrRANKHdq"}

image

 

えー、他の項目と一緒にやってくれないのかい。。。
これならどうだ?

 

$ curl -X POST -d '{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.c
om/rest/saving-data/test.json'

{"name":"-KJL_u_efy7lsP-O_Hxy"}~$

image

やれやれだぜ。

センサー側がどういう時間で思っていようと、サーバ側が受け取った時間で管理すればよい場合が多いから、これでよいだろう。
そうすると、RTCとかなくてもいいしね。
まあ、これを使うとなるとHTTPアクセスするくらいだから、NTPとかやってしまえばいいのだけど、時間みたいな繊細な情報は扱いたくないのだよ。

 

ともかく、こういう時間情報が値に含まれることで、ユニークIDがどうであっても時系列は把握できるだろう。
あとは、自分が誰か、というセンサーIDみたいな値があれば、クエリーで引っ張ってきやすいんじゃなかろうか。

Firebaseをcurlで少しだけ試す

Googleに統合されたFirebaseを使ってIoTとスマホの連携を実践する ~ BRILLIANTSERVICE TECHNICAL BLOG

 

よくわからんが、ESP8266でもできるらしい!
と思ってやってみようかと思ったが、Arduino版か・・・。

curlでやれるようなので、少しだけ試してみよう。


上記の記事を見ながら、「プロジェクト登録」と「FirebaseのプロジェクトURLと認証シークレットIDを取得」までやっておく。

 

まず、データの書き込みをやってみる。
が、認証がいるらしい。。。
めんどうなので、認証無しでやれるようにしておく。
テストだからそうしているけど、危ないだろうね。。。

image

FirebaseのRulesを理解する - Qiita
理解はできてないのだけど、"true"にすると認証が不要ということはわかった。

試しに、同じページにあるシミュレータでやってみると、認証済みをオフにしていても実行は成功する。


Saving Data - Firebase

データをサーバに保存してもらうにはいくつか方法があるらしいが、PUTが簡単そうなのでそれをやってみよう。

 

$ curl -X PUT -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://XXXXXXXX.firebaseio.com/rest/saving-data/test.json'
{"test1":{"date":"06/03","name":"hiro"}}

 

URLのところは、上記で取得したプロジェクトURLというやつになる。
ブラウザで見ると、こうなった。

 

image

JSONのところは、整形するとこうなる。

{
  "test1":{
    "name": "hiro",
    "date": "06/03"
  }
}

test.jsonというファイル名として保存されたということかな?

もう一度、JSON部分のパラメータだけ変更して実行すると、test.jsonは上書きされていた。
なので、追加したかったら既存のに足すか、JSON名を変えるのだろう。


Retrieving Data - Firebase

こっちは読む方だろう。

$ curl 'https://XXXXXX.firebaseio.com/rest/saving-data/test.json?print=pretty'
{
  "test1" : {
    "date" : "06/03",
    "name" : "hiro"
  }
}

orderByを試そうとしたが、Indexが定義されていない、というエラーが返ってくる。
Indexがいるとか何とか。


ドキュメントは、こっちが新しいようだ。
でも、古い方がREST APIなんかは探しやすいかも。
Firebase Database REST API  |  Firebase

 

では、最後に削除して終わろう。
サーバ側のrulesも忘れず元に戻しておこう。

$ curl -X DELETE 'https://XXX.firebaseio.com/rest/saving-data/test.json'
null

[android]ダイアログにListViewを載せよう - 後編

support.v4とかsupport.v7の数字はAPIバージョンかと思っていたのだが、履歴を見るとv7にAPI19以下向けが追加されていたので、言い切れないと思った。

Support Library Revision History | Android Developers

 

試しに全部外してみたのだが、Android 6.0以降で必要となる位置情報許可の確認で使うAPIが、v4かAPI23以降しか無さそうだった。
そう考えると、この数字は「APIレベルXX以降」ではあるのだが、XX未満というのがわからないのだな。
ActivityCompat.checkSelfPermission()も、API4以降だったら使う機会があるということなのか。

SDKのMINを設定して、それでもコンパイルが通るならsupportは使わなくてもよいと考えて良い気がする。


ダイアログの表示はできるようになったので、次はリストの表示だ。
リストを追加する

データが固定の場合は、こういうイメージでよいのかな。

image

データの見せ方はAdapterViewから派生したListViewなどが制御する。
AdapterViewが扱うデータはAdapterから派生したArrayAdapter<T>などが持つ。
まあ、持つといっても、実際はnewするときに引数で配列を渡すくらいだろう。

DialogFragmentの場合は、AdapterViewに相当する部分はDialogFragment側が準備してくれるので、データを準備してsetSingleChoiceItems()などで渡してやるとよい。

内容が可変でないならば、string-arrayで準備するのもよいだろう。


さて、内容が可変で、動的に更新したい場合はどうだろうか。

幸い、BDS Clientの生成ソースは通常のListViewを動的に更新するようになっているので、まねをするとよいと思われる。

BaseAdapterをextendsしたclassをまるまる持ってきた。
これのインスタンスをsetSingleChoiceItems()に設定すると、とりあえず動いた。
image
接続の実装は、次回だ。


あとは、画面外をタップしたらダイアログが閉じるのだけど、そのときにScanを停止させたい。

builderにsetOnCancelListener()というのがあったのでOnCancelListenerをnewしてみたのだが、ダイアログ表示時に落ちてしまった。
ドキュメントを読むと、onDismiss()をオーバーライドすると呼ばれるそうだ。
適当にやっちゃいかんな。

public void onDismiss(DialogInterface dialog);
うん、呼ばれた。

ここまでをコミットした。
サンプルそのままの箇所も多いが、数日前よりはましになったんじゃないかね。
hirokuma/AndroidTwoSpinners at 6136b8111d9978b800b4a68d1d90d3775cc1b42a

[android]ダイアログにListViewを載せよう - 前編

前回、BLE Peripheralを接続するにはユーザに選択してもらうのが一番よさそうだ、という結論に至った。
BDS ClientではListViewにデバイス名が並ぶようになっているのだが、

  • 文字を表示する
  • 何台あるかわからない
  • ユーザが選択できる

という条件になると、ListView形式くらいしか思いつかない。

もし、Peripheralの位置がはっきりわかるのであれば、地図風に表示させてタップするというやり方もあるだろうが、BLEだと位置も方向も距離もはっきりしない。

Spinnerも近いものがあるのだけど、台数が後から増えるので、期待するデバイス名があるかどうかを毎回閉じて開いてで確認するのは使いづらかろう。

Buttonが増えていってもいいけど、ボタンはあまり動的に増えたり減ったりするのはよくないように思うのだ。
物理的なボタンがそうだからだろうか?

もう、ListViewでいいよね?となってしまう。
ListViewにするのはよいとして、なるべく既存のActivityを変更せずに使えるようにしたい。
理由は、その部分だけコピーして使い回したいからだ。
Scanの開始までを独立させるのには成功していないのだが、そこまでであれば私でもコピーで対応できそうな気がする。
でも、既存のUIを変更させてListViewを追加するのは難しそうだから、ダイアログにして上にかぶせてしまえば、UIはそのままでいけるんじゃなかろうか。


古い記憶だが、Androidのダイアログは生成方法が途中で変わったような気がする。
きっと、今も変わり続けているに違いない。

ダイアログ | Android Developers
すごい、本家で日本語ページがある!
以前は日本語ページは情報が古かったので見ていなかったのだけど、自動で日本語ページが表示されているから、更新されるようになったのだろう(以前は、表示言語の選択ができた気がする)。
時代は変わっているんだねぇ。

 

デザインのガイドラインはこちらを読め、とのことだった。
Dialogs - Components - Google design guidelines
長い。。。
目次はこうなっている。

  • Behavior
  • Alerts
  • Simple menus
  • Simple dialogs
  • Confirmation dialogs
  • Full-screen dialogs
  • Specs

最後のSpcsは、ダイアログ自体の仕様みたい。

最初のBehaviorは、ダイアログはこういうものですよ、という振る舞いの説明だろうか。

Alerts、Simple menus、Simple dialogs、Confirmation dialogsは「ダイアログタイプ」のようだ。

そして、Full-screen dialogsはモバイルのみと書かれている。モバイル以外ってなんだろう?
うちのNexus7でも表示されているし。でも、Nexus7はモバイル扱いかもしれない。
Pixel Cとかだと除外されるのかな。

 

今回のイメージは、Confimation dialogsになるだろうか。
ユーザの明示的な確認を行うことになるからだ。
しかし、これだとOK/Cancelボタンを押さないといけなくなるみたいだ。
戻るボタンやCancelの場合は何もせずにダイアログを閉じるように、と。

デバイス名をタップするだけじゃなくて、ラジオボタン式にするのか。。。
うん、間違って触ってしまって接続するよりはよいのかな。


ダイアログのガイドラインを読んで、イメージが沸いたので、実装していこう。

Android Developerのページを読むと、AlertDialog+DialogFragmentを使う説明が書かれているから、それがよいのだろう。

Fragmentというと、ハードディスクとかのフラグメンテーションを思い起こしてしまうのだが、Androidでの意味は「小部分」のようなイメージみたい。
フラグメント | Android Developers
挙動を小部分に分けて、組み合わせて作り上げる、ということができるようだ。
その小部分は、Activityに由来したものを指すらしいので、UIに関する小部分、になるのかな。

試しに、サンプルソースをそのまま組み込んでみた。
Android Studioが、AlertDialogで「どっちにする?」と聞いてくるので、v7とついている方にしてみた。
classicとか書いてあるので、古いバージョンでも使えるようにとか、そういう意味なんじゃなかろうか。

今回は、Android5.0(API21)を下限にしている。
Windows版のAndroid Studioってバージョン表示が切れててわからんぞ。

image

名前だけPeripheralSelectDialogFragmentにして、まねしてみる。

DialogFragment dlg = new PeripheralSelectDialogFragment();
dlg.show(getSupportFragmentManager(), "fire");

Android Studioに怒られる。。。

Cannot resolve method 'show(android.support.v4.app.FragmentManager, java.lang.String)'

このgetSupportFragmentManager()で取得できるのが、v4ということなのだろう。
show()が期待するのは、FragmentManagerかFragmentTransactionらしい。
getSupportFragmentManager()はFragmentManagerを返すようになっているのだが、これはv4のFragmentManagerなのだ。
ということは、DialogFragmentもv4にしてやらないかんということか。

import android.support.v4.app.DialogFragment;
こうしたら直った。
この、v4とかv7とかが、あまりわかってないな。
v4, v7, v8, v13, v14, v17まであるみたいだ。
このバージョンは、APIレベルに合わせてあるように思うのだが。。。
はっ!
今回はAPI21以上と決めているから、v4とかを選んでいた時点で間違っていたのか!
 
とりあえず、ダイアログは表示されているからよしとしよう。

image

 

長くなったので、ここで区切ろう。

2016/06/02

[android]BLEデバイスを指定せずに接続するのは無理だ

極力Bluetooth Developer StudioのAndroid Clientで生成されたソースファイルを使って、とりあえずBLEのスキャンだけ行えるようにした。

hirokuma/AndroidTwoSpinners at c3993a58d49eafd8b213014e4f4566f41509cd02

namespaceがcom.blogpost.hiro99maの直下だが、そこはいずれ整理しよう。
どうでもよいが、Package名はこのブログのURLとはちょっと違っている。
blogspotではなく、blogpostだ。
Bloggerに移動してから初めてAndroidアプリを作るときに打ち間違えて以来、そのままにしている。
合わせた方がよいのかな?

まだScanした結果は、デバイス名をlogcatに出力させるだけになっている。
タイムアウトも作っていないので、ひたすらスキャンしまくるので、電池の持ちは悪いだろう。

 

それはともかくとして、私はこれからどうするつもりなのだろう?
ペアリングしているわけでもないから、誰かを選んで接続しなくてはならない。
が、ListViewみたいなものもないので、選択することができないのだ。
最初に見つかったデバイスを接続する、だと、あまりにも狂犬過ぎると思う。

なので、もっとエレガントな方法がいるのだけど、そうなるとデバイス名の一覧を作ってユーザに選んでもらうしかない。
一覧を出すとなると、画像があるわけでもないので、リストになるだろう。
そしたら、ListView形式になるのは必然であった。。。

 

まあ百歩譲って、Peripheralを自分で作り、サービスも独自で、サービスのUUIDもAdvertisingしていて、それでフィルタし、デバイスが1台しかないのがわかっていれば、最初に見つけたデバイスに接続する、でもよいかもしれない。

その場合は、ScanFilterFactory.javaにフィルタする設定を実装することになる。
BDSで、GAPの設定にデバイス名を記載すると、自動的にデバイス名でフィルタするようになるのだ。
ScanFilterを使っているだけなので、そこの書き方を変更すれば、他のフィルタ方法も可能だと思う。
フィルタできるのは、以下だ。

  • フィルタ無し
  • デバイス名
  • デバイスアドレス
  • Manufacturer Data
  • Service Data
  • Service UUID

使い方はテストアプリを見るのがよさそうだ。

 

Advertisingするデータは、本編とScanRspをそれぞれ使ったとしても31バイト×2なので、そんなにたくさんのデータを入れることができない。
最近は、DeviceNameと128bitのService UUIDを流すのがよいかな、と思っている。
大きい会社だったら16bitのUUIDも取得できるかもしれないけど、うちは無理だからManufacturer Dataは使えないし。

IncompleteとCompleteのどっちがよいかと言われると、よくわからん。
Generic Accessみたいな標準サービスは載せなくてもいいような気がするが、「完全」を問われると自信がなくなる。
厳密さを問われるものでもない気がするので、メインのサービスが1つだけであればCompleteでいいんじゃないの?と個人的には思っている。
どうせ独自だから、相互接続性などきにしなくていいしね。

 

話がいろいろ脱線したが、ペアリングしてなくて特定できない機器の場合は、一覧を出してユーザに選ばせるのが一番よさそうだ、というお話でした。

[android]layout weightを空欄にしても収まるようになったが、そもそも再現しない

BLE PeripheralをScanして、デバイス名を出力させようと思う。
Bluetooth Developer StudioでAndroidのClientソースを吐かせるとListViewになるのだが、勉強を兼ねているのでそれは使わず、ログ出力っぽくだらだらとテキストとして流そうと思う。

Scanボタンの下に、layout heightをmatch_parentにしたTextViewを置いてみた。

ああ!はみでる!

image

こちらにあるように、layout weightを1にすると、収まった。
Androidのレイアウトで画面いっぱいに表示したい時の設定 « Androidアプリ覚え書き

image

 

でも、画面からはみ出ても仕方ないから、デフォルトでlayout weightは1でもいいんじゃないの?と思い、その上にあるScanボタンのlayout weightを1にした。
そしたら、高さがなくなった。
???
「余っている領域を重み付けが高い順に幅を取っていく」ということで、他の部品は0にしているから、このButtonとTextViewで2分すると思っていたのだが・・・。
Buttonのlayout heightがwrap_contentだからかと思ったが、match_parentにしても変わらない。
うーむ。。。


ごちゃごちゃいじっていたら、今度はweightを空欄にしてもTextViewが画面内に収まるようになってしまった。。。
いつの間にかLinearLayoutにweightSumというパラメータが追加されて1.0になっていたから、そのせいかとも思ったのだが関係なさそうだ。
部品を消して追加し直したのだが、最初のはみ出る現象自体が起きない。

 

AndroidStudioのWindows版は比較的バグが多めだという話もあったし、自分もスキルが全然ないしで、もはや何を疑っていいのかがわからぬ。。。

[nrf51]Bluetooth Developer Studioサンプル(nRF51822+S130)

AndroidやらnRF51やらESP8266やら、あれこれやっているように見えるが、やりたいのは対抗機になってくれるAndroidアプリを作れるようになろう、ということなのだ。

つくっているのがAndroidのテスト用アプリになっているのだが、それはデバイスを使った題材が思いつかないので、まずは通信するだけのしくみを自作したいだけである。

 

相手になってもらうnRF51822のサンプルがなかったので、適当に作った。
SDKがnRF51からnRF5になる前しかなかったので、また作り直した。

hirokuma/nrf51822v11_bds_sample: nRF5 SDK v11.0.0 Bluetooth Developer Studio sample

とうとうS130だ。
中身はまったく見ておらず、テンプレートにBDSが生成したソースを突っ込んで、Makefileを書き直した程度。
それでも動いてくれるのだから、Serviceの部分は作るだけだったらお仕事にならないですな。。。

まあ、その辺りはできる前提で、あとはどう周辺機器を動かすか、とかが腕の見せ所ですかね。

 

さて、相手はできたので、これにアクセスするAndroidアプリを作りましょうかね。

[android]Android Studioでプロジェクトを別のところで使う

いま、TwoSpinnersという、スピナーを2つ使ったAPIテスト用のプロジェクトを作っている。
実際に使うとしたら、これを別名でコピーして、Package名なんかも変更することだろう。

AndroidStudio 2.1.1(Windows10)でやってみる。


まず、コピー元のプロジェクトで、以下を削除しておく。

  • .gradle
  • build
  • app/build

.gradleは移動した後の方がよいのかな?
buildたちは、ファイルがたくさん入っているので、邪魔なだけだ。
あと、このあとで全検索するので、不要なファイルがあると邪魔なのだ。

 

別の場所にコピーして、フォルダ名を変更する。
ついでに、直下にあるxxx.imlファイルを削除する。
リネームしてしまえばよいのだろうが、.gitignoreにimlファイルが入っているので、きっと自動生成してくれるはず。

あと、忘れやすいけど、バージョン管理しているのなら管理ファイル(Gitなら.git)も削除しておこう。
隠し属性になっていて気付きにくいけどね。

 

フォルダ以下を、変更前の名前で全文検索する。
今回であれば「TwoSpinners」だ。
検索対象は「*.*」。全部だ。
今のところ、私の環境ではこれらのファイルが引っかかった。

  • .idea/.name
  • .idea/modules.xml
  • .idea/workspace.xml
  • app/app.iml
  • app/src/main/res/values/strings.xml

問題が無さそうであれば、新しい名前で置換していく。

app.imlは、同じルールで行けば自動生成してくれるはずだが、もう検索できてしまったので変更することにした。

 

さあ、Android Studioで開いてみよう!
・・・Unsupported Modules Detectedというエラーが出てきた。
が、Syncが終わるまで待っても、それ自体はエラーにならなかった。
プロジェクトを閉じて開き直すと表示されなかったので、たぶんimlファイルを削除していたせいじゃないだろうか。


あとは、Package名の変更。
名前だけならよいのだが、フォルダ階層の変更も発生するので、Refactor>Renameでは対応できなさそうだ。

ネットで検索すると、Refactor>Moveがよいらしい。
やってみると・・・、なるほど、これは「移動」だ。
フォルダがなければ作成してくれるし、参照しているPackage名なんかもRefactorするか確認してくれて、変更もするようなのだが、移動なのだ。

たとえば、今回だと「com.blogpost.hiro99ma.twospinners」というPackage名で、移動先を「jp.blogpost.hirokuma」とすると、全体としては「jp.blogpost.hirokuma.twospinners」というPackage名になる。
つまり、twospinnersを「jp.blogpost.hirokuma」に移動したのだ。

よって、一番最後のNamespace名(でよいのかな?)を変更したいのであれば、さらにRefactor>Renameをするとよいだろう。


ツールに全部任せるのは心配な気もするのだが、私くらいのAndroid/Javaスキルだったらツールでやってもらった方が間違いが少ないだろう。

いつかは「そんなのツールでやるより自分でやった方が安心だよ」と言えるくらいまでなりたいものだ。