CPUやメモリなどの情報を取得するgopsutilのご紹介

Go Advent Calendar 2015の16日目です。

Pythonにはpsutilという、CPUやメモリなどの情報を取得するライブラリがあります。拙作gopsutilはこのpsutilをgolangに移植しようと始まりました。

gopsutilは、以下の特徴があります。

  • Linux/Darwin/FreeBSD/Windowsで動作します
    • もちろん、対応状況はかなり違います
  • (ほぼ) pure golangで実装されています。そのため、クロスコンパイルが容易です
    • ほぼ、というのはdarwinのCPU利用率だけcgoを使っています。cgoを使わない場合は単にnot implementedが返ってきます。
  • psutilにない情報も取れます
    • docker(cgroup)の情報だったり、仮想化状況だったり、好き勝手に機能を追加しています

gopsutilは1年半以上前からこつこつと開発を続けており、おかげさまで今ではgithubのスターが800以上を超えました。

また、

といったソフトウェアからライブラリとして使用されています。

使い方

使い方はREADMEに書いてありますが、以下の通りです。github.com/shirou/gopsutil/memなどをimportして、パッケージに置いてあるメソッドを呼び出すだけです。

import (
    "fmt"

    "github.com/shirou/gopsutil/mem"
)

func main() {
    v, _ := mem.VirtualMemory()

 // structが返ってきます。
    fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent)

 // PrintするとJSON形式の結果が返ってきます
    fmt.Println(v)
}

これを実行するとこんな感じになります。

Total: 3179569152, Free:284233728, UsedPercent:84.508194%

{"total":3179569152,"available":492572672,"used":2895335424,"usedPercent":84.50819439828305, (以下省略)}

structとして取得できるので、あとは好きな様にいじって下さい。あるいは、PrintしてあげればJSONとして扱えますよ、という感じです。

取得できる情報

結構いろいろな情報が取れるのですが、その一部を紹介します。

  • CPU
    • CPU使用率、CPUのハードウェア情報
  • memory
    • メモリ使用率、スワップ使用率
  • disk
    • パーティション情報、I/O、ディスク使用率、ディスクのシリアル番号
  • host
    • ホスト名、起動時刻、OSや仮想化方式、
    • ログインユーザー情報
  • load
    • Load1, 5, 15
  • Process
    • 個々のプロセスのPIDや状態、起動プロセス名、メモリやCPU使用率など
  • Docker
    • コンテナ内部のCPU使用率やメモリ使用率など

要望があれば既存APIを壊さない範囲であればどんどん増やしていこうかな、と思っています。

中身

gopsutilは非常に泥臭いことをたくさんやっています。まず、pure goでいく、という大原則を立てているため、cgoは使えません。また、Linux/BSD/Windowsでは方式が大きく異なります。

Linux
procファイルシステムなど、ファイルベース
FreeBSD/Darwin
sysctl
Windows
DLL及びWMI

これらはcpu_darwin.goなどのようにファイル名で分けています。

Linux

基本的にテキストファイルベースなので結構楽ですね。

と思いきや、Linuxのバージョンによって取れる情報が違ったり、コンテナ内部では/sysが使えないのでパスを入れ替えられるようにする必要があるなど、細かな点が異なります。

また、ユーザー情報は/var/run/utmpでバイナリ(utmp構造体)で格納されていますので、ちゃんとparseしてあげる必要があります。このあたりは2015年6月のGoConで公開しました(発表はしてません)。

FreeBSD/Darwin

BSD系はsysctlコマンドで各種の情報が取得できます。sysctl vm.stats.vm.v_page_sizeでページサイズが取れたりですね。

ただし、sysctlコマンドで取得できるのはテキスト形式の情報だけです。Proc構造体の情報などはコマンドからは叩けないので、syscall.Syscall6などを使って叩きます。 (余談ですが、godocで出てくるのはLinuxのコードだけですので、Linux以外を知りたい場合はソースコードを読む必要があります)

mib := []int32{CTLKern, KernProc, KernProcProc, 0}
miblen := uint64(len(mib))

