見出し画像

最小限の労力でラジコンカーを作る2

結論

少し長いので先に結論。↓Bluetoothでラジコンを制御できるこういうのを作った。

前作「最小限の労力でラジコンカーを作る」の問題点

もう1年以上前に「最小限の労力でラジコンカーを作る」というタイトルで初のnoteデビューを果たし、それから数件リリースした後は、ほとんど記事を書かずにnoteを放置していた。ただ、ラジコンカー自体は2歳になる息子のおもちゃとしてたまに活躍していた。そして、おもちゃとしてセットアップするたびにある問題に直面していた。それは、

ラジコンのIPアドレスが変わる問題

前回のラジコンカーの構成をおさらいすると以下のようなもの。

画像2

このとき、ラジコンカー側のRaspberry Piのネットワーク設定はデフォルトでDHCPとなっているため、時間が経つとIPアドレスの割当が変わってしまう。そのため、都度IPアドレスを調べる必要があった。固定IPにすれば良いと言ってしまえばそれまでだが、そもそもアクセスポイントを経由するというのが、回りくどくてイケていない。さらに言うと、PCのキーボードでラジコンを操作するというのもイケていない。そこは「最小限の労力で」という特性上仕方ない部分があるが、現状の利用頻度を考慮すると、多少の労力を追加して利便性を向上させる必要もあるのではないか?いや必要があるだろう。

Bluetoothで接続しよう。専用のスマホアプリも作ろう。

ということで、「Bluetoothで接続しよう。専用のスマホアプリも作ろう。」と考えた。Bluetooth(BT)であればペアリング情報は基本的に保持されるし、スマホアプリで制御すれば、利便性も上がる。

画像3

設計

今回の変更をまとめると以下のようになる。

■変更前 
  

画像2

■変更後
  

画像3

BTのプロファイルにはSPP(Serial Port Profile)を用いる。Serial Port Profileとはシリアル通信のためのポートをエミュレートするプロファイルだ。スマホアプリには前進、右折、左折、後進の4つのボタンを用意し、ボタンタップに連動してBT経由でメッセージをRaspberry Piに送信する。Raspberry Pi側ではメッセージを受信すると対応するモータ制御を行う。モーター制御は前回作った実装が流用できる。ざっくりとした流れは以下となる。

画像1

尚、Raspberry Piのモデルには前回同様Raspberry Pi Zeroを使う。

実装

■Raspberry Pi側

Raspberry Pi側でやるべきことは、以下となる。

1. サポートプロファイルにSPPを追加
Raspberry PiはデフォルトではSPPをサポートしていないため、SPPで接続ができるようにSPPをサポートプロファイル追加する必要がある。以下のように、/etc/systemd/system/dbus-org.bluez.serviceを編集して、bluetoothdの起動パラメータにSPPを追加するだけで良い。

● /etc/systemd/system/dbus-org.bluez.service

[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/lib/bluetooth/bluetoothd -C # デフォルトの記述に"-C"を追加
ExecStartPost=/usr/bin/sdptool add SP      # 上の行に続けてこの一行をまるごと追加

上記の変更により、スマホからのSDP(Service Discovery Protocol)の応答メッセージにSPPが付与される(Raspberry PiがSPPをサポートしている旨がスマホに伝えられる)。変更が終わったら、変更を反映するために再起動しておく。

2. SPP通信路を確立
1. でスマホアプリに対してSPPをサポートしていることを通知すると、スマホアプリは接続要求をRaspberry] Pi側に投げてくる。Raspberry Pi側ではSPP接続要求を待ち受けて通信路を確立するためのサービス(rfcomm.service)を作る。

●/etc/systemd/system/rfcomm.service(新規作成)

[Unit]
Description=RFCOMM service
After=bluetooth.service
Requires=bluetooth.service

[Service]
ExecStart=/usr/bin/rfcomm watch hci0 1 getty rfcomm0 115200 vt100 -a pi

[Install]
WantedBy=multi-user.target

起動時にサービスが自動起動するように登録。

sudo systemctl enable rfcomm.service

rfcomm.serviceを起動する。

sudo systemctl start rfcomm.service

