2012年2月25日土曜日

ApplicationsPlatform対応Teleopアプリを作ってみた

前々回作ったAndroid デバイスを傾けるとルンバが動くというアプリを
前回紹介したApplicationsPlatformに対応させました。
というか別アプリで登録しました。
(こっちはApplicationsPlatformに対応したロボットがないと動かないので。)

アイコンも少しやる気をだしました。

以下スクリーンショット。

App Chooserでのアプリ選択画面

センサ初期化待ち

傾けた時の画面。ドロイド君が傾くよ。
ダウンロードはこちらから。(要App Chooser)
https://market.android.com/details?id=com.ogutti.ros.android.roomba_app

前回のアプリもRoomba Controllerといいながら、

「Roombaを所有し、ROIでルンバをPCから操作するデバイス(FRISK Roomba等)を所持し、すでにROSでRoombaを操作している人で、Androidデバイスを所有している」だけが使えるRoombaコントローラでした。
(全世界推定ユーザー数15人)

もしくは
「ROSで動くロボットを所有し、Androidデバイスを所有している人」が使える
ロボットコントローラでした。
(こっちは研究者いれれば結構いるかも)


今回はApplicationsPlatformに対応したロボットを持っている人用のAndroidアプリです。Turtlebot、PR2をお持ちのあなた!是非お試しください。

使い方は以下を参照ください。
http://ros.org/wiki/otl_android_roomba_app/

まず必要なスタックのソースをダウンロード。(ROS_PACKAGE_PATH

svn checkout https://otl-ros-pkg.googlecode.com/svn/trunk/otl_android

次にあなたのロボットの.installedファイルを編集してください。Turtlebotなら、turtlebot_bringupパッケージのmock_apps/turtlebot_apps.installedだと思います。


 - display: Tilt Teleop
   app: otl_android_roomba_app/roomba


あとはApp ChooserからTilt Teleopを選択すれば使えるはずです。
.appファイルを編集すると速度とか、cmd_velのremapとかができます。

(前々回のアプリ同様、不正なURIを指定してしまうと、強制終了させる必要が有ります。ライブラリの不具合っぽいです。もしかしたら使い方の問題かもですが。)

2012年2月24日金曜日

ApplicationsPlatformの紹介

最近、Androidでrosjavaを試していました。

WillowはさらにApplicationsPlatformというフレームワーク?を用意しています。

http://www.ros.org/wiki/ApplicationsPlatform

Over Viewを引用すると・・・

The applications platform allows users to interact with contained demos and utilities called "applications". These applications allow users to easily share and interact with demos and software.
ということでよく分からないですね。

どうやら、Android側からアプリケーションを選択して、ロボットを動かす仕組みのようです。
構成要素は以下のようです。

  • Androidのある特定タスク用専用アプリ
  • そのアプリに必要なロボット側のソフトの起動
  • そのアプリのロボット依存部の設定
確かにROSではノードをずらーっと起動するけど、いろんなことやらせるときに、
いらないノードとか、競合しちゃうノードとかあると思うので、どうするのかな〜?
って思ったことがありました。

この仕組みを使うと、Android側でやりたいことを決めると、ロボット側が必要なノードを上げてくれる、アプリが終われば戻してくれる、ということができるようです。
概要は↓に。

赤く囲った部分が自作するAndroidアプリで、app_chooserから呼ばれます。
また、このアプリに必要なノードをこのアプリから立ち上げることができます。(青囲み)

デモするためにコンソールを開く必要がなくなり、さっと、Android携帯からロボットソフト立ち上げから、インタフェースとして利用するところまで一発でできる、スマートな仕組みだと思いました。

2012年2月17日金曜日

ROS Android アプリ公開してみた

ルンバ操縦用に作ってみたrosjavaで作ったAndroidアプリをのりで公開してみました。
実用性はほとんどありません。(自己満足公開です)

https://market.android.com/details?id=com.ogutti.ros.android.roomba


最初のROS_MASTER_URIの設定間違えると強制終了するしかないようなので、
注意してください。

Willowが作ったやつが結構公開されていますね。
Android 3用が多いので、2.3でも動くというのが売りですかね・・・・。

2012年2月16日木曜日

ROSの日本ユーザ用メーリス作ってみた!

現状ROSの日本人コミュニティは存在しないも同然な気がします。
ユーザは結構英語が堪能なので英語メーリスに直接投げていますね。

でも気軽に日本語で雑談、質問、情報共有できる場があるといいかと思って、
勝手にGoogle Group(メーリス)を作ってみました。
https://groups.google.com/group/ros-japan-users

(もともとWillowの大山さんが作ってくれた日本語メーリスがあったのですが、
いつの間にかなくなっていました。)

気軽な質問とか、なんでもありですんで、
がんばって英語書く元気ないとき、日本人ならではの話題等ありましたら
ご活用ください。

僕が答えられる範囲なら答えるし、誰かが英語に直してros-usersに投げてくれるかも?


また、ROS JAPAN User's Groupというのを @knishida777 さんが作ってくれました。


http://rosjp.org

まだ何もないですが、そのうち何かしらやるかも??

2012年2月11日土曜日

海外のロボットホビイストの紹介

@tera_koji さんに教えてもらったロボットホビイストがすごすぎるので紹介させてください。

http://www.dshinsel.com/

Lokiというロボットです。

うーん。お金もかかってそうだけどクオリティが尋常じゃない。。。。
どこぞの会社のロボットよりよっぽど出来がいいのではないか、と思ってしまう。

何より(Wall-Eによく似た)カメラのデザインとかまばたきとか、
とにかくカッコイイ!

僕もこれくらい作れるようになりたいな。
でもたぶん、このロボットむちゃくちゃお金かかってます。

中の人はIntelに勤めてる人みたいです。経歴見るとロボットとは無関係に見えますね。
すごい。。。。

2012年2月5日日曜日

ロボット・エンジニア交流会のお知らせ

イベントの宣伝です。

ロボット系の集まりをやります〜。
http://atnd.org/events/25086
ちょっと先ですが、3月3日(土)、場所は新宿です。

主催者は↓の記事を書いた西田さんです。
http://i.impressrd.jp/e/2011/08/22/1152

ROS系の話がしたい方、OpenRTMの方、メカの話、
主に夢を語る場になると思いますが、ぜひご参加ください!

2012年2月4日土曜日

Android + rosjava でRoombaを操作

当初の目標だったAndroidでのルンバ操作アプリが一応できました。

Androidで端末の傾きセンサの値を取得し、それを速度に変換し、rosjavaを使ってtopicとして出力します。



おまけで掃除機能のOn/Off、ドックへの合体指示も出せるようにボタンをつけました。
ただし、この機能はotl_roombaパッケージのroomba_twist_node.pyを想定しています。
システム図は↓のような感じです。
Roomba制御のためにPCを経由しています。本当はAndroidからBluetoothで直接Roombaを操作可能なので、実はこのアプリはかなり冗長です。

でもこのノードは/cmd_vel (geometry_msgs/Twist)を出力するので、制御対象がルンバだろうが、PR2だろうが、動かすことができるというメリットもあります。


シミュレータでの動作画面は↓のようになっています。
スライダで最大動作速度を変えたり、ボタンでクリーナーのOn/Off等ができます。
一番上のでかいロゴに意味はありません。


リポジトリに上げましたが、
ソースもさらしておきます。まずはルンバ操作用クラス


/**
 * @license New BSD License
 * 
 */
package com.ogutti.ros.android.roomba;

import org.ros.message.geometry_msgs.Twist;
import org.ros.message.std_msgs.Bool;

import org.ros.namespace.GraphName;
import org.ros.node.NodeMain;
import org.ros.node.topic.Publisher;
import org.ros.node.Node;

/**
 * class for Controlling a roomba by Twist msg
 * 
 */
public class RoombaControllerNode implements NodeMain {
 private Publisher<Twist> velPublisher;
 private Publisher<Bool> cleanPublisher;
 private Publisher<Bool> dockPublisher;

 @Override
 public void onShutdown(Node arg0) {
 }

 @Override
 public void onShutdownComplete(Node arg0) {
 }

 @Override
 public void onStart(Node node) {
  velPublisher = node.newPublisher("/cmd_vel", "geometry_msgs/Twist");
  cleanPublisher = node.newPublisher("/roomba/clean", "std_msgs/Bool");
  dockPublisher = node.newPublisher("/roomba/dock", "std_msgs/Bool");

 }

 @Override
 public GraphName getDefaultNodeName() {
  return new GraphName("roomba_controller");
 }

 /**
  * start/stop cleaning motor
  * @param isOn if true start cleaning, false stop.
  */
 public void publishClean(boolean isOn) {
  if (cleanPublisher != null) {
   Bool msg = new Bool();
   msg.data = isOn;
   cleanPublisher.publish(msg);
  }
 }

 /**
  * Publish roomba's velocity (Vx, Vtheta)
  * @param linearX twist.linear.x forward speed [m/s]
  * @param angularZ twist.angular.z rotational speed [rad/s]
  */
 public void publishVelocity(double linearX, double angularZ) {
  if (velPublisher != null) {
   Twist vel = new Twist();
   vel.linear.x = linearX;
   vel.angular.z = angularZ;
   velPublisher.publish(vel);
  }
 }

 /**
  * start docking
  */
 public void publishDock() {
  if (dockPublisher != null) {
   Bool msg = new Bool();
   msg.data = true;
   dockPublisher.publish(msg);
  }
 }
}
つぎはメインのActivityのクラス
/**
 * @license New BSD License
 */

package com.ogutti.ros.android.roomba;

import java.text.DecimalFormat;
import java.util.List;
import android.os.Bundle;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;

import org.ros.address.InetAddressFactory;
import org.ros.android.MessageCallable;
import org.ros.android.RosActivity;
import org.ros.android.views.RosTextView;
import org.ros.node.NodeConfiguration;
import org.ros.node.NodeMainExecutor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import com.ogutti.ros.android.roomba.R;
import org.ros.message.geometry_msgs.Twist;

/**
 * MainActivity of this App (roomba controller)
 * 
 */
public class MainActivity extends RosActivity implements SensorEventListener {

 private RosTextView<Twist> rosTextView;
 private SensorManager sensorManager;
 private RoombaControllerNode controllerNode;
 
 private static final double LINEAR_VELOCITY_RATE  = 0.05;
 private static final double ANGULAR_VELOCITY_RATE = 0.1;

 /**
  * 1.0 means max speed, 0.0 means stop always.
  */
 private double speedRate;

 public MainActivity() {
  super("Roomba Controller", "Roomba Controller");
  speedRate = 0.5;
 }

 @SuppressWarnings("unchecked")
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
  rosTextView = (RosTextView<Twist>) findViewById(R.id.text);
  rosTextView.setTopicName("/cmd_vel");
  rosTextView.setMessageType("geometry_msgs/Twist");
  rosTextView
    .setMessageToStringCallable(new MessageCallable<String, Twist>() {
     @Override
     public String call(Twist message) {
      DecimalFormat df = new DecimalFormat();
      df.setMaximumFractionDigits(2);

      return "vel_x:\n" + df.format(message.linear.x) + "\n"
        + "vel_theta:\n" + df.format(message.angular.z);
     }
    });
 }

 @Override
 protected void init(NodeMainExecutor nodeMainExecutor) {
  // create ROS nodes
  controllerNode = new RoombaControllerNode();
  NodeConfiguration nodeConfiguration = NodeConfiguration
    .newPublic(InetAddressFactory.newNonLoopback().getHostName());
  nodeConfiguration.setMasterUri(this.getMasterUri());
  nodeMainExecutor.execute(controllerNode, nodeConfiguration);
  nodeMainExecutor.execute(rosTextView, nodeConfiguration);

  // set callback for accelerometer
  List<Sensor> sensors = sensorManager
    .getSensorList(Sensor.TYPE_ACCELEROMETER);
  if (sensors.size() > 0) {
   Sensor accelerometer = sensors.get(0);
   sensorManager.registerListener(this, accelerometer,
     SensorManager.SENSOR_DELAY_FASTEST);
  } else {
   android.util.Log.v("MainActivity", "NOT found sensor!");
  }
  
  // set toggle button's callback
  ToggleButton tb = (ToggleButton) findViewById(R.id.toggleButton1);
  tb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
   @Override
   public void onCheckedChanged(CompoundButton buttonView,
     boolean isChecked) {
    controllerNode.publishClean(isChecked);
   }
  });

  // set seekbar callback. 
  SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar1);
  final TextView tv1 = (TextView) findViewById(R.id.textView1);
  seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
   public void onProgressChanged(SeekBar seekBar, int progress,
     boolean fromUser) {
    tv1.setText("Speed:" + progress + "%");
    speedRate = progress * 0.01f;
   }

   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {
   }

   @Override
   public void onStopTrackingTouch(SeekBar seekBar) {
   }
  });
  Button dockButton = (Button) findViewById(R.id.button1);
  dockButton.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(android.view.View v) {
    controllerNode.publishDock();
   }
  });

 }

 @Override
 protected void onResume() {
  super.onResume();

 }

 @Override
 protected void onStop() {
  super.onStop();
  sensorManager.unregisterListener(this);
 }

 @Override
 public void onAccuracyChanged(Sensor sensor, int accuracy) {
 }

 /**
  * callback of sensor change(accelerometer)
  */
 @Override
 public void onSensorChanged(SensorEvent event) {
  if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
   double linearX = event.values[1] * -LINEAR_VELOCITY_RATE * speedRate;
   double angularZ = event.values[0] * ANGULAR_VELOCITY_RATE * speedRate;
   if (Math.abs(linearX) < LINEAR_VELOCITY_RATE) {
    linearX = 0.0d;
   }
   if (Math.abs(angularZ) < ANGULAR_VELOCITY_RATE) {
    angularZ = 0.0d;
   }
   controllerNode.publishVelocity(linearX, angularZ);
  }
 }
}
ちょっと長いですが以上になります。
Androidアプリは作るのが結構簡単なのでチャレンジしてみてはどうでしょうか。