背景・目的
私自身や同僚を対象とした Android アプリ開発の勉強用に、サンプルアプリ『Nature Remo API Sample』を作りました。 Apache License 2.0 で公開しています。 Nature Remo と連携し、テレビのチャンネル切替を行うだけのシンプルな Android アプリです。
以前は IRKit を使用していましたが、 2018/12/03 現在は販売終了しているため Nature Remo に置き換えることにしました。 IRKit SDK for Android は公開されていますが、残念ながら Nature Remo 用の SDK は公開されていません。
Nature Remo API は公開されているので、 IRKit SDK for Android に習い、 Retrofit で Local API を使うコードを実装しました。
Nature Remo API Sample について
Nature Remo Local API v1.0.0 を使って、 Android 端末と同一ネットワーク上にある Nature Remo と通信します。 Local API を使い、 Android 端末から Nature Remo へ赤外線信号データを送信すると、 Nature Remo が赤外線信号を出力します。
送信する赤外線信号データは、事前に Panasonic 製テレビリモコンから取得したものをソースコードに埋め込んでいます。 Local API を使うことで、実物の赤外線リモコンから赤外線信号データを取得できます。
対応 Android OS バージョン
- Android 5.1 (API level 22) 以上
対応する Nature Remo モデル
Nature Remo には現在 3 つのモデルがあり、各モデルで搭載するセンサーなどが異なります。本サンプルアプリでは赤外線通信を使用していますが、赤外線通信は 3 つ全てのモデルで搭載されているため、下記のどれでも対応します。
- Nature Remo - 型番: Remo-01
- Nature Remo mini - 型番: Remo-2W1
- Nature Remo (2nd Generation) - 型番: Remo-1W2
※ アフィリエイトリンクではありません
使用ライブラリ
Name | Version | License |
---|---|---|
Retrofit | v2.5.0 | Apache License 2.0 |
Gson Converter | v2.5.0 | Apache License 2.0 |
アプリ設計パターン
MVP パターン - 参考: Android Architecture Blueprints
Retrofit 2 + Gson による Nature Remo Local API の呼び出しを実装
app/build.gradle に使用ライブラリを追記します。
dependencies {
// 〜省略〜
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
// 〜省略〜
}
NatureRemoLocalApiClient.java に Nature Remo Local API の呼び出しを実装します。
(Retrofit 2 の使い方は公式を参照)
/*
* Copyright 2018, Satoki Mizoguchi
*
* 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.mizo0203.natureremoapisample.data.source;
import android.support.annotation.NonNull;
import android.util.Log;
import com.mizo0203.natureremoapisample.data.IRSignal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.Dispatcher;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.HeaderMap;
import retrofit2.http.POST;
/**
* Nature Remo Local API v1.0.0
* <p>
* これは Remo で利用できる Local API です。
* HTTP クライアントが Remo と同じローカルネットワーク内にある場合、
* クライアントは Bonjour を使用して Remo の IP を検出して解決し、 IP に HTTP 要求を送信できます。
* <p>
* This is the local API available on Remo.
* When the HTTP client is within the same local network with Remo,
* the client can discover and resolve IP of Remo using Bonjour,
* and then send a HTTP request to the IP.
* <p>
* http://local.swagger.nature.global/
*/
public class NatureRemoLocalApiClient {
private static final String TAG = NatureRemoLocalApiClient.class.getSimpleName();
private static final String X_REQUESTED_WITH = NatureRemoLocalApiClient.class.getSimpleName();
private final Map<String, String> mHeaders;
/**
* Retrofit 用 Nature Remo Local API 定義インターフェイス
*/
private NatureRemoLocalApiService mNatureRemoLocalApiService;
/**
* @param remoIpAddress Nature Remo の IP アドレス
*/
public NatureRemoLocalApiClient(@NonNull String remoIpAddress) {
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("http")
.host(remoIpAddress)
.build();
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequestsPerHost(1);
OkHttpClient localHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.callTimeout(30, TimeUnit.SECONDS)
.dispatcher(dispatcher)
.build();
// Add X-Requested-With header to every request to Nature Remo Local API
mHeaders = Collections.unmodifiableMap(new HashMap<String, String>() {
{
put("X-Requested-With", X_REQUESTED_WITH);
put("Content-Type", "application/json");
}
});
Retrofit retrofit = new Retrofit.Builder()
.client(localHttpClient)
.baseUrl(httpUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
mNatureRemoLocalApiService = retrofit.create(NatureRemoLocalApiService.class);
}
/**
* 最新の受信 IR 信号を取得する
* <p>
* Fetch the newest received IR signal
*
* @param callback IR信号 / IR signal
*/
/*package*/ void getMessages(@NonNull final NatureRemoRepository.Callback<IRSignal> callback) {
mNatureRemoLocalApiService.getMessages(mHeaders).enqueue(createCallback(callback));
}
/**
* リクエストから提供された IR 信号を出力する
* <p>
* Emit IR signals provided by request body
*
* @param message 赤外線信号を記述する JSON シリアライズオブジェクト。
* "data"、 "freq"、 "format" キーが含まれています。
* <p>
* JSON serialized object describing infrared signals.
* Includes "data", “freq” and “format” keys.
* @param callback 正常に送信されました / Successfully sent
*/
/*package*/ void postMessages(@NonNull IRSignal message, @NonNull final NatureRemoRepository.Callback<Void> callback) {
mNatureRemoLocalApiService.postMessages(mHeaders, message).enqueue(createCallback(callback));
}
private <T> Callback<T> createCallback(final NatureRemoRepository.Callback<T> callback) {
return new Callback<T>() {
@Override
public void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
if (response.isSuccessful()) {
callback.success(response.body());
} else {
Log.e(TAG, "failure: message=" + response.message() + " code=" + response.code());
callback.failure();
}
}
@Override
public void onFailure(@NonNull Call<T> call, @NonNull Throwable t) {
Log.e(TAG, "failure", t);
callback.failure();
}
};
}
/**
* Retrofit 用 Nature Remo Local API 定義インターフェイス
*/
private interface NatureRemoLocalApiService {
@GET("/messages")
Call<IRSignal> getMessages(@HeaderMap Map<String, String> headers);
@POST("/messages")
Call<Void> postMessages(@HeaderMap Map<String, String> headers, @Body IRSignal message);
}
}
Android 9 Pie 対応
Android 9 (API level 28) 以降はクリアテキスト(平文)ネットワーク通信がデフォルトで無効になっています。 1 Nature Remo Local API は HTTP (平文) で通信するため、明示的にクリアテキストネットワーク通信を有効にする必要があります。方法はいくつかありますが、今回は AndroidManifest.xml に android:usesCleartextTraffic="true"
を追記します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mizo0203.natureremoapisample">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<!-- 省略 -->
</application>
</manifest>