RaspberryPiで複数のWiFiのアクセスポイントで固定IPの設定を登録する

RaspberryPiで複数のWiFiアクセスポイントで固定IPの設定を登録する

RaspberryPiを家で使用している時には固定IPで運用しているのですが、ハッカソンなどにいくと会場に設営されたWiFiに接続したり、個人持ち込みのPocketWiFiで運用したりとなり、最初にネットワークの設定で手間取ることが多くてRaspberryPiを使用した作品の制作から少し離れてしまうことが多くありました。(デモで機器が使えないのは正直キツイです)

そこで自宅でも、出先でも固定IPで使用できるようにしようと思いました。


参考にしたのは以下になります。ただ、少し古いためwheezベースになっていたので少し変更する必要がありました。

www.1ft-seabass.jp


アクセスポイントの情報を取得する

この部分は1つのアクセスポイントであっても同じですが、設定する可能性のあるアクセスポイントすべての情報を取得します。 結果部分にはパスフレーズの暗号化されていないものも含まれていますが、不要なので削除して使います。

$ sudo wpa_passphrase 【SSID】 【パスフレーズ】
network={
  ssid="【SSID】"
  #psk="【パスフレーズ】(入力値)"
  psk=【パスフレーズ(暗号化された値)】
}

使用する可能性のあるアクセスポイントの情報が取得できたら次のステップに進みます。

設定ファイルを編集する

今回編集するのは/etc/wpa_supplicant/wpa_supplicant.conf/etc/network/interfacesの2つのファイルです。どちらもアクセスポイントが1つの時にも設定するファイルなのでそれほど気にしなくても良いかなと思います。

/etc/wpa_supplicant/wpa_supplicant.confの編集

以下で編集を行います。(可能であればオリジナルファイルのバックアップをとっておきます。)

$ sudo vi /etc/wpa_supplicant/wpa_supplicant.conf 

以下のように編集します。

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="【家で使用する1つ目のアクセスポイントのSSID】"
    psk=【パスフレーズ(暗号化された値)】
    key_mgmt=WPA-PSK
    priority=0
    id_str="HOME"
}

network={
    ssid="【外出時に使用する2つ目のアクセスポイントのSSID】"
    psk=【パスフレーズ(暗号化された値)】
    key_mgmt=WPA-PSK
    priority=1
    id_str="OUTSIDE"
}

アクセスポイントが1つの時と異なるのはpriorityとid_strになります。設定値通り優先順位と設定の識別になります。id_strは家で使用する設定をHOME、外で使用するPocketWiFiの設定をOUTSIDEとしました。

/etc/network/interfacesの編集

以下で編集を行います。(可能であればオリジナルファイルのバックアップをとっておきます。)

$ sudo vi /etc/network/interfaces

以下のように編集します。実際にはauto lo以下の行になります。 今回は家で使用するIPアドレス体系をを192.168.0.xとし、RaspberryPiのアドレスを192.168.0.200とし、外で使用するPocketWiFiのIPアドレス体系を192.168.179.xとし、RaspberryPiのアドレスを192.168.179.200としています。

 interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# interfaces(5) file used by ifup(8) and ifdown(8)

# Please note that this file is written to be used with dhcpcd
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'

# Include files from /etc/network/interfaces.d:
source-directory /etc/network/interfaces.d

auto lo

iface lo inet loopback
iface eth0 inet dhcp

auto wlan0
allow-hotplug wlan0

iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp

iface HOME inet static
address 192.168.0.200
netmask 255.255.255.0
gateway 192.168.0.1

iface OUTSIDE inet static
address 192.168.179.200
netmask 255.255.255.0
gateway 192.168.179.1

複数のアクセスポイントを使う設定として特徴のある点は /etc/wpa_supplicant/wpa_supplicant.confで設定したid_strifaceの設定として与えている部分とWiFiの設定ファイルの呼び出し部分を(旧)wpa-conf /etc/wpa_supplicant/wpa_supplicant.confから(新)wpa-roam /etc/wpa_supplicant/wpa_supplicant.confとしている部分になります。


実験

設定画終了したので念のためリブートをしてみたところ、一応アクセスができるようになりました。ただ、両方のアクセスポイントがあると設定に時間がかかってしまうようです。(相当時間はかかるのですが、その後両方のアクセスポイントのIPアドレスが使えるようになるようです。) とはいえ、アクセスポイントが共存することはなかなか無いかなと思うのでこれで良しとします。

まとめ

