【自律型ロボット製作記】#7 AndroidタブレットでROSランチャーアプリを作る

どうもこんにちは!InomaCreateです。
ROSによる自律型ロボットを起動する際、ラズパイやubuntuPCでROSの起動コマンドを入力するのが面倒でしたので、全てタブレットからROSプログラムを起動できるように改良してみました。
今回、タブレットーラズパイorUbuntuPC間は、MQTTプロトコルという通信を使って実現しました。
目次
1.MQTTとは
MQTTは、Message Queueing Telemetry Transport の略です。
MQTTはデバイス間で短いメッセージを頻繁に送受信することを想定して作られており、軽量、省電力な通信のため、Iotデバイス間の通信で頻繁に使われているプロトコルです。
MQTT通信の仕組みをざっくりと説明すると、Publisher(送信側)とSubcriber(受信側)があり、Brokerという処理サーバーが存在します。
Publisherがトピックと呼ばれるメッセージをBrokerへ通知すると、そのメッセージをSubcribeしていたSubscriberにトピックが通知されるようになっています。
MQTTの仕組みを本の出版で例えると、Subcriberが読者、Publisherが作者、Brokerが出版会社という位置づけになります。
読者(Subscriber)が読みたい本(トピック)を出版会社(Broker)に購買予約(Subscribe)しておくと、作者(Publisher)が本を出版(Publish)した時に、出版会社(Broker)から読者(Subscriber)へ本(トピック)が通知されるイメージです。

2.今回作成したランチャーアプリの概要
さて、今回このMQTTの仕組みを使って、Androidタブレットからロボットやナビゲーション用UbuntuPCにトピックを通知し、受け取ったロボットやUbuntuPCでROSのプログラムを起動するようなランチャーアプリを作ってみました。
今まで、ロボットのシステムを起動するために、UbuntuPCを立ち上げ、ROSを実行し、リモートでロボット側へログインしてROSを起動する・・・などの面倒な手順を踏んでいましたが、このランチャーアプリを使うことで、システムの起動を効率化できました。
動画で紹介
3.Android端末と端末間のMQTT送受信サンプル
参考程度ですが、今回Android端末とUbuntu端末間でMQTT通信を実装しましたので、備忘録も兼ねてまとめておきます。
※2021.09.22時点での実装方法であり、今後ライブラリが更新されると動作しなくなる可能性もあるのでご注意ください。
なお、今回はAndroid端末側にBrokerを立てています。
3-1.Android端末側にBroker,Publish処理を実装
AndroidStudioを立ち上げ、新規プロジェクトを作成します。
gradleに以下を追加します。
packagingOptions {
pickFirst "META-INF/INDEX.LIST"
pickFirst "META-INF/io.netty.versions.properties"
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'io.moquette:moquette-netty-parser:0.9'
implementation 'io.moquette:moquette-broker:0.12.1'
implementation 'io.moquette:moquette-parser-commons:0.8.1'
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
}
AndroidManifest.xml(マニフェストファイル)に以下を追加します。
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<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/Theme.Mqtt_comm"
android:requestLegacyExternalStorage="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="org.eclipse.paho.android.service.MqttService" >
</service>
</application>
事前に、activity_main.xmlにPubishするトリガーとなるTESTボタンを追加しておきます。(コードは省略します)
そして、MainActivity.javaに以下の処理を追加しましょう。
TESTボタンを押下すると、 mqtt/testというトピックで”message”という文字列を通知する処理となります。
public class MainActivity extends AppCompatActivity {
static final String TAG = "MainActivity";
io.moquette.broker.Server broker;
String IPAddress=null;
private MqttAndroidClient mqttAndroidClient;
private String ID = "hoge@github";
private String PASS = "sango";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Broker start K.Inomata
try {
broker = new io.moquette.broker.Server();
MemoryConfig memoryConfig = new MemoryConfig(new Properties());
memoryConfig.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator + BrokerConstants.DEFAULT_MOQUETTE_STORE_H2_DB_FILENAME);
broker.startServer(memoryConfig);
Log.i(TAG,"startServer");
} catch (IOException e){
e.printStackTrace();
}
// 自分のIPアドレスを取得する
try {
for (NetworkInterface n : Collections.list(NetworkInterface.getNetworkInterfaces())) {
for (InetAddress addr : Collections.list(n.getInetAddresses())) {
if(addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
Log.i(TAG,"IP ADDRESS:"+addr.getHostAddress());
IPAddress = addr.getHostAddress();
}
}
}
} catch (SocketException e){
Log.e(TAG,"IP address not get!!");
e.printStackTrace();
}
if(IPAddress!=null) {
mqttAndroidClient = new MqttAndroidClient(this, "tcp://" + IPAddress + ":1883", "d:lite:test:"){
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
}
};
try {
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(ID);
options.setPassword(PASS.toCharArray());
mqttAndroidClient.connect(options, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken iMqttToken) {
Log.d(TAG, "onSuccess");
}
@Override
public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
Log.d(TAG, "onFailure");
}
});
} catch (MqttException e) {
Log.d(TAG, e.toString());
}
}
// TESTボタン
findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mqttAndroidClient == null)
return;
try {
mqttAndroidClient.publish("mqtt/test", "message".getBytes(), 0, true);
} catch (MqttPersistenceException e) {
Log.d(TAG,e.toString());
} catch (MqttException e) {
Log.d(TAG,e.toString());
}
}
});
}
}
※注意:
このままAndroid端末で実行すると、以下のようなエラーが発生します。
Caused by: java.lang.IllegalStateException: Could not open file nio:/storage/emulated/0/moquette_store.h2 [1.4.196/1]
一旦、Android端末の設定から作成したアプリの設定に入り、アプリの許可でストレージを許可してください。
これでエラーは回避できます。
3-3.Ubuntu端末側にSubscriber処理を実装
次に、Ubuntu端末側にMQTTのSubscriber処理を実装していきます。
その前に以下コマンドを実行して、ubuntu端末にpaho-mqttをインストールしておきます。
“xxx.xxx.xx.xx”は、broker(Android端末側)のIPアドレスを設定します。
#!usr/bin/env python
# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt
import subprocess
from time import sleep
import threading
def on_connect(client, userdata, flag, rc):
print("Connected with result code " + str(rc))
client.subscribe("mqtt/test")
def on_disconnect(client, userdata, flag, rc):
if rc != 0:
print("Unexpected disconnection.")
def on_message(client, userdata, msg):
print("Receive message ="+str(msg.payload)+" on topic = "+ msg.topic+ " with Qos "+str(msg.qos))
client = mqtt.Client()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
try:
client.connect("xxx.xxx.xx.xx",1883,300)
except:
print("broker host is not started!! waiting!!")
client.loop_forever()
3-3.簡単な動作確認
実装したら、以下コマンドでpythonプログラムを実行してみましょう。
Android端末からダミーのトピックをPublishしてみましょう。
UbuntuPC側で、トピックが表示されれば、通信成功です。
4.最後に
以上、今回は、ロボットのROSをAndroid端末から起動できるランチャーアプリの紹介と、簡単なMQTT送受信プログラムを紹介しました。
ロボット開発は少しずつですが、形になってきました。まだまだ課題は多いですが、ぼちぼちやっていこうと思います。
それでは!!

