Paho mqttでリモートカメラ

Paho MQTTでリモートでシャッターを切れるカメラを作ってみました。撮った写真もMQTTで送られてきます。

2015/2/28 UPDATE!!

カメラ側の設定

まずは、デバイス(カメラ)側のスクリプト

ハードウエアとして、Raspberry Pi B+に専用のカメラモジュールをつけています。

remotecam.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python
#
#

import paho.mqtt.client as mqtt
import os

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, rc):
    print("Connected with result code "+str(rc))
  # Subscribing in on_connect() means that if we lose the connection and
  # reconnect then subscriptions will be renewed.
    client.subscribe("my/device/stillcam/command")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    cmd = str(msg.payload)
    print(msg.topic+" "+str(msg.payload))
    if cmd == "shoot":
        print "Say cheeees!"

        dummy = os.system("raspistill -w 1024 -h 768 -t 10 -o /run/shm/temp.jpg")
        dummy = os.system("mosquitto_pub -h my.broker.jp -t my/device/stillcam -f /run/shm/temp.jpg")

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

client.connect("my.broker.jp", 1883, 60)

# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
client.loop_forever()

パブリッシュ設定したメッセージが来ると、on_messageがコールされます。
メッセージが”shoot”だったら、osコマンド”raspistill”を実行して写真を撮ります。
さらにそのデータをosコマンド”mosquitto_pub”でパブリッシュします。

ちょっと格好悪いですけど、MQTTで写真データを送るのにmosquittoを使っています。ざっくり試した感じですと、paho-mqttではバイナリのペイロードをうまくハンドリングできないようで、多分なんか設定があるのだと思います。探してみます。

2015/2/11追記:

上記のバイナリデータのハンドリングの件ですが、うまく行かない理由は私のスキル不足です。多分python内部での変数のデータ扱いをうまく変換してやる必要が有りそうです。 只今勉強中。

2015/2/28 追記:

上記、バイナリデータの送信の件ですが、
実際に出てくるエラーは
UnicodeDecodeError: 'ascii' codec can't decode
というものでしたので、そのまま検索をかけてみると、デコードがうまく行っていない、旨のエラーのようです。
であれば、変換をしないように設定すればいいのかなあ、どうするのかなあ、と思いつつソースコードを(わからないなりに)眺めていると、

http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/src/paho/mqtt/client.py
1
2
3
4
5
6
7
8
        if isinstance(payload, str):
            upayload = payload.encode('utf-8')
            payloadlen = len(upayload)
        elif isinstance(payload, bytearray):
            payloadlen = len(payload)
        elif isinstance(payload, unicode):
            upayload = payload.encode('utf-8')
            payloadlen = len(upayload)

という記述を見つけ、payloadのタイプによってエンコードを分けていることがわかりました。bytearrayなら何もせずにそのままのデータが送られるのでbytearrayにすればいい。
再び検索して、読み込んだファイルをbytearray型で変数に代入する方法を調べ、結果として次のようなソースコードになりました。

replace with ‘mosquitto_pub ……’
1
2
3
4
5
6
# 撮影した写真ファイルは/run/shm/temp.jpg
       with open('/run/shm/temp.jpg', 'rb') as source:
            payload_pub = bytearray(source.read())

# bytearray 型にして変数に代入、それをそのままペイロードとしてパブリッシュ
        client.publish(topic_root+topic_pub, payload_pub)

これで無事バイナリファイルを送信することが出来ました。

USBのカメラを使う場合はraspistillのコマンドを適宜変更すればいいかと思います。

このスクリプトを実行可能に設定して、実行させます。これで待ち受け状態。

コントローラ側

別のPCでは、

  • シャッターを切るコマンドを発行する
  • 送られてきた写真のデータを保管する

という作業があります。

まずは、送られてきたデータを保管するスクリプトを作ります。

photo_sub.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python
#
#
#

import paho.mqtt.client as mqtt
import datetime

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, rc):
    print("Connected with result code "+str(rc))
  # Subscribing in on_connect() means that if we lose the connection and
  # reconnect then subscriptions will be renewed.
    client.subscribe("my/device/stillcam")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):

    # print(msg.topic+" "+str(msg.payload))
    filename = "./image/" + datetime.datetime.today().strftime("%H%M%S%f") + ".jpg"
    outfile=open( filename , 'w')
    outfile.write(msg.payload)
    print "subscribe: " + filename
    outfile.close

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

client.connect("my.broker.jp", 1883, 60)

# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
client.loop_forever()

このスクリプトでは、my/device/stillcamというトピックにパブリッシュされたデータを取り込んでファイル名(タイムスタンプ)をつけて保存します。

このスクリプトを実行して、データを待ち受けます。

最後にシャッタを切るコマンドを送ります。
とりあえずは、コマンドラインからです。

1
$ mosquitto_pub -h my.broker.jp -t my/device/stillcam/command -m "shoot"

これで、メッセージ”shoot”をトリガにして、写真を撮影し、それを転送して保存するまでの一連の作業が行われます。

デバイス側の処理負荷を確認するため1秒に1回シャッターを切る動作を続けて見ました。 結果的には、カメラと転送の処理で数%程度の負荷のようです。
ちなみにこのカメラ、この設定では1秒間隔以上のスピードで連続して撮影することはできませんでした。

カメラがRaspi用のモジュールですので、かなりオーバーヘッドが小さい感じもします。 CPU的には処理が軽くていいのですが、ハードウエア的には取り回しが悪く、ちょっといまいちな感じもします。
応用のシーンによりますが、今回私が想定しているケースでは使いにくいです。

あとで、USBcamでやってみようと思います。