株式会社アイズ・ソフトウェア

/~eyes-software-co-jp/A1A2
お問い合わせ |  情報セキュリティへの取組について情報セキュリティ方針個人情報保護方針) |  ブログ

BLOG 社員ブログ

ラズベリーパイでホームサーバーを構築しよう -- メディアサーバー構築編

今回はラズパイにDLNAサーバー機能をインストールします。

● DLNAサーバーとは
DLNAサーバーとは、ホームネットワークを介してビデオ、写真、音楽等のコンテンツを共有する機能です。
DLNAに対応した機器間では他の機器に保存してあるビデオ、写真、音楽等を再生することができます。 AV家電では「ソニールームリンク」「お部屋ジャンプリンク」等の名前でBDレコーダーに機能が組み込まれています。また、スマホでも対応ソフトをインストールし、ホームネットワークへ接続すればコンテンツの共有や再生が可能です。

● DLNAサーバーをインストール
ラズパイでDLNAサーバーを構築するにはReadyMedia(旧名miniDLNA)というソフトウェアが主流です。
名称はReadyMediaですが、インストールする際のパッケージ名称やネットでググる際にも旧名のminiDLNAの方が通りが良いので、本ページでもminiDLNAの名称で通すことにします。

miniDLNAのインストールは簡単です。以下のコマンドでパッケージをインストールします。
$ sudo apt-get install -y minidlna 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが新たにインストールされます:
  minidlna
アップグレード: 0 個、新規インストール: 1 個、削除: 0 個、保留: 0 個。
132 kB のアーカイブを取得する必要があります。
    :
インストール完了したらPC等のブラウザから以下のURLを表示させてみます。
<http://raspberrypi:8200/>
正しくインストールされていれば、下記の様に表示されます。

(この画面では既にコンテンツを登録していますが、インストール直後ならメディアライブラリの数は0件です)

miniDLNAサーバーのインストール時には、 /var/lib/minidlna の下のディレクトリのコンテンツを管理するようになっています。
今回は外部ディスクをサーバーにするのが目的なので、外部ディスクのコンテンツを管理するように設定します。
併せて、ログ等も外部ディスクに作成されるようにします。

外部ディスクのコンテンツを保存するディレクトリは以下としました。
格納先のルートディレクトリを/mnt/hdd/minidlna とし、その下にサブディレクトリを作成します。
  • /mnt/hdd/minidlna/musics
  • /mnt/hdd/minidlna/movies
  • /mnt/hdd/minidlna/photos
  • /mnt/hdd/minidlna/cache
  • /mnt/hdd/minidlna/log
上記のディレクトリは以下のコマンドで作成してください
$ cd /mnt/hdd
$ mkdir minidlna
$ cd minidlna
$ mkdir musics movies photos cache log
$ sudo chmod 777 *

次に設定ファイル/etc/minidlna.confを編集します。
変更する項目は以下の項目です。
 media_dir : コンテンツを格納するディレクトリを記述する
 db_dir: コンテンツを読み込んだデータベースのディレクトリを記述する
 log_dir: ログファイル出力先ディレクトリを記述する

今回の設定に当てはめると/etc/minidlna.confの変更箇所は以下の様になります。
---- ここから ----
media_dir=/mnt/hdd/minidlna
db_dir=/mnt/hdd/minidlna/cache
log_dir=/mnt/hdd/minidlna/log
---- ここまで ----

/etc/minidlna.conf を変更したらminidlnaサービスをリスタートさせます。
$ sudo service minidlna restart

● メディアファイルの格納
ここまでの設定で 音楽は/mnt/hdd/minidlna/musics、
静止画は/mnt/hdd/minidlna/photos、
動画は/mnt/hdd/minidlna/movies の下に格納するよう設定されました。
それぞれのディレクトリにコンテンツのファイルを格納するとminiDLNAはディレクトリを自動的にスキャンして管理します。

どの様な方法でコンテンツを格納しても良いのですが、一つの例としてPCからWindowsMediaPlayerを使ってCDよりファイルを読み込んで格納する方法を示します。
この方法はDLNAの機能を使用しているのではなく、前回の投稿「ラズベリーパイでホームサーバーを構築しよう -- NAS構築編」にて共有したNASへ録音しているだけですので、この例の方法で録音する場合は音楽の格納先が共有されている必要があります。
ちょうど、サンプルにふさわしいCDを持っていました。「eyes 渡辺美里
これをラズパイのDLNAサーバーに録音してみましょう。
  • WindowsMediaPlayer (WMP)を起動する
  • WMPのメニューバーよりツール->オプション を選択し、オプション画面の「音楽の取り込み」タブを表示させる。

  • 取り込んだ音楽を保存する場所 の「変更」ボタンを押し、フォルダーの参照画面を開く。

  • フォルダーツリーより「\\raspberrypi\raspberry-pi-smb\minidlna\musics」を選択する。
    ツリーになければ、フォルダー入力エリアに直接書いて指定する。
  • 「OK」ボタンを押す
  • 「音楽の取り込み」タブで形式を指定する。
    形式は何を指定しても良いが、こだわらなければ、MP3でよい。
  • 「OK」を押して設定を完了する。
  • PCにCDを入れ、WMPのCD再生画面より取り込む曲にチェックを入れる。「アルバム」横の箱にチェックを入れればすべての曲にチェックが入る。

  • 「CDの取り込み」を押して録音を開始する。
これで録音は完了です。

録音できているか、コンテンツの参照ができるかをスマホの音楽プレイヤーで確認しましょう。
使用した音楽プレーヤーはAndroidの「BubbleUPnP」というアプリです。
アプリを起動し、右下の「Library」をタップしてライブラリ画面を表示させ、左上の「SelectLibrary...」をタップすると

ライブラリの選択肢に「raspberrypi:minidlna」が現れました。これを選択し、Musicのフォルダを掘ってくと、録音したeyesが現れました。
曲をタップして再生するとブツ切れることも無く普通に再生されました。


これでDLNAサーバーのインストールと確認は完了です。
これから手持ちのCDをたくさん登録して音楽に浸りましょう。