// まずlengthを0にして叩き、必要となるバッファ量を得る
length := uint64(0)
_, _, err := syscall.Syscall6(
    syscall.SYS___SYSCTL,
    uintptr(unsafe.Pointer(&mib[0])),
    uintptr(miblen),
    0,
    uintptr(unsafe.Pointer(&length)),
    0,
    0)

// 必要な情報を得る
buf := make([]byte, length)
_, _, err = syscall.Syscall6(
    syscall.SYS___SYSCTL,
    uintptr(unsafe.Pointer(&mib[0])),
    uintptr(miblen),
    uintptr(unsafe.Pointer(&buf[0])),
    uintptr(unsafe.Pointer(&length)),
    0,
    0)

ただし、Darwinはsysctlで取れる情報はFreeBSDに比べてかなり少ないので諦めたところもあります。

Windows

DLLを呼び出して情報を取得しています。

procGetDiskFreeSpaceExW := modkernel32.NewProc("GetDiskFreeSpaceExW")

diskret, _, err := procGetDiskFreeSpaceExW.Call(
     uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
     uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
     uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
     uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))

という感じですね。ただし、さすがにこれはいろいろツライので、github.com/StackExchange/wmiを使ってWMIを叩くようにしています。

type Win32_Processor struct {
    LoadPercentage            *uint16
    Family                    uint16
    Manufacturer              string
    Name                      string
    NumberOfLogicalProcessors uint32
    ProcessorId               *string
    Stepping                  *string
    MaxClockSpeed             uint32
}

func get() {
    var dst []Win32_Processor
    q := wmi.CreateQuery(&dst, "")
    err := wmi.Query(q, &dst)
    if err != nil {
        return ret, err
    }
    fmt.Println(dst)
}

性能

測ってはいませんが、外部コマンドを呼んだりなどを気軽にしているため、そんなに性能はでないはずです。ものすごい高頻度で実行するとホスト側に負荷がかかるでしょう。その点は使う側で適宜キャッシュするなどをして頂ければと思います。

まとめ

ホストのCPUやメモリなどの情報を取得するgopsutilの紹介をしました。

作り始めたのがgoを使い始めて間もないころであり、さらにいろいろなプラットフォームに対する知見は後から得たりしていたので、統一感がなかったりします。そのうちちゃんとしたいと思ってはいるのですが…

もしもgoでシステムの情報を得たいと思った場合には、gopsutilのことを思い出していただけるとありがたく思います。また、PRは随時お待ちしております。

Ansible Docker Connection Pluginを使う

1年以上前の2014年4月にこんな記事を書きましたdocker containerに対して直接ansibleを実行するそれからいろいろあって、Ansible 2.0では標準でDocker Connection Pluginが入っています。(といってもぼくの実装ではありませんが)

Docker Connection Pluginとは

まず、Connection Pluginについて説明します。Ansibleでは通常SSHを使って対象となるホストに接続します。しかし、 Connection Plugin を使うことで、接続方式を切り替えることができます。

代表的なものはlocal connectionです。以下のように書くと、sshではなく、localhostでそのまま実行されます。sshのlocalhostと異なる点は、sshはまったく使わずに、そのままのユーザーがのまま実行する、という点です。開発時に便利ですね。

- hosts: all
  connection: local
  tasks:
    - file: path=/tmp/this_is_local state=directory

その他、以下のconnection pluginが用意されています。paramikoやwinrmは使ったことがある方も多いかと思います。

accelerate
accelaretモード(過去の遺産なので覚える必要なし)
chroot
chroot
funcd
Func: Fedora Unified Network Controller 経由
zone
SolarisのZone
jail
FreeBSDのJail
libvirt_lxc
virtのLXC
paramiko
sshのpython実装
winrm
Windows

この中の一つがdocker connection pluginです。

Docker connection pluginの利点

Docker Connection Pluginを使うことで、Dockerコンテナに対して直接Ansibleを実行できます。具体的にはdocker execでコマンド実行を、ファイルのコピーはdocker cpを実行します。Dockerコンテナの中にsshdを建てる必要はありません。

