空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

Google Test C++の使い方(初級)

f:id:robonchu:20181007164810p:plain

やりたいこと

C++のテストがしたい。

Google Testがいい感じという噂。

教科書

  1. 入門ガイド — Google Test ドキュメント日本語訳

  2. 超入門編 — Google Mock ドキュメント日本語訳

  3. クックブック — Google Mock ドキュメント日本語訳

  4. 上級ガイド — Google Test ドキュメント日本語訳

入門まとめ

基本的なアサーション

致命的なアサーション 致命的ではないアサーション 検証内容

ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition が true

ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition が false

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

簡単なテスト

プロダクトコード

int Factorial(int n); // nの階乗を返す

テストコード

// 0 の階乗をテスト
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}

// 正の数の階乗をテスト
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

テストフィクスチャ:複数のテストで同じデータ設定を使う

プロダクトコード

template <typename E> // E は要素の型
class Queue {
public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // queue が空の場合は NULL を返します.
  size_t size() const;
...
};

テストのフィクスチャ

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

このフィクスチャとTEST_F() を利用したテストコード

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

テストの呼び出し

テストを定義したら,RUN_ALL_TESTS() でテストを実行できる

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// テスト対象となるクラス Foo のためのフィクスチャ
class FooTest : public ::testing::Test {
 protected:
  // 以降の関数で中身のないものは自由に削除できます.
  //

  FooTest() {
    // テスト毎に実行される set-up をここに書きます.
  }

  virtual ~FooTest() {
    // テスト毎に実行される,例外を投げない clean-up をここに書きます.
  }

  // コンストラクタとデストラクタでは不十分な場合.
  // 以下のメソッドを定義することができます:

virtual void SetUp() {
  // このコードは,コンストラクタの直後(各テストの直前)
  // に呼び出されます.
}

virtual void TearDown() {
  // このコードは,各テストの直後(デストラクタの直前)
  // に呼び出されます.
}

// ここで宣言されるオブジェクトは,テストケース内の全てのテストで利用できます.
};

// Abc を行う Foo::Bar() メソッドをテストします.
TEST_F(FooTest, MethodBarDoesAbc) {
  const string input_filepath = "this/package/testdata/myinputfile.dat";
  const string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}