ラズベリーパイでホームサーバーを構築しよう -- NAS構築編

前回の投稿ではRaspberry Pi Model B へ最新のRaspberry Pi OSをインストールするところまで完了しました。
今回はネットワーク設定を行い、NASを構築するまでを行います。

● ホスト名の設定
ラズパイを複数使用していたり、参加ネットワークの決まり等でホスト名(WindowsでいうところのPCの名前)を変更したい場合は、以下のファイルを編集します。
/etc/hostname
/etc/hosts

例えばホスト名を「RPi-Server」にしたい場合、これらのファイルの中の「raspberrypi」の部分を「RPi-Server」に変更します。
変更後はラズパイをリブートします。

なお、今回の例ではホスト名の変更はせずに、初期設定の「raspberrypi」のまま進めています。

● IPアドレスの設定
ラズパイをサーバーにする場合、IPアドレスが変わらないように固定します。(一般に固定IPといいます)
これはPC等から接続する際に毎回IPアドレスが変わってしまうと、IPアドレスが分からなくなりサーバーに接続できなくなるためです。
ラズパイのIPアドレスを固定IPにする方法については以下のサイト等を参考にしました。 「ラズベリーパイで固定IPアドレスを設定する

最初にIPアドレスを決めます。
我家のLANは192.168.0.1~192.168.0.10までのローカルアドレスをルーターのDHCP機能で割り当てる様にしています。
ラズパイも同様にルーター側の設定で固定IPアドレスを振る様にしますが、最初はラズパイ側で固定IPアドレスを設定してみます。
ラズパイのIPアドレスは自動で割り当てられるアドレスと重ならないように 192.168.0.15 に設定する事とします。
デフォルトゲートウェイ、DNSサーバーのIPアドレスは、ルーターがそれらの機能を兼ねており、そのIPアドレスが192.168.0.1ですので、192.168.0.1とします。

ラズパイのネットワーク設定は画面から、又は端末から設定できますが、今回は画面から設定することとします。
画面上部のメニューバーの右にある「↑↓」のアイコンがネットワークの状態を示す物です。これをクリックすると、プルダウンメニューが出ますので、「Wireless & Wired Network Setting」を選択します。

表示された「NetworkPeferences」画面の右上にあるプルダウンメニューより「eth0」を選択します。

「Automatically configure empty option」のチェックを外します。これはIPアドレスが自動割り当てされる場合にチェックするものです。
「Disable IPv6」はIPv6ネットワークでは使用しないので付けたままとします。
「IPv4 Address」にはラズパイのIPアドレス 192.168.0.15 を設定します。
「Router」「DNS Server」には 192.168.0.1 を設定します。

全ての入力を終えたら「適用」「閉じる」押して設定を完了させてから、ラズパイをリブートします。

リブート後、仮想端末からネットワークの設定を確認しました。192.168.0.15になっていますね。


● 外部のPCから操作できるように設定する
使い慣れたPC上でTeraTermというターミナルエミュレータでコマンドを使った操作ができる様に設定します。
これはPCから操作したいという事でなければ設定しなくても構いません。

PC等他のコンピュータからログインしたり、ファイルをコピーしたりする為にはSSHという機能を使用します。
ラズパイの場合、SSH機能は無効になっていますので、以下の手順で有効にします。

スタートメニュー->設定->Raspberry Piの設定 を選択し、「Raspberry Piの設定」画面を開きます。

「Raspberry Piの設定」画面の「インターフェイス」タブを表示させ、「SSH」という項目を有効にします。

その後、「OK」ボタンを押して完了です。
これで、PCからTeraTermでログインできるようになりました。

● 外部ディスクを接続する
ラズパイのNASを構築するにあたって、HDDはラズパイでの使用をやめてもPCにつなげて使える様にするため、HDDはNTFSとすることにしました。
NTFSフォーマットのHDDを使用するにあたってHDDは事前にPC側でフォーマットしておきます。
ラズパイでNTFSフォーマットしたHDDはPC側では認識しません。この事には注意してください。
PCに接続しないのであれば、HDDはどの形式でフォーマットしても構いません。

フォーマット済の外付けHDDをラズパイに接続してみました。
ディスクの状態を見るdfコマンドを打ったところ、下の様に表示されました。
$ df
ファイルシス   1K-ブロック    使用    使用可 使用% マウント位置
/dev/root         30358348 3150888  25921652   11% /
devtmpfs            187244       0    187244    0% /dev
tmpfs               220224       0    220224    0% /dev/shm
tmpfs               220224    3252    216972    2% /run
tmpfs                 5120       4      5116    1% /run/lock
tmpfs               220224       0    220224    0% /sys/fs/cgroup
/dev/mmcblk0p1      258095   48676    209420   19% /boot
tmpfs                44044       4     44040    1% /run/user/1000
/dev/sda1        160833532   96120 160737412    1% /media/pi/work
自動的にマウントされ、/media/pi/work にマウントされたことがわかります。

NASとして使うには自動マウントでなく、OS起動時から固定的にマウントされるようにします。
その為には、/etc/fstab というファイルを編集します。fstabは起動時にマウントするディスク等の情報を記述するファイルです。
fstabを編集するにあたっては、事前に以下を決めておきます。
  • デバイス名
  • マウントポイント
  • ファイルシステムの種類
  • マウントオプション
デバイス名とはOSが認識するディスクの名前で、上の場合 /dev/sda1がそれにあたります。
デバイス名の代わりにUUIDを指定することもできます。UUIDとはデバイスを識別するためのIDで、ディスク、パーティションごとに異なった値を持つものです。
複数のディスク、複数のパーティションがある場合やディスクを接続するUSBポートが変わった場合には接続後のデバイス名が変わることがあります。なので、UUIDを指定することを推奨します。
UUIDを調べるには、以下のコマンドを使用します
$ sudo blkid /dev/sda1
/dev/sda1: LABEL="work" UUID="C01C7B171C7B07A4" TYPE="ntfs" PARTUUID="d686d686-01"
UUIDは「C01C7B171C7B07A4」であることがわかりました。
マウントポイントとは接続したディスクのディレクトリの事です。
自動的にマウントされた時には /media/piの下にマウントされましたが、/mediaは自動的にマウントされるメディアの為のディレクトリですので、NASとして使うにはあまり適切ではありません。
なので、/mntの下に hdd というディレクトリを作成し、そこへマウントする事とします。
以下のコマンドでマウントポイントを作っておきます。
$ cd /mnt
$ sudo mkdir hdd
$ sudo chmod 777 hdd