DockerfileによるBuildが一番シンプルであることは確かです。しかし、

  • Layerを増やさないために\で何行も増やしていく場合がある
  • templateがないため、複数種類のimageを作り分けるのがめんどう
  • 他はAnsibleで管理しているのに、ここだけDockerfileになると管理が分断されてしまう

などの理由で、Ansibleを使いたくなる場合がありますので、そういう場合に有用です。

なお、私はDockerfileで済ませられるのであればその方が良いと思います。Ansibleをわざわざ使う必要はないでしょう。しかし、複雑な構成になってくると、Ansibleの方が便利な場合もあると考えられるため、ここで紹介している次第です。

Docker connection pluginを使う

能書きはこのあたりにして、早速使ってみましょう。ほとんどの人はAnsible 2.0RC1を使っていると思いますので、新たなインストールは必要ないですが、万が一 1.9.4を使っている人はこちらからdocker.pyをダウンロードし、connection_pluginsというディレクトリを作成してその中に置きましょう。以下のような構成になります。

.
|-- connection_plugins
|   `-- docker.py
|-- hosts
`-- site.yml

また、 pipでdocker-pyをインストールしておきましょう。(ansible v2.0では必要ないです)

あとは以下のようにplaybookを書きます。

- name: Dockerコンテナを起動
  hosts: localhost
  connection: local
  vars:
    base_image: ubuntu:latest
    docker_hostname: test

  tasks:
    - name: Dockerコンテナを起動
      local_action: docker image={{ base_image }} name={{ docker_hostname }} detach=yes tty=yes command=bash
    - name: ホストを追加
      add_host: name={{ docker_hostname }}

- name: Dockerコンテナ内を構成
  hosts: test
  connection: docker   # ここで docker connectionを指定
  tasks:  # 好きなように書きます
    - file: path=/tmp/docker state=directory
    - file: path=/tmp/ansible state=directory
    - group: name=admin state=present
    - user: name=johnd comment="John Doe" uid=1040 group=admin
    - copy: src=site.yml dest=/tmp/ansible/

  post_tasks:
    - local_action: shell /usr/local/bin/docker commit {{ inventory_hostname }} ubuntu:ansible

この例のplaybookは以下の二つから構成されています。

  1. Dockerコンテナを起動
  2. 起動したDockerコンテナ内部を構成管理

1の方では、dockerモジュールを使って起動します。ここは普通にlocal接続です。2の方がDocker接続を使用しています。

重要なのは、connection: dockerという行だけが異なっており、そのほかは通常のPlaybookと何ら変わりないという点です。

最後にdokcer commitを実行して、イメージとして保存しています。それまでの箇所はすべて docker execなどで行われているため、保存されず、layerは全体で最後にdocker commitを実行した時に出来る一つだけとなります。これにより、Dockerfileで何行も続けたり、ということをしなくてすみます。

commitも自動化

先ほどの例ではpost_tasksとして、docker commitを実行しています。しかし、Ansible を使って Docker コンテナーをプロビジョニングするという記事では、callback pluginを使って毎回のtask実行毎にcommitをする例を示しています。

この方式はDockerfileによる方式と同じくlayerがたくさんできることになります。その代わり、キャッシュされるため、次回は高速になるという利点もあります。

RemoteのDockerホストを使う

Dockerホストは手元だけでなく、リモートでも構いません。

export DOCKER_HOST=tcp://192.168.0.10:4243

DOCKER_HOSTを環境変数で設定すれば、そのホストを経由してDockerコンテナにアクセスします。試していませんが、Swarmなどでもきちんと動くと思います。

これで、

  • インスタンス起動などのクラウドサービスの利用
  • dockerホスト自体の構築
  • dockerコンテナ/イメージの構築
  • ELBの付け外しなど、デプロイに必要な機能

のすべてがAnsibleで可能となります。

まとめ

この記事ではAnsibleからDockerコンテナを直接触れる Docker Connection Pluginを紹介しました。一つのpythonファイルを置くだけで、Dockerコンテナにたいして通常のsshホストと同じことができるようになります。また、Dockerホストはローカルだけでなく、リモートに対しても可能です。

最後に。

最初の方に述べましたが、Dockerfileで済ませられるのであればその方が良いでしょう。なんでもAnsibleでやりたくなる気持ちも分かりますが、Ansibleを使わなくてはならない理由はありません。適材適所、不要な苦労をしないために、もう一度考えましょう。

そして、そもそもDockerコンテナ内部が複雑な時点で間違っていると思います。golangをDockerでデプロイするで示したように、golangであれば、1バイナリだけ置けば動くため、「プロビジョニング」なんてものは存在しなくなります。現在はGoogleに転職したIanさんも(より)小さいDockerイメージを作ろうという記事を書いており、必要な最小限のファイルだけ置くのが理想です。

自動化をするまえに、「そもそも本当に必要なのか?」を考えましょう。

AWS IoTにmqttcliからつなぐ

mqttcliという、CLIで動くMQTTクライアントを開発し、公開しています。

AWS IoTに対して、このmqttcliから接続してみましょう。

mqttcliのダウンロード

mqttcli filesから、

  • Linux (arm/amd64)
  • FreeBSD (arm/amd64)
  • darwin (amd64)
  • Windows (amd64)

が用意してありますので自身のアーキテクチャにあったバイナリをダウンロードして下さい。その後、chmod u+x mqttcliと実行権限を付与して下さい。

AWS IoTでthingsを作る

  1. AWSコンソールからAWS IoTを開きます。
  2. Create ResourceからCreate Thingを選びます
  3. Nameを入力して、Createを押します
  4. 下の一覧に先ほど入力した名前のThingが出てくるので、選択し、右のタグからConnect a Deviceをクリックします。
  5. Connect a Deviceという画面が出てくるので、NodeJSを押し、Generate Certificate and Policyを押します
  6. 10秒ぐらい経つと、
  • Download Public Key
  • Download Private Key
  • Download Certificate

という三つのファイルをダウンロードするように指示されるので、全部ダウンロードしておきます。

  1. Confirm & Start Connectingを押します。すると、以下の様なJSONが表示されるので、コピーして、ファイルに保存しておきます。

    {
      "host": "A3HVHEAALED.iot.ap-northeast-1.amazonaws.com",
      "port": 8883,
      "clientId": "something",
      "thingName": "something",
      "caCert": "root-CA.crt",
      "clientCert": "2a338xx2xxf-certificate.pem.crt",
      "privateKey": "aad380efffx-private.pem.key"
    }
    
  2. root-CA.crtというファイルをAWS IoT SDKのここに書いてあるとおり、こちらのSymantecのページから取得してください。

  3. 先ほどダウンロードした三つのファイルとJSONファイル、そして、 root-CA.crt を同じディレクトリに置いて下さい。

以上で準備は終わりです。

AWS IoTに接続

ファイルを入れたディレクトリに移動し、 以下のようにmqttcliを起動します。-tでtopicを指定しますが、$をエスケープする必要があるかもしれません。--confで指定するのは先ほど保存したJSONファイルです。-dはデバッグ用です。

$ mqttcli sub -t "\$aws/things/something/shadow/update" --conf something.json -d
INFO[0000] Broker URI: ssl://A3HVHEAALED.iot.ap-northeast-1.amazonaws.com:8883
INFO[0000] Topic: $aws/things/something/shadow/update
INFO[0000] connecting...
INFO[0000] client connected

と出れば成功です。MQTTで接続できています。

Thing Shadowを更新

Thing Shadowを更新するには以下の様なJSONを送ります。

{
  "state": {
    "reported": {
      "from": "mqttcli"
    }
  }
}

mqttcliで送ってみましょう

echo '{"state": {"reported": {"from": "mqttcli"} } }"' | mqttcli pub -t "\$aws/things/something/shadow/update" --conf something.json -d -s

これで、AWS Consoleから見ると、stateが書き換わっているはずです。

このように、mqttcliとを使って、AWS IoTを触ることが出来ました。同様のことがmosquitto_subでもできることを確認しています。

さいごに

MQTTを直に叩くのではなく、AWS IoTのSDKを使いましょう。そうすればこんなふうにMQTTを意識する必要はありません。

AWS IoTおよびThing Shadowsに関する雑感

注意: この文章には思い込みが含まれています。

AWS IoTが発表されました。AWS IoTはManagedなMQTT Serverを用意し、運用が大変となるMQTT Serverを面倒見てくれる、というMQTTに対する評価がちらほら聞こえます。

しかし、違います。AWS IoTの本質は、Thing Shadowsの仕組みです。

とはいえ、まだちゃんと使ったわけではないですし、間違いがあったらご指摘をお願いします。

Thing Shadows

AWS IoTでは、Thingsというものが定義されています。このThingsには二種類が存在します。

Things
実際の物理的なモノ。デバイス
Thing Shadow
Thingsの状態をネットワーク(AWS)上に持たせたもの

Thingsはそのままですね。新しいのはThing Shadowです。これは、「物理的なデバイスを、仮想的にネットワーク上に写した(写像)もの」です。

ThingsとThing Shadowは1対1で結びついています。Thingsになにか変更があれば、Thing Shadowsにも変更が起こります。逆もまたしかり。Thing Shadowsに変更を起こした場合、それはThingsにも変更を起こすのです。

これはすなわち、

  • 物理空間
  • 仮想空間

とが一体化したことにほかなりません。

Thing Shadowの情報

Thing Shadowの実装は、単なるJSONです。:

{
    "state" : {
        "desired" : {
          "color" : "RED",
          "sequence" : [ "RED", "GREEN", "BLUE" ]
        },
        "reported" : {
          "color" : "GREEN"
        }
    },
    "metadata" : {
        "desired" : {
            "color" : {
                "timestamp" : 12345
            },
            "sequence" : {
                "timestamp" : 12345
            }
        },
        "reported" : {
            "color" : {
                "timestamp" : 12345
            }
        }
    },
    "version" : 10,
    "clientToken" : "UniqueClientToken",
    "timestamp": 123456789
}

ここ重要なのは stateとmetadataそれぞれが持つ、desiredreportedです。

1. Thingが更新された場合

物理的なものである、Thingが更新された場合、その情報はMQTTやHTTPSでThing Shadowに通知されます。

これに伴い、reportedの状態が更新されます。

2. Thing Shadowが更新された場合

Thing Shadowは、MQTTやHTTPによって、仮想空間上で更新できます。その場合、desiredの情報が更新されます。

この時、desiredreportedに差があった場合、 このThing ShadowをsubscribeしているThing(一つとは限らない)に対してメッセージが送られます。Thingはこれを受け取り、自分自身の情報を更新することができるのです。そして、更新出来たら、reportedとしてThing Shadowを更新します。


これらの動作により、ThingとThing Shadowsは同期が取れます。もしも、repoteddesiredが異なっている場合は同期がとれていない、ということになります。

さらに言うと、APIとして、 update/get/deleteが用意されており、それぞれに対してacceptedrejectedが用意されています。このため、Things Shadowsを更新しようとしたが、できなかった、ということも分かるようになっています。

MQTTとの違い

ここまで、ThingsとThing Shadowsについて説明してきました。ところで、AWS IoTでは、MQTTの以下の機能がありません。

  • Retain
  • Will
  • QoS2

なぜでしょうか。それはThing Shadowがあるからです。

  • RetainはShadowそのものです
  • Wllはそもそもオフラインという状態が存在しないので必要ありません
  • QoS2による、同期はShadowのdesired/reportedを使えば実現できます

AWS IoTはMQTTというメッセージプロトコルではなく 「状態」を扱うためのものである、という点を重要視するといいのではないでしょうか。

まとめ

AWS IoTを単なるManagedなMQTT Serverとだけ捕らえてしまうと、本質を見誤る気が致します。仮想空間と物理空間の融合、Internet of Things、とはなにか、というのをもう一度見つめなおしてみると面白いかもしれません。

また、今回はまだRuleに関して踏み込んでいません。Thing Shadows/Ruleを組み合わせることにより、人間が介在しない、Machine to Machine、 Things to Things の世界を作ることが出来るはずです。

という、あまりこのblogには書いていない、ちょっとエモーショナルな話でした。(実はこういう研究を10年以上前にやっていたのです。それがここまで広がったのか、とつい嬉しくなり、勢いに任せて書いた次第です)

WEB+DB PRESS vol88にMQTTの記事を書きました

2015年8月22日発売のWEB+DB PRESS vol88速習 MQTTというタイトルでMQTTに関する記事を書かせていただきました。

内容は以下の通りです。

  • MQTTとは
  • MQTTの利用場面
  • MQTTの特長
  • その他のプロトコルとの比較
  • MQTTを使ってみる

速習、というタイトルの通り、利用場面や特長の説明がメインの、MQTTという聞きなれないプロトコルの内容を一通り説明する内容になっています。一応MQTTを使ってみる、という箇所でMQTTを使うアプリをpythonで実装してみていますが、eclipse pahoのおかげもあり、非常に短い行で実装が完了しており、分量は正直あんまりです。

最近すっかりバズワード化してしまい、「MQTTはIoT時代の主役となるプロトコルだ」「MQTTで全部できる」というような言説をたまに目にするようになってきました。今回の内容は、MQTTの実情をなるべく正確に伝えることを目指しました。MQTTの使い道は限られたものであり、もちろん完全に適した利用形態もありますが、他のプロトコルを使う方がいい場合も多々あると考えています。その辺りを過不足なく伝えられたらと思います。

Sphinx InDesign Builder

さて、本題です。

今回、reStructuredText(rst)形式で執筆しました。しかし、rst形式ではいろいろそのあとの作業が発生することは分かっていたので、Sphinx Indesign BuilderというSpinx拡張を作成しました。

% pip install sphinxcontrib_indesignbuilder

でインストールした後、conf.pyにて

extensions = ['sphinxcontrib.indesignbuilder']

と書けば準備は完了です。

あとは、

% sphinx-build -b singlewebdb -d build/doctrees source build/singlewebdb

とすれば、build/singlewebdbにWEB+DB PRESSで使用するためのInDesignのXMLファイルが作成されます。singleと付いているのは一つのxmlファイルにまとめる場合で、一つにまとめないことも可能です。WEB+DB PRESSの編集さんはこのXMLをInDesignに流しこみ、図などのフロート要素を配置すればPDFが作成できる状態になります。md2inaoを使うのとほぼ変わらない手間となったとのことです。(ただし、md2inaoで実現できる機能のうち、現時点では今回の原稿に必要であった機能のみ実装している点に注意)

実際には、フロート要素の配置には人力が必要なため、rst形式ファイルからPDFまで一貫したフローにはなっていません。そのためにはInDesgin力がかなりいろいろ必要そうです。

なお、webdbと付いているように、今回作成したのはWEB+DB PRESS用のsphinx拡張です。他の本や雑誌ではスタイル命名規則が異なるため、そのままでは適用できません。しかし、今回作成した拡張を参考にすれば、他のスタイル命名規則に沿った別の拡張を実装するのはかなり簡単だと思います。もしかしたら、ある設定ファイルを読みこませればそれだけでそのスタイル命名規則に沿ったXMLを書き出すことが出来るかもしれません。そうなれば実装は必要無くなります。

reStructuredText(rst)形式は、そのままの形式でも可読性が高いという点も重要ですが、拡張ポイントが決まっていることによる高い拡張性が魅力です。例えば「この文章のこの部分は特別にこういう表現にしたい」という要望に対しても柔軟に応えられます。これにより、表現力は他の記述形式に比べて非常に高くなっています。

まとめ

  • WEB+DB PRESS vol88にMQTTの記事を書きました
    • MQTTのできること、できないことを一通り解説する内容です
  • Sphinx InDesign BuilderというSphinx拡張を実装しました
    • reStructuredText(rst)形式 で脱稿しました

WEB+DB PRESS vol88はモバイル開発やLINEの開発、Elixirについての記事も載っていますのでぜひ買ってください。

EdisonでgolangからBLEを扱う

Intel Edisonが人気です。特に、WiFiとBLEが両方とも使えるのは非常にIoT向きといえるでしょう。

Edisonには標準でNodeJSを使った開発環境が搭載されていますが、やはりここはgolangで扱いたいところです。

