Help us understand the problem. What is going on with this article?

Retrofit 2 + Gson で Nature Remo Local API を使う

More than 1 year has passed since last update.

背景・目的

私自身や同僚を対象とした 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 に習い、 RetrofitLocal 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 つ全てのモデルで搭載されているため、下記のどれでも対応します。

※ アフィリエイトリンクではありません

使用ライブラリ

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 に使用ライブラリを追記します。

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 の使い方は公式を参照)

NatureRemoLocalApiClient.java
/*
 * 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.xmlandroid:usesCleartextTraffic="true" を追記します。

AndroidManifest.xml
<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>
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away