マウントオプションはNTFSの場合、以下の物を指定する事を推奨します。
 nofail : 指定するとHDDが壊れたり取り外されていてもOSをブートできる。指定しない場合、マウントに失敗するとOSは起動しない。
 permissions : ファイルのオーナーによるアクセス権の管理を行う
 default : 上記以外はデフォルトを使用する
自動でマウントされたディスクを一旦アンマウントしてマウントしてマウントに成功するか確認しておきます。
以下のコマンドでアンマウントとマウントを行います。
$ sudo umount /media/pi/work
$ sudo mount UUID=C01C7B171C7B07A4 /mnt/hdd
マウントしたらdfコマンドで正しくマウントできているか確認しておきます。

UUIDを調べ、マウントポイントも作成したので、次にfstabを編集します。
編集にはエディタを使用します。ラズパイにインストールされているエディタには端末上で使用する vi、nano。画面上で使用するテキストエディタがありますが、使いやすいものを使えばよいです。 ただし、特権ユーザーでファイルを編集する必要があるので、
$ sudo vi /etc/fstab のように、プログラム名の前にsudoを付けるてエディタを起動する必要があります。
以下の行を /etc/fstabへ追加します。
----- ここから ----
UUID=C01C7B171C7B07A4 /mnt/hdd ntfs default,nofail,permissions 0 0
----- ここまで ----

fstabの編集が終わったら、再起動してdfコマンドでマウントできているかを確認してください。

● Sambaをインストールする
Sambaとは、Windowsネットワークの共有ファイルサーバーを構築する為のフリーソフトウェアです。
ラズパイなど、Linux系OSの走るコンピュータでNASを構築するのには一般的に使用されているものです。
Sambaのインストールには以下のコマンドを使用します。
$ sudo umount /media/pi/work
$ sudo apt-get install -y samba
途中、「DHCP から WINS 設定を使うよう smb.conf を変更しますか? 」というメッセージが表示され、<はい><いいえ>の選択を促されます。 ここでは<いいえ>を選択してください。


インストールが完了したら、設定ファイル /etc/samba/smb.conf の編集をおこないます。 samba.confの最後に、以下の行を追加します。
---- ここから ----
[raspberry-pi-smb]
comment = Raspberry Pi USB DISK /dev/sda1
path = /mnt/hdd
guest ok = yes
browseable = yes
writable = yes
read only = no
create mask = 0777
directory mask = 0777
---- ここまで ----

上の例で「raspberry-pi-smb」という名前がありますが、これは共有ディレクトリの名前になります。好きな名前を付けてください。
samba.confの編集が終わったら以下のコマンドを実行して変更した設定を有効にするか、リブートします。
$ sudo services smbd restart
次にPC側でNASを参照できるかを確認します。
エクスプローラーのアドレスバー に 「\\raspberrypi」 と入力し、リターンを押します。
(raspberrypiはラズパイのホスト名です。ホスト名の設定をしていない場合はこの名前になります。)

すると「raspberry-pi-smb」というフォルダが見えます。
これをクリックしてフォルダを開き、ラズパイの外付けHDDのファイルが見えるか、書き込めるかを確認します。

NAS側でユーザー毎のアクセス制御をしないのであれば、設定はここまでです。
Windowsのユーザーごとにファイルのアクセス権を管理する場合の設定については、またいつか書きましょう。

ラズベリーパイでホームサーバーを構築しよう -- インストール編

会社で使われていない古いラズベリーパイがあるので、借りてきました。
このラズベリーパイで自宅のホームサーバーを構築します。

● そもそも、「ラズベリーパイ」略して「ラズパイ」とはどのような物でしょう?
ラズパイは小型のシングルボードコンピュータです。そしてラズパイはパソコンとほぼ同じ事ができ、そのうえ、GPIOという仕組みにてコンピュータ外部の機器と接続してそれらを操作することもできる物です。

我社でも、ラズパイをETロボコンのリモートスタート用に使用したり、インターンシップでソケット通信の教育に使用したりと活用してきました。

● ラズパイ始めるぞ。
まず、用意したのは以下のもの。
  1. Raspberry Pi Model B + 32Gbyte SDカード
  2. 液晶モニタ(VGA入力のみ)
  3. 164Gbyte HDD + USB接続HDDケース
  4. FOMA充電器
  5. HDMI->VGA変換器
  6. FOMA - MiniUSB変換コネクタ
ラズパイも含めて、現役でない物の寄せ集めで構築します。

心配なのがFOMA充電器と、HDMI->VGA変換器。
FOMA充電器は出力が5.4V 700mAとラズパイの推奨電源(5.0V 1.2A)より電圧が高く電流が少ないです。
HDMI-VGA変換器はラズパイ側で適切な設定が可能かどうか次第です。

結局FOMA充電器はアンペア不足でダメでした。ブラウザを起動すると画面がチラチラと揺れます。なので、スマホ用充電器に変えました。

◆ インストールの前に、
まず、最初に借りてきたラズパイの動作確認をしておきます。
この時点でラズパイを壊したくないので、PCからUSB電源を取り、PCで使っているディスプレイを接続して起動を確認しました。インストール完了までは不安のある機器は使用しません。
はい、問題なく立ち上がりました。

次に現在のSDカードのバックアップを取ります。それには「Win32 Disk Imager」というソフトを使います。
以下のサイトからダウンロードし、インストールします。
Win32 Disk Imager 日本語情報トップページ
Win32 Disk ImagerをインストールしてSDカードのバックアップを取る手順については以下のページに詳しく書いてあります。それを参考にしてください。
【Raspberry Pi】SDカードを丸ごとバックアップする方法について

