RaspberryPiとArduinoを連携する【Firmata編】

RaspberryPiとArduinoを連携する【Firmata編】

前回のエントリではRaspberryPiとArduinoをUSBシリアルで接続をしてみたというものでした。

uepon.hatenadiary.com

すると・・・

ぐえ、スペル適当にしてあとで治そうと思っていたのですが直してませんでした。Johnny-Fiveもスペル違ってたしw。 ってことで、引き続きFirmataでRaspberryPiとArduinoを連携させてみたいと思います。

んでFirmataって?

Firmataとは?

http://firmata.org/wiki/Main_Page

いろいろググってみたところFirmataはシリアル通信を介してPC等のホストマシンから Arduinoやその他マイコンボード等のデバイスを制御するためのプロトコルのようです。

RaspberryPiというかpythonからこのプロトコルを使う場合には

github.com

github.com

上記2つがあるようです。更新履歴をみるとpyFirmataのほうが新しいようですのでそれを使用してみます。python-firmataのメンテナンスは長期間行われていないっぽいですね。

Firmata使用のためのArduino側の準備

以下のドキュメントを見ると

https://raspberrypi-aa.github.io/session3/firmata.html

  1. Click File->Examples->Firmata->StandardFirmata
  2. From the Tools->Board menu, select the type of Arduino you are using.
  3. From the Tools->Serial Port menu, choose the USB port to which your Arduino is connected.
  4. Click the upload button (it looks like a right arrow, just next to the checkmark) and wait for your sketch to upload. A message in the bottom black windowwill indicate success or failure
  5. Once the Firmata sketch is loaded on your Arduino, you can test it out with the Firmata Test Program.

という手順で準備ができるそうです。

まずはArduinoIDEを起動します。

f:id:ueponx:20170101212659p:plain

メニューバーから【ファイル】→【スケッチ例】→【Firmata】→【StandardFirmata】を選択します。

f:id:ueponx:20170101212924p:plain

するとエディタ部分にStandardFirmataのソースコードが表示されます。これを接続したArduinoに書き込みを行います。 このときArduinoを接続したポートとボードタイプを適切にしておく必要があります。

f:id:ueponx:20170101213421p:plain

【Ctrl+U】で書き込みを行います。

f:id:ueponx:20170101213725p:plain

このようにエラーが無ければ作業は完了です。

pyFirmataのインストール

続いてはRaspberryPi側でpython環境の Githubのドキュメントを見ながらのインストールになります。

$ sudo pip install pyfirmata
Downloading/unpacking pyfirmata
  Downloading pyFirmata-1.0.3-py2.py3-none-any.whl
Requirement already satisfied (use --upgrade to upgrade): pyserial in /usr/lib/python2.7/dist-packages (from pyfirmata)
Installing collected packages: pyfirmata
Successfully installed pyfirmata
Cleaning up...

インストール作業としてはこれで完了です。以下のようにpipのlistで以下のように確認します。

$ pip list
()
pyFirmata (1.0.3)
()

リスト上に表示されればインストールはできています。 あとはpythonインタプリタで動作を確認します。 以下では13pinに配置されているLEDのON/OFFを行います。

$ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyfirmata import Arduino, util
>>> board = Arduino('/dev/ttyACM0')
>>> board.digital[13].write(1)
>>> board.digital[13].write(0)

ポートのアクセスですが、

pin13 = board.get_pin('d:13:o')
pin13.write(1)

という風にも記載することができます。d:13:oはデジタル13pinをoutputにするという意味になります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pyfirmata import Arduino, util
import time

board = Arduino('/dev/ttyACM0')
pin13 = board.get_pin('d:13:o')

print('start')
pin13.write(1)
time.sleep(3)
pin13.write(0)
time.sleep(3)
print('end')

まとめ

使っていて感じたのはFirmataはどちらかというと主従関係がはっきリしている場合に使うのかなと。 Arduino側のコードをほとんど記述しなくてもGPIOなどのポートが自由に使用できるのでそういった意味では実装が容易にできるもメリットだと思います。 接続したArduinoが並列で動作できる点ではシリアルを使っての制御がいいかなと思いますが、データの収受で同期を取るのが面倒かなと思いますが、そのあたりは臨機応変にやるということでしょうか。

RaspberryPiとArduinoを連携する【USBシリアル編】

RaspberryPiとArduinoを連携する

RaspberryPiを使っているとセンサ系での物足りなさを感じてくることが良くあります。 特にアナログ値を使うようなセンサでは回路の知識がないと悲しくなります。 そこでそういった部分をカバーすることを考えるとArduinoとの連携を行いたくなってきます。 ArduinoであればGroveインターフェースで回路側の負担が少なくできることもメリットです。 あとオリジナルのUnoではなく激安品まで考えると1枚当たりのコストはセンサより安価にもできます。 (300円程度の商品もあるようです。絶対という保証はないですが。)

Arduino以外にmbedも使うことができると思うのですが、まずはArduinoってことで。

Arduinoとの接続は

  • I2C接続
  • シリアル通信
  • Firmata

とありますが、今回はシリアル通信を行うことにします。 個人的にはシリアルが一番汎用的に使用できるロジックだと思いますが、I2Cもいい選択肢かなと思います。 コードのベースができてしまえば使いまわしができます。Firmataはどうなんでしょうか?(RaspberryPiでFirmata接続するArduinoの エントリがあんまりないなと思いますが一般的じゃない?node.jsでJohnny-Fiveのほうが一般的?)

USBポートにArduinoを接続してみる

【接続前】

$ lsusb
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後】

$ lsusb
Bus 001 Device 005: ID 2341:0001 Arduino SA Uno (CDC ACM)
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Bus 001 Device 005: ID 2341:0001 Arduino SA Uno (CDC ACM)が追加されています。Arduinoって書いてあるので発見は楽です。認識はされているので今度はシリアルデバイスとしてどのように認識されているか確認してみます。

【接続前】

$ ls /dev/ttyA*
/dev/ttyAMA0

【接続後】

$ ls /dev/ttyA*
/dev/ttyACM0  /dev/ttyAMA0

接続後にはシリアルデバイスとして/dev/ttyACM0が追加されています。これを使うことになります。

Arduinoのシリアル接続制御のひな形

Arduinoとの接続がうまく行ったのでArduinoでシリアル送受信をする処理を書いてみました。stringのライブラリがうまく使えなかったので微妙なところはあるのですが…

f:id:ueponx:20170101132859j:plain

#include <String.h>
#define maxLength 64
String inString = String(maxLength);

void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  inString = "";
}

void loop() {
  if (Serial.available() > 0) {
    char inChar = Serial.read();
    if (inString.length() < maxLength) {
      inString.concat(String(inChar));
      if (inChar == '\n') {
        if (inString.startsWith("ON")) {
          Serial.println("<--HI-->");
          digitalWrite(13, HIGH);
        } else if (inString.startsWith("OFF")) {
          Serial.println("<--LOW-->");
          digitalWrite(13, LOW);
        }
        inString = "";
      }
      Serial.println(inString);
    }
    else {
      Serial.println("Maximum number of characters.");
    }
  }
}

このコードではON【CR】【LF】(またはON【LF】='\n')を受信すると13pin(オンボード上)のLEDをONとし、 OFF【CR】【LF】(またはOFF【LF】)を受信するとLEDをOFFするという動きになります。 (厳密にいうとONから始まり【LF】で終わるような場合にONし、OFFから始まり【LF】で終わるような場合はOFFにします。)

この受信文字("ON"など)の処理部分を拡張することで多様な処理ができるかなと思います。 ただ、センサの値によりトリガとして動作させる時には別途処理は考えないといけないかと。

pythonからシリアル制御を行う

pySerialを使用します。

http://pythonhosted.org//pyserial/

このブログでも過去に使用していますので以下を参考にします。

uepon.hatenadiary.com

インタラクティブシェルで以下のようにするとLEDのON/OFFができました。

$ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import serial
>>> import time
>>> s = serial.Serial('/dev/ttyACM0',9600)
>>> time.sleep(2)
>>> s.write("ON\n")
3
>>> s.write("OFF\n")
4
>>> s.write("ON\n")
3
>>>