一応、複数のアクセスポイントが固定IPでも使用できるように設定ができました。これで、少しはRaspberryPiの作成のハードルも低くなるかなと思います。 ただ、インターフェースにIPが複数割り当てられるのが気になります、これってどういう状況なんでしょうか。

BitBalloonの使い方メモ

BitBalloonの使い方のメモ

今回にエントリーは内容が薄いので読まなくてもいいかなと思います。

ぜひ参照先をみてください。

blog.mlkcca.com

先日(といっても一ヶ月ほど立ってしまいましたが)、Milkcocoaのハンズオンに参加してきました。 Milkcocoaのサービスは少しかじっていましたが、ちゃんとした説明を受けたことがなかったので、いい刺激になりました。 その中で可視化するための仕組みでどうしてもWebサイトらしきものを作って見ないといけないことがあるので、その部分に関しては 敷居が少しあるなと感じていた(これ見ておいてって言う感じでメールを投げにくいかった)のですがハンズオンの中でBitBalloonという サービスの紹介があり、これには結構驚きました。

すでに一ヶ月も前のことでもあるので、備忘録としてメモを取っておこうと思います。多分、名前さえ覚えておけばこのメモも不要なぐらいに簡単だと思います。

BitBalloonとは

簡単に言うとhtmlなサイトを超簡単にホスティングしてくれるサービスになります。

サイト作成

以下にアクセスします。

www.bitballoon.com

f:id:ueponx:20160916120547j:plain

サインアップしろという内容がありますが、今回はテストとしてなので気にせず、右側にある「Drag Your Site Folder Here」にHTMLやJavascriptなどのファイルをドロップしてみます。

今回使ったのはMilkcocoaさんのChatサンプルのデータとなります。

f:id:ueponx:20160916120809j:plain

ファイル構成としては上のような3つのファイルからの構成になります。文面としてはFolderをドロップしない行けないように見えますが、使用するファイルすべてドロップしても問題はないみたいです。

ドラッグ&ドロップすると処理が開始します。

f:id:ueponx:20160916121102j:plain

ファイルが少なければものの数十秒で処理が完了し、サイトへアクセスできるようになります。

f:id:ueponx:20160916121333j:plain

URLなども生成してくれるので、これに対してアクセスをすると 以下のように使用ができるようになります。

f:id:ueponx:20160916121636j:plain

ちゃんとJavaScriptからMilkcocoaへのデータのやり取りもできるので短時間でサイトを作らないといけないときや、一時的にチェックしたい用途には便利そうです。

サイトの削除

管理画面の下にある【Delete the site】をクリックすると

f:id:ueponx:20160916122158j:plain

削除用のダイアログが開くので、入力ボックスに作成したサイトの名前を入力し【Delete】ボタンをクリックすれば削除が行われます。

f:id:ueponx:20160916122557j:plain

所感

すでにバックエンドが完成していて、フロントエンドをなんとしないと行けない場合には、かなりいい感じでしょうか。ハッカソンなどで短時間にサービスを作る場合にはかなり役に立つかなと思います。ある程度セキュリティの担保も行いたい場合にはパスワードなどもかけられるようなので、ちゃんとログインしてつかうことはいうまでもありませんけど。

しあし、FTPなどではなく、ドラッグ・アンド・ドロップでもういい時代は結構きているんだなと関心しました。

ちなみに

画像などの静的ファイルだけを置こうとすると

f:id:ueponx:20160916123146j:plain

index.htmlは必須だよと怒られます。index.htmlと一緒にドラッグ・アンド・ドロップを行えばいいようですが、その時はフォルダとしてドロップしないと処理が完了しないようです。成功すれば静的ファイルのURLをダイレクトに指定すれば参照は可能のようです。

Windows 10 IoT Coreのタイマー関係を調べてみる(追記)

Windows 10 IoT Coreのタイマー関係を調べてみる(追記)

前回のエントリーの続編となります。

uepon.hatenadiary.com

前回のエントリーでmsecレベルのdelayは難しいのかなと思っていたのですが、Facebook上でMatsuokaさんからコメントで

「sw.ElapsedMilliseconds がNG」

というコメントをいただき

ソースもいただけました。ありがとうございます。

以下のようにすればμsec単位のdelayができるということでやってみました。

private static void xDelay(int microSeconds)
{
    var ticks = (long)(microSeconds * Stopwatch.Frequency / 1e6);

    var sw = new Stopwatch();
    sw.Restart();
    while (sw.ElapsedTicks <= ticks)
    {
        // Nothing.
    }
}

Ticksを使うっていうのはあながち間違っていなかったようなのですが、System.Diagnostics.Stopwatch.FrequencyでTicksの周波数をμ秒の単位に合わせて Ticksの値が該当の値になるようにすることでうまくできるようです。

