LoginSignup
6

More than 5 years have passed since last update.

rasberry pi zero+C#で定点カメラサーバーぽいものが出来るまで

Last updated at Posted at 2017-03-15

未だ品薄が続いたり、Amazonでは4倍以上の値段で売られたりしているRasberryPiZeroで遊んでみました。購入したのはカメラkitなので、定点カメラサーバーをとりあえずの目標に開発開始。
ぱいそん? は使ったことがないので、monoを入れてC#で開発を目論見ます。

いきなり完成品

http://dividepizero.bf1.jp:8080/public/
※我が家の猫によるRasberryPi電源切断が多々あるので、繋がったらラッキーぐらいで
※題名は「定点カメラ」となってますが、汚い我が家を晒すのもアレなので、ちょっと違うモノになってます。
※携帯からはアクセスしても面白くないようです。詳しくは↓で

前準備

まず、monoのインストール。

sudo apt-get upgrade
sudo apt-get install mono-complete

特にエラー等なく順調にインストールは終了したけれど、実は30分以上かかったような(体感)

まずはシンプルなチャット作成

まずは http://www.buildinsider.net/small/raspisignalr/01 に書いて有ることほぼそのままでOWIN Self Hosting と SignalR による双方向通信を組み合わせてチャット作成

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.Hosting;
using Microsoft.Owin.StaticFiles;
using Owin;

namespace OWinSignalRChat
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            using (WebApp.Start<StartUp>("http://*:8080/"))
            {
                Console.WriteLine("Server running...");

                while (true)
                {
                    var line = Console.ReadLine();
                    if (line == "exit") break;
                }
            }
        }
    }

    public class StartUp
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.MapSignalR();
            appBuilder.UseFileServer(new FileServerOptions{EnableDirectoryBrowsing = false, FileSystem = new PhysicalFileSystem(AppDomain.CurrentDomain.BaseDirectory)});
        }
    }

    public class EchoHub : Hub
    {
        public void Send(string text)
        {
            if (string.IsNullOrEmpty(text) == false)
            {
                Clients.All.Receive(text);
            }
        }
    }
}

サーバーはコンソールでexitと打つまで終わらない構成に。
たったこれだけでサーバーになるっていうんだからすごいですね。

次にクライアント側を。
キモは↑のappBuilder.MapSignalR();によって生成されるhubs(中身はJavaScript)を使うことでHubを継承したクラス(今回だとEchoHub)の実装に従ってサーバーとリアルタイムで双方向通信ができるとかなんとか?。うん。よくわからない。

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script src="../Scripts/jquery-1.6.4.min.js"></script>
    <script src="../Scripts/jquery.signalR-2.2.1.min.js"></script>
    <script src="../signalr/hubs"></script>

    <script>
        $(function () {
            var echo = $.connection.echoHub;

            echo.on("Receive",
                function (text) {
                    $("#list").prepend("<li>" + text + "</li>");
                });

            $("#send").click(() => {
                var message = $("#message").val();
                echo.invoke("Send", message).done(function () {
                    $("#message").val("");
                });
            });

            $.connection.hub.start(function () {
                $("#send").prop("disabled", false);
            });
        })
    </script>
</head>
<body>
<div>
    <input type="text" id="message"/>
    <input type="button" id="send" value="送信" disabled="disabled"/>
    <br/>
    <ul id="list"></ul>
</div>
</body>
</html>

ちなみに僕はjqueryは触ったことないです。 頑張ってノリでカバー。

なお、件のappBuilder.MapSignalR();でマッピングされるはずのEchoHubがされなくて小一時間悩むハメになりましたが、原因はclassがprivateだった事でした。 ぐげげー。

とりあえずチャットができる状態に

このチャットで重要なのは、クライアントからのpull型ではなく、サーバーからのpush型でチャットが成り立っているということです。
サーバーからのpushでメッセージが送信できているということは、メッセージの代わりにRasberryPiのカメラから取得した画像ファイルのbyte配列を送り付けるようにして、jqueryで受け取ってimg:srcに入れて表示するだけ・・・。やだ、簡単すぎ。

まず、
raspistill -o cam.jpg
と、コマンドラインからカメラモジュールへの通電を確認。

んで、monoからどうやってカメラモジュールを・・・? これがまたあんまり情報がなくて。

まず試してみた
https://www.nuget.org/packages/RaspberryCam
は、エラーが出て使えず(使い方が間違っているだけかも)

色々探し回っていると、更新日が最近の
https://www.nuget.org/packages/Unosquare.Raspberry.IO/
が、GPIOに加えてカメラモジュールも操作できるっぽかったので、これを使うことに。

サーバー側(C#)追加処理

    public static async Task SendImage()
    {
        var hubContext = GlobalHost.ConnectionManager.GetHubContext<EchoHub>();
        var cam = new Unosquare.RaspberryIO.Camera.CameraController();
        var result = await cam.CaptureImageJpegAsync(640, 480,System.Threading.CancellationToken.None);
        hubContext.Clients.All.DataSend(result);
    }

カメラからJpeg画像を取得して、echoHubに接続されているClient全てに「DataSend」というメソッド名で画像のbyte[]を送ってあげるように。

クライアントはそれを受け取り、表示。

            echo.on("DataSend",
                function (data) {
                    $("#image").attr("src", "data:image/jpg;base64," + data);
                }
            );

byte配列はbase64となって送られるっぽくて、画像のsrcにbase64であることを指定しつつ渡してあげるだけでなんと表示できるらしいです。

なお、もちろん

        <img id="image"/>

のようにimgタグをどこかにおいておきます。

そんなこんなで

スクリーンショット 2017-03-15 23.17.40.png

で、出来たー(汚い机の上が写っております)

と。せっかく作ったので公開したいんですが、流石にそのままだと我が家の隠し撮りをそのまま発信することになってしまうので、もうひと工夫したものを。

ゆっくりしていってね

ちょっと調べてみると http://www.a-quest.com/products/aquestalkpi.html なるものがあり、個人の非営利使用であれば無償で使えるとのこと。 これを使ってボイスチャットを作ろう!(ちょっと意味が違う)

というわけで、AquesTalk Pi Ver.1.00 をDLあんど展開。
デフォルトユーザー(pi)のホームに展開したので、home/pi/aquestalkpi/AquesTalkPi がモジュールのパスになってます。

流れとしては

  • クライアントからチャットで送られてきたメッセージをAquesTalkPiを通してwavデータに、
  • それを写真データを送るのと同じ要領で全クライアントにpush。
  • クライアントは受け取ったwavデータをHTML5のaudioタグに展開して再生。

となります。 

キモは、コマンドラインでAquesTalkPiを呼んで、wavのbyteデータを取得しなくてはいけないというところ。
ファイルに出力もできるので、出力されたファイルを改めて読み込んでも良いのですが、一度Diskを挟む意味が本来はないはずで。 色々やってみたところ、以下のような書き方をすれば標準出力をそのままbyte[]として取得・送信できました。

        private static readonly IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<EchoHub>();
        public static async Task SendTalk(string talk)
        {
            var p = new Process{
                StartInfo ={
                    FileName = "/home/pi/aquestalkpi/AquesTalkPi",
                    Arguments = talk,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardInput = false
                }
            };

            p.Start();
            using (var output = new MemoryStream())
            {
                await p.StandardOutput.BaseStream.CopyToAsync(output);
                hubContext.Clients.All.TalkSend(output.ToArray());
            }

            p.WaitForExit();
            p.Close();
        }

このSendTalkメソッドをクライアントからのメッセージ受信(Send)の時に呼んであげることにします。

    public class EchoHub : Hub
    {
        public void Send(string text)
        {
            if (string.IsNullOrEmpty(text) == false)
            {
                Clients.All.Receive(text);
                Task.Run(() => Program.SendTalk(text));
            }
        }
    }

サーバー側はTalkSendというメソッドでwavのbyte[]を送っているので、クライアント側ももちろんTalkSendで待ち構えてあげます。

            echo.on("TalkSend",
                function (data) {
                    var audio = new Audio("");
                    audio.src = "data:audio/wav;base64," + data;
                    audio.pause();
                    audio.load();
                    audio.oncanplaythrough = audio.play();
                });

なお、audioは携帯だと再生されないことが多い(というか、僕の手持ちの端末は全滅でした)ため、PCからのアクセス前提です。 さらに言えば、結構な量のデータ(生wavデータ)がpushで飛んでくるので、パケ死(死語)しないよう気をつけてください。


おまけ。

さらっと作っているように見えますが、そもそもプログラムをする以前の問題で、ハードウェア的な準備の方が時間とお金がかかってるのでダラダラと。
情弱おじさんの愚痴なんて聞きたくないという人は回れ右して戻るボタン。

2月24日(金)

  • 誕生日かつ、rasberry pi zero の販売日。
  • 前から興味があったので購入を決意するも気付いたときには単品は品切れ。
  • かろうじてカメラモジュールセットの在庫があったのでそちらを購入する事に。

購入物

razberry pi zero カメラモジュール付き 5,400円
ケース 810円
計 6,210円

2月27日(月)

  • 仕事から戻るとrazberry pi(とカメラモジュールとケース)が届いていた。
  • 箱を開けて品物を確認。説明書など一切入っておらずびびる。
  • あまりに思いつきで購入したので他に何が必要なのか全然わかっておらず、このままではただのフリスクケースなので色々調べて必要と思われるものをピックアップ
  • この日は箱を開けただけで通電すらせず終了

2月28日(火)

  • 仕事帰りにEDIONで前日ピックアップしたものを探す。
  • microsdは32Gで1580円という謎のメーカーの安物を(一応クラス10)
  • usbのwifiドングルを買うつもりだったけど目的のものが売り切れで。ふと目についた有線LANのUSBアダプタ(http://www2.elecom.co.jp/products/WRH-150BK.html) が980円と投げ売りされていたので買ってみることに(無謀)
  • hdmiの変換アダプタは800円くらいだったのでこれに。

購入物

32Gbyte microsdカード 1,580円
USB有線LANアダプタ(WRH-150BK) 980円 
hdmi変換アダプタ 880円
本日計 3,440円
総計 9,650円
  • zeroでGUIは無謀な気がしたので、https://www.raspberrypi.org/downloads/raspbian/ から LITEの方をDL。 
  • zeroは使えるUSBインタフェースがマイクロUSB1つしかないので、AndroidにBluetoothコントローラをペアリングするのに使っていたOTG対応 microUSB変換ケーブル経由で、家にあったUSBハブ接続。余っていたキーボードと買ったUSB有線LANアダプタを接続。
  • imgファイルのSDカードへの書き込みは diskimager(https://ja.osdn.net/projects/sfnet_win32diskimager/) とやらを使用するのがいいらしいのでその通りに。
  • 無事sdに書き込めたらrasberry piに差し込んで電源接続(なお、これが初通電。無謀)

  • 最初、ディスプレイに何も出力されなくてあわあわするが、ちゃんと接続できてなかっただけで、しっかり差し込んだところディスプレイに表示されました。感動。

  • あとは色んなページに書いてある通り、初期設定。 以下参考にしたページ

  • raspi-configでパスワード変えたり、SDカードを最大限使えるようにしたり、日本語使えるようにしたりするっぽい(雑)

  • 順調。と思いきや、USB有線LANアダプタが認識していなっぽい。ぐぬぬる。

  • 相性もあるようなので、今度こそちゃんと動作確認が取れているwifiドングルを買おうと決意。ふて寝。

3月1日(水)

購入物

USB無線LANアダプタ(WLI-UC-GNME) 1,180円 
総計 10,830円
  • 帰宅後深夜作業。 wifiドングルを装着。 $lsusb認識したー!
  • /etc/wpa_supplicant/wpa_supplicant.confに家のairstationのssid、パスワード等を書いて$sudo reboot
  • すると、起動後usb wifi認識せず。 えー。
  • 試しにもう一度$sudo rebootすると今度は・・・。 キーボードが認識していない。
    あ。これはもしかするとあれだ。 USBハブがアホな子なのでは。

  • 試しにPS4に繋いでいたUSBハブを使ったところ普通に認識。

  • 恐る恐る昨日ダメだったUSB有線LANアダプタを接続してみたところ問題なく認識。あーー。

  • ふて寝。

ここから、やっとmonoインストールで最初に戻ります。 本体5ドルなのになんだかんだで1万円くらいかかっててるし、すぐにZeroW(Wifi・Bluetoothモジュール付き)が発売されるわで・・・。

みなさん、ご利用は計画的に。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6