// Xyz を行う Foo をテストします.
TEST_F(FooTest, DoesXyz) {
  // Foo の Xyz を検査
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

Mockまとめ

わからないことがあったとき

基本ステップ

  1. 簡単なマクロを使ってモック化したいインタフェースを記述します.これが,モッククラスの実装に展開されます.

  2. モックオブジェクトを作成し,直感的な構文を利用して Expectation と 動作を規定します.

  3. モックオブジェクトを利用するコードを実行します.Google Mock は,Expectation 違反が起こると即座にそれをキャッチします.

使うための準備

C++ ソースファイル内で, “gtest/gtest.h” と “gmock/gmock.h” を #include するだけで準備は終わり。

モック理解のためのサンプル

class Turtle {
  ...
  virtual ~Turtle() {}
  virtual void PenUp() = 0;
  virtual void PenDown() = 0;
  virtual void Forward(int distance) = 0;
  virtual void Turn(int degrees) = 0;
  virtual void GoTo(int x, int y) = 0;
  virtual int GetX() const = 0;
  virtual int GetY() const = 0;
};

モックの定義

  1. Turtle から MockTurtle クラスを派生させます.

  2. Trutle の仮想関数を調べて,引数の数を数えます.

  3. 派生クラスの public セクションに,MOCK_METHODn();(const メソッドをモック化する場合は, MOCK_CONST_METHODn();)を書きます.ここで n は,引数の数を表しますが,これを数え間違えると,コンパイルエラーで注意されます. 関数のシグネチャを調べて,その関数名をマクロの1番目の引数に,残りを2番目の引数にします.

  4. これを,モック化したい仮想関数全てに対して繰り返します.

MOCK_METHOD0 のように最後の数字は引数の数を指定。

#include "gmock/gmock.h"  // Google Mock はこのヘッダに.
class MockTurtle : public Turtle {
 public:
  ...
  MOCK_METHOD0(PenUp, void());
  MOCK_METHOD0(PenDown, void());
  MOCK_METHOD1(Forward, void(int distance));
  MOCK_METHOD1(Turn, void(int degrees));
  MOCK_METHOD2(GoTo, void(int x, int y));
  MOCK_CONST_METHOD0(GetX, int());
  MOCK_CONST_METHOD0(GetY, int());
};

モックの使い方

  1. モックを制限無く利用できるように,Google Mock の名前をテスト用の名前空間からインポートします

  2. モックオブジェクトを作成します.

  3. その Expectation を設定します

  4. モックを利用したコードを実行します.Google Test のアサーションを利用して結果のチェックを行っても良いでしょう.モックメソッドが期待よりも多く呼び出される,または引数が誤っている,などの場合は,即座にエラーが起こります.

  5. モックがデストラクトされると,その全ての Expectation が満足されたかどうかを Google Mock が自動的に検証します.

#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast;                     // #1

TEST(PainterTest, CanDrawSomething) {
  MockTurtle turtle;                          // #2
  EXPECT_CALL(turtle, PenDown())              // #3
      .Times(AtLeast(1));

  Painter painter(&turtle);                   // #4

  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}                                             // #5

int main(int argc, char** argv) {
  // 以下の行は,テスト開始前に Google Mock (と Google Test)
  // を初期化するために必ず実行する必要があります.
  ::testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

Exceptionの作り方

Expectation を設定するために EXPECT_CALL() マクロを使う。

このマクロには2つの引数があり、1番目はモックオブジェクトで,2番目はメソッドと引数。

using ::testing::Return;...
EXPECT_CALL(turtle, GetX())
    .Times(5)
    .WillOnce(Return(100))
    .WillOnce(Return(150))
    .WillRepeatedly(Return(200));

turtle オブジェクトの GetX() メソッドが5回呼ばれ,最初は 100 を返し,次に 150,それ以降は 200 を返す,という動作のテスト。

所感

unittestに似てる。書きながら使えるようになろう~

RPLIDAR A1をROSで動かしてみる

やりたいこと

低価格Lidar RPLIDAR A1を授かったので、ROSで動かしてみる

教科書

実行手順

びっくりするくらいシンプル。素晴らしい。

  1. git clone https://github.com/Slamtec/rplidar_ros.git

  2. catkin_make

  3. source devel/setup.bash

  4. ポートに権限付与

    1. ls -l /dev | grep ttyUSB

    2. sudo chmod 666 /dev/ttyUSB0

  5. roslaunch rplidar_ros view_rplidar.launch

このlaunchを立ち上げると、こんな感じ ↓

f:id:robonchu:20180915091225p:plain

ノードだけ立ち上げるときは

  1. roslaunch rplidar_ros rplidar.launch

出力されているTopicは

  • scan (sensor_msgs/LaserScan) : it publishes scan topic from the laser.

How to install rplidar to your robot

rplidar rotate with clockwise direction . The first range come from the front ( the tail with the line).

f:id:robonchu:20180915091416p:plain

所感

とてもシンプル!ありがたい!!

次はルンバで自律移動にチャレンジ。どのSLAMの手法を使おう!?

gmappingかKarto SLAMがよさげかな。。。

参考 :

GPD pocketにUbuntu16.04 & ROS install

f:id:robonchu:20180912232540p:plain

超小型でなかなかスペックが良い♪

やりたいこと

Windows10が入っているGPDにUbuntu16.04とROSをインストール。

ざっくりまとめ。

Ubuntuインストールのための準備

以下はすべてGPD上で実施

  1. BIOSの変更

    1. 2017/06/28版のZipファイルをGPD Pocket/BIOS - GPD Wikiから取得。2017/06/28版でないとUbuntuは動かないらしい。

    2. Zipを解答し、update_win.batをGPD上で管理者権限で実行。詳細は以下を参考。

    3. msinfoでBIOSバージョンが正しくかわっているか確認。

  2. ISOファイルのダウンロード

    1. 第3版 ubuntu-16.04.1-desktop-amd_0809_2.iso をGPD Pocket/OS/Ubuntu - GPD Wikiから取得
  3. UltraISOのインストール

    1. ダウンロード UltraISO 9.7.1.3519 日本語 – Vessoft
  4. 2017/06/28版のZip内のUbuntu安装说明.pdfGoogle翻訳のファイルから翻訳を利用して翻訳しておく

ISOイメージの作り方

Ubuntu安装说明.pdfを参考。中国語なので、Google翻訳したものを見るとわかりやすい。

  1. ブート用USBをさす。

  2. UltraISOを開き、ファイルを開くからISOファイルを選択、読み込み。

  3. 上部タブのブートを選択し、イメージの書き込みをクリック。

  4. 書き込み設定がUSB-HDD+になっていることを確認し、USBに書き込み実行。

Ubuntuのインストール

  1. ブータブルUSBをさして、再起動し、Fn + F7を複数回打ち込む。

  2. f:id:robonchu:20180912233938p:plainのような画面がでてくるので、UEFIを選択。

  3. Try Ubuntu without installingを選択。

  4. Gpartedでパーティションを作成。ルートとswap。

    1. Ubuntu 16.04 インストール(UEFI) その2 - UEFIのPCにUbuntu 16.04をインストールする(パーティションの作成 〜 ブートローダーの設定) - kledgebを参考
  5. Ubuntu install アイコンを選択。

  6. ドライバを入れる項目はチェック無しで実行。インストールはそれ以外を選択し、上記作成パーティションにインストール。

  7. 終了したら再起動!

画面が90度回転している時

Ubuntuのシステム設定のディスプレイで回転させる。

ROSのインストール

以下の手順をコピペで完了!

これでGPD pocketでROS KINETICが動いた〜↓

f:id:robonchu:20180912235727j:plain

所感

小さい割にスペックいいし、Ubuntu動くし、これからいろいろ試したい! 小さいロボットにもギリ載せれそう。

中国語の資料つらかった。。。

Linux(Ubuntu16.04)でのプリンタの設定方法&コマンドラインでの印刷の仕方

f:id:robonchu:20180826104622p:plain

実験用のために小型フォトプリンタCP710を1000円で購入。安かった。

このシリーズは外付けバッテリで動くのが個人的には魅力的。

f:id:robonchu:20180826153703p:plain

外付けバッテリ: Amazon CAPTCHA

やりたいこと

Linux PCでプリンタを使いたい。

コマンドラインで任意のタイミングで印刷したい!

参考HP

ドライバの入手先

  1. 公式HPをチェック。なければ↓

  2. Gutenprint Printer Driversをチェック。なければ↓

  3. openprinting:start [Linux Foundation Wiki]

openprintingでドライバインストール

上記から 5.2.7 (DEB for LSB 3.2) を選択し、ダンロード

openprinting:database:driverpackages [Linux Foundation Wiki]

に従って

  1. sudo apt-get install lsb

  2. $ dpkg -i "name of the .deb package"

でインストール完了

設定前のインストール

  1. sudo apt-get install cups

  2. sudo apt-get install smbclient

プリンタの追加・設定

CUPSでの設定

が参考になりました。ありがとうございます!

CUPSで印刷トライ

CUPSを使ったUbuntu16.04LTSでのプリンタの設定

上記に従って登録後、下記写真のMaintenance -> Test Print Pageを選択

f:id:robonchu:20180826143835p:plain

で印刷完了 ↓

f:id:robonchu:20180826144305j:plain

設定の確認

Test Printできたプリンタ設定がデフォルトのプリンタ設定になっているか(左上のチェックがついてるか)を確認。

なっていない場合は右クリック->デフォルトに設定で変更。

f:id:robonchu:20180826151843p:plain

コマンドラインで印刷

【Linux入門】しっかりわかる!プリンタ管理と印刷コマンドがわかりやすい。感謝です。

ここまでできていると、印刷したい写真ファイル名がtest.pngだとすると、以下lpコマンドで

$ sudo lp test.png

印刷ができる〜!

参考:

所感

はじめてLinuxでプリンタの設定や印刷をしてみた。コマンドラインで印刷できたし、いろいろな用途に使えそう♪

おまけ

以下のシステム設定で素直に設定した場合だと、プリンタがすぐ「一時停止」状態になり、印刷出来なかった。

参考: プリンタの「一時停止」状態を解除して印刷可能にする

システム設定

システム設定 -> プリンタ で以下のような画面になっていたらCP710が正しく認識されている。

f:id:robonchu:20180826104133p:plain

エラー時の対処

もし、追加時に"client-error-not-possible"がでたら...

  • sudo cp gutenprint52+usb gutenprint52usb

参考: 14.04 - Installing CP400 printer gives 'client-error-not-possible' error - Ask Ubuntu

Raspberry Pi Zero W で遊んでみる(3)~アクセスポイント化~

やりたいこと

WIFI環境がない場所でもひとつのraspi zero wをアクセスポイントとして通信がしたい

なので、raspi zero w をアクセスポイント化にトライ

教科書

MACアドレスを調べる

pi@raspberrypi:~$ iw dev

udev ruleを追加

ファイルを作成

$ sudo nano /etc/udev/rules.d/70-persistent-net.rules

以下の"b8:27:eb:ff:ff:ff"の部分を先に調べたMACアドレスに書き換えて上のファイルに記述

SUBSYSTEM=="ieee80211", ACTION=="add|change", ATTR{macaddress}=="b8:27:eb:ff:ff:ff", KERNEL=="phy0", \
  RUN+="/sbin/iw phy phy0 interface add ap0 type __ap", \
  RUN+="/bin/ip link set ap0 address b8:27:eb:ff:ff:ff"

Dnsmasq and Hostapdのインストール

$ sudo apt-get install dnsmasq hostapd

Dnsmasq and Hostapdのためのファイル設定

3つのファイルを書き換える

1. /etc/dnsmasq.conf

interface=lo,ap0
no-dhcp-interface=lo,wlan0
bind-interfaces
server=8.8.8.8
domain-needed
bogus-priv
dhcp-range=192.168.10.50,192.168.10.150,12h

2. /etc/hostapd/hostapd.conf

以下のssidとpassphraseを自分の好きな名前に設定する。" "で囲む必要はない。

ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
interface=ap0
driver=nl80211
ssid=YourApNameHere
hw_mode=g
channel=11
wmm_enabled=0
macaddr_acl=0
auth_algs=1
wpa=2
wpa_passphrase=YourPassPhraseHere
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP

3. /etc/default/hostapd

DAEMON_CONF="/etc/hostapd/hostapd.conf"

Interfaces Fileの修正

1. /etc/wpa_supplicant/wpa_supplicant.conf

使用できるWIFIがあればそのWIFISSIDとパスワードを設定

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="YourSSID1"
    psk="YourPassphrase1"
    id_str="AP1"
}

2. /etc/network/interfaces

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo
auto ap0
auto wlan0
iface lo inet loopback

allow-hotplug ap0
iface ap0 inet static
    address 192.168.10.1
    netmask 255.255.255.0
    hostapd /etc/hostapd/hostapd.conf

allow-hotplug wlan0
iface wlan0 inet manual
    wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface AP1 inet dhcp

起動時の設定

/home/piに以下シェルスクリプトを作成

pi@raspberrypi:~$ cat ./start-ap-managed-wifi.sh
#!/bin/bash
sleep 30
sudo ifdown --force wlan0 && sudo ifdown --force ap0 && sudo ifup ap0 && sudo ifup wlan0
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 192.168.10.0/24 ! -d 192.168.10.0/24 -j MASQUERADE
sudo systemctl restart dnsmasq

実行権限を付加

pi@raspberrypi:~$ chmod +x ./start-ap-managed-wifi.sh

起動時に実行するために、cronに設定

$ sudo crontab -e

以下を最終行に追加

@reboot /home/pi/start-ap-managed-wifi.sh

最後に...

リブート!

これで母艦PCのwifiを見ると設定したSSIDが見えるはず!!

コメント

wifi環境がない場所でsshで作業できるのは便利 ♪

おまけ

sshのホスト名を変更する方法

$ sudo -e /etc/hostname
変更する
$ reboot

再起動するとssh -X pi@変更後のホスト名.localで接続可能

udevについて

有線LANを接続する方法

USB OTG

名前解決

参考

Raspberry Pi Zero W で遊んでみる(0)~インストール・設定~

やりたいこと

raspberry pi zero w のインストールや設定方法をまとめる

f:id:robonchu:20180319195157j:plain

使っているバッテリーはこれ👇でとても小さい

ポータブル充電器02 通販 | au オンラインショップ | スマホ・携帯電話向けオプション品

もっと小さいバッテリーを知ってる方いたら教えてください> <

pin配置

f:id:robonchu:20180319165644p:plain

OSダウンロード

OSの書き込み

設定教科書

wifi設定

SDカード内のbootディレクトリを探し、以下のファイルをboot直下に作成

  • emacs -nw /Volumes/boot/wpa_supplicant.conf
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
        ssid="your-SSID"
        psk="your-passphrase"
}