https://msdn.microsoft.com/ja-jp/library/system.diagnostics.stopwatch.frequency(v=vs.110).aspx

上の例ではμ秒なので1e6=1000000で割ることで1マイクロ秒あたりのTicksを計算しています。

このxDelay()メソッドを使って以下のようにコードを書いて実験をすると

private string delay(int msec)
{
    var times = 0;

    s.Restart();
    for (times = 0; times < 1000; times++)
    {
        if (pin.Read() == GpioPinValue.High)
        {
            pin.Write(GpioPinValue.Low);
        }
        else
        {
            pin.Write(GpioPinValue.High);
        }
        xDelay(msec*1000); //引数はμsec単位なので1000倍してmsec単位にしています
    }
    s.Stop();

    return msec.ToString() + "msec->" + s.ElapsedMilliseconds / (double)times + "ms" + Environment.NewLine;
}

f:id:ueponx:20160819233559j:plain

このようになります。GPIOの処理も行っているので少し負荷がかかっていると思いますが以前に比べると大きく処理が改善しています。 RaspberryPi2で実行していますが、RaspberryPi3でも同じような結果になるそうです。

以下がダメダメだった時の結果。

f:id:ueponx:20160813170143j:plain

誤差の1msec以下なのでかなりいい感じになっています。これならかなりいい感じで使用できそうです。 Matsuokaさん、アドバイスありがとうございました。

Windows 10 IoT Coreのタイマー関係を調べてみる

Windows 10 IoT Coreのタイマー関連を調べてみる

【注】このエントリーの情報は誤りがありますので追記も必ず御覧ください。

uepon.hatenadiary.com

Arduinoとかmbedなんかのソースを移植するときにmicros()とかsleep()とかdelay()とかがあるんで、Windows10 IoT Coreではどの程度の精度が出るのか調べてみました。

delay()の代わりになるもの

以前のエントリーでも使ってますが、await Task.Delay()を使う感じでしょうか。(Thread.Sleep()でもいいのかも)

private async Task<string> delay(int msec)
{
    var times = 0;

    s.Restart();
    for (times = 0; times < 1000; times++)
    {
        if (pin.Read() == GpioPinValue.High)
        {
            pin.Write(GpioPinValue.Low);
        }
        else
        {
            pin.Write(GpioPinValue.High);
        }
        await Task.Delay(TimeSpan.FromTicks(10 * 1000 * msec));
    }
    s.Stop();

    return msec.ToString() + "msec->" + s.ElapsedMilliseconds / (double)times + "ms" + Environment.NewLine;
}

こんな感じのメソッドを考えてみました。引数で与えた秒数間隔でGPIOでLチカを行い、1000回実行して平均値をとっています。

引数にTimeSpan.FromTicks(10 * 1000 * msec)を入れたのはミリ秒以下のものを試せるようにする意味だったのですが、結果を知ってしまうと無駄です。普通に値を入れてしまえばいいと思います。ちなみにTickは100ナノ秒とのことです。

これを使って実行すると

f:id:ueponx:20160813161053j:plain

あー。

もしかしてGPIOのLチカが影響しているのかもと思ったのですが

f:id:ueponx:20160813161042j:plain

夢でした。ありがとうございます。

これでは秒単位でなければ使い物にならないのかも…そういう用途なの?

別の手段を考えてみた

これではソースの移植なんかもできないかなと思い、別の手段を考えてみました。というか、処理時間の計算に使っていたSystem.Diagnostics.Stopwatch()を使っているだけです。ストップウオッチのように使えるのですが、タイマーを動かしながらでも値の取得ができるのでそれでDelayする値がくるまでループをぶん回しているだけです。

private string myDelay(int msec)
{
    var times = 0;

    var sw = new System.Diagnostics.Stopwatch();
    sw.Restart();

    s.Restart();
    for (times = 0; times < 1000; times++)
    {
        if (pin.Read() == GpioPinValue.High)
        {
            pin.Write(GpioPinValue.Low);
        }
        else
        {
            pin.Write(GpioPinValue.High);
        }
        var old = sw.ElapsedMilliseconds;
        while ((sw.ElapsedMilliseconds - old) <= msec)
        {
        }
    }
    s.Stop();
    sw.Stop();
    return msec.ToString() + "msec->" + s.ElapsedMilliseconds / (double)times + "ms" + Environment.NewLine;
}

実行すると以下のようになります。

f:id:ueponx:20160813162403j:plain

上がawait Task.Delay()を使用、下がSystem.Diagnostics.Stopwatch()を使用したものになります。

だいぶ値的には使える範囲まで来ているかなと思います。 同様の考え方で、Arduinoのmicros()にはならないもののmsec単位の値を取ることもできます。

まとめ

await Task.Delay()ではasync/awaitのコストが結構きいているのかなと思います。秒単位、分単位ならこれでもいいかなと思いますし、UI関連が絡むのであれば選択肢としてありかなとも思えます。 ただ、Arduinoやmbedの移植を考えた時にはシングルタスクで動いていると思われるのでSystem.Diagnostics.Stopwatch()をつかってループをぶん回してもいいのかなと思います。 本当はもっといい方法があるのかもしれません。むしろ教えてほしい・・・。

追記

ネットをみてたら、こういう記事があった。

https://devhammer.net/blog/thread-sleep-equivalent-uwp/

await Task.Delay(1)Task.Delay(1).Wait()変えるって話らしい。

f:id:ueponx:20160813170143j:plain

値は変わらない・・・

Windows10 IoT CoreでもSlackのOutgoingWebhooksを実装してみる

Windows10 IoT CoreでもSlackのOutgoingWebhooksを実装してみる

以前のエントリーでまどべんよっかいちにてRaspberryPi(Raspbian)でSlackのIntegrationであるOutgoingWebhooksの連携を行いましたが、少しもやもやしていたのでWindows10 IoT Coreでも実装してみました。

uepon.hatenadiary.com

www.slideshare.net

OutgoingWebhooksの設定に関してはSlideShareの資料を見てもらえれば良いかと思います。

今回もヨロイ元帥botを使っていきます。

ヨロイ元帥Botとは

Slackの書き込み中に「仮面ライダー」というキーワードあったら、

【投稿したUser_name】+“裏切者は殺す。デストロンを代表して。ヨロイ元帥”

と書き込むBotになります。

先日のアメトーーク!の放送で「仮面ライダー芸人」のOAがあったのでヨロイ元帥もかなり認知度が上がっているのではないでしょうか。

Windows10 IoT Core特有の問題

今回はUWPとC#という組み合わせだったので少してこずりました。というのも、Webサーバ的な機能がデフォルトでUWPでは持たせることができないところに理由があります。 もう少しダイレクトにいうならば、System.Net.HttpListenerが使えないのでHTTPの待ち受けをしてくれるような便利クラスがないということになります。 そのため、

  • TcpListenerクラス
  • StreamSocketListenerクラス

のいずれかを使用することになるのかなと思います。

試してはいませんがIoT向けのIotWeb HTTP Serverというライブラリもあるようなのでそっちを使ったほうが楽なんだと思います。

IotWeb HTTP Server

今回はStreamSocketListenerクラスをつかってWebサーバを作成してOutgoingWebhooksに対応することにします。

実はStreamSocketListenerを使ったサンプルがGithubにあったはずなのですがなくなってしまったようです。残念。

App2App WebServer https://github.com/ms-iot/samples/tree/develop/App2App%20WebServer

現在は404になっています。

WebServerのクラス

基本的には素のソケット通信でWebServerを作成することになります。 ただ、StreamSocketListenerはもう少し便利になっていて、 通信を検知するとイベント(ConnectionReceived)を検知してくれます。 そのため、このイベントに対処するイベントハンドラを作成すればデータの受信時の動きを簡単に書くことができます。

Listener_ConnectionReceived()メソッドがコネクションが張られたときに動作するイベントに該当します。 逆にデータの送信に関してはSendResponse()メソッドが該当しています。 送信に関してはOutgoingWebhooksではJSONを使用しているため、nugetでNewtonsoft.Jsonを行う必要があります。

SlackのOutgoingWebhooksのデータ送信形式に関してはSlideShareの資料を参考にしてください。

nugetするパッケージ

f:id:ueponx:20160806161529j:plain

今回はtokenデータによる簡単な認証を行っています。

using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

public class SimpleOutgoingWebhooksWebServer
{
    StreamSocketListener listener;
    StreamSocket socket;
    public event Action<string> OnReceived;
    private string tokenBlock = "token=";

    public int PORT { get; set; }
    public string TOKEN { get; set; }

    public SimpleOutgoingWebhooksWebServer(int port, string token)
    {
        this.TOKEN = token;
        tokenBlock += this.TOKEN;
        this.PORT = port;
    }

    public async void Start()
    {
        listener = new StreamSocketListener();
        listener.ConnectionReceived += Listener_ConnectionReceived;
        await listener.BindServiceNameAsync(PORT.ToString());
    }

    private async void Listener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
    {
        socket = args.Socket;
        var dr = new DataReader(socket.InputStream);

        /// 受信データの取り出し
        StringBuilder request = new StringBuilder();
        uint BufferSize = 2048;
        using (IInputStream input = socket.InputStream)
        {
            byte[] data = new byte[BufferSize];
            IBuffer buffer = data.AsBuffer();
            uint dataRead = BufferSize;
            while (dataRead == BufferSize)
            {
                await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
                request.Append(Encoding.UTF8.GetString(data, 0, data.Length));
                dataRead = buffer.Length;
            }
        }

        // メソッド部分の処理
        var receiveString = request.ToString();
        var source = receiveString.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
        var content = source.Last().Split(new[] { '&' });

        var requestMethod = source[0];
        string[] requestParts = requestMethod.Split(' ');
        var text = requestParts[1];

        if (content.Contains(tokenBlock) == false)
        {
            return;//token認証NG
        }

        var x = content.Where(e => e.Contains("user_name=")).FirstOrDefault();
        var userName = "";
        if (x != null)
        {
            userName = x.Substring("user_name=".Length);
        }

        if (text.Contains("favicon.ico") == false) //faviconのリクエストは無視
        {
            /// 受信イベント
            if (this.OnReceived != null)
            {
                OnReceived(userName);
            }
        }
    }

    public async void SendResponse(string text)
    {
        if (socket == null) return;

        var data = new SendJSONData
        {
            text = text
        };
        var json = JsonConvert.SerializeObject(data);
        byte[] bodyArray = Encoding.UTF8.GetBytes(json);
        MemoryStream stream = new MemoryStream(bodyArray);
        string header = String.Format(
                            "HTTP/1.0 200 OK\r\n" +
              "Content-Type: text/javascript; charset=utf-8\r\n" +
                            "Content-Length: {0}\r\n" +
                            "Connection: close\r\n\r\n",
                            stream.Length); //ヘッダ部分
        var dw = new DataWriter(socket.OutputStream);
        dw.WriteString(header);
        dw.WriteString(json);
        await dw.StoreAsync();
    }

    private class SendJSONData
    {
        public string text { get; set; }
    }
}

このクラスをもとにメイン処理を記述していきます。

MainPage.xaml.cs

メインの処理は先ほどのSimpleOutgoingWebhooksWebServerクラスを使うことで比較的短く書くことができます。 コンストラクタの第1引数の部分が受け待ちになるport番号です。 また、コンストラクタの第2引数の部分が設定時に指定されるtokenになりますので適宜変更してください。

server_OnReceived()はデータの受信後に発生するイベント(.OnReceivedイベント)のハンドラになります。

今回はLEDなどを点灯していませんが、ここでLEDの処理を入れればいいかなと思います。

using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Core;

namespace IoTCoreWebServer
{
    public sealed partial class MainPage : Page
    {
        SimpleOutgoingWebhooksWebServer server;

        public MainPage()
        {
            this.InitializeComponent();

            server = new SimpleOutgoingWebhooksWebServer(8888, "xxxxxxxxxxxxxxxxx");
            server.OnReceived += server_OnReceived;
            server.Start();
        }
        private async void server_OnReceived(string data)
        {
            // Request受信
            await Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal,
                () => { textReceive.Text = data; }); // 受信データをテキストボックスに表示


            // 応答を送信
            server.SendResponse(data + "裏切り者は殺す。デストロンを代表して。ヨロイ元帥");

        }
    }
}

一応これでコーディングは終了です。

Package.appxmanifestの変更

ただし、これだけでは動作しません。ソリューションマネージャにあるPackage.appxmanifestファイルを開き機能の追加を行います。

  • インターネット(クライアント)
  • インターネット(クライアントとサーバー)

Package.appxmanifestをクリックして

f:id:ueponx:20160806161536j:plain

機能タブを選択すると表示されます。

f:id:ueponx:20160806161542j:plain

この2つにチェックをいれて保存します。これが設定されていないと動作しません。注意が必要です。

動作

何とか動いてくれました。

f:id:ueponx:20160806161554j:plain

終わりに

Raspbianに比べると特殊事情がおおいWindows10 IoT Coreですが、VisualStudioのデバック機能が優秀なため、引っかかっても悩むことは少ないと感じました。 また、UWPアプリであるところが癖でもありますが、セキュリティーの面はそれなりにいいかもなあという印象もあります。

まだまだ接続機器に不安が残りますが、どちらも使っていこうと思います。

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