◆ OSはどれをつかうの?
バックアップを取ったので、OSのインストールを開始します。
Rasupberry Piで使用できるOSはいくつかあります。そのうちのどれを入れたら良いのでしょう。
一般的な使い方なら
  • Raspberry Pi OS with desktop (以前はRsapbianという名称でした)
  • ubuntu
また、用途毎に特化したOSというのもあります。
動画、画像、音楽を配信するメディアセンターを作るなら
  • OSMC
  • LibreELEC
レトロゲームに使うなら
  • RetroPie
組み込み制御等に使うなら
  • Raspberry Pi OS Lite
  • Windows 10 IOT core (Windowsという名前が付いていますが、PCで使うWindowsとは異なるものです)

今回は一番一般的で、ネット上に情報の溢れているRaspberry Pi OS with desktop を使うことにします。

◆ インストールを始めよう
「Raspberry Pi Imager」というツールを使ってOSを"SDカード"にインストールします。
その後にOSを入れたSDカードを使ってラズパイを起動し、初期設定を行う手順です。

「Raspberry Pi Imager」とOSのインストール手順については以下のページに詳しく書いてあります。
Raspberry Pi OS (Raspbian)のインストール - 公式Imager対応
raspberry pi OS のダウンロードページへ行き、そこで「Download for Windows」のボタンを押して、「imager_1.5.exe」をダウンロードします。
ダウンロードしたimager_1.5.exe をダブルクリックで起動し、Raspberry Pi Imagerをインストールします。
以降の手順で「このアプリがデバイスに変更を加えることを許可しますか?」の画面が表示される個所があります。表示されたら「はい」を押します。
Raspberry Pi Imagerがインストールできたらスタートメニューに追加されたRaspberry Pi Imagerのアイコンをクリックして起動します。


Raspberry Pi Imager画面が表示されます。


画面にて 「CHOOSE OS」ボタンを押し、表示されたプルダウンメニューより「Raspberry Pi OS(32-bit)」を選択します。
他のOSをインストールしたい場合は、このプルダウンメニューからそのOSを選択します。


画面にて「CHOOSE SD CARD」ボタンを押して、インストールしたいSDカードを選択します。


選択完了すると「WRITE」ボタンが有効になるので、それを押します。


十数分で書き込みが完了します。完了したらRaspberry Pi Imagerを終了し、SDカードをPCから取り外します。




◆ 初期設定をしよう
インストールの完了したSDカードをRaspberry Piに挿入し、ディスプレイ、キーボード、マウスを接続します。

最後にUSB端子に電源を接続してRaspberry Piを起動します。

すると、起動画面に続きセットアップ画面が表示されます。


セットアップ画面では「NEXT」ボタンを押します。


次に国、言語、タイムゾーンの設定を行います。国に日本を選択すると、言語とタイムゾーンは日本のものになります。


次にpiユーザーのパスワードを入力します。2カ所に同じパスワードを入力します。


次に画面設定を行います。セットアップ画面には「デスクトップの周りに黒い枠が見えますか」という質問が表示されます。
画面がディスプレイいっぱいに表示されていなければ質問の左側のボタンをチェックします。
今回はディスプレイいっぱいではなかったですが、実際に使用するディスプレイでは無いので、そのまま「NEXT」ボタンを押しました。


次にアップデートを行います。今回はまだネットワークケーブルを接続していないので「SKIP」を押しました。
有線ネットワークやWiFiの設定が済んでいる場合は「NEXT」を押してRaspberry Pi OSのアップデートをさせます。


アップデート画面の次はセットアップ完了画面になります。ここで「Done」を押してセットアップ完了です。


初期設定時にはLANをつなげていなかったので、アップデートをスキップしました。
その為、LAN接続後に、OSを最新にアップデートします。
以下のページを参考にしました。
Raspberry Pi の OS を最新の安定版にアップデートする
画面上部のツールバーからターミナルを選択してコマンド操作できる端末を開きます。
その端末で順に以下のコマンドを入力します。
$ sudo apt update

$ sudo apt full-upgrade

$ sudo apt clean
ここまで終わったらシャットダウン又はリブートします。

ssh/scpでパスワード無しで接続する方法

以前参加したプロジェクトで、複数のLinuxマシン間で設定ファイルを共有したいという要求がありました。
普通であれば、nfsやSMBでファイル共有するのですが、そのプロジェクトはファイルサーバーを持たないシステムでした。
そこで、scpを利用してリモートコピーでファイル等化を実現しようということになりました。 幸い、等化元となるファイルの存在するマシンは決まっていたので、 システム起動時にそのマシンからファイルをscpでコピーすることで実現可能でした。
通常scpはコピー時にパスワードを聞いてきますが、下記の設定を行う事でパスワード入力が不要となるようにしました。

設定方法について以下に例をあげて記載します。
この例では コピー元マシンのIPアドレス、ユーザー名は以下としています。
     IPアドレス:192.168.1.10
    ユーザ :user1
    パスワード:pass1
コピー先のマシンのIPアドレス、ユーザー名は以下とします。
    IPアドレス:192.168.1.11
    ユーザ :user2
    パスワード:pass2

コピー元マシンにログインし、以下のコマンドを入力します。

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user1/.ssh/id_rsa):
Created directory '/home/user1/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user1/.ssh/id_rsa.
Your public key has been saved in /home/user1/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:p7Jl9VtQhtEdQHdP6Y1boxApXDbuLaEgD399OXey4go user1@192.168.1.10
The key's randomart image is:
+---[RSA 2048]----+
|        . .+++.+=|
|         ooo.ooo+|
|     o .  .oo +.o|
|      = . +.ooooo|
|       oSoo+o=o+o|
|        .+ .oo+o.|
|      . E   o o  |
|       = . . +   |
|      .   ..o    |
+----[SHA256]-----+

