Google Glass Enterprise Edition 2の公式サンプルCardSampleにRetrofit2.0 GET送信を加えてNode-REDと連携するメモ

Google Glass Enterprise Edition 2の公式サンプルCardSampleにRetrofit2.0 GET送信を加えてNode-REDと連携するメモです。
経緯
Google Glass Enterprise Edition 2が来たので、他のテクノロジーとの連携を探っています。
@shanonim さんとワイワイ協力し、つどつど知見集めながら Mobile Advance 社から購入して Planet Express を初めて利用して Google Glass Enterprise Edition 2 が先日届きました!#GoogleGlass2 #Google pic.twitter.com/1appBIugLd
— Tanaka Seigo (@1ft_seabass) February 19, 2020
Node-REDにGET送信を受けつける仕組みを作る
自分のネットワーク上にあるNode-REDに以下のようにシンプルな通信の設定をします。
フローをインポートできるJSONファイルも置いておきます。
js
[{"id":"23b80426.65da4c","type":"http in","z":"f5146fe6.90b9f","name":"","url":"/gesture","method":"get","upload":false,"swaggerDoc":"","x":300,"y":400,"wires":[["2d961849.aa9138","77444174.11c4b"]]},{"id":"f554fc49.4dd16","type":"http response","z":"f5146fe6.90b9f","name":"","statusCode":"","headers":{},"x":730,"y":440,"wires":[]},{"id":"2d961849.aa9138","type":"debug","z":"f5146fe6.90b9f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":530,"y":380,"wires":[]},{"id":"77444174.11c4b","type":"change","z":"f5146fe6.90b9f","name":"{\"response\":\"OK\"}","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"response\":\"OK\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":440,"wires":[["f554fc49.4dd16"]]},{"id":"28fd5f4e.2e34e","type":"comment","z":"f5146fe6.90b9f","name":"Glassからジェスチャー来る {\"gesture\":\"SWIPE_FORWARD\"}","info":"","x":680,"y":340,"wires":[]},{"id":"7e81bd1e.7269d4","type":"comment","z":"f5146fe6.90b9f","name":"Glassへの返答","info":"","x":540,"y":480,"wires":[]}]
gestureというURLに、/gesture?gesture=SWIPE_FORWARDのようにGET通信を受け付けていてdebugノードが結果を表示します。また、Glass側にはresponse OKというJSONデータを返事します。
Glass Enterprise Edition 2 の公式サンプルをゲットする
こちらに公式サンプルがありまして、CardSampleというものがあります。
Code samples | Glass Enterprise Edition 2 | Google Developers
まず、公式サンプルがあるのでゲットします。
googlesamples/glass-enterprise-samples: Glass Enterprise Edition 2 Samples
CardSampleというフォルダがあるのでそれをAndroid Studioで読み込みます。2020/02/23現在、私は3.5.3を使っています。
余談:RunできないのでSync Project with Gradle Files対応
これは、Android開発で熟練した方であれば問題ないと思うのですが、Runできないエラーにはまったので対応。
こういう感じです。
:appモジュールがGradleに認識されていないエラーです。
プロジェクトのルートディレクトリにある、settings.gradleにinclude ':app'と書かれた行があるので、これを削除してからSync Projectします。で、完了したら削除したinclude ':app'を元通り書き直してもう一度Sync Projectします。
teratailのアドバイスの通り、File→Sync Project with Gradle Files の対応で解決しました。
CardSample\app\build.gradle にretrofit2.0を登録
Android:Retrofit2.0ではてなAPIとおしゃべりしてみた - techium
こちらの記事を参考に、CardSample\app\build.gradle のdependenciesのところにretrofit2.0を使えるように登録しました。
dependencies {
implementation 'com.example.glass.ui:gesture-lib-sample:0.1.0-SNAPSHOT'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.1'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.squareup.retrofit2:retrofit:2.6.4'
implementation 'com.squareup.retrofit2:converter-gson:2.0.0'
implementation 'com.google.code.gson:gson:2.6.2'
}
最後の3行がretrofit2.0関連です。
retrofit2.0の実装をする
先ほどのNode-REDの仕組みに合わせてretrofit2.0でGET送信の実装をします。このあたりは、めちゃくちゃ以下が役に立ちました。
【Android】【Retrofit】Retrofit 2.0.1使い方メモとハマりどころメモ - Tumbling Dice
ほんと感謝です。
com.example.android.glass.cardsampleにdataを掘って実装します。
API の設定
API の設定をNodeRedApiServiceというインタフェースで定義します。
package com.example.android.glass.cardsample.data;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface NodeRedApiService {
@GET("/gesture")
Call<NodeRedSample> getGesture( @Query("gesture") String gesture );
}
特に、詳細の挙動は Query (Retrofit 2.7.1 API) ここのドキュメントで把握できました。
戻り値の定義
戻り値はNodeRedSample データクラスで定義します。
package com.example.android.glass.cardsample.data;
public class NodeRedSample {
public String response;
public String getResponse() {
return response;
}
}
(今思えば、NodeRedResponseでよかったかもしれないが、一旦進む)
MainActivity に仕込む
MainActivity のコードを頑張って理解して、ジェスチャーをGET送信できるよう仕込みます。
ソースは以下の通りです。
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.glass.cardsample;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import com.example.android.glass.cardsample.data.NodeRedApiService;
import com.example.android.glass.cardsample.data.NodeRedSample;
import com.example.android.glass.cardsample.fragments.BaseFragment;
import com.example.android.glass.cardsample.fragments.ColumnLayoutFragment;
import com.example.android.glass.cardsample.fragments.MainLayoutFragment;
import com.example.glass.ui.GlassGestureDetector.Gesture;
import java.util.ArrayList;
import java.util.List;
// retrofit2を追加
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Main activity of the application. It provides viewPager to move between fragments.
*/
public class MainActivity extends BaseActivity {
private List<BaseFragment> fragments = new ArrayList<>();
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager_layout);
final ScreenSlidePagerAdapter screenSlidePagerAdapter = new ScreenSlidePagerAdapter(
getSupportFragmentManager());
viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(screenSlidePagerAdapter);
fragments.add(MainLayoutFragment
.newInstance(getString(R.string.text_sample), getString(R.string.footnote_sample),
getString(R.string.timestamp_sample), R.menu.main_menu));
fragments.add(MainLayoutFragment
.newInstance(getString(R.string.different_options), getString(R.string.empty_string),
getString(R.string.empty_string), R.menu.special_menu));
fragments.add(ColumnLayoutFragment
.newInstance(R.drawable.ic_note_50, getString(R.string.columns_sample),
getString(R.string.footnote_sample), getString(R.string.timestamp_sample)));
fragments.add(MainLayoutFragment
.newInstance(getString(R.string.like_this_sample), getString(R.string.empty_string),
getString(R.string.empty_string), null));
screenSlidePagerAdapter.notifyDataSetChanged();
final TabLayout tabLayout = findViewById(R.id.page_indicator);
tabLayout.setupWithViewPager(viewPager, true);
}
@Override
public boolean onGesture(Gesture gesture) {
// ジェスチャーの送信
sendGesture(gesture.name());
switch (gesture) {
case TAP:
fragments.get(viewPager.getCurrentItem()).onSingleTapUp();
return true;
default:
return super.onGesture(gesture);
}
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return fragments.get(position);
}
@Override
public int getCount() {
return fragments.size();
}
}
// 送信する部分の設定
private void sendGesture(String gesture) {
Log.d("sendGesture", gesture);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.XX.XX:1880/")
.addConverterFactory(GsonConverterFactory.create())
.build();
NodeRedApiService service = retrofit.create(NodeRedApiService.class);
Call<NodeRedSample> call = service.getGesture(gesture);
call.enqueue(new Callback<NodeRedSample>() {
@Override
public void onResponse(Call<NodeRedSample> call, Response<NodeRedSample> response) {
Log.d("onResponse", response.body().getResponse());
}
@Override
public void onFailure(Call<NodeRedSample> call, Throwable t) {
Log.d("onFailure", t.getMessage());
}
});
}
}
具体的には、
// retrofit2
を追加 の部分で関連クラスのインポート- sendGestureで retrofit2.0に合わせて送信する部分の関数を作った。
http://192.168.XX.XX:1880/
は、実際には自分のネットワーク上のNode-REDのアドレス- onGesture部分でジェスチャーイベントをとらえているのでsendGestureにジャスチャーチを流すようにした
といった実装です。
AndroidManifest.xml にandroid.permission.INTERNET追加
HTTP通信が使えるように、パーミッションを設定します。
<uses-permission android:name="android.permission.INTERNET"/>
こちらを加えました。
余談: not permitted by network security policy が出たので対応
HTTPS上に応答するサーバーを置くなら問題ないと思いますが、Node-REDをサッと起動するとHTTPサーバーとして立ってしまうので、
D/debug2: CLEARTEXT communication to 192.168.XX.XX not permitted by network security policy
通信を試みたところ、上記のようなエラーが出ました。
以下、調べたところ以下の対応で解消できました。
Android 9(Pie)でHTTP通信を有効にする - Qiita
まず、AndroidManifest.xmlで、android:networkSecurityConfig設定。
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
CardSample\app\src\main\res\xml の中に、network_security_config.xmlを設置します。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.XX.XX</domain>
</domain-config>
</network-security-config>
192.168.XX.XXは今回接続するNode-REDのアドレスとなります。
動かしてみる
さて動かしてみましょう。
このようにVysorでミラーリングしたGoogle Glass2の画面とNode-REDの画面を一緒に表示しています。
これができると、外部連携ができて、いろいろと広がりますね!