paypal/gatt

GolangでBLEを扱うには、github.com/paypal/gattが最適です。このライブラリはBLEのperipheralの動作などからすべてgolangで実装しており、pure golangで動作します。

以下にサンプルプログラムを記載します。この例では、 mainでgatt.NewDeviceでdeviceを作り、onStateChangedonPeriphDiscoveredという二つの関数をハンドラに登録するだけで、BLE機器の探索が行えます。

func onStateChanged(d gatt.Device, s gatt.State) {
     fmt.Println("State:", s)
     switch s {
     case gatt.StatePoweredOn:
             fmt.Println("scanning...")
             d.Scan([]gatt.UUID{}, false)
             return
     default:
             d.StopScanning()
     }
}
func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
     fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
     fmt.Println("  Local Name        =", a.LocalName)
     fmt.Println("  TX Power Level    =", a.TxPowerLevel)
     fmt.Println("  Manufacturer Data =", a.ManufacturerData)
     fmt.Println("  Service Data      =", a.ServiceData)
}
func main() {
     d, err := gatt.NewDevice(option.DefaultClientOptions...)
     if err != nil {
             log.Fatalf("Failed to open device, err: %s\n", err)
     }
     // Register handlers.
     d.Handle(gatt.PeripheralDiscovered(onPeriphDiscovered))
     d.Init(onStateChanged)
     select {}
}

paypal/gattを使うことで、golangから簡単にBLEを扱えるのですが、一つ困ったことがあります。

paypal/gattは、HCI USER CHANNELを前提としており、これはLinux 3.14以降でのみ搭載されています。つまり、Linux 3.10で動いているEdisonでは、paypal/gattは動作しない、ということになります。

noble

しかし、 NodeJSで動作するnobleは動くよな、と思い調べていくとnobleは以下の二つの小さなヘルパープロセスをインストール時にbuildし、実行時に使っていることが分かりました。

nobleをインストールすると、以下の二つのバイナリができあがっています。

  • node_modules/noble/build/Release/hci-ble
  • node_modules/noble/build/Release/l2cap-ble

その名前の通り、hci-bleがHCIを、l2cap-bleがL2CAPを扱っています。この二つはBluezのライブラリを直接linkしているため、kernel 3.10でも動作します。

noblechild

ここまで調べたところで、思いつきます。

「nobleのヘルパープロセスを使えばPure golangでもkernel 3.10でBLE扱えるんじゃね」

ということで作ったのがnoblechildです。

noblechildは、nobleのヘルパープロセスを起動し、nobleと同じようにヘルパープロセスを扱うことで、Pure golangからkernel 3.10でもBLEを扱えるようになります。

使い方

  1. nobleをインストールします。
  2. NOBLE_TOPDIR環境変数を、nobleをインストールしたディレクトリのトップに設定します。

以上です。あとは、ほぼpaypal/gattと同じインタフェースを提供していますので、そのまま使えます。

func main() {
     d, err := noblechild.NewDevice(DefaultClientOptions...)
     if err != nil {
             log.Fatalf("Failed to open device, err: %s\n", err)
     }
     d.Handle(noblechild.PeripheralDiscovered(onPeriphDiscovered))
     d.Init(onStateChanged)
}

まとめ

Intel EdisonでgolangからBLEを扱えるようにするnoblechildというライブラリを作成しました。

ぶっちゃけハックなので、筋は良くありません。Edisonが3.14に上がれば直接paypal/gattを使えるので、それまでの間のつなぎ、という位置づけと考えています。

golangでBLEを扱えると便利ですね。弊社も関連しているMQTT ゲートウェイのfujiにも搭載し、問題なく動作していますので、そのうち公開したいと思います。

お仕事募集中

ツキノワ株式会社では、BLEやMQTT、Golangに関するお仕事を募集しております。お気軽にお問い合わせください。

CのヘッダファイルからGoの構造体を生成する

Gocon 2015で発表しませんでした。

カテゴリー

r_rudi

CTO of Tsukinowa.inc

#python, #postgres, #sphinx, #golang, #freebsd