$ ssh-copy-id -i ~/.ssh/id_rsa.pub user2@192.168.1.11
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/user1/.ssh/id_rsa.pub"
The authenticity of host '192.168.1.11 (192.168.1.11)' can't be established.
ECDSA key fingerprint is SHA256:yYVW4kiK3FlmSR+PPodLBhmH9sNdpivwwStcln+L76A.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
user2@192.168.1.11's password: pass2
Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'user2@192.168.1.11'"
and check to make sure that only the key(s) you wanted were added.
上記手順の中で、ssh-keygen コマンドはファイル名やパスフレーズの入力を求められますが、何も入力せずにエンターキーを押してください。
ssh-copy-idコマンドではパスワードの入力が求められます。コピー先ユーザーのパスワードを入力してください
これでパスワード無しでscpやsshができるようになります。
ssh-copy-idの出すメッセージの最後にあるように、"ssh user2@192.168.1.11" でコピー先ユーザーへログインしてみましょう。

以上

マルチスレッド処理のサンプル

コンピュータのプログラミングにおいて、複数の処理を並行して実行したい場合、main()関数から順次実行されるメインスレッド以外に、並行動作させるサブスレッドを生成することで平行処理を実現します。
では、複数の処理を並行して実行させたいプログラムって、どんなプログラムでしょうか?
例えば「処理A」ボタンと「処理B」ボタンを置き、押された方の処理を実行するプログラムを作成する事とします。シングルスレッドでは、処理Aが終了するまで処理Bを行えません。
これがマルチスレッドであれば、「処理A」を行っている間にも「処理B」ボタンを受け付け、処理Bを行うことが出来るようになります。
今回のサンプルとしたチャットプログラムも同様で、相手に送信する通信文の入力を受け付け、送信している間は相手からの通信文は受信できません。
マルチスレッド化する事で通信文の入力および送信と、相手からの通信文の受信を並行して行えるようにできます。(注)

ここからC言語でマルチスレッド処理を行う手順についてサンプルプログラムにて説明します。
本サンプルプログラムはソケット通信で送受信を行うチャットプログラムの一部です。実際のソケット通信は関数化しており、本サンプルでは省略しています。

このプログラムはメイン関数(メインスレッド)でコンソールからの通信文の入力を受け付け、それをチャット相手に送信します。
並行して動作するサブスレッドではチャット相手からの通信文を受信し、コンソールへ表示します。
メインスレッドもサブスレッドも相手が終了してソケットが切断されるか、Ctrl-Dが入力されるまでループして通信を繰り返します。

  1. recv_thread()がメインスレッドと並行して動くスレッドになります。
    引数の param はスレッドを生成する pthread_create()の第4引数が渡されます。スレッドに渡すパラメータがある場合は、これを使用します。
    渡す引数が複数ある場合は、パラメータブロックを構造体や配列で作成し、それをポインタで渡すのが一般的です。
  2. end_flag、complete_flag はサブスレッドの動作を制御するためのフラグです。
    メインスレッドとサブスレッドの間で相互の通知が必要ない場合は不要です。
  3. start_thread()はスレッドを起動する関数です。
    スレッド起動にはpthread_create()を使用します。引数はスレッドID、スレッド属性、スレッド、スレッドへ渡す引数です。
    スレッドIDはpthread_create()が生成したスレッドIDを取得するものなので、ポインタ渡しになることに注意してください。
    スレッド属性はpthread_attr_init()で生成した属性を渡します。スレッド属性変更の必要が無ければNULLでかまいません。
    スレッド属性にはデタッチ状態、スケジューリングポリシー、スケジューリングパラメータがあります。
    デタッチ状態はとはスレッド終了時のリソース解放処理を変更するもので、PTHREAD_CREATE_DETACHEDに設定するとスレッド終了を待ち合わせるpthread_join()が不要になります。
    詳しくはpthread_attr_initのmanページを見てください。
  4. terminate_thread()はスレッドを終了させる関数です。
    end_flagをtrueにセットし、受信スレッドの繰り返し処理を終わらせます。その後、pthread_join()を呼んでスレッドの終了を待ち合わせます。
    このサンプルコードではcomplete_flagを参照してスレッドの処理終了を待ち、スレッドが一定時間以内に終了しなかった場合はpthread_cancel()を呼び出し強制終了するようにしています。これはスレッドがループの中で待ちになるような場合への対応です。
  5. main()はメインスレッドで、コメントと合わせて読めばどんな処理をしているかは判るのでここでの説明は省略します。


============ ここから ============

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <pthread.h>

#include "socket.h"
#include "console.h"

volatile bool end_flag;
volatile bool complete_flag;

void * recv_thread( void * param )
{
    int fd = *((int *)param);  // 引数で渡される通信ソケットのディスクリプタ
    char message_buffer[79];
    int rtn;

    while( !end_flag )
    {
        
        // ソケットから通信文を受信する
        rtn = receive_socket( fd, message_buffer, sizeof( message_buffer ));
        // 通信文が取得できた場合
        if ( rtn > 0 )
        {
            // 通信文をコンソールへ表示する
            rtn = output_console( message_buffer );
        }
        // ソケットが切断された場合
        else if ( rtn == 0 )
        {
            fprintf( stdout,"disconnected.\n" );
            // ループから抜ける
            break;
        }
        // ソケット通信エラーの場合
        else
        {
            fprintf( stdout, "receive_soket error.\n");
            // ループから抜ける
            break;
        }
    }
    end_flag = true;
    complete_flag = true;
}

int start_thread( pthread_t * thread_id, int fd)
{
    int rtn;
    pthread_attr_t attr;
    // スレッド属性オブジェクトを初期化し、値を設定する
    pthread_attr_init( &attr );
    pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );

    end_flag = false;
    complete_flag = false;
    // 受信スレッドを生成する
    rtn = pthread_create( thread_id, &attr, recv_thread, &fd );

    // スレッド属性オブジェクトを破棄する
    rtn = pthread_attr_destroy( &attr );
    return rtn;
}

