Nordic nRF5を使ってIPv6 Globalする

NordicさんからnRF52 Dev Kitで動くIoT SDK(自分の中で通称6LoWPAN SDKと呼んでいる)が出てきたので早速使ってみることに。SDKとかはこの辺からダウンロードしてきて書き込み。

ダウンロードして展開すると、documentationに使い方が詳しく書いてあるから見れば使える。Getting Started(この辺はRaspberry PIをGWにした方法)、IoT SoftDevice(利用するSoftDevice)あたりを読めばOK。examples以下にサンプルが山ほどあるから、Keil5(を自分は使った)で書き込んで完了という簡単な流れ。

IMG_9796

ただ、RPiを使わなくてもPC(Bluetoothが付いているPC)でVMを使えば大丈夫。方法はこの辺で。一応、Win/MacともにBTが付いているPC上でVMを作って、IPv6 Gatewayにして疎通できた。

あと、これはICMPv6のサンプルコードに書いてある部分の画面キャプチャ。ボタンを押すとRAをしたり、これ便利ね。SDKダウンロードしてきて、ソースを見れば書いてあるけど。

IPv6リンクローカルなら疎通は問題なし。すぐ出来ると思われ。ただ、やっぱあれよね、Global IPv6から疎通したみたいぜ!!と思うのは自然だと思われで、やってみたらこれも問題なし。方法は次の流れで。

前提) IPv6 /64プレフィックスが使える環境
1) ルータのIPv6遮断(フィルタを切る)
2) RPiをルータに接続して、IPv6/Bluetooth Gatewayとして機能させる

前提) IPv6 /64プレフィックスが使える環境
まず前提のIPv6 /64プレフィックスが使える環境は、幸いな事に自分はAU光の回線&プロバイダを使っているおかげで、特に追加費用とか何もいらずにIPv6 /64 Globalが使えている。AU光な人はすぐ実験&IPv6できるですね。
よくIPv6の促進がどーとか聞くけど、Flet’s系のGlobal追加は知らんけど(追加費用が必要と思われ。そのままでも使えるけどGlobal抜けられないw。この辺は脈々と古くから続いているシガラミ)、AU光だとIPv6が降ってきて知らないうちに使えている人は多いような。しらないうちにIPv6が使えているってやつっすね。
ということで、この普通に宅内に引いてきているAU光を使う。

1) ルータのIPv6遮断(フィルタを切る)
これは、一応だけど超注意が必要。心配ならフィルタを切るなりした方が良い。IPv6 Globalで直接、宅内のIPv6 NWまで侵入される可能性もあるから(IPv4側は大丈夫)。ただ、設定はAterm(プロバイダから貰ったやつ)のでこの辺。自分は保護しているから、この部分は心配せずに全公開でwww

2) RPiをルータに接続して、IPv6/Bluetooth Gatewayとして機能させる
これはNordic SDKの資料のGetting Startedあたりにも書いてあるけど、radvd(IPv6のRAをするデーモン)を使ってやる。radvdとかもうヲレ的に10年前からずっと、RAをLinux上でするならradvdだよね感がする、枯れて普通に動くものですね。

RPi上でのコマンドの流れはこんな感じで、RPiを上位のAU光からの/64のGWにして、Bluetooth側に流している。本当はブリッジして…とかの方が良いのかもだけど、btXXの増減を考えるとbrを作るか、NDP Proxyでとりあえず代理応答しておくか…は考え所かなと思われ。とりあえず手軽にNDP Proxyした訳だけど、どっちもどっちだと思われ。
ちなみに、以下、プレフィックスを特に隠さず全部書いちゃっているけど、このアドレス&プレフィックスにアクセスしてもすでにアクセス不能にしているので気にしてないっす。ONUを再起動すれば変わっちゃったりするし。

# 6lowpanを有効にする
modprobe bluetooth_6lowpan
echo 1 > /sys/kernel/debug/bluetooth/6lowpan_enable

