2015年4月13日

Gunosy.go#12に参加してきました


一昨日のFlashAirハッカソンに続き、今日は株式会社グノシーさんで行われたGo言語の勉強会であるGunosy.go#12に参加してきました。

今回のテーマは”GolangNotHttpNight”という事で、httpを使わないGo言語という切り口でさまざまな発表が行われました。
(追ってプレゼン資料とかも公開されるかと思うのでメモ書きで)

gomaについて

  • JavaのDomaにインスパイアされたDBアクセスフレームワーク
  • CRUDするコードを生成(dao/entity/sql)
  • SQLファイルはそのまま使える
  • DB側を正とするため、テーブル定義が変わっても再生性すればOK
  • 別のSQLライブラリと併用して使える
  • go-bindataを使ってバイナリにパッケージ化することも可能
  • xormを使ってテーブル構成情報を取得
  • テンプレートエンジンにはERB風のegoを使用

golintを使おう

  • 17個の関数でファイルごとにチェック
  • lint.goしきい値を設定

go-timeoutもしくはUnixツールをgolangで書く話

  • mackerelエージェントはGo言語製
  • 内部ではメトリクス投稿と収集それぞれのgoroutineを実行
  • チャンネルをキューのように管理
  • エージェントのプラグインはGolangじゃなくてもOK
  • GNU CoreutilsのtimeoutコマンドをGolangで
  • plan9はexit codeが数字じゃない
  • GolangでUnixっぽい事をやろうとすると大変

Golang+RasPiで趣味的IoT入門的な話

  • gobotembdの利用推奨
  • GOARCH=arm go buildしたものがRasPiで動く
  • gobotはMQTTも初期からサポート
  • embdはもう少し低レイヤ
  • 軽量などで個人工作での組み込みには使いやすい

Goでのステート管理の仕方

  • HTTPは基本stateless
  • cookieなどを使って間接的にステート管理
  • FSM(有限状態機械)
  • gotoでどんなFSMも書けるが見通しが悪い
  • tail call elimination(末尾再帰)
  • 関数と同じシンタックスでgotoを書ける
  • 最適化などの影響で実装系に依存
  • goroutineだとスタックを消費しない
  • 複数のFSMが同時に動く状態を自然に書ける
  • Golangでも将来的にはtail call eliminationが改善されるかも?

GoとTOML

  • XMLは人間が書くものじゃない
  • TOML = Advanced INI
  • 人間が読みやすく配列&階層構造が扱える
  • BurntSushi/toml
  • DecodeFile呼び出しで構造体に値がマッピングされる
  • 設定をTOMLにエクスポートすることも可能

Goでゲームを作る

  • cgoを使ってOpenGLを呼べる
  • GCあっても二次元のゲームくらいなら大丈夫
  • gxuiはシンプルなターンベースゲームならいける
  • Ebiten
  • SNESライクな2Dゲームフレームワーク
  • ENGi
  • もう一つの2Dゲームフレームワーク
  • Updateはどういうタイミングで呼ばれるのか分からない
  • 引数のdtを使って時差が分かる

DBじゃないoracleの話

  • Golangは公式や準公式の支援ツールがたくさんある
  • golang.org/x/tools/oracle
  • IDEのようなソースコード解析

GoConについて


この後の懇親会では、みなさんの会話に耳を傾けつつ、Go言語を会社で導入していくための四方山話や最近の開発動向など、刺激のある時間を過ごすことができました。
(+ピザごちそうさまでした!)

個人的にはgobotやembdのIoT回りが意外と充実しているのを知ることができたのが収穫でした。この前のFlashAirハッカソンと合わせて、開発にGo言語を使ってみるきっかけにしていきたいところです。

2015年4月12日

FlashAirハッカソンに参加してきました

昨日になりますが、川崎の東芝スマートコミュニティーセンターで開催されたFlashAirハッカソンに参加してきました。

FlashAirは、Eye-FiのようなSDカードに無線LAN機能が搭載された製品で、一般的にはEye-Fiと同様にカメラで撮影された画像がFlashAirに保存されたら、それを無線LAN経由でネットに送信する、といった事に使われます。

ただ、FlashAirが他の製品と違うところとして、無線LAN機能やSDIOといったハードウェアの機能が開発者にAPIとして公開されており、それらを使ってカメラ以外の用途にも使えるようになっている事です。