int terminate_thread( pthread_t thread_id )
{
    int rtn;
    // 受信スレッドを終了させる
    end_flag = true;
    // 受信スレッドの処理終了を1秒まで待つ
    for ( int i = 0 ; i < 10 && !complete_flag ; i++ )
    {
         struct timespec tm = { 0, 100*1000*1000 };
         nanosleep( &tm, NULL );
    }
    // スレッドが終了しなかった場合
    if ( !complete_flag )
    {
        // 強制的にスレッドを終了させる
        rtn = pthread_cancel( thread_id );
    }
    // 受信スレッドの終了を待つ
    rtn = pthread_join( thread_id, NULL );
    return rtn;
}

int main( int argc, char *argv[] )
{
    int fd;
    pthread_t  thread_id;
    char message_buffer[79];
    int rtn;

    // サーバーソケットを生成し、接続されるのを待つ
    fd = create_socket_server();
    // 接続失敗の場合、終了する
    if ( fd == -1 )
    {
        exit(1);
    }

    // 受信スレッドを生成する
    rtn = start_thread( &thread_id, fd );

    // 通信文の送受信を繰り返す
    while( !end_flag )
    {
        // コンソールから入力された通信文を取得する
        rtn = input_console( message_buffer, sizeof( message_buffer ));
        // 通信文が取得できた場合
        if ( rtn > 0 )
        {
            // ソケットで通信文を送信する
            rtn = send_socket( fd, message_buffer );
        }
        // Ctrl-Dが入力された場合
        else if ( rtn == 0 )
        {
            fprintf( stdout, "disconnect.\n");
            // ループから抜ける
            break;
        }
        // 入力エラーの場合
        else
        {
            fprintf( stdout, "input_console error.\n");
            // ループから抜ける
            break;
        }
    }
    // 受信スレッドを終了させる
    rtn = terminate_thread( thread_id );

    // ソケットをクローズする
    destroy_socket( fd );

    exit(0);
}
============ ここまで ============

注) 非同期通信を利用してプログラミングすることでも、同様の動作を実現できます。

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

Linux Tips (Twitter過去記事)

我が社のツイッターでは過去にlinuxのTips(ヒント、ノウハウ等)をツイートしていたことがありました。
その過去記事をここでまとめて紹介します。

linuxでディレクトリをマウントする方法

以前参加したプロジェクトで、NASをマウントして使用する物がありました。
そのシステムではNASが起動していない等の理由でマウントできなかった時には、予備として自ホスト内のディレクトリを同じパスで参照するというものでした。

そこで、予備側を参照させる為に考えたのが以下の方法です。

  1. シンボリックリンクを使う。
  2. 自ホストをNFSマウントする。
しかし、前者はマウントとシンボリックリンクの切り替え時にいちいちファイルを消してディレクトリまたはシンボリックリンクを作成しなければならないという手間があります。
また、後者は /etc/exportsの設定とnfsdを起動させる必要があります。
そこで他に簡単な方法がないかなとググってみました。

すると、「mount --bind」でディレクトリをマウントできるという記事を見つけました。

例えば、/home/APP/Config というディレクトリを /home/ext-mount/Config というパスで参照できるようにするには、
/home/APP/Config を /home/ext-mount/Config にマウントすればよいのです。
このコマンドは下記となります。

# mount --bind /home/APP/Config /home/ext-mount/Config


通常、ファイルやディレクトリの実体とアクセスするパスを関連させるにはシンボリックリンクを使用したほうが特権も不要で便利です。
使うのは今回の例のように条件によりマウント先を切り替える様な時ぐらいで、使用頻度は高くないと思います。

以上

timerfdによる周期処理のサンプル

Linuxでtimerfdを使用したウェイト処理の手順について説明します。

一定時間ウェイトする関数には以下のものがあります。

  1. sleep
  2. usleep
  3. nanosleep
  4. selectのタイムアウト指定で待つ
  5. settimer
  6. timerfd
それぞれメリット、デメリットがありますが、 ここに挙げたほとんどの関数はSIGNALが発生すると中断してしまい、正しく指定時間待つには残り時間を再度待つ為にウェイトのリトライを行う必要があります。
リトライでウェイトを継続できるとはいえ、指定時間の分解能や中断中の処理のオーバーヘッドにより当初指定した時間を正しく待てる保障はありません。

上の一覧のうち、timerfdを使用すればリトライ処理やオーバーヘッドを気にする事無く正確に指定時間を待つ事が出来ます。

ではtimerfdとはどの様なものでしょう?
timerfd_createのmanページには「満了通知をファイルディスクリプター経由で配送する タイマーの生成と操作を行う。……このファイルディスクリプターを select(2), poll(2), epoll(7) で監視できるという利点がある。」とあります。
つまり、タイマーが満了したらそのファイルディスクリプターにデータが入るというものです。

timerfdのその他のメリットについて
  1. selectを使用する事で、ファイルI/Oやソケット通信などのディスクリプタを使用する他のI/Oとの同時待ちができる。
  2. 複数のタイマーを同時に使える。
  3. 周期的なタイマーを設定できる。
以下にtimerfdを使用してreadで待つ例、selectで待つ例、ポーリングする例を示します。
timerfdに関しては本ブログ等よりオンラインマニュアルの方が詳しく書かれており、使用例も有ります。
Linuxマシンの端末でman timerfd_create と打つか、man timerfd_create でググって見ることをお勧めします。


========== readで待つ場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>

void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t     rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;        // ①
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    do {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
    } while ( rtn < 0 && errno == EINTR);

    printf("timerfd timeout.  exp=%lu\n", exp);
    close( tfd );
}
① itimerspec構造体で初回のタイマー及び2回目以後の周期タイマーを設定する。
  it_valueが初回タイマーで、it_intervalが周期タイマーである。
② readでtimerfdのタイマー満了を待つ。
  この例ではタイマー待ち中にシグナルの発生でreadがエラー終了する場合、リトライする様に対処している。

========== selectで待つ場合のサンプル ==========

#include <sys/select.h>
#include <sys/timerfd.h>