3. モーター制御
2. の接続完了後、スマホアプリを操作することで、前進、後退などの制御指示が送られてくる。これらのメッセージを処理してモーターを制御するサービス(camrobo_bt_controller.service)を作成する。

●/etc/systemd/system/camrobo_bt_controller.service(新規作成)

[Unit]
Description=Start Cam Robo on Bluetooth

[Service]
ExecStart=/usr/bin/python /usr/lib/camrobo_bt/server.py
Restart=always

[Install]
WantedBy=multi-user.target

次に、camrobo_bt_controllerの実体であるserver.pyを作る。

●/usr/lib/camrobo_bt/server.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import argparse
import os
from time import sleep

from driver import Moter

left = Moter("Left", 20, 21)
right= Moter("Right", 27, 22)

def handle_message(message):
   if message == 'forward':
       right.forward()
       left.forward()
   elif message == 'back':
       right.back()
       left.back()
   elif message =='right':
       right.brake()
       left.forward()
   elif message =='left':
       right.forward()
       left.brake()
   elif message =='brake':
       right.brake()
       left.brake()

def importargs():
   parser = argparse.ArgumentParser("Processing CAM COMTROLLER server")
   parser.add_argument('--port', '-p', required=False, default='/dev/rfcomm0')
   args = parser.parse_args()
   return args.port

def run(port='/dev/rfcomm0'):
   while True:
       if os.path.exists(port):
           print('Found port {}'.format(port))
           break
       else:
           sleep(1)
   try:
       with open(port) as p:
           while True:
               message = p.readline().strip()
               print('received message {}'.format(message))
               handle_message(message)
   except KeyboardInterrupt:
       print("\nCtl+C")
   except Exception as e:
       print(str(e))
   finally:
       left.cleanup()
       right.cleanup()
       print("\nexit program")

def main():
   right.brake()
   left.brake()
   port = importargs()
   run(port=port)

if __name__ == '__main__':
   main()

起動時にサービスが自動起動するように登録。

sudo systemctl enable camrobo_bt_controller.service

camrobo_bt_controller.serviceを起動する。

sudo systemctl start camrobo_bt_controller.service

■スマホアプリ側

今回はAndroidアプリを作る。SPPを用いた通信としてはあまりにも有名(?)なオープンソースのBluetoothChatを流用する(最小限の労力)。

基本的な機能はBluetoothChatで実装されているため、変更点だけ掻い摘んでここに記載する。

1. チャット画面削除、コントローラの追加
BluetoothChatはその名の通りSPPを用いてチャットをするためのサンプルアプリなので、接続完了後はチャットをやりとりする画面になる。このようなチャット画面を削除し、前進、後退なのでボタンを追加してコントローラを作る。以下のようなUIが出来上がる。

画像6

2. ボタンの操作に応じたメッセージ送信処理の追加
1.でボタン操作ができるようになったので、ボタンがタップされたら対応するメッセージをSPPでRaspberry Piに送信する。

準備

実際に動かしてみるにはスマホとRaspberry Piをペアリングする必要がある。まず、Raspberry Pi側で以下を実行

sudo bluetoothctl
[bluetooth]# scan on    (←bluetoothctlのコンソール画面になる)

Raspberry Pi側のコンソールは一旦そのまま、Androidスマホので「設定アプリ」→「接続済のデバイス」→「新しいデバイスとペア設定する」と辿りスマホを他のデバイスに見えるようにする。これでRaspberry Pi側でスマホを見つけられるようになるので、以下で接続、保存、終了する。

[NEW] Device XX:XX:XX:XX:XX:XX Pixel 2 (←XX:XX・・・はスマホのデバイスアドレス)
[bluetooth]# pair XX:XX:XX:XX:XX:XX
[agent] Confirm passkey 574060 (yes/no): (←yesと打つ。スマホ側でも「ペア設定」)
[bluetooth]# connect XX:XX:XX:XX:XX:XX
[bluetooth]# trust XX:XX:XX:XX:XX:XX
[bluetooth]# scan off
[bluetooth]# exit

実験

動かしてみた。


ソースコード

■ラジコンカー側のソースコード

https://github.com/K-TEE-R/camrobo_moter_server_bt

■スマホアプリのソースコード

https://github.com/K-TEE-R/BluetoothCamRoboController


この記事が気に入ったらサポートをしてみませんか?