これでシリアル通信を介したRaspberryPiとArduinoの通信処理はうまく行ったようです。

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

import serial
import time

s = serial.Serial('/dev/ttyACM0', 9600)
time.sleep(2)
s.write("OFF\n")
time.sleep(10)
s.write("ON\n")
s.close()

まとめ

とりあえず、RaspberryPiとArduinoをUSB接続してシリアルで制御できました。 これでそれなりにセンサデバイスとしてArduinoが使えそうです。 なんとなくですが、能動的にセンサーのデータを取得するのはArduinoで行い、RaspberryPiは受信に徹する方がいいのかなと思います。その場合は、RaspberryPi側での受信にはtimeoutを設定することでブロッキング時間を短縮したほうがいいかなと思います。例えば以下のような感じ。

s = serial.Serial('/dev/ttyACM0', baudrate=9600, timeout=3)

折角なので他の方法も試してみたいと思います。

RaspberryPiのSDカードアダプタを使用する

RaspberryPiのSDカードアダプタを使用する

実はこれまでRaspberryPiはRaspberry Pi 1 Model B使用していたのですが、やっぱりSDカードの出っ張りが気になってきました。 ということで、出っ張らないSDカードアダプタを購入し、SDカードをMicroSDカードにに変更することにしました。 これまで使っていたSDカードはこれになります。

16GbyteのSDカードだったので以下のカードへの移行を行います。

出っ張らないSDカードのアダプタは

を使用することにしました。

早速SDカードのバックアップをとります。いつも通りWin32DiskImagerを使用します。

ja.osdn.net

バックアップはこのあたりのエントリで。(昔は使い方の勘違いがあったので)

uepon.hatenadiary.com

バックアップが作成できたらMicroSDカードに書き込むわけですが、

f:id:ueponx:20161228130224j:plain

あれ?サイズが違うエラー?

カードの刻印には16Gbyteと書いてあるのですが、どうも微妙にサイズを違う?

再インストールに向けての準備(Sambaのインストール)

これでは折角のアダプタが無駄になるのでファイルのバックアップ(イメージのバックアップではなく)を行って再インストールを行おうと思います。 FTPでバックアップをするのは面倒なので、RaspberryPiにsambaをインストールします。

Sambaのインストールと設定と接続

こちらのブログを参考にしました。以下でインストールします。かなり時間がかかるのでびっくりしますが待機です。

$ sudo apt-get update
$ sudo apt-get install samba

ファイルのバックアップをとるだけなのでブログの設定と同様に