void timerfd_select()
{
    int tfd;
    struct itimerspec stime;
    fd_set fds;
    ssize_t    rtn;
    uint64_t exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, 0 );

    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while( 1 ){
        FD_ZERO( &fds );
        FD_SET( tfd, &fds );

        rtn = select( tfd+1, &fds, NULL, NULL, NULL );     // ①
        if ( rtn > 0 ){
            if ( FD_ISSET( tfd, &fds )){
                read( tfd, &exp, sizeof(uint64_t));        // ②
                printf("timerfd timeout.  exp=%lu\n", exp);
                break;
            }
        }
    }
    close( tfd );
}
① selectでタイマー満了を待つ
② selectで待った時でもreadでtimerfdのデータを読み込む必要がある。

========== readでポーリングする場合のサンプル ==========

#include <sys/timerfd.h>
#include <signal.h>
#include <signal.h>
#include <stdint.h>
void timerfd_read()
{
    int tfd;
    struct itimerspec stime;
    ssize_t    rtn;
    uint64_t   exp;

    tfd = timerfd_create( CLOCK_MONOTONIC, TFD_NONBLOCK );        // ①
    stime.it_value.tv_sec  = 1;
    stime.it_value.tv_nsec = 0;
    stime.it_interval.tv_sec  = stime.it_value.tv_sec;
    stime.it_interval.tv_nsec = stime.it_value.tv_nsec;
    timerfd_settime( tfd, 0, &stime, NULL );

    while (1) {
        rtn = read( tfd, &exp, sizeof(uint64_t));        // ②
        if ( rtn < 0 && errno == EINTR  ) continue;
        if ( rtn < 0 && errno == EAGAIN ) continue;        // ③
        break;
    }
    printf("timerfd timeout.  exp=%lu, rtn = %ld, errno=%d\n", exp, rtn, errno);

    close( tfd );
}
① TFD_NONBLOCKを指定する事でtimerfdはノンブロッキングになる。
② ノンブロッキンッグなので、このreadはタイマー満了していなくても終了する。
③ タイマー満了していない場合、readの戻り値は-1でerrnoにEAGAINがセットされる。
  readが成功完了するまで繰り返してタイマー満了を待つ。

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

複数通信先のソケット通信サンプル

C言語で、複数の通信先とソケット通信を行う手順についてサンプルプログラムにて説明します。

本サンプルプログラムはソケット通信のサーバー側で、1つのポートで最大10個の通信先プロセスからデータを受信するものです。
クライアント側のサンプルプログラムについては以前のソケット通信サンプルのを参照すること。

  1. socket()にてソケットを生成し、ディスクリプタを得る。
  2. bind()にてソケットにポート番号及びIPアドレスを設定する。
  3. listen()にて接続を待ち状態とする。
  4. 接続待ちのディスクリプタをディスクリプタ集合に設定する。
    ディスクリプタ集合はFD_ZERO()にて初期化し、FD_SETにてディスクリプタを設定する。
  5. 2回目以降で受信ディスクリプタがある場合、受信ディスクリプタをディスクリプタ集合に設定する。
  6. struct timeval 構造体の変数tvにタイムアウト値を設定する。
    変数tvはselect()にて値が変更される場合がある。その為、タイムアウト値は毎回設定すること。
  7. select()にて接続&受信を待ち受ける。
      select()の第一引数へはディスクリプタ集合にセットしたディスクリプタの最大値+1を指定する。
      第二引数へは接続及び受信待ちのディスクリプタ集合を指定する。
      第三引数へは送信完了待ちのディスクリプタを指定する。
      第四引数へは例外を監視するディスクリプタを指定する。
      本サンプルでは第三、第四引数は使用しない為、NULLを指定している。
      第五引数へはタイムアウト値を指定する。タイムアウト時間を指定しない場合はNULLを指定する。
      select()はシグナル受信した場合、タイムアウトした場合、ディスクリプタ集合で指定したディスクリプタにデータ受信などの変化があった場合にリターンする。
  8. select()の戻り値が負の値の場合、select()がエラー終了している。ただし、errnoがEINTRの場合はシグナル受信での終了なので、リトライで継続できる。
    本サンプルではシグナル受信の場合は4.からの処理を繰り返すようにしている。
  9. select()の戻り値が0の場合、タイムアウトしている。
    本サンプルでは何もしないため、4.からの処理を繰り返すようにしている。
  10. select()の戻り値が正の値の場合、ディスクリプタ集合で指定したディスクリプタのどれかに変化が有ったことを示している。
    どのディスクリプタに変化があったかはFD_ISSET()にて調べることが可能である。
  11. 接続待ちディスクリプタに接続があったかをFD_ISSET()にて調べ、接続があればaccept()にて接続を確立する。
    本サンプルではaccept()にて取得したディスクリプタをfd2配列にセットし、受信ディスクリプタとして使用する。
  12. 受信ディスクリプタに受信データがあるかをFD_ISSET()にて調べ、受信データがrecv()にてデータを受信する。
    recv()の戻り値が0であった場合、そのディスクリプタはクライアント側から切断された事を示している。
  13. 本サンプルではfd2配列にて10個の受信ディスクリプタを管理している。接続されたらディスクリプタをセットし、切断されたら-1へリセットすることで同時に最大10個の通信先からのデータを受信できる。
  14. なお、ソケット通信とは無関係であるが、何箇所か出てくる sizeof(fd2)/sizeof(fd2[0]) はfd2配列の要素数を取得するものである。

========== サーバー側 ============


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

int main(int argc, char** argv)
{
    int fd;             // 接続待ち用ディスクリプタ
    int fd2[10];        // 通信用ディスクリプタの配列
    struct servent *serv;
    struct sockaddr_in addr;
    socklen_t len = sizeof(struct sockaddr_in);
    struct sockaddr_in from_addr;
    char    buf[2048];
    int     cnt;

    // 受信バッファを初期化する
    memset(buf, 0, sizeof(buf));
    // 通信用ディスクリプタの配列を初期化する
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        fd2[i] = -1;
    }
    // ソケットを作成する
    if (( fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) {
        fprintf( stdout, "socket error : fd = %d\n", fd );
        return -1;
    }

    // IPアドレス、ポート番号を設定
    addr.sin_family = AF_INET;
    addr.sin_port = ntohs(50000);
    addr.sin_addr.s_addr = INADDR_ANY;
    // バインドする
    if ( bind( fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ) {
        fprintf( stdout, "bind error\n" );
        return -1;
    }
    // 接続待ち状態とする。待ちうけるコネクト要求は1個
    if ( listen( fd, 1 ) < 0 ) {
        fprintf( stdout, "listen error\n" );
        return -1;
    }

    int     maxfd;          // ディスクリプタの最大値
    fd_set  rfds;           // 接続待ち、受信待ちをするディスクリプタの集合
    struct timeval  tv;     // タイムアウト時間

    while ( 1 ){
        // 接続待ちのディスクリプタをディスクリプタ集合に設定する
        FD_ZERO( &rfds );
        FD_SET( fd, &rfds );
        maxfd = fd;
        // 受信待ちのディスクリプタをディスクリプタ集合に設定する
        for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
            if ( fd2[i] != -1 ){
                FD_SET( fd2[i], &rfds );
                if ( fd2[i] > maxfd ) maxfd = fd2[i];
            }
        }
        // タイムアウト時間を10sec+500000μsec に指定する。
        tv.tv_sec = 10;
        tv.tv_usec = 500000;

        // 接続&受信を待ち受ける
        cnt = select( maxfd+1, &rfds, NULL, NULL, &tv );
        if ( cnt < 0 ){
            // シグナル受信によるselect終了の場合、再度待ち受けに戻る
            if ( errno == EINTR ) continue;
            // その他のエラーの場合、終了する。
            goto end;
        } else if ( cnt == 0 ) {
            // タイムアウトした場合、再度待ち受けに戻る
            continue;
        } else {
            // 接続待ちディスクリプタに接続があったかを調べる
            if ( FD_ISSET( fd, &rfds )){
                // 接続されたならクライアントからの接続を確立する
                for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                    if ( fd2[i] == -1 ){
                        if (( fd2[i] = accept(fd, (struct sockaddr *)&from_addr, &len )) < 0 ) {
                            goto end;
                        }
                        fprintf( stdout, "socket:%d  connected. \n", fd2[i] );
                        break;
                    }
                }
            }
            for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
                // 受信待ちディスクリプタにデータがあるかを調べる
                if ( FD_ISSET( fd2[i], &rfds )){
                    // データがあるならパケット受信する
                    cnt = recv( fd2[i], buf, sizeof(buf), 0 );
                    if ( cnt > 0 ) {
                        // パケット受信成功の場合
                        fprintf( stdout, "recv:\"%s\"\n", buf );
                    } else if ( cnt == 0 ) {
                        // 切断された場合、クローズする
                        fprintf( stdout, "socket:%d  disconnected. \n", fd2[i] );
                        close( fd2[i] );
                        fd2[i] = -1;
                    } else {
                        goto end;
                    }
                }
            }
        }
    }
end:
    // パケット送受信用ソケットクローズ
    for ( int i = 0; i < sizeof(fd2)/sizeof(fd2[0]); i++ ){
        close(fd2[i]);
    }
    // 接続要求待ち受け用ソケットクローズ
    close(fd);
    return 0;
}
============ ここまで ============

上記サンプルプログラムを使用して発生した障害,問題などに関して,プログラムの作者および(株)アイズ・ソフトウェアは一切の責任を負いません。
使用はご自身の責任で行ってください。

ETロボコン技術要素・自己位置推定

ETロボコン・デベロッパーズ部門プライマリークラスは基本的にはコースに引かれたラインをトレースして走る競技です。
ですので、自己位置については1次元的に走った距離がわかっていれば位置がわかります。
しかし、チームアイズでは少しでもタイムを縮めるために、急カーブや複合カーブではラインをはずして走るという戦略をとりました。
最初はバランスコントロールライブラリに渡す"ターン値"を決めて曲がるという事を試しましたがロボットの速度、バッテリー残量等の違いで常に同じ弧を描いて曲がるという事が出来ませんでした。
そして考えたのが仮想の円をライントレースするという方法です。内側が黒、外側が白の仮想の円を描き、そのエッジをPID制御でライントレースして走行させていると考えてください。
この仮想円は中心の座標と半径で決まります。あと、ロボットの座標がわかれば、ロボットが仮想円のエッジ(半径)からの距離が計算できます。この距離を明るさの差分に当てはめ、PID制御でライントレースします。

この案の実現の為、ロボットの自己位置推定処理を実装しました。



ETロボコンのロボットで自己位置推定の為に使用できるのは左右の車輪モーターから取得できる回転角度だけです。
この回転角度より 2πr×(回転角度÷360) で車輪の進んだ距離が算出できます。

ロボットが一定期間の間に図1のように走ったとします。
進んだ距離と曲がった角度を計算する為に、図2のように変形させます。
図2よりロボットを取り除き、計算に必要な記号等を追加したのが図3です。

進んだ距離と曲がった角度は下記の式で計算できます。
   R = Rモーター回転角度×車輪直径×π÷360
   L = Lモーター回転角度×車輪直径×π÷360
   d = (L+R)÷2
   θ= atan((R-L)÷W)

進んだ距離と曲がった角度より新しい座標を求めます。この座標と角度を始点として上記の計算を繰り返して自己位置推定をしています。

自己位置推定の結果をエクセルで座標にプロットしたのが図4です。コースは2010年版のコースです。

クリックでエクセルをダウンロード



図1から図2への変形でわかるとおり、この自己位置推定処理は誤差を含んだ計算となっています。長く走るほど誤差が累積していきます。その為、遠くまで正確にコースを走るにはコースの何らかの印を検出し、位置情報を補正する処理が必要となります。

本ブログの説明はチームアイズのロボットに実装している自己位置推定処理を説明したものです。
本ブログの記載を元にETロボコンのモデル又はプログラムを作成した結果については(株)アイズ・ソフトウェアは一切の責任を負いません。使用はご自身の責任で行ってください。