このSDを挿すとWIFIに接続される

USB経由でインターネット接続したいとき

参考: Raspberry Pi Zero(W)のセットアップ

bootドライブ内のcmdline.txtに "modules-load=dwc2,g_ether" を追加します。

rootwaitとquietの間です。エディタはvimを使っていますが、適宜変えてください。

次に、config.txtの末尾に"dtoverlay=dwc2"を追加します。

$ echo "dtoverlay=dwc2" >> /Volumes/boot/config.txt

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether quiet init=/usr/lib/raspi-config/init_resize.sh quiet splash plymouth.ignore-serial-consoles

SSH対応

設定

touch /Volumes/boot/ssh

ログイン

ssh -X pi@raspberrypi.local

初期ユーザーはpi、初期パスワードはraspberryになります。

wifi安定化

参考:

rfkillフラグのリセット問題が発動すると面倒なので、念のため。

$ sudo apt install rfkill

$ sudo vi /etc/rc.local

下記を追加。

#By default this script does nothing.

/usr/sbin/rfkill unblock wifi

 初期設定

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install tmux emacs
$ sudo raspi-config

スペースキーで選択

"4. Localization Options"から"11. Change Locale"を選択して、"ja_JP.UTF-8"を有効に。

同様に"4. Localization Options"から"12 Change Timezone"。"Asia"、"Tokyo"と順にたどっていく。

参考

Raspberry Pi Zero W で遊んでみる(2)~ROS KINETIC INSTALL~

やりたいこと

ラズパイゼロにROSを入れる

教科書

ROSではじめるホビーロボット#06がほしい。手に入る方法ないのかな。。。

インストール

$ sudo apt-get update
$ sudo apt-get install -y build-essential gdebi python-defusedxml libboost-all-dev liblog4cxx-dev libconsole-bridge-dev
$ sudo pip install netifaces
$ mkdir -p ~/tmp
$ cd ~/tmp
$ wget https://github.com/nomumu/Kinetic4RPiZero/releases/download/v_2017-10-15/rpi-zerow-kinetic_1.0.0-1_armhf.zip
$ unzip rpi-zerow-kinetic_1.0.0-1_armhf.zip
$ sudo gdebi rpi-zerow-kinetic_1.0.0-1_armhf.deb
$ sudo /opt/ros/kinetic/initialize.sh

設定

$ sudo rosdep init
$ rosdep update
$ echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
$ source ~/.bashrc

roscore

f:id:robonchu:20180721094018p:plain

動いた〜!

コメント

のむむさんありがとうございますm( )m