Help us understand the problem. What is going on with this article?

Arduino で超音波距離センサ(SRF02)を読んで Mac や Raspberry Pi から MQTT で Sango に publish して、websocket で Chart.js に描画したり、PC で excel に出力したりしたメモ

More than 5 years have passed since last update.

概要

登場する要素を全部羅列したら日本語としてとても読むに耐えないタイトルになってしまいましたことをお詫びいたします。基本的に自分への備忘録としての性格のドキュメントなのですが、どなたかのご参考になることもあるかと存じ、僭越ながらここにメモを残させていただきました次第にございますれば、記述内容のおかしい所や効率的でない箇所などのご指摘をいただけましたら恐縮至極でございます。

システム構成を図示すると以下になります

  1. センサー側の実装
    図の左下緑色の丸のLAN。この LAN の中でセンサー(SRF02)を Arduino で読み取り、シリアルを通じて Mac や Raspberry Pi 等の Network Interface を持つ機器にに送ります。さらにこれらの機器から mqtt で broker (Sango) に publish します。Internet への接続は mobile router を使います

  2. MQTT Broker
    図の右下、mqtt broker として時雨堂さんご提供の mqtt broker service である Sango をつかわせていただきます。また、Sango を subscribe して Chart.js でグラフを描画する Web Application を置く Web Server も用意します。Sango には WebSocket で接続します

  3. アプリケーション側の実装
    図の上部、上で述べた Web application を iOS や PC から ブラウザでアクセスすることで、センサの値をリアルタイムのグラフで参照することができます。さらに PC などで subscribe した値を csv で書き出すことで、遠隔地のセンサーのリアルタイムな値の記録を自席PCで Excel などの表計算ソフトで処理できるようになります

スクリーンショット 2014-10-26 19.54.51.png

ソフトウェア構造と配置は以下のようになります

  1. SRF02 の値をシリアルに書く Arduino のスケッチ
    この部分を書き換えることで、このシステムは他のセンサーについても同様に利用できます。配置先はもちろん Arduino になります

  2. Arduino のシリアルを読んで、センサー値を mqtt で publish するプログラム
    シェルスクリプトで行う方法もあるとおもいますが、今回は python で実装しました。mqtt のクライアントには python のライブラリが存在するので paho を使用しましたが、シェルスクリプトでの実装だと mosquitto での実装もあるかと思います。配置先は Raspberry Pi や Mac などの TCP の使える環境になります

  3. publish された topic を読んでグラフにする Web Application
    普通の Web Application です。Sango の WebSocket インターフェースに接続し、paho の javascript library を使って mqtt で topic を読んで Chart.js でグラフを書きます。WebServer にデプロイします

  4. publish された topic を読んで CSV でロギングする
    こちらもシェルスクリプトにすることも可能だと思いますが paho を利用した python アプリケーションとして実装しました。配置先は PC などを想定していますがなんでもかまいません。遠隔地のセンサーの値を自席PC でCSV ロギングできれば便利ですね

完成品の挙動はこのyoutubeのようになります

1.センサー側の実装

SRF02 を Aruduino で読む

SRF02は小型軽量な超音波測距センサーです。Arduino とのピンの接続及び計測値をシリアルに書き出すためのスケッチは開発元のサイトで公開されています。この資料の "Connection Diagram(IIC Mode)" のとおりに SRF02 と Arduino を結線ます。さらに"Sample Code(IIC Mode)"のスケッチは接続動作確認に利用する他、このI2Cのスケッチを流用して、Mac や Raspberry Pi からの距離測定コマンドが来た時だけ測定した距離をシリアルに書くように以下のように変更します

sketch_SRF02_from_serial
#include <Wire.h>
byte val=0;

void setup()
{
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}

int reading = 0;