個人的にRaspberry Piに始まり、同じSDカードに無線LAN機能を持つFlucard Pro(発熱がすごい)や、SDカードをWiFiで共有(しかも内蔵電池あり)するPQI Air Driveなど、数千円で買えるネットワーク機器に目がなく、今回も3,000円でFlashAir+使い方の説明が聞ける(^o^)と知り、思い切って申し込んだ次第です。

午前中は「ユーザーズミーティング」という事で、東芝さんの技監(響きがかっこいい)の方のご挨拶から、共催のFIXSTARSさんによるFlashAir概要説明、余熱@れすぽんさんのFlashAirプロトタイピング環境のご紹介(MTO-EV101はお値段聞きそびれたorz)、各人に配布されたFlashAirを使ったワークショップが行われた後、同じく共催のMicrosoftさんとMakuakeさんの製品とサービスのご紹介がありました。

ランチ休憩のあと午後は、FlashAirを使ったアイデアを時間内に考える”アイデアソン”で、最初に運営のGUGENさんからオリエンテーリング(共創の原理や諦めた課題などすごく参考になりました)から始まり、自分のアイデアを形にしていくアイデアブラッシュアップ(自分のアイデアを3分間で説明→ブレインライティングシートにアイデアを書いて回覧&肉付け→そのシートをまた別の方に3分間プレゼン→それを経て一番いいアイデアをスケッチブックにまとめる)を経て、最後に皆さんのスケッチブックを見ていいと思ったものに投票を行う形で行われました。

その後投票結果の集計が行われ、上位数名の方のアイデアをピックアップし、残った方は各人のスキルが役立つアイデアにチームとして参加する形で、今度はチーム内でそのアイデアを来週のハッカソンでどう形にしていくかのミーティングが行われました。

そのミーティングが終わった後は、その結果を5分間でプレゼン&質疑応答を行うという流れを行ってその日は終了。午後の部はアイデアソン初体験ということもあり最後までたどたどしかったですが、新しいサービスの生み出し方や熱量はとても新鮮に感じることができました。

#ちなみに会場に折りたたみ傘忘れたのは私ですorz
#勉強会参加するといつも忘れるなぁ。

とりあえず頂いたFlashAirはまだ使えていませんが、今回の経験を刺激にして自分も暇があればなんか使えないかなーと考えてみたいと思います。
(FlashAir版IFTTTとかやりたい。カードが刺さる/抜ける/ネットワークに参加する/SDカードに書き込まれるなどを検知したら指定された動作やWebHookを呼び出すみたいな。まずはそういうイベントが拾えるか調べねば。。)

2015年4月4日

お名前.comのDNSレコードをRaspberry PiからCasperJSで自動更新する

去年までここのブログのドメイン管理はvalue-domainで行っていたのですが、つい最近同じGMOグループのお名前.comにドメイン移管しました。

ただ、value-domainではダイナミックDNS機能が標準で提供されており、自宅のIPアドレスをRaspberry Piからcurlコマンドで自動更新できていたのですが、お名前.comではその機能が提供されていないことに移管してから気づき、手動で更新する羽目に。。

よくよく調べると、WindowsであればDiCEなどで自動更新できるみたいですが、DNSレコード更新のためだけにWindows機を常時起動しておくわけにもいかず、できれば今まで通りRaspberry Piから自動更新できる方法を模索していました。
(DiCEはLinux版もあるみたいですが、Raspbery Piで動くものは無いようでした)

そこで今回は、ヘッドレスブラウザのPhantomJSを使った自動テストフレームワークであるCasperJSを使って、お名前.comの管理ページからDNSレコードを変更するまでの操作をスクリプト化し、無理やり自動更新するようにしてみました。

注意:本方法を使った場合、設定によっては先方のサーバに思わぬ負荷を与えてしまうことがあります。ご利用にあたっては自己責任でお願い致します。
  1. Raspberry Pi(raspbian)にPhantomJSをインストール
    apt-getではインストールできないため、こちらのビルド版を/usr/local/bin以下にコピーして利用
  2. 続けてCasperJSをインストール
    公式サイトにある1.1.0-beta3のzipファイルをダウンロードし、/usr/local/casperjsに展開後、PATH環境変数に/usr/local/casperjs/binを追加
  3. 以下スクリプトをonamaedns.jsの名前でコピー
    var casper = require('casper').create({
     pageSettings: {
      userAgent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
     }, 
     logLevel: 'info', 
     waitTimeout: 30000, 
     verbose: true
    });
    casper.on('remote.message', function(msg) {
     this.log(msg, 'info');
    });
    var new_ip = null;
    
    function exit_usage() {
     casper.echo('usage: casperjs --ssl-protocol=any --ignore-ssl-errors=true onamaedns.js [options]');
     casper.echo('options:');
     casper.echo('  --userid    onamae.com id');
     casper.echo('  --passwd    onamae.com password');
     casper.echo('  --domain    update domain');
     casper.echo('  --host      update hostname');
     casper.exit();
    }
    var options = casper.cli.options;
    if (!options['userid'] || !options['passwd'] || !options['domain'] || !options['host']) {
     exit_usage();
    }
    
    casper.start();
    
    casper.thenOpen('http://whatismyip.akamai.com/', function() {
     new_ip = this.fetchText('body');
     this.log('new_ip=' + new_ip, 'info');
    });
    
    casper.thenOpen('https://www.onamae.com/domain/navi/domain.html', function() {
     this.evaluate(function(options) {
      document.querySelector('input[name="username"]').value = options['userid'];
      document.querySelector('input[name="password"]').value = options['passwd'];
      document.querySelector('input[name="login"]').click();
     }, options);
    });
    
    casper.waitForSelector('a[href*="domain/navi/menu/domain"]', function() {
     this.click('a[href*="domain/navi/menu/domain"]');
    });
    
    casper.waitForSelector('a[href*="domain/navi/dns_manage"]', function() {
     this.click('a[href*="domain/navi/dns_manage"]');
    });
    
    casper.waitForSelector('input[type="radio"][value="' + options['domain'] + '"]', function() {
     this.click('input[type="radio"][value="' + options['domain'] + '"]');
    });
    
    casper.waitForSelector('a.btn07.idSubmitexternal > span', function() {
     this.click('a.btn07.idSubmitexternal > span');
    });
    
    casper.waitForSelector('a[title*="domain/navi/dns_controll/input"]', function() {
     this.click('a[title*="domain/navi/dns_controll/input"]');
    });
    
    casper.waitForSelector('#dns_controll_inputForm', function() {
     var ret = this.evaluate(function(new_ip, options) {
      var num = document.querySelector('input[value="' + options['host'] + '.' + options['domain'] + '"]').getAttribute('id').replace('hostNameUsed', '');
      var old_ip = document.querySelector('#hdd_add_recvalue_a_used' + num).value;
      if (new_ip == old_ip) {
       return false
      }
    
      document.querySelector('#hdd_add_recvalue_a_used4').value = new_ip;
      document.querySelector('#idSubmit').click();
      return true;
     }, new_ip, options);
     if (ret === false) {
      this.die('same ip adress');
     }
    });
    
    casper.then(function() {
     this.click('#idSubmit');
    });
    
    casper.waitForSelector('a[href*="submitDnsconfirmForm"] > span', function() {
     this.click('a[href*="submitDnsconfirmForm()"]');
    });
    
    casper.waitForUrl(/domain\/navi\/dns_controll\/result/, function() {
     this.exit();
    });
    
    casper.run();
    
  4. お名前.comの管理ページにログインして、本スクリプトで更新するDNSレコードを登録しておく
  5. スクリプトを実行してみて動作テスト(問題なければcronに登録)
    casperjs --ssl-protocol=any --ignore-ssl-errors=true onamaedns.js
ハマった所としては、
  • PhantomJSのバージョンが古いため、(お名前.comを含む)POODLE対策を行ったサーバにHTTPS接続できない
    →casperjs --ssl-protocol=any(またはtlsv1) --ignore-ssl-errors=trueをつける
  • たまにタイムアウトで失敗する
    →Raspberry Pi(特に初期のやつ)だと重い処理らしく、waitForSelector()で応答を待つようにした
なところで、一応1ヶ月前くらいから毎朝3時に無線LANルーターの再起動とともに実行するよう仕掛けていますが、無事に成功しているようです。
(その度にお名前.comから更新完了の通知メールが届くのが難点ですが。。)