共有設定は以下で追加します。

  • 共有名 : pi
  • 共有するフォルダ : /home/pi (ユーザー名「pi」でログインした際のホームフォルダ
  • 読み込みだけでなく、書き込みも可能
  • クライアントからの接続は、ゲスト接続
  • サーバー側では、ファイル操作を、ユーザー「pi」 によって実行

設定ファイルをvimで開き

$ sudo vim /etc/samba/smb.conf
[pi]
path = /home/pi
read only = No
guest ok = Yes
force user = pi

以上を追加します。

設定の追加後に以下のようにサービスを再起動します。

$ sudo service smbd restart

これでWindows側からアクセスが可能になります。

まとめ

単純に16GByteと書いてあるメモリカードのサイズが一致しないこともあったので結構焦ってしまいました。 折角なのでRASPBIAN JESSIE WITH PIXELも2016-11-25に出たので、これまでの環境をクリーンインストールして移行したいと思います。

www.raspberrypi.org

RaspberryPiのマイクで会話してみる【ヒミツのクマちゃん 完結編】

RaspberryPiのマイクで会話してみる【ヒミツのクマちゃん 完結編】

これまでのエントリの内容に最後に「今日会った出来事を教えてね」感じで会話するようにします。

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

動作の概要は以下のような感じになります。

f:id:ueponx:20161226015507j:plain

これまでのソースファイルに音声認識APIへのアクセスをいれ、そのあとにNobyAPIに送信することで 会話を行うことになります。少しアレンジした点は音声認識の開始と終了に効果音をいれたところになります。

                # pygame
                pygame.mixer.music.load("./Q.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

このように音声認識開始と終了の部分に短い効果音をいれると話す人の戸惑いが少ないと思います。(ペッパーなどでも入れてますね。) 他のロボットと違い、目などの色で話している人にロボットがどのような状態かを知らせる手段がないので こういう工夫が必要だと思います。

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

import requests
import json
import types
import time
import picamera
import os
import sys
import pyaudio
import pygame.mixer
import wave

# MSEmotionAPIのwait
SHORTSLEEP = 10
LONGSLEEP = 60

# 音声録音のパラメータ
chunk = 1024*2
FORMAT = pyaudio.paInt16
CHANNELS = 1
#サンプリングレート、マイク性能に依存
RATE = 16000
#録音時間
RECORD_SECONDS = 5

# pygame
pygame.mixer.init()

try:
  while True:
        # MS Emotion APIで顔の認識を行う
        with picamera.PiCamera() as camera:
                camera.start_preview()
                camera.hflip = True
                camera.vflip = True
                time.sleep(2) #カメラ初期化
                camera.capture('foo.jpg')

        url = 'https://api.projectoxford.ai/emotion/v1.0/recognize'
        MS_APIKEY = '【Microsoft Emotion APIのキー】'
        headers = {'Content-type': 'application/octet-stream', 'Ocp-Apim-Subscription-Key': MS_APIKEY}
        payload = open('./foo.jpg', 'rb').read()

        r = requests.post(url, data=payload, headers=headers)
        data = r.json()

        ninshiki = len(data)

        # 顔認識が出来たらNoby APIにアクセスしておみくじを引く

        if ninshiki > 0:
                dict = data[0]['scores']
                max_key = max(dict, key=(lambda x: dict[x]))

                print "key: %s,\t value:%f" %(max_key, dict[max_key])

                ENDPOINT = 'https://www.cotogoto.ai/webapi/noby.json'
                NOBY_APIKEY = '【NobyのAPIキー】'

                payload = {'text': 'おみくじ引きたいな。', 'app_key': NOBY_APIKEY}
                r = requests.get(ENDPOINT, params=payload)
                data = r.json()

                response = data['text']
                # print "response: %s" %(response)

                uranai = response.split("\r\n")[1]
                print uranai

                # おみくじのフレーズを話す
                voice1 = 'こんにちは、今日は来てくれてありがとう。今日の運勢を占うね。'
                # shを呼び出す。
                cmdtext = './jtalk2.sh ' + uranai
                # print cmdtext

                os.system('./jtalk2.sh ' + voice1) #成功すると0 # import os
                os.system(cmdtext.encode('utf-8')) #成功すると0 # import os

                os.system('./jtalk2.sh ' + '最近あったできごとを教えて?')
                print 'マイクに5秒間話しかけてください >>>'

                # pygame
                pygame.mixer.music.load("./Q.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

                # pyaudio
                p = pyaudio.PyAudio()
                #マイク0番からの入力を5秒間録音し、ファイル名:voice.wavで保存する。
                #マイク0番を設定
                input_device_index = 0
                #マイクからデータ取得
                stream = p.open(format = FORMAT,
                                channels = CHANNELS,
                                rate = RATE,
                                input = True,
                                frames_per_buffer = chunk)
                all = []
                for i in range(0, RATE / chunk * RECORD_SECONDS):
                        data = stream.read(chunk)
                        all.append(data)

                stream.close()
                data = ''.join(all)
                out = wave.open('voice.wav','w')
                out.setnchannels(1) #mono
                out.setsampwidth(2) #16bits
                out.setframerate(RATE)
                out.writeframes(data)
                out.close()

                p.terminate()
                print '<<< 録音完了'

                pygame.mixer.music.load("./OK.mp3")
                pygame.mixer.music.play()
                time.sleep(1)
                pygame.mixer.music.stop()

                path = 'voice.wav'
                DOCOMO_APIKEY = '【docomo音声認識APIのキー】'
                url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(DOCOMO_APIKEY)
                files = {"a": open(path, 'rb'), "v":"on"}
                r = requests.post(url, files=files)
                print r.json()['text']

                payload = {'text': r.json()['text'], 'app_key': NOBY_APIKEY}
                r = requests.get(ENDPOINT, params=payload)
                print r.json()['text']

                cmdtext = './jtalk2.sh ' + r.json()['text']
                os.system(cmdtext.encode('utf-8')) #成功すると0 # import os

                os.system('./jtalk2.sh ' + '教えてくれてありがとう。また会いにきてね!')

                print 'LONG_SLEEP_MODE'
                time.sleep(LONGSLEEP)
        else:
                print 'SHORT_SLEEP_MODE'
                time.sleep(SHORTSLEEP)

except KeyboardInterrupt:
  sys.exit()

もう少し関数とかクラスとかを使用するときれいにまとまりますが、それは今後の自分のpythonスキルの向上のために今後修正していこうと思います。 また、音声認識に関しては非常にネットワークAPIとの相性が良くない(特にリアルタイムのレスポンスを求めるものは難しい)のでスレッドなどを使用したりなどのイライラさせない工夫がカギになってくると思います。

まとめ

やっと音声関係というか「ヒミツのクマちゃん」を使ったもので完結編になりました。 軽い気持ちで購入したヒミツのクマちゃんですが、ちゃんとした会話のできるロボットになってくれました。 ぬいぐるみの出来がよいので、最終的にはRaspberryPiをどういう風に隠すか、マイクをどのようにするかなど考えればきりがないのですが。 それも含めて工作や工夫の醍醐味のあるテーマだったかなと思います。

Raspberry Pixelを古いPCにインストールしてみる

RASPBIAN JESSIE WITH PIXELが発表されましたが、更にこれにPCとMACに対応したバージョンが登場しました。 Raspberry Pixelは軽量デスクトップOSとして使用できそうです。昔から古いPCにUbuntuにをインストールしようと思っていたので これを機にPixelをインストールしようと思います。

掲載ページ

www.raspberrypi.org

The MagPi magazineという雑誌のおまけについたDVDの公開されたもののようですが、そのイメージをダウンロードできるようになっています。

ダウンロードリンク

http://downloads.raspberrypi.org/pixel_x86/images/pixel_x86-2016-12-13/2016-12-13-pixel-x86-jessie.iso

今回はUSBメモリに関してはSANDISKのコンパクトタイプのものAmazonでは1,000円ほどですが、秋葉原あたりではもう少し安く購入できました。 出っ張りも少ないし、アクセススピードも結構早いかなと思います。

そしてターゲットとなる古いPCは以下になります。もう購入してから5~6年ほど立ったものかなと思いますが、【Acer Aspire One 753】となります。 スペックとしては以下のようになります。以前からメモリは増設してあったものなので4Gほど搭載されています。Windows7のプリインストールモデルだったのですが、 Windows10ではSSDに換装してもCPUが100%になることもしばしばという状態だったので、ほどんど使っていませんでした。

www.notebookcheck.net

かなり旧式のCeleronですし、しかたないです。

公式サイドからISOファイルをダウンロードしたらサイト推奨の【Etcher】というツールを使ってイメージ書き込みをUSBにするわけですが、 【Win32DiskImager】でも試してみましたが問題なくできました。

etcher.io

sourceforge.net

あとは、USBを挿して起動すれば問題なくOSが起動できます。

https://www.raspberrypi.org/wp-content/uploads/2016/12/VirtualBox_PIXEL-CD_18_12_2016_10_25_32.png

https://www.raspberrypi.org/wp-content/uploads/2016/12/splash-768x576.jpeg

f:id:ueponx:20161225214324p:plain

f:id:ueponx:20161225214328p:plain

ChromiumVLCもインストールされているのでそれなりに使用できるようです。使用したPCでも負荷もほとんどないようなので常用できそうです。ただし、キーマップとか日本語化はしないといけないようですし、コマンド関係もかなり絞られているようなので別途インストールは必要の様です。

使用してみた感想としてはわりといい感じです。むしろPCで使用するよりもVMとして使用したほうがいいのかも?

【関連】

uepon.hatenadiary.com

/* -----codeの行番号----- */