void loop()
{
  // step 1: instruct sensor to read echoes
  Wire.beginTransmission(112); // transmit to device #112 (0x70)
  // the address specified in the datasheet is 224 (0xE0)
  // but i2c adressing uses the high 7 bits so it's 112
  Wire.write(byte(0x00));      // sets register pointer to the command register (0x00)
  Wire.write(byte(0x51));      // command sensor to measure in "centimeters" (0x51)
  // use 0x51 for centimeters
  // use 0x52 for ping microseconds
  Wire.endTransmission();      // stop transmitting

  // step 2: wait for readings to happen
  delay(70);                   // datasheet suggests at least 65 milliseconds

  // step 3: instruct sensor to return a particular echo reading
  Wire.beginTransmission(112); // transmit to device #112
  Wire.write(byte(0x02));      // sets register pointer to echo #1 register (0x02)
  Wire.endTransmission();      // stop transmitting

  // step 4: request reading from sensor
  Wire.requestFrom(112, 2);    // request 2 bytes from slave device #112

  // step 5: receive reading from sensor
  if (2 <= Wire.available())   // if two bytes were received
  {
    reading = Wire.read();  // receive high byte (overwrites previous reading)
    reading = reading << 8;    // shift high byte to be high 8 bits
    reading |= Wire.read(); // receive low byte as lower 8 bits
    // 'a' が入力されると値を返す
    val = Serial.read();
    if (val == 'a'){
      Serial.println(reading);   // print the reading
      //Serial.println("cm");
    }
  }

//  delay(250);                  // wait a bit since people have to read the output :)
  delay(10);                  // wait a bit since people have to read the output :)
}

元のスケッチに対して、シリアルから 'a' が送られてきた時にだけシリアルに値を書く等の変更をしています

paho-mqtt のインストール

pip がインストールされている前提で、以下になります

sudo pip install paho-mqtt

その他、便利なソフトウェアのインストール

Raspbian はデフォルトで screen がないのでインストールしておきます(Mac にはデフォルトであります)

sudo apt-get install screen

Raspberry Pi で mqtt を publish する場合

Arduino と Raspberry Pi を USB で接続

Aruduino を USB ケーブル経由で Raspbery Pi に接続します。接続の確認は /dev 配下に Arduino が tty として見えていれば成功しています。具体的には Raspberry Pi の terminal を開き

ls /dev/ttyACM*

で、ttyACM0 があれば接続しています。Arduino を複数接続していると ttyACM1 等、同様の tty が複数みえるかもしれません。
Arduino から Raspberry Pi への USB ケーブルの接続が浅く、Arduino への給電はできているものの接続ができていない場合、Arduino は正常に動作していながらも tty は見えず、少しハマッたりします(私ははまりました)

SRF02の疎通確認

開発元のサイトの資料の"Sample Code(IIC Mode)"のスケッチを Arduino に書き込み、 "Connection Diagram(IIC Mode)" のとおりに SRF02 と Arduino を結線し、Arudino と Raspberry Pi を接続します。Arduino が動き出すと SRF02 の赤い LED がピコピコと点滅します。
Raspberry Pi のターミナルを開き、/dev/ttyACM0 が見えていることを確認し、以下のようにこの tty を開きます

screen /dev/ttyACM0 9600

SRF02 で読み取った距離がターミナルに表示されれば疎通 OK です
screen の終了は CNTL + A, CNTL + ¥ です
次に、Arduino のスケッチを前述のsketch_SRF02_from_serialに変更し、再度 screen で接続します。今度は Raspberry Pi 側から 'a' を入力した時だけ、距離が帰ってきます

Paho による publish

そこはかとなく書くよん。を参考にさせていただいて以下のコードを用意しました。「Sango のアカウント情報の定義」の部分をご自身の Sango のアカウント情報で書き換えてください
トピックはここではでは test_topic としていますが、subscribe 側を # で受けるのなら何でもいいです。

pub.py
import paho.mqtt.client as mqtt
from time import sleep
import serial
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)

if __name__ == '__main__':

###
### 以下、Sango のアカウント情報の定義
###
    username = Sango のユーザ名
    password = Sango のパスワード
    host = 'free.mqtt.shiguredo.jp'
    topic = 'Sango のユーザ名/test_topic'