# nRF52に接続する
echo "connect 00:B9:89:15:EF:B1 1" > /sys/kernel/debug/bluetooth/6lowpan_control

# IPv6 forwarding(GW)を有効
echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

# とりあえずlink localのIPv6 pingを投げて確認
ping6 fe80::2b9:89ff:fe15:efb1 -I bt0

# bt0でRAを受けれるようにする(brさせなきゃ、別にいらない気もするけど)
echo 2 > /proc/sys/net/ipv6/conf/bt0/accept_ra 

# ICMPv6 NDP Proxyを有効(これはRPiのeth0に入ってくるRA応答用)
sysctl -w net.ipv6.conf.all.proxy_ndp=1

# 生成されるアドレス用の上位ルータ向けNDP Proxy
ip -6 neigh add proxy 240f:1:6c14:1:2b9:89ff:fe15:efb1 dev eth0

# とりあえず適当にbtにアドレスを振っておく
ifconfig bt0 add 240f:1:6c14:1::4/68

こっちはradvdの設定

cat /etc/radvd.conf 
interface bt0
{
    AdvSendAdvert on;
    # このIPv6 prefixはAU光から配布されたもの
    prefix 240f:1:6c14:1::/64
    {
        AdvOnLink off;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
};

ifconfigの状態はこんなん

root@raspberrypi:~# ifconfig
bt0       Link encap:UNSPEC  HWaddr 00-19-86-FF-FE-00-00-78-00-00-00-00-00-00-00-00  
          inet6 addr: 240f:1:6c14:1:219:86ff:fe00:78/64 Scope:Global
          inet6 addr: 240f:1:6c14:1::4/68 Scope:Global
          inet6 addr: fe80::219:86ff:fe00:78/64 Scope:Link
          UP POINTOPOINT RUNNING MULTICAST  MTU:1280  Metric:1
          RX packets:65 errors:0 dropped:0 overruns:0 frame:0
          TX packets:98 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:6339 (6.1 KiB)  TX bytes:8122 (7.9 KiB)

eth0      Link encap:Ethernet  HWaddr b8:27:eb:d8:d5:d6  
          inet addr:192.168.0.3  Bcast:192.168.0.255  Mask:255.255.255.0
          inet6 addr: 240f:1:6c14:1:d709:d6c2:f28f:a2d9/64 Scope:Global
          inet6 addr: fe80::7dec:ae50:4f54:7d90/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1147 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1164 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:123007 (120.1 KiB)  TX bytes:212157 (207.1 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:155 errors:0 dropped:0 overruns:0 frame:0
          TX packets:155 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:12940 (12.6 KiB)  TX bytes:12940 (12.6 KiB)

bt0はnRF52と接続しているBluetoothドングルのインタフェイス。プレフィックスが若干適当感がするけど、とりあえずOK。ここまで出来たら、radvdを起動すればBluetooth側にIPv6 Global側からping6で疎通できる。IPv6が大量に配布してくれるConoHaというクラウド(特に何の追加費用もいらずに16個もデフォでもらえる。IPv6使った簡単な疎通テストとか、IPv6使った何かをする時はめっちゃ便利と思われ)があって、そこからping6を投げて、RPi上のbt0でtcpdumpをとってみるとこんな感じ。

[Global IPv6 Server] [宅内ルータ] [RPi:BTドングル] [nRF52]

といった感じで、RPiのbt0の所でちゃんとnRF52側(240f:1:6c14:1:2b9:89ff:fe15:efb1)と行って戻ってきているのが分かるですね。NDP Proxyした方が接続したbtXに合わせて上位のrouterをGW(RPi)で選択できるし、開け閉め出来そうだから良いかなーとは思ったけど、ちゃんと据え置きGWにするならbrでも良い気もする。イメージしたのは、キャリアにIPv6を使うよう通達が出ているあれで、スマホでIPv6を/64で終端した時にスマホに接続したBTとかをGW(スマホ)を経由して流す感じの。まぁ、AndroidとかLinuxだしカーネルツリーにすでに6lowpanモジュールは入っていて、その辺は時間の問題だと思われ。
とりあえず、これは実験ってことでね。

会社でArduinoとか配布した〜IOTには実際にモノを触ってみる

社内でArduino&センサー類を無償配布した。これには少し動機があって、IoTという言葉が流行っているのもあるけど…

1. IoTはWebサービス側だけじゃなく、デバイスも関わってくる。
2. ガジェット・デバイス物の基礎を知ることは、ネット・サービス系でも重要な要素の一つ。
3. Things=モノは実際に触れて、作って理解することが大切になる。

こういう立て付けにて配布を行うことに。もちろん、結果や物が出来ても出来なくてもOKで、無償で配布した。

「IoT、IoTとかってネット・サービス側から言っても、実際にデバイスやモノを触ってみないと分からんべ」という所もある。ネット・サービス系のエンジニアでも面白いガジェットが出れば興味津々で、どんなアプリに?何に使おうか?といったモノに対する気持ちはあると思うし。
まぁ、机上でIoTやガジェット物・デバイスやらArduinoってあるよねー的に知っているより、自分でやってみる方が文字通り、百聞は一見に如かずってやつですね。

効果としては、IoTを支える基礎技術に触れることで、そこから何か新しいサービスやアプリ・ガジェットを考えたりも出来る可能性が生まれてくる。あとこういう学習を通じた会社/グループの基礎体力の向上という側面もあるし、IoTという言葉でなにを行うか?といった時、それぞれの人が各グループ会社での相談先の一人になるという事もあるかなと。

前置や建て付けは色々あるけど、化学反応的な何かが起きるかも知れないという期待は高くなったりもした。何はともあれ、まずは使いやすいArduinoを使ってという路線でやる事にした。ググれば大抵の方法は書いてあるし。流れ的にはこんな感じにて。

1. ワークショップに必要な部材を検討・予算確保・購入
2. 参加者を募集
3. 購入部材を参加者に渡す
4. ノウハウと共に使い方を説明(WP)

流れを追って書いていく。

1. ワークショップに必要な部材を検討・予算確保・購入

部材は大量に購入した。一覧は次の通り。わりと大量で型番まで書く元気が無いから、とりあえず名称/機能だけ記載しておく。

Arduino UNO(もちろん純正)
USBケーブル

- LED類
赤色LED
青色LED
黄色LED
緑色LED
赤外線LED
赤外線リモコン受光モジュール

- センサ/WiFi
フォトリフレクタ
温度センサ
照度、光センサ、Cdsセル
赤外線距離センサ(GP2Y0A21YK)
圧力センサ
音センサ、コンデンサマイク(4個入り)
6軸加速度センサ(3軸加速度・ジャイロ) MPU 6050
WiFi ESP8266
圧電スピーカ

- モータ
サーボモータ : SG90x2
サーボモータ用ブラケット
DCモータ
DCモータドライバ TP7291P

- 接続類
ブレッドボード
ジャンパーコード
ジャンパーワイヤー
ジャンパーワイヤー
小型クリップ付きコード
トグルスイッチ
基板用トグルスイッチ3P
タクトスイッチ、赤、黒、黄

- トランジスタ/IC
トランジスタ(PNP)、2SA1015L-GR
トランジスタ(NPN)、2SC1818Y
オペアンプ、LM358N
オペアンプ、LM386N

- 抵抗類
半固定抵抗、10KΩ
抵抗 10KΩ
抵抗 1KΩ
抵抗 330Ω
抵抗 220Ω
抵抗 470Ω
抵抗 1MΩ
抵抗 100KΩ
抵抗 22KΩ
抵抗 220KΩ

- コンデンサ
コンデンサ、0.1μF
コンデンサ、1μF
コンデンサ、10μF

予算は社長に内容を直訴して、IoTということ・必要性・効果について説明して確保した。理解がある会社・社長で良かった。
そして、これらは全て備品扱いで、個別に参加者に渡して自由に使って貰う事にした。変に管理するより、自由に使って貰った方が良いからね。個別に全部パックして渡して「自由に使ってOKです!!」と。

2. 参加者を募集

これは会社のSNSで呼びかけです。参加したい人は、メールかメッセを貰えたらーということで。
グループ全部で数千人(6,000人くらい)の社員さんがいるけど、クリエイター・エンジニア・非エンジニアの区別なく募集した。この辺の条件は特に無しで。全員を引き受けようとはしたけど、先着40名ということで。ただ、めっちゃ応募が来たから、フルフルに使い切って全部で50-60パックほど配布した。予算・在庫確保を先にしたというのもあるけど、全員に配れなかったのは申し訳無かった。。。

3. 購入部材を参加者に渡す

備品はこんな感じで(これは一部分ですね)、一つづつパックに入れてラベリングした。

大量に準備

これはいきなり抵抗やら電子部品を渡しても、混ぜちゃったり、これは一体なに?と思うだろうなぁ…と思い、地味にラベルを作って内職のように貼り&包み分けしまくったですね。結構根気がいる単純作業で手伝って貰ったりして。

4. ノウハウと共に使い方を説明(WP)

これは次回はなにをやるか?というのをSNSで流して参加性で、業務時間後の19時以降に毎回行った。「これを作ってやってみよう!!」「レッツ!!」といった感じで。

ワークショップだと、他にも自分の手持ちのAgICを使って静電容量センサを作ってみたり(スマートフォンの静電容量式タッチパネルの原理の基礎)、基本的な原理についても説明して進めたりした。

これは圧力センサを使ったもの。

たまに小ネタで、果物をタッチすると音が鳴るという組み合わせも.

ただ、作る人は自分で作っちゃうもんで、赤外線でエアコンをつけたりする物を自宅で作って動画をSNSにアップして見せていたり(赤外線リモコンっすね)、最近だとL3D Cubeに渡したArduinoをつけて動かしてみた人とかも。

他にも3Dプリンターやドローンも使えたり、わりと何でも作れる環境だったりする。突然、IoTワークショップをやりたいです!!と会社(GMO Internetという会社っすね)に言ってみたら、OK!!とサクサク進んで感謝です。

elastic/Kibanaでセンサーデータの可視化

これは Elasticsearch Advent Calendar 2015 の記事です。

複数のセンサーデータを取得してfluentd経由でelasticsearchに流して可視化する内容です。構成的にはこんな感じです。

これだけ見れば普通にサクッと出来そうですね。golang/fluentdとセンサー周辺の設定、データ形式やコードについてはこちらの記事を参照。elastic/kibanaは次のような感じです。

elastic : 2.0.0-rc1
kibana : 4.3.0

kibanaについてはgitにある最新版を使ってみました。これは一つにダークテーマを使いたいという、これ1点に尽きます。開発時点ではテーマの選択が出来なくて白い感じので、何となくクールでカッコイイテーマを使いたくて、4.3を使おうとしたらelasticのバージョンも上げないとダメ…という事でどちらも最新版を使ったですね。多分だけど、ダークなテーマが好きって人は多いと思われ。

んでセンサーデータの表示画面はこんな感じで。

照度(光センサーの値)が上がると、電球マークが光る。

そうですね…kibanaのUIというか表示をいじって、インタラクティブな要素を入れています。ってことで、kibanaのざっくりした構成を読み解くと。

実装: NodeJS
グラフ周り : D3
フレームワーク : AngularJS

デバックモードで起動してあげると、コードを修正すると自動でリロードがかかってdeploy&チェックできるという仕組み(その時、js周りの圧縮もかかったりも)だったり、nodejsのmoduleが山ほど入っている。ただ、このNodeJS/D3/AngularJS周りを抑えておけば、独自の改造はどーにかなると思われ。基本的にpluginの形式で、ほとんどのUI(dashboardも)が作られているからね。

んで、このダッシュボードに追加するボックスの単位はlegendという扱いになっていて、中のUIを変更するために src/ui/public/vislib/lib/legend.js にゴリゴリっと手を入れている。他にも手を入れているけど、メインはその周辺あたり。

[その他]
今回、地域毎に大量のダッシュボードを用意した。色んな地域、国にセンサーを置いていて、それ毎にダッシュボードを用意した。

このダッシュボードの追加での対応を楽にしたい!!という欲求があって、色々と試してみたらKibanaの便利機能を発見(というか、ですよねー的な)した。流れとしては、

1. visualization を基本要素(温度、湿度、照度)で作る。
2. Dashboardを作る
3. Dashbaordの”Edit dashboard Object”でクエリーを直接書いて、地域選択

という流れ。これでvisualizationで作る要素を基本要素だけにして、dashboardを用意してちょっとクエリでキーを追加してあげれば完成という流れ。これはメチャクチャ便利だと思った。基本のvisualizationだけ用意すれば、あとの表示要素はどうとでもなるから。

実際の流れは…

1. visualization を基本要素(温度、湿度、照度)で作る。
実際、3つしか作っていない。

2. Dashboardを作る
とりあえず、Dashbaordの”+ボタン”で追加する。

3. Dashbaordの”Edit dashboard Object”でクエリーを直接書いて、地域選択
dashboardの一覧画面の編集から..

んで、IDを設定して完了。

という流れ。表示要素はほぼ同じで、ダッシュボードを大量に作って区別するときは、こんな感じでVisualizationで基本要素を作っておいて、Dashboard側のちょっとした変更でとっても簡単&大量に作ることができる。

golangでセンサーデータをMQTTで受けてloggerする

Go その2 Advent Calendar 2015の記事です。

golangで複数のセンサーからのデータをMQTTで受けてfluentdを使ってロギング、elastic/kibana, splunkで可視化します。これは実際にET展のマクニカさんのMpressionによるIoTのPoC(Proof of Concept)にて展示してきた内容の詳細です。実際の展示中の内容はこちらです。会場と世界各国のマクニカさんのオフィスにセンサーを置いて頂き、色んなところからモニタリングしています。

構成
以下の図のような構成です。色んなセンサーを使っています。MQTTを使えるセンサーがあるので、MQTTサーバとしてはmosquittoを使っています。

送信データフォーマット
センサーからのデータは基本的には、温度、湿度、照度を扱っています。他にも色々取れるのですが(加速度、地磁気、音声, IR…etc)基本データとして3個を扱う事にします。MQTTで各センサーデバイスからデータを送信するのですが、その時のデータフォーマットは以下にしています。(一部抜粋です)

DeviceId   string : センサデバイスに振られているID
DeviceType string : TI/BCM/Kivo...etc センサーのタイプ
Temp       string : 温度
Lux        string : 照度
Humidity   string : 湿度

デバイスIDはシステム側で管理して、存在するものだけをloggingしたり、検索時の引き当てで入れておいた方が良いでしょう。センサーのタイプはシステム側でIDから引ければ問題無しです。温度、照度、湿度はそのままですね。

サーバ側
こちらはMQTTで受け側です。センサデータを受けたらfluentdに流しています。fluentdで直接受けて流してもOKですが、ここではgolangで受けてfluentdに投げてみましょう。fluentd, mqttのIPとか設定すれば動くです。

package main

import (
    "encoding/json"
    "flag"
    "log"
    "fmt"
    "time"

    MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
    "github.com/t-k/fluent-logger-golang/fluent"
)

// 受信するjsonデータ
type SensorData struct {
    DeviceId   string `json:"DeviceId"`
    DeviceType string `json:"DeviceType"`
    Temp       string `json:"Temp"`
    Lux        string `json:"Lux"`
    Humidity   string `json:"Humidity"`
}

// デフォルトハンドラ(Subscribe時にトピック毎に指定も可能)
var defaultMTTTHandler MQTT.MessageHandler = func(client *MQTT.Client, message MQTT.Message) {
    fmt.Printf("Received MQTT message on topic: %s\n", message.Topic())
    fmt.Printf("Message: %s\n", message.Payload())

    sensorData := SensorData{}
    json.Unmarshal([]byte(message.Payload()), &sensorData)

    logger, err := fluent.New(fluent.Config{FluentPort: 24224, FluentHost: "host.name"})
    if err != nil {
        fmt.Println(err)
    }
    defer logger.Close()

    tag := "sensor.data"
    t := time.Now()

    // ちょっと加工用
    data := map[string]string{
        "temperature":      sensorData.Temp,
        "lux":              sensorData.Lux,
        "humidity":         sensorData.Humidity,
        "device-type":    sensorData.DeviceType,
        "device-id": sensorData.DeviceId,
    }
    logger.PostWithTime(tag, t, data)
    log.Println("Send to fluentd")
}

func main() {
    opts := MQTT.NewClientOptions().AddBroker("tcp://host.name:1883").SetClientID("sensorlogger")
    opts.SetDefaultPublishHandler(defaultMTTTHandler)

    client := MQTT.NewClient(opts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
       panic(token.Error())
    }

    qos := flag.Int("qos", 1, "The QoS to subscribe to messages at")
    if token := client.Subscribe("/sensor/put", byte(*qos), nil); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }

    for {
        time.Sleep(1 * time.Second)
    }
    client.Disconnect(250)
}

センサー側(TI/RPi2)
こちらはセンサー側のRaspberry PI、TI(nodejs)側。SensorTagとBLEで通信して、データを取得したらMQTTで投げているだけですね。

var SensorTag = require('sensortag');
var mqtt    = require('mqtt');
var request = require('request-json');
var mqttclient  = mqtt.connect('mqtt://mqtt server');

SensorTag.discoverByAddress("sensor tag BTのMacアドレス", function(tag) {
    tag.on('disconnect', function() {
        console.log('disconnected!');
        process.exit(0);
    });

    function connectAndSetUpMe() {
        console.log('connectAndSetUp');
        tag.connectAndSetUp(enableIrTempMe);
    }

    function enableIrTempMe() {
        console.log('enableSensor');
        tag.enableHumidity(notifyMe);
        tag.enableLuxometer(notifyLux);
    }

    function notifyLux() {
    }

    function notifyMe() {
        tag.notifyHumidity(listenForHumidity);

        tag.setHumidityPeriod(1000, function(error) {
            if (error != null)
                console.log(error);
        });
    }

    function listenForHumidity() {
        tag.on('humidityChange', function(temperature, humidity) {
            tag.readLuxometer(function(error, lux) {
                var data = {
                    DeviceId: "unique device id",
                    DeviceType: "TI SensorTag",
                        Temp: temperature.toFixed(1),
                         Lux: lux.toFixed(1),
                    Humidity: humidity.toFixed(1)
                };
                mqttclient.publish('/sensor/put', JSON.stringify(data, null, "\t"), {qos : 1});
            });
        });
    }
    connectAndSetUpMe();
});

結果
実際の可視化の様子はこちらです。
elastic/kibana

splunk

Pub/Subのライブラリは色々あって、なぜにGolangを使ったか?というと、とっかかり易くて便利というのもあるけど、センサーデータはストリームで恐ろしい勢いで機械的にデータが飛んできたり、IPは変えられるけどエンドポイントは固定のパスとかあったりする可能性を考えて、並列部分を簡単に書けて、endpointづつ小さいAPIでサクサク作れるgolangで良いかなと。