###
###
###
    port = 1883

    client = mqtt.Client()
    client.username_pw_set(username, password=password)
    client.connect(host, port=port, keepalive=60)

    while 1:
        # value = random.random() * 100
        ser.write('a'); # シリアルへの書き出しコマンド
        sleep(0.05)
        value = ser.readline()
        print '[{}] Sending message to sango.'.format(value)
        client.publish(topic, '{}'.format(value))
        ser.readline() # もしさらに値があれば読み捨て
        sleep(0.5)

内容は paho と serial を import し、ループを回して定期的にシリアル(につながった Arduino)に 'a' コマンドを送り、少し待ってからシリアルに帰ってきている距離値を読み、mqtt で publish します。

python pub.py で、Sango への距離データの publish が始まります

Mac で mqtt を publish する場合

Mac による実装も Raspberry Pi による実装とほぼ同じです、SRF02 をつないだ Arduino を USB で Raspbery Pi に繋ぐ変わりに Mac に繋ぎ、screen で疎通確認をした後、python のプログラムでシリアルを読んで Sango に publish します。Raspberry Pi との変更点は Arduino の tty の見え方が違います

Raspberry Pi
- /dev/ttyACM0

Mac
- /dev/tty.usbmodem1421

pub.py も、この部分のみ違い、以下のようになります

pub.py
import paho.mqtt.client as mqtt
from time import sleep
import serial
ser = serial.Serial('/dev/tty.usbmodem1421', 9600, timeout=1)

if __name__ == '__main__':

###
### 以下、Sango のアカウント情報の定義
###
    username = Sango のユーザ名
    password = Sango のパスワード
    host = 'free.mqtt.shiguredo.jp'
    topic = 'Sango のユーザ名/test_topic'
###
###
###
    port = 1883

    client = mqtt.Client()
    client.username_pw_set(username, password=password)
    client.connect(host, port=port, keepalive=60)

    while 1:
        # value = random.random() * 100
        ser.write('a'); # シリアルへの書き出しコマンド
        sleep(0.05)
        value = ser.readline()
        print '[{}] Sending message to sango.'.format(value)
        client.publish(topic, '{}'.format(value))
        ser.readline() # もしさらに値があれば読み捨て
        sleep(0.5)

2. MQTT Broker

MQTT Broker には時雨堂様の MQTT as a Service である Sango の無料プランを使わせていただきました。サポートの方がとても親切で感激いたしました

3. Web でグラフ表示

Sango の使い方 Javascript Websocket 編Chart.js の documentation の "Line CHart" を参考にさせていただき、以下のようなコードを用意しました。ここでも「Sango のアカウント情報の定義」の部分をご自身の Sango のアカウント情報で書き換えてください

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>チャート</title>
  <script src="mqttws31.js" type="text/javascript"></script>
  <script src="Chart.js"></script>
  <script>

  var myLineChart;
  window.onload = function (){
  // コンテキストの取得
  var ctx = document.getElementById("myChart").getContext("2d");
  // イニシャルデータ
  var data = {
    labels: ["", "", "", "", "", "", "", "", "", "", ""],
    datasets: [
        {
            label: "My Second dataset",
            fillColor: "rgba(151,187,205,0.2)",
            strokeColor: "rgba(151,187,205,1)",
            pointColor: "rgba(151,187,205,1)",
            pointStrokeColor: "#fff",
            pointHighlightFill: "#fff",
            pointHighlightStroke: "rgba(151,187,205,1)",
            data: [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00]
        }
    ]
    };
  // 描画
  //var myLineChart = new Chart(ctx).Line(data/*, options*/);
//  myLineChart = new Chart(ctx).Line(data/*, options*/);
  myLineChart = new Chart(ctx).Line(data, {scaleOverride: true,scaleSteps: 25,scaleStepWidth: 10,scaleStartValue: 0,scaleShowGridLines : false});

  myLineChart.addData([0], "");
  // 先頭データ削除
  myLineChart.removeData();
    }
  </script>
  <script>
    var client; // MQTTのクライアントです
    var clientId = "clientid-test"; // ClientIDを指定します。

    function connect(){
//
// Sango のアカウント情報の定義
//
        var user_name = Sango のユーザ名;
        var pass = Sango のパスワード;
//
//
//
        var wsurl = "ws://free.mqtt.shiguredo.jp:8080/mqtt";

        // WebSocketURLとClientIDからMQTT Clientを作成します
        client = new Paho.MQTT.Client(wsurl, clientId);

        // connectします
        client.connect({userName: user_name, password: pass, onSuccess:onConnect, onFailure: failConnect});

    }

    // 接続が失敗したら呼び出されます
    function failConnect(e) {
        console.log("connect failed");
        console.log(e);
    }

    // 接続に成功したら呼び出されます
    function onConnect() {
        console.log("onConnect");
            subscribe();
    }

    // メッセージが到着したら呼び出されるコールバック関数
    function onMessageArrived(message) {
        console.log("onMessageArrived:"+message.payloadString);
      // データ追加
      myLineChart.addData([parseFloat(message.payloadString)], "");
      // 先頭データ削除
      myLineChart.removeData();
    }

    function subscribe(){
        // コールバック関数を登録します
        client.onMessageArrived = onMessageArrived;

        //
        // Sango のアカウント情報の定義(その2)
        //
        var topic = "Sango のユーザ名/#";
        //
        //
        //

        // Subscribeします
        client.subscribe(topic);
    }
    connect();
    //subscribe();
  </script>
</head>
<body>
    <p>計測値</p>
        <canvas id="myChart" width="800" height="400"></canvas>
</body>
</html>

index.html と同じフォルダに mqttws31.js と Chart.js が必要です。これらはそれぞれ、以下からダウンロードできます。

mqtt31.js: http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.javascript.git/plain/src/mqttws31.js
Chart.js: https://github.com/nnnick/Chart.js

コードの内容は以下です
1. HTML5 Canvas 内に、値がすべて 0 でチャートを描画する
2. paho で Sango のトピックを subscribe
3. メッセージを受け取るたび(onMessageArrived)に Chart に値を add し、先頭を remove することでチャートをリアルタイムに更新する

私は最初 connect が非同期である事を見落としていて、上の index.html でもコメントアウトしてあるとおり connect と subscribe を逐次的に呼び出すコードを書いていて
Error: AMQJ5001E Invalid state not connected.
とエラーになるのが解決できず、Sango のサポート様にQAを上げてしまい諦めて寝てしまったのですが、朝起きるともう回答をいただいていて大変感激いたしました。稚拙な質問を上げてしまいご迷惑をお掛けしたことをこの場を借りてお詫び致しますとともに、激烈に迅速なご対応をいただけた事に感銘いたしました次第です。

4. CSV でロギング

そこはかとなく書くよん。を参考にさせていただいてというかほとんど流用させていただき以下のコードを用意しました。ここでも「Sango のアカウント情報の定義」の部分をご自身の Sango のアカウント情報で書き換えてください

sub.py
import paho.mqtt.client as mqtt
import sys
import os

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

def on_connect(client, userdata, flags, rc):
    #print('Connected with result code '+str(rc))
    client.subscribe(Sango のユーザ名 + "/#") # ここも変更


def on_message(client, userdata, msg):
    st = msg.payload[:-2]
    #print(repr(msg.topic + ' ' + str(msg.payload)))
    print(st)


if __name__ == '__main__':

###
### 以下、Sango のアカウント情報の定義
###
    username = Sango のユーザ名
    password = Sango のパスワード
    host = 'free.mqtt.shiguredo.jp'
    topic = 'Sango のユーザ名/test_topic'
###
###
###
    port = 1883

    client = mqtt.Client()
    client.on_connect = on_connect
    client.on_message = on_message

    client.username_pw_set(username, password=password)
    client.connect(host, port=port, keepalive=60)
    client.loop_forever()

python sub.py > log.csv で、Sango から受け取った距離データの log.csv へのロギングが始まります

5. 感想

「速くて早い」というか、動作はとにかく速く読み取ったセンサの値がリアルタイムでチャートに反映されている感じです。また MQTT Broker (Sango) がメッセージ・キューを管理してくれているおかげでコードが大変シンプルになり、実装も実質一晩とまさに quick development でした
テレメータの案件、今後は全部これでいいような気も...

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした