バルス!さくらのクラウドに物理的に「バルス」を実装してみた

f:id:febc_yamamoto:20161210212946p:plain

本稿はさくらインターネット Advent Calendar 2016(その1)の19日目の記事です。
前日の記事は@BakeTanukiさんによるKUSANAGI + ウェブアクセラレータという組み合わせについてでした。

a-lab.biz

この記事の中で拙作「wp-sacloud-webaccel」についてもご紹介いただきました!ありがとうございました!!


さて、今回はさくらのIoT Platformを使ったネタを仕込んでみました。
成功例だけでなく、途中の失敗体験を含めて詰め込んでしまったために長い記事になってしまいました。すいません。

さくらのIoT Platformに取り組む/取り組まれている方の参考になれば幸いです。

言い訳:
筆者はWeb系エンジニアであり、ハードウェアについてはいわゆる「Lチカ」ができる程度です。また、IoTへの取り組みは今回初チャレンジでした。
このため、当記事で作成するものについて実用性や安全性など考慮出来ていませんので、当記事を参考する際はその辺りご注意ください。

はじめに:Terraformプラグイン開発中のお悩み

Advent Calendar1日目の前佛さんの記事「Terraform for さくらのクラウド入門チュートリアル」にてご紹介いただいた、 Terraform for さくらのクラウドというTerraformプラグインについてですが、プラグインを開発している時、実際にさくらのクラウド上にリソースを作成するテストも多いため、こんな悩みがあります。

さくらのクラウド上にゴミリソースが溜まっていく。。。」

実際にさくらのクラウド上にリソースを作成するようなテストを行う際、 テストの異常終了などでクリーンアップ処理が正常に行われず、ゴミが溜まっていくことがあります。

後でまとめて消しても良いのですが、対象リソースによっては作成数の上限があるため、 こまめに削除しないとテストの実行がままならないというものもあります。 例えばデーターベースアプライアンスは上限数が1のため、リソース作成済みだとテストで新たに作成しようとしてもエラーになってしまいます。

「手動でポチポチ削除するのめんどくさい、、、」

「めんどくさいなー」と思いつつ、さくらのクラウド上のコントロールパネルをポチポチ操作してリソースを削除しようとすると、

  • 電源を切ってからじゃないと削除できない
  • 他のリソースと接続している場合、切断してからじゃないと削除できない(スイッチとか)

といったことがあり、泣きながら個々のリソースの電源断/切断などを行なってから削除、という作業をしていました。

そこでふと思い浮かんだのが、、、

バルス」って叫んだら、さくらのクラウド上の全リソース消せないかな?

ということでした。(バルスとは:Wikipedia)

とりあえずやってみよう!ということで、「バルス on さくらのクラウド」を実装してみることにしました。

バルス」を実行するための構成要素は?

参考サイト:天空の城ラピュタ:Wikipedia

まずはどのような機能/要素が必要なのかの検討のため、「バルス」の動きを以下の図で表してみました。

f:id:febc_yamamoto:20161210144707p:plain

①飛行石に触れ②「バルス」と叫ぶことで飛行石が反応します。

バルス発動条件には諸説あるようですが、ここでは飛行石に接触した状態で「バルス」と言えば発動すると仮定しています。

続いて飛行石が③呪文が「バルス」かの判定を行います。

飛行石は他にもいくつかの呪文に反応するため、 飛行石自体が音声への反応/認識ができるようになっているものと思われます。

条件を満たすと、飛行石が④ラピュタの中枢にあるコンピューターへ破壊を指示し、 ⑤ラピュタの破壊処理が行われ、ラピュタが落ちることになります。

バルス」を現実世界に落とし込んでみる:その1(失敗編)

先ほど「バルス」の機能/要素を抽出したため、それを現実世界で実現可能な方法に落とし込んでみます。

後述しますが、この方法は結果的に失敗に終わりました。。。
このため、具体的なコードは最低限のもの以外省略しています。

f:id:febc_yamamoto:20161210144552p:plain

飛行石に触っているかの判定を実装する

バルス発動条件である飛行石に触れるについては①タクトスイッチを押すことで実装できそうです。

バルス」と言われたかの判定を実装する

飛行石はそれ自体が音声を認識しているようですね。
現実世界においては音声認識モジュールなどを利用すれば実装できそうですが、音声認識モジュールは高価な部品です。
私のようなハードウェアに疎いエンジニアにとって、高価なハードウェアは使いこなせなかった時の心理的ダメージやトラブル時の対応への不安があるので出来るだけ避けたいものです。

複雑な処理はさくらのIoT Platformの通信モジュールを使ってリモートで全部処理しちゃえ!

そこで、今回は「さくらのIoT Platform」の通信モジュールを利用し、 ハードウェアは最小限の動き(AD変換とデータ送信)のみ行うようにすることで安価な部品で事足りるようにしてみました。

f:id:febc_yamamoto:20161210144610p:plain

さくらのIoT Platformは、公式サイトにて以下のように説明されています。

「さくらのIoT Platform」は、モノとネットワークでデータを送受信するための通信環境、データの保存や処理に必要なシステムを一体で提供するIoTのプラットフォームです。
モノに組み込むための「さくらの通信モジュール」と当社のデータセンターを、安全性を確保するためのLTE閉域網で接続し、ストレージ、データベースなどのバックエンドシステム、外部のクラウドやアプリケーションサービスとAPI連携システムを一体型で統合的に提供します。

さくらの通信モジュールは複雑な設定など必要なく、専用のシールドなどを利用してArduinoに接続すればすぐ使えるようになっています。
Arduinoで利用できるライブラリも提供されているため、簡単なメソッド呼び出しで通信を実現することができそうです。

マイク入力 + AD変換 + データ送信

②「バルス」と叫ぶと、

  • (③の1)マイクで音声を入力、ArduinoでAD変換
  • (③の2)変換したデータを通信モジュールでそのまま送信

という処理を行うようにします。

さくらのIoT Platformは外部との連携用にWebhookが使える

また、さくらのIoT Platformでは通信モジュールからのデータ受信時に外部のシステムと連携するためのOutgoing Webhookが用意されています。
今回はこれを利用して、Arukas上にバルス判定サーバーを立てて、そこにWebhookで通知、各種処理を行うようにします。

f:id:febc_yamamoto:20161210174514p:plain

ArukasはDockerコンテナのホスティング環境です。まだβ期間ということで無料で使えます。

バルス判定サーバーでの処理

バルス判定サーバーでは以下の処理を行います。

waveファイルは非常に単純なファイルフォーマットのため、簡単なバイナリーの操作(ヘッダの設定)で作成することができます。

今回はWebhookの受信処理やバイナリー操作を行うため、実装にはGo言語を採用しました。

さくらのクラウド上のリソース全消し処理

バルス」判定がOKだった場合はさくらのクラウド上のリソース全消しを行います。
これはさくらのクラウドAPIAPIライブラリを利用すればすぐに実装できますね。
バルス判定サーバーをGo言語で書いたため、こちらもGo言語で実装することとしました。


ここまでの処理をまとめると以下のような感じになります。
f:id:febc_yamamoto:20161215224045p:plain

では後はこれらが本当に実現可能かプロトタイピングしながら確認してみましょう。

プロトタイピングしてみる!

これらの機能要素が本当に実現できるのか、小分けにして実装を試してみました。

マイクの入力を扱う

まずはマイクとArduinoでどこまで音声が拾えるのか試してみました。

マイクは1個240円で売っていたノーブランド品のMAX4466を調達しました。
(2016年12月現在はノーブランドのものは品切れのようですが、スイッチサイエンスさんでも同等品を扱っているようでした)

エレクトレットマイクアンプモジュール

エレクトレットマイクアンプモジュール

Arduinoは永久保証の付いているスイッチサイエンスさんの品を選びました。
また、安価なArduino UNO互換品もありましたので、万が一に備えて予備として購入しておきました。

【永久保証付き】Arduino Uno

【永久保証付き】Arduino Uno

こちらのサイトを参考に、マイク+Arduinoと手元のMacをUSB接続し、Processingでwaveファイルを作成してみました。
以下の流れで、マイクからの入力をシリアル接続(USB)経由で手元のMacに送ってwaveファイル作成を行なっています。

  • (1) 手元のMacにてProcessingを起動
  • (2) Arduinoにてマイク(MAX4466)からのアナログ入力を取得
  • (3) 取得した値はArduinoからシリアル出力
  • (4) Processingにてシリアル入力を読みwaveファイルを作成

f:id:febc_yamamoto:20161210144619p:plain:w400

結果、多少のノイズは乗っているものの、十分に声が判定できるwaveファイルが出来ました。
必要があればバルス判定サーバー上でwaveファイルに対して波形編集を行うことで音圧調整やノイズ除去などを行うようにします。

(なお、参考サイトのコードだとサンプリングレートが足りず、Arduinoレジスタ設定でanalogRead()の速度アップを行ったりしました。 )

Azure Cognitive Servicesで音声(wave)からテキストへ変換してみる

Azure Cognitive Servicesとはマイクロソフトが提供している、AIを利用して様々な「モノゴトを認識させる」ためのAPI群です。

f:id:febc_yamamoto:20161210170932p:plain

ここでは、先ほど作成した音声ファイルをAzure Cognitive ServicesのAPIを利用してテキストへ変換してみます。

REST形式でAPIを呼び出してみる

以下のドキュメントを参考に、REST形式でAPIを呼び出してみました。

Azure Cognitive Services(Bing speech API)ドキュメント

  • Azure側の準備

本稿では詳細な手順は省略しますが、APIを利用するには、AzureポータルにてCognitive Services APIsを有効化しておく必要があります。
こちらのサイトなどを参考に準備し、 APIの呼び出しに必要なキーを発行しておきましょう。

  • curlコマンドでAPIを呼び出し

APIの挙動を確かめるため、以下のようなシェルスクリプトcurlコマンドを利用してAPIを呼び出してみました。
対象の音声ファイルはカレントディレクトリにtest.wavという名前で配置しています。

#!/bin/sh

# 認証トークンの取得
curl -X POST -H "Ocp-Apim-Subscription-Key:[Azureポータルで取得したAPIキー]" -H "Content-Length:0" https://api.cognitive.microsoft.com/sts/v1.0/issueToken > test_token

# API呼び出し
VERSION=3.0
REQUEST_ID=`uuidgen`
APP_ID=D4D52672-91D7-4C74-8AD8-42B1D98141A5
FORMAT=json
LOCALE=ja-JP
INSTANCE_ID=`uuidgen`
DEVICE=test

curl -v -L -X POST -H "Authorization: Bearer `cat test_token`" \
     -H "Content-Type: audio/wav; samplerate=8192" \
     --data-binary @test.wav \
     -m 30 \
"https://speech.platform.bing.com/recognize?version=$VERSION&requestid=$REQUEST_ID&appID=$APP_ID&format=$FORMAT&locale=$LOCALE&device.os=$DEVICE&scenarios=ulm&instanceid=$INSTANCE_ID"

呼び出しが成功すると、以下のようなJSONが取得できます。

{
    "header": {
        "lexical": "バルス", 
        "name": "バルス", 
        "properties": {
            "HIGHCONF": "1", 
            "requestid": "xxxxxxxx-nnnn-xxxx-nnnn-xxxxxxxx10xx"
        }, 
        "scenario": "ulm", 
        "status": "success"
    }, 
    "results": [
        {
            "confidence": "0.8237563", 
            "lexical": "バルス", 
            "name": "バルス", 
            "properties": {
                "HIGHCONF": "1"
            }, 
            "scenario": "ulm"
        }
    ], 
    "version": "3.0"
}

無事認識されているようですね!
これと同等の処理をバルス判定サーバー上に実装すれば良さそうです。

さくらのIoT Platformとの連携用にGo言語でWebhook受信サーバーを立てる

続いて音声データをさくらのIoT Platformから受け取るために、Go言語でWebhook受信用サーバーを実装してみました。
さくらのIoT PlatformとのWebhookやりとり部分は今後も汎用的に使いそうなので「sakura-iot-go」という名前でライブラリとして切り出してみました。

このライブラリを利用すれば、以下のようなコードでさくらのIoT PlatformからのWebhook受信サーバーを実装できます。

package main

import (
    "fmt"
    sakura "github.com/yamamoto-febc/sakura-iot-go"
    "net/http"
)

func main() {

    // パス"/"で待ち受け
    http.Handle("/", &sakura.WebhookHandler{

        // IoT Platformの管理画面(Outgoing Webhook)で設定したSecret
        Secret: "[put your secret]",

        HandleFunc: func(p sakura.Payload) {
            // [ここにWebhook 受信時の処理を書く]
        },
    })
    // ポート番号8080で待ち受け
    http.ListenAndServe(":8080", nil)
}

これで無事にIoT PlatformからのWebhookを受信できました。
バルス判定サーバーはこのライブラリを利用して実装するようにします。

Arukasで動かすためにDockerイメージ化

Arukas上で動かすためには、DockerイメージをDockerHubで公開しておく必要があります。
そこで、Go言語で実装したバルス判定サーバーを実行するDockerイメージを作成し公開することにします。

Dockerfileの作成

Dockerfileは以下のようになります。
Go言語でアプリケーションを実装すると、実行ファイル1つにパッケージングすることができるので、Dockerイメージ化は非常に楽です。
実行ファイルをADDしてENTRYPOINTに指定するだけ済んじゃいます。非常にシンプルですね。

FROM alpine:3.4
LABEL maintainer="Kazumichi Yamamoto <yamamoto.febc@gmail.com>"
MAINTAINER Kazumichi Yamamoto <yamamoto.febc@gmail.com>

RUN set -x && apk add --no-cache --update ca-certificates

ADD 作成した実行ファイル /bin/

ENTRYPOINT ["/bin/作成した実行ファイル"]

いよいよ通信モジュールを繋いでみる

f:id:febc_yamamoto:20161210144838p:plain

次に実際に通信モジュールを繋いでさくらのIoT Platform経由で音声データを送ってみます。

音声データのサイズはどれくらい?

今回は1秒間の音声データを取得することにします。
音声認識をするためには、サンプリングレート8000程度は欲しいところです。

1回のサンプリング(Arduino上でanalogRead()を実行する)で1byte(0〜255)の値を取得するようにすると、
1秒 × 8000回 × 1byte = およそ8Kbyte となります。

バッファが足りない、、、!?

通信モジュールが1回で送信できるデータには限りがあります。 (通信モジュールβ版ではバッファ/データキューは32個とのことでした。参考:データシート)
このため、溢れたデータはArduino上のメモリなどのバッファに置いておかないと消失してしまいます。

そして今回利用しているArduino(UNO)には利用可能なメモリとして2KbyteのSRAM、1KbyteのEEPROMしかありません。
このままではおよそ8Kbyteある音声データが溢れて消えてしまいます。

このため、バッファリング用のSRAMモジュール(32Kbyte)を個別に追加して対応しています。

F-RAMモジュール FM25W256

F-RAMモジュール FM25W256

音声データを送信してみる

タクトスイッチを押している間、マイクからの入力を取り込むことにします。
入力データは一旦バッファに置いて、順次通信モジュールで送信してみました。

ここで問題が!!!

ここまでは順調だったのですが、とうとう問題が発生してしまいました。その問題とは、、、

  • 通信モジュールからCMD_ERROR_BUSYというエラーが返ってくる
  • 1秒分のデータを送信完了するまで40秒以上かかる

という2つです。40秒では支度できないということですね。困りました。。。

何が悪いのか?

Arduino側で、以下のようなコードでデータ送信を行なっていました。

void flushSamples(){

  // Tx Queue
  uint8_t ret;
  uint8_t avail;
  uint8_t queued;
  uint8_t buf[8] = {0};

  // SRAMに置いたデータを順次処理する  
  for(int i = 0;i < MAX_DATA_LEN;i++){

    // バッファ(8byte)にデータが溜まったら通信モジュールのキューに入れる
    if (i > 0 && i % 8 == 0){
      ret = sakuraio.enqueueTx(DATA_CHANNEL, buf);
      // キューに入れたらバッファをクリア
      memset(buf , 0 , sizeof(buf));
    }

    buf[i%8] = (uint8_t)data.read(i);

    // 通信モジュールのキューの現在サイズを取得    
    sakuraio.getTxQueueLength(&avail, &queued);
    if(queued >= 30){  // 30個キューに溜まったら送信実施
      ret = sakuraio.send(); // [ここでCMD_ERROR_BUSYというエラーが頻発]
      while(ret != 1){       // send()が成功するまでインターバルを入れながらループする
        delay(500);
        digitalWrite(STATUS_LED_PIN, HIGH);
        delay(500);
        digitalWrite(STATUS_LED_PIN, LOW);
        ret = sakuraio.send();
        Serial.print(".");
        if (ret == 1){
          Serial.println("Done.");
        }
      }
    }
  }  
  
  // コントロール用のチャンネルに終了コードを入れておく
  sakuraio.enqueueTx(CONTROL_CHANNEL, END_CODE);

  sakuraio.send(); // キューに残っているデータを送信
}

とにかく短時間で大量のデータを送る必要があるのがネックなようです。
CMD_ERROR_BUSYとなったらインターバルを入れているため、さらに時間がかかることになります。。。

データ量を減らせばなんとかなるか?

データ量を減らしつつ、「バルス」を送信できないか、、、

と悩んでいると、ふと違うやり方を思いつきました。

そうだ!モールス信号があるじゃないか!

音声入力の代わりにモールス信号で「バルス」という文字を入力してもらうようにすればデータ量を減らせそうです。

しかもモールス信号の復号程度ならArduinoだけで処理ができるため、飛行石自体が「バルス」という言われたか認識するという元の処理に近い形が取れそうです。

ということで設計から見直して再チャレンジしてみました。

バルス」を現実世界に落とし込んでみる:その2(成功編)

f:id:febc_yamamoto:20161210144601p:plain

シンプル!非常に単純になりました。

Arduino①モールス信号入力を処理します。信号の入力にはタクトスイッチを利用することにしました。

入力されたモールス信号が「バルス」だった場合は通信モジュールで②データ送信を行います。

サーバー側はデータが到着したらリソース全消し処理を呼び出すだけです。

いけそう!実装してみる!

ということで、以下のような回路にしてみました。

Arduino + LEDが数個 + 入力用のタクトスイッチという非常にシンプルな構成です。

f:id:febc_yamamoto:20161210144546p:plain

入力は「バルス」の英語表現である「balus」をモールス信号にて行います。

注:英語表記はbalus以外にも諸説あるようです。

モールス信号で1文字正しく入力するごとにLEDを点灯させるようにしました。

こんな感じに仕上がりました。

上から見た写真:
f:id:febc_yamamoto:20161210144817p:plain

Arduinoのスケッチはこんな感じです。(抜粋)

#include <SakuraIO.h>
#include "constants.h"
#include "morse_code.h"

// モールス信号で入力するキーワード[ l = long , s = short]
String magicalSpell = "balus"; // [ lsss , sl , slss , ssl , sss]
const byte spellLen = 5;

// モールス信号入力用バッファ
String inputBuf[spellLen];
// バッファの現在位置
byte bufIndex = 0;

// タイマー(スイッチON開始時刻、スイッチOFF開始時刻、最終操作時刻)
unsigned long startTime , endTime , silentTime;

// モールス信号での文字入力ごとに点灯させるLEDの配列
int LEDs[] = {LED1,LED2,LED3,LED4,LED5};

// 文字区切り連続入力防止用
bool isSpacePushed = false;

// バルス処理中フラグ
bool isSpellCasting = false;

// さくらの通信モジュール(I2C)
SakuraIO_I2C sakuraio;

// -------------------------------------------------------------------

void setup(){

  Serial.begin(9600);

  // Arduino各ピンの入出力モード設定
  setupPinMode();

  // さくらの通信モジュールがオンラインなるまで待つ
  connecToSakuraIoT();

  // 初期化完了をシリアル出力でお知らせ
  printInitMessage(magicalSpell);

  // 初期化完了をLEDでお知らせ
  blinkLED(BLINK_INIT);

}

void loop(){

  // バルス処理呼び出し中か?
  if (isSpellCasting){
    checkSakuraResponse();
    return;
  }

  // 無操作になって一定時間経過したら文字の区切りとする
  if (isMorseSilent()){
    Serial.println("space");
    pushMorse(CODE_SILENT);
    isSpacePushed = true;
  }

  if (digitalRead(SW) == SW_ON){     // タクトスイッチが押されている
    digitalWrite(LED_STATUS , HIGH); 
    if (startTime == 0) {
      startTime = millis(); // スイッチONにした時刻
      silentTime = 0;
      isSpacePushed = false;
    }
  }else{                            // タクトスイッチを離した
    digitalWrite(LED_STATUS , LOW);
    if (startTime > 0 && endTime == 0) {
      endTime = millis(); // スイッチをOFFにした時刻
      isSpacePushed = false;
    }
  }

  // タクトスイッチのON/OFF両方の時刻から信号の長短(ツー/トン)を判定
  if (startTime > 0 && endTime > 0){
    if ( isMorseShort() ){      // short(トン)か?

      // 入力バッファへプッシュ
      Serial.println("short");
      pushMorse(CODE_SHORT);
      
    }else if ( isMorseLong() ){ // long(ツー)か?

      // 入力バッファへプッシュ
      Serial.println("long");
      pushMorse(CODE_LONG);
    }

    //判定したら時刻をクリア
    resetStartTime(); 

    // 無操作時間の開始時刻を保持しておく
    silentTime = millis();
  }

  // 無操作時間が閾値(RESET_DUR)を超えたら、それまでの入力をリセット
  if (silentTime > 0){
    if ( millis() - silentTime > RESET_DUR) {
      resetAll();
      blinkLED(BLINK_TIMER_RESET);
    }
  }


}

// 入力バッファにモールス信号を登録
void pushMorse(String code){
  
  if (code.equals(CODE_SHORT)){
    inputBuf[bufIndex].concat(CODE_SHORT);
  }else if (code.equals(CODE_LONG)){
    inputBuf[bufIndex].concat(CODE_LONG);    
  }else if (code.equals(CODE_SILENT)){

    // 単語区切りを受信した。キーワードの文字数までは1文字づつ正答確認を行う。    
    if (bufIndex < magicalSpell.length()-1){
      // 入力されたモールス信号(1文字分)をシリアル出力
      printInputedMorseCode(bufIndex, inputBuf[bufIndex]);
      
      if (isInputMorseOK(bufIndex)) {   // 正しい(キーワードと合致した)モールス信号が入力されたか?
        digitalWrite(LEDs[bufIndex] , HIGH);
        bufIndex++;
      }else{
        // 間違ったコードが入力されたため、メッセージを表示して入力リセット
        printWrongInputMessage();
        
        resetAll();
        blinkLED(BLINK_NOT_CASTED);
        return;
      }
      
    }else{

      // === 入力信号が「バルス」だった場合の処理 ===

      // 入力OKメッセージをシリアル出力
      printOKInputMessage(magicalSpell);
      
      blinkLED(BLINK_CASTED);
    
      sendToSakuraIoT();
    }
  }
}

// さくらのIoT Platformへ指示(処理開始コード)を送信する
void sendToSakuraIoT(){
  sakuraio.enqueueTx((uint8_t)SAKURA_IOT_CHANNEL, (uint32_t)SAKURA_IOT_START_CODE);
  uint8_t ret = sakuraio.send();
  if (ret == CMD_ERROR_NONE) {
    // エラーがなかったら呼び出し中フラグを立てる
    isSpellCasting = true;
  }
}

// さくらのIoT Platformからの応答をチェック
void checkSakuraResponse(){
  uint8_t avail , queued;
  // 受信キューの状態を問い合わせ(現在利用可能なキュー長、キューの中のデータ数)
  sakuraio.getRxQueueLength(&avail, &queued);

  // キューにデータがあれば
  if (queued > 0) {
    uint8_t channel;    // チャンネル
    uint8_t type;       // データのタイプ[i,I,l,L,f,d,b]
    uint8_t values[8];  // データ
    int64_t offset;     // オフセット
    
    uint8_t ret = ret = sakuraio.dequeueRx(&channel, &type, values, &offset);
    if (ret == CMD_ERROR_NONE) {
      // エラーがなかったらデータの確認
      if (channel == SAKURA_IOT_CHANNEL){
        if (values[0] == SAKURA_IOT_END_CODE){

          // 正常終了コードを受信
          blinkLED(BLINK_NORMAL_END);
          isSpellCasting = false;
          return;
          
        }else if (values[0] == SAKURA_IOT_ERROR_CODE){
          // エラーコードを受信
          blinkLED(BLINK_ERROR_END);
          isSpellCasting = false;
          return;          
        }

        // 未知の値は無視
      }
      // 未知のチャンネルでの受信は無視
    }
  }
  blinkLED(BLINK_WAITING_RESPONSE);
}

サーバー側実装

後はサーバー側の実装ですが、こちらはさくらのクラウドAPIを順次呼び出して削除処理を行うだけです。
せっかくなので先ほどの「sakura-iot-go」を利用するために、Go言語で実装してみました。

sacloud-delete-all

なお、この処理は汎用的に使えそうなのでGitHubで独立したリポジトリとして公開しています。

実行!

では順番に実行していきます。

ArukasやさくらのIoT Platformの設定を適切に行なった上で、モールス信号で「balus」と入力してみましょう!

やった!無事動きました!
さくらのクラウドのコントロールパネルを見ると、無事リソースが削除されていることも確認できました!

9Vの電池をつなげて動かすこともできますので、外出先でも「バルス」できますね!

まとめ

ということで、さくらのIoT Platformを使って悪ふざけをしてみましたw

今回の処理内容について

今回の処理の流れは

  • 信号の入力があったら
  • さくらのIoT Platformを通じて通信し
  • サーバー上で削除処理を実行

というものですが、ちょっとしたアイディアで色々な応用ができそうですね。

例えばサーバー上で商品の注文処理を実装すれば Amazon Dashボタンのような動きをさせることもできます。
夢が広がりますね!

失敗/設計変更について

最初の設計では、通信時のBUSYエラーや通信時間が結構かかるという辺りで躓いてしまいました。
大量のデータを短期間で送るということがそもそも通信モジュールの特性とマッチしてないように感じました。

結局、何がおきているのか/何が原因でダメなのかがわからず、設計変更で逃げるような対応となりました。
ハードウェアの絡む問題が発生した際はやはり詳しい専門家のアドバイスが欲しいなーと思いました。 (今回みたいにそもそも設計がダメとかあるだろうし)

とはいえ、さくらのIoT Platformならお試しするための敷居が低いため、まずはプロトタイプで検証という動き方をするのがいいのかなとも思いました。

感想

今回IoT初挑戦だったのですが、めんどくさそうな通信や連携の部分が思ったよりすんなりと書けたことが驚きでした。

電子工作=Lチカというレベルでも、さくらのIoT Platformを使うことで簡単に通信/外部との連携処理まで実装でき、しかもセキュリティーやその後のスケールアップはプラットフォーム側に任せてしまって関心のあるサービスの実装に注力できるというのはいいなーと思いました。

おまけ:今回の副産物

今回のものは悪ふざけで作成したものですが、そんな中でも副産物として実用できるライブラリが数個収穫できましたので以下にまとめておきます。

ついでに今回使ったコードについても全部(ダメだった方も)公開しています。

失敗した方がどんなソースだったのか気になる方は参照してみてください。

ライブラリ

今回のコード(失敗バージョン)

今回のコード(成功バージョン)

以上です。ここまでお読みいただきありがとうございました。

最近の活動について

気づいたら4年ぶりのブログ投稿です。

この間活動していなかったわけではないのですが、、

  • 業務にて古い技術を採用せざるをえない事情があり、積極的に技術関連のアウトプットをするモチベーションが低下してた
  • Qiitaに投稿するようになった

などにより、こちらには投稿できてませんでした。 最近はまた活動再開してきてますのでぼちぼち投稿していきたいです。

最近作ったもの紹介

Docker Machine さくらのクラウド

DockerMachineさくらのクラウド用ドライバを作りました。

Terraform for さくらのクラウド

Terraformさくらのクラウド用プロバイダを作りました。

紹介記事では概要や実際の設定メモ程度しか書いてないので ぼちぼちちゃんとしたスタートガイドを書く予定です。

libsacloud

さくらのクラウドAPIのGo言語用ライブラリを作りました。 saklientを参考にしていますが、フルスクラッチで開発してます。

上記のTerraform for さくらのクラウドでも利用しています。

最近の活動その他

他にもAutoScalingのサンプル記事書いたり、 カンファレンス/勉強会に参加したりしてます。

直近だとPHPカンファレンス福岡に参加しました。

今後の活動

未定ですが、もう少しTerraform for さくらのクラウドの ドキュメントを整備しておきたいですね。

本日は以上です。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

DevOpsを支えるHashiCorpツール大全 ThinkIT Books

DevOpsを支えるHashiCorpツール大全 ThinkIT Books


JavaScriptMVCとCanJS、jQuery++、そしてDoneJSへ

何気なくTwitterでJavaScriptMVCについてつぶやいていたら、 JavaScriptMVC作者さんであるJustin Meyerさんから メンションいただきました。

私が集めてた情報であまり間違っていないようですのでまとめてみました。

 

CanJSとは

  • CanJSはJavaScriptMVCからMVC関連部分のみを切り出したもの

 

jQuery++とは

  • jQuery++はJavaScriptMVCのサブプロジェクトjQueryMXからDOM HelperとEventsを切り出したもの

 

のようです。

 

以下CanJSのドキュメントでのサンプルコードですが、JavaScriptMVCとほぼ同じですね。

var Todo = can.Model({ findAll : 'GET /todos', findOne : 'GET /todos/{id}', create : 'POST /todos', update : 'PUT /todos/{id}', destroy : 'DELETE /todos/{id}' }, {}) 

もちろん細かな所では変わっています。

今後のバージョンアップ(DoneJS)について

今後のことがCanJSのブログに 方針含めて記載がありました。 (ちなみに、現在のJavaScriptMVCのバージョンは3.2.2です)

  • JavaScriptMVC3.3(次期リリース)

    コアとしてCanJSを含むことになるが、移行の為にAPIに互換性を持たせる

  • JavaScriptMVC4.0(次期メジャーリリース、「DoneJS」に名称変更される)

    コアのAPIについてもCanJSにフィットするように変更される

本家フォーラムでCanJSやjQuery++関連が混在しているのは当然だったんですね。(フォーラム眺めてると、「JavaScriptMVC関連なんだけど、ここでいいのか?」なんて投稿もありました。)

私は現在JavaScriptMVCのドキュメント翻訳を進めてるんですが(一緒にやってくれる人大歓迎ですよ〜)、

とりあえずこのまま進めても無駄になるってことはなさそうですね。 一安心です。

 

追記:CanJSはroutingも含んでいるようですね。

http://forum.javascriptmvc.com/topic/canjs-and-bitovi 

 

それでは本日は以上です。

JavaScriptMVCでモデルのCRUD操作をPOSTで行う方法

今日もJavaScriptMVCの小ネタです。

 

<今日のお題>

1:モデルの基本的なCRUD操作方法

2:POSTでリクエストする方法

 

  では早速本題に入ります。

 

1:モデルの基本的なCRUD操作

 まずはジェネレータで作ったモデルを見てみましょう。

 

ジェネレータで作ったモデルには既にCRUD操作用のメソッドが用意されています。

本家ドキュメントを見ると、それぞれのメソッドは以下のHTTPメソッドが使われます。

findAll  : GET

findOne : GET

create  : POST

update : PUT

destroy : DELETE

 

GETはなんか嫌だとか、PUTとかDELETEとか知らないよ、とか

様々な事情でPOSTでリクエストしたいことがあるかと思います。

そこで次のお題。

 

2:POSTでリクエストする方法

まずは更新系をPOSTにする場合。

この場合は簡単に対応できます。

updateとdestroyに指定されているURLにちょっと細工すればOK。

 

 

これで既存資産がPUT/DELETEに対応してなくても大丈夫ですね。

 

 

 

後は、JavaScriptMVCを使うアプリはそれなりの規模のアプリと思いますが、

規模が大きくなるにつれて、全リクエストで共通のパラメータを送ったり、

共通のエラー処理を行う必要が出てくることもあろうかと思います。

 

こんな場合はそれぞれのCRUDメソッドをオーバーライドしたり、

独自でリクエストを送信するメソッドを作ったりします。

この方法であれば取得系もPOSTを使うことが可能です。

この辺は改めて記事にします。ではまた次回。

javascriptMVC入門 ちょっと脱線

早めにJavaScriptMVCの記事まとめたいんですが、

時間作るのって覚悟とやる気が必要だと痛感してます、、、

 

さて、今日はちょっと脱線して、JavaScriptMVCのチュートリアル

サンプルを読み進める上でのポイントを解説しておきます。

 

JavaScriptMVCの中でも特徴があるのがコントローラの部分です。

コントローラのポイントとして、

  1:コントローラのdefaultsとoptionsプロパティ

  2:コントローラでのイベントのバインド方法

  3:追加されるjQueryヘルパーメソッド

くらいを押さえておけば処理の流れは追えます。

※ちなみにJavaScriptMVCでのjQuery拡張であるjQueryMXを使う前提です。

JavaScriptMVC自体はjQueryとは独立で利用可能です。

 

では順番に説明します。

 

1:コントローラのdefaultsとoptionsプロパティ

本家ドキュメントの「Using Controller」の部分にあるサンプルで解説します。

ドキュメントでは以下のソースが掲載されています。 

 

順に見ていきましょう。$.Controllerというのがコントローラのクラス定義です。

3つの引数を渡していますね。

$.Controller("クラス名",{/*②Static*/} ,{/*③ProtoType*/})

 ①はクラス名となります。サンプルでは"MyWidget"ですね。

後で触れるjQueryヘルパーメソッドに関わる部分です。

  

 ②はStaticメンバーの定義となります。defaultsプロパティが定義されていますね。

 ③はProtoTypeメンバーです。initメソッドと、何やら見慣れぬ

"div click" : function(div, ev){~}

というメソッド定義らしきものがありますね。

これは2:でイベントバインドの説明する時に詳しく見ていきます。

 

initメソッドは定義しておけばインスタンス生成時にコールされます。

※実はsetupという似たようなメソッドもあるんですが割愛。ドキュメント読んでね。

 

これでコントローラのクラス定義完了です。

インスタンス生成時は new MyWidget(関連づけるエレメント)みたいに

HTMLエレメントを引数に指定します。
※のちほど3:でインスタンス生成を楽に書ける方法が出てきます。

 

<ポイント>

defaultsプロパティに定義されている値は、コントローラのインスタンス生成時にoptionsプロパティに初期値として設定されます。

ただし、インスタンス生成時にパラメータを指定した場合はそちらが優先されます。

また、インスタンス生成時に指定されたパラメータはdefaultsに定義されていなくてもoptionsに保持されます。

詳しくは下記のコードを見てください。

 

 

 

 

 

 2:コントローラでのイベントバインド方法

JavaScriptMVCのコントローラは特定のHTMLエレメントと関連付けられます。

コントローラは関連付けられたHTMLエレメント(配下のエレメント含む)の

イベントを管理する役割を持っています。

 

イベントの登録はjQuery流に$('div').on('click' , function(){ /* event action*/} )などとしてもよいのですが、JavaScriptMVCでは宣言的にイベント登録を行うことが出来ます。

1:で出てきた以下のコードを再度見てみましょう。

"div click" : function(div, ev){~}
これは、
  1)コントローラに関連付けられたHTMLエレメント配下のdic要素に対し、
  2)clickイベントを登録
をしています。書式は以下の通りです。
"[セレクタ] [イベント名]" : function(div, ev){~}
[セレクタ]部分にはjQueryで利用しているセレクタを記載できます。
下記の例ではclass="title"エレメントのtapholdイベントが登録されます。
".title taphold" : function(elm, ev){~}
ちなみにイベントの第1引数にはイベント発生もとHTMLエレメントをラップしたjQueryオブジェクトが渡されます。
 
さらにここからがJavaScriptMVCらしいところ。
[セレクタ]、[イベント名]の部分をパラメータを使って動的に置き換えることが可能です。
例えば以下のように定義されているとします。
"{targetClassName} taphold" : function(elm, ev){~} 
{targetClassName}の部分は1:で説明したoptionsプロパティの内容で置き換えられます。
例えばoptions.targetClassNameに'li'が設定されていればliエレメントに対してtapholdイベントが登録されます。
defaultsと組み合わせれば再利用しやすい仕組みになりそうですね!!
 
詳細は本家ドキュメントのRapid Startが詳しいです。
英語読めない方でもソース例が豊富ですので雰囲気はつかめるかと思います。
 
 
 
3:追加されるjQueryヘルパーメソッド
コントローラのクラス定義を行うとjQueryヘルパーメソッドが追加されます。
ヘルパーメソッドはHTMLエレメントとコントローラの関連づけを楽に行えるようにしてくれます。
ヘルパーメソッドはコントローラのクラス名をもとに追加されます。
基本的にはクラス名を小文字+アンダーライン区切りにしたものです。
1:の場合はmy_widgetという名前になります。
 
$.fnに対して追加されるため、
$(".selector").my_widget();
jQueryオブジェクトに対して利用できるようになっています。
上記の書き方は
new MyWidget( $(".selector"));
と書くのと同じです。
 
※コントローラのクラス名に名前空間を指定した場合は若干独特なルールが適用されます。
例えば、コントローラのクラス名が'SimpleMemo.Controllers.MemoWidget'
の場合は"simple_memo_memo_widget"という名前になります。
この辺は改めてまとめます。
 
以上です。
少しでも本家ドキュメントを読む助けになればうれしいです。

javascriptMVC入門 その2−1

前回に引き続きjavascriptMVC入門です。

 

2:モデルの作成

MVCM、モデルを作成します。

javascriptMVCの場合、Railsなんかと同じくCRUD操作も担当しています。

 

今回はメモを表すモデルを作成します。

モデル作成の際はmodelジェネレータを使用します。

 

 

引用:本家ドキュメント

http://javascriptmvc.com/docs.html#!steal.generate

model

Creates a jQuery.Model and test files.

js jquery/generate/model App.Models.Name [TYPE] [OPTIONS]

  • App.Models.Name - The namespaced name of your model. For example, if your model is named Cookbook.Models.Recipe, the generator will createcookbook/models/recipe.js.

 

モデル名は以下で指定するとのことです。

[ルート名前空間].Models.[モデル名]

 

モデル名は「Memo」にします。

前回の通り名前空間は「SimpleMemo」となりますので、実行するのは以下のコマンドとなります。

 

$./js jquery/generate/model SimpleMemo.Models.Memo

 

実行すると、

$./js jquery/generate/model SimpleMemo.Models.Memo

      simple_memo/models/memo.js

      simple_memo/test/qunit/memo_test.js

      simple_memo/models/models.js (steal added)

      simple_memo/test/qunit/qunit.js (steal added)

      simple_memo/fixtures/fixtures.js (code added)

こんな結果が出力されます。

 

モデルオブジェクトに加えて、テスト用コードや、モデルをロードするためのコードが追記されます。

 

早速確認してみましょう。

http://localhost/jmvc/simple_memo/qunit.html

 

おなじみ?のqunitの画面が表示されますね。

f:id:febc_yamamoto:20120527112931j:plain

 

では気になるモデルの中身を確認してみましょう。

 

simple_memo/models/memo.js

steal('jquery/model', function(){

 

$.Model('SimpleMemo.Models.Memo',

/* @Static */

{

  findAll   : "/memos.json",

  findOne : "/memos/{id}.json"

  create   : "/memos.json",

  update  : "/memos/{id}.json",

  destroy : "/memos/{id}.json"

},

/* @Prototype */

{});

 

 

なんだかクラス定義っぽいのが並んでますね。

順番に解説していきます。

 

まず一番外側のsteal関数ですが、前回の説明の通りファイルの読み込みと

渡された関数の実行を行います。

'jquery/model'という指定がファイルパスとなります。

このファイル読み込み後に、もう一つの引数である関数が実行されます。

 

<補足>

前回は説明を省略しましたが、このsteal関数は'jquery/model'みたいな指定をすると、

自動的にファイルパスへと変換してくれます。

この辺のルール/お作法は改めて説明します。

 

 

では関数の中身をみてみましょう。

以下の関数を実行しています。

$.Model('モデル名(名前空間含む)' , staticなメンバー, インスタンスのメンバー);

 

これはJavaScriptMVCで提供されているクラス定義用の関数です。

実際には$.Model$.Classを継承しています。

 

JavaScriptMVCのクラス定義の特徴としては

 ・Staticメンバーの継承

 ・イントロスペクションのサポート

 ・名前空間への対応

 ・コンストラクタ用関数の提供

 ・コールバック関数の簡易な作成

などがあります。

 

詳細は本家ドキュメントを参照してください。

http://javascriptmvc.com/docs.html#!jQuery.Class

http://javascriptmvc.com/docs.html#!jQuery.Model

 

$.Model関数の第2、3引数にオブジェクトを渡すことで

クラス定義を行ってくれています。

 

staticメンバーにfindAllやらfindOneやらが定義されていますが、

これは後回しにして、とりあえずメンバーの追加をしてみます。

 

追加するのはメモ本文を表すプロパティ「memoText」関数です。

引数なしで実行するとメモ本文を取得し、

引数に文字列を指定すると、メモ本文が設定されます。

 

まずはテストを追加しましょう。

modelジェネレータで作成された「test/qunit/memo_test.js」を開きます。

 

simple_memo/test/qunit/memo_test.js

 

steal("funcunit/qunit", "simple_memo/fixtures", "simple_memo/models/memo.js", function(){

module("Model: SimpleMemo.Models.Memo")

 

test("findAll", function(){

    expect(4);

    stop();

    SimpleMemo.Models.Memo.findAll({}, function(memos){

        ok(memos)

        ok(memos.length)

        ok(memos[0].name)

        ok(memos[0].description)

        start();

    });

})

 

test("create", function(){

    expect(3)

    stop();

    new SimpleMemo.Models.Memo({name: "dry cleaning", description: "take to street corner"}).save(function(memo){

        ok(memo);

        ok(memo.id);

        equals(memo.name,"dry cleaning")

        memo.destroy()

        start();

    })

})

 

test("update" , function(){

    expect(2);

    stop();

    new SimpleMemo.Models.Memo({name: "cook dinner", description: "chicken"}).

        save(function(memo){

            equals(memo.description,"chicken");

                memo.update({description: "steak"},function(memo){

                    equals(memo.description,"steak");

                    memo.destroy();

                    start();

                })

            })

 

    });

 

test("destroy", function(){

    expect(1);

    stop();

    new SimpleMemo.Models.Memo({name: "mow grass", description: "use riding mower"}).

        destroy(function(memo){

            ok( true ,"Destroy called" )

            start();

        })

})

})

 

 

すでに単体テストが記載されています。

ここにテストケースを一つ追加します。

 

simple_memo/test/qunit/memo_test.js

 

test("class_define", function(){

    expect(3);

var memo = new SimpleMemo.Models.Memo();

  ok(memo.getMemoText()   , "Memo getMemoText defined");

  ok(memo.setMemoText()   , "Memo setMemoText defined");

 

  memo.setMemoText("test");

  ok(memo.getMemoText() == "test" , "Memo memoText setted/getted");

})

 

getter/setterの確認となっています。

実行するとエラーになりますね。

早速実装します。

 

simple_memo/models/memo.js

steal('jquery/model', function(){

 

$.Model('SimpleMemo.Models.Memo',

/* @Static */

{

  findAll   : "/memos.json",

  findOne : "/memos/{id}.json"

  create   : "/memos.json",

  update  : "/memos/{id}.json",

  destroy : "/memos/{id}.json"

},

/* @Prototype */

{

    getMemoText : function(){

        return this.memoText;

    },

    setMemoText : function(val){

        this.memoText = val;

    }  

});

 

});

 

グリーンバーを獲得できましたね。

 

続きは後ほど編集します、、、

 

javascriptMVC入門 その1

 

javascriptMVCとはクライアントサイドでのMVC開発を強力にサポートしてくれるフレームワーク+ツールセットです。

http://javascriptmvc.com/

 

javascriptMVCの主な構成要素は以下5つ

 ・javascriptMVC本体

 ・StealJS:本家によると「A code manager」とのこと。依存性管理やコードクリーニング、デプロイなど

 ・jQueryMXjQueryの拡張

 ・FuncUnit Webテストフレームワーク

 ・DocumentJS ドキュメンテーションエンジン。javadocrdocみたいな感じ

 

なにぶん日本語の解説が少ない+本家のドキュメントが若干散らかっている部分があるんで、

チュートリアルαとして本家のTODOアプリみたいなのを作りながら解説します。

 

 

<作るアプリの概要>

 ・短い文章をメモとして登録/更新/削除/一覧ができる

 ・メモはセッションストレージに保存

 ・jQueryMobileを利用してモバイル用UIとして作成

 

実はCRUDをするだけならscaffold出来るんですが、、わかりやすさの為にあえて作ります。

また、javascriptMVCLESSCoffeeScriptもサポートしているんですが、

今回はシンプルにするために使いません。

 

<インストール&環境準備>

zipをダウンロードするか、gitで取得してくるか。

今回はお手軽にzipをダウンロードします。

http://javascriptmvc.com/

の画面右上の方のDownloadから取得します。

 

zipをダウンロードしたら展開し、そのディレクトリへ移動。

/jmvcに配置

ついでにApacheのドキュメントルートにシンボリックリンクを作成しておきます。

これで「http://localhost/jmvc/」でjavascriptMVCを配置したディレクトリが参照できる。

$ cd /var/www/

$ sudo ln -s /jmvc jmvc

 

 

<アプリ作成>

今回作るアプリの名前は「SimpleMemo」とします。

これにより、

 ・アプリのルートディレクトリ名は「simple_memo

 ・モデルやコントローラなどの属するルート名前空間は「SimpleMemo

となります。

この後使う「app」ジェネレータが「SimpleMemo」みたいな

キャメルケースを渡すことが出来ないため、注意が必要です、、

 

本家ドキュメントにもちょっとだけ注意事項が触れられていますが、

この現象、バグっぽいんですよね。

引用:本家ドキュメント

http://javascriptmvc.com/docs.html#!steal.generate

 

 

app

Creates a JavaScriptMVC application structure.

js jquery/generate/app path/to/app [OPTIONS]

  • path/to/app - The lowercase path you want your application in. Keep application names short because they are used as namespaces. The last part of the path will be taken to be your application's name.

 

この説明でキャメルケース渡す方が悪いっちゃ悪いと思いますが、、

うまいこと対応してほしいもんです。

 

という訳で、名前に気をつけつつアプリ作成に入ります。

 

1:アプリケーションのひな形作成

まずはappジェネレータを使ってひな形を作ります。

先ほども触れた通り、指定するアプリ名は「simple_memo」とし、以下のコマンドを実行します。

 

$./js jquery/generate/app simple_memo

 

出来上がるファイルは、、

simple_memo/             // アプリのディレクト

  simple_memo.css        // 当アプリのcssファイル

  simple_memo.html       // 当アプリのメインhtmlファイル

  simple_memo.js     // 当アプリのメインjsファイル。主に他のファイルの読み込みを行う

  docs/                  // APIドキュメントの生成先ディレクト

  fixtures/              // AJAXリクエストのシミュレーション用js

  funcunit.html          // FuncUnitを使ったテストの実行用ページ

  models/                // モデル&データレイヤ

  qunit.html             // qunitによるテスト実行用ページ

  scripts/               // コマンドラインスクリプト(中身の説明は省略)

  test/                  // テスト用js

 

 

 

確認のため「http://localhost/jvmc/simple_memo/simple_memo.html」をブラウザで確認。

念のためリンク切れなどないかFireBugなどで確認しておく。

 

まずはsimple_memo.htmlを開いて、不要なものを消しちゃいます。

今のところ重要なのは以下のscriptタグ。

<script type='text/javascript'

        src='../steal/steal.js?simple_memo'>

</script>

 

以外は消しちゃいましょう。こんな感じになってればOK

ちなみにlang="en"になってるんで"ja"に変更しましょう。ついでにcharsetも追加します。

<!DOCTYPE HTML>

<html lang="ja">

  <head>

    <meta charset="UTF-8" />

    <title>simple_memo</title>

  </head>

 

  <body>

    <div data-role="page"></div> <!--  -->

    <script type='text/javascriptpt'

            src='../steal/steal.js?simple_memo'>

    </script>

  </body>

 

 </html>

 

気になる'steal.js?simple_memo'ですが、?以降をアプリケーション名として解釈し、

メインのjsファイルを読み込んでくれます。

読み込み対象はアプリケーション名.jsファイルです。

今回読み込まれるのは「simple_memo.js」となります。

 

ではsimple_memo.jsをのぞいてみましょう。

steal(

    './simple_memo.css',           // application CSS file

    './models/models.js',       // steals all your models

    './fixtures/fixtures.js',   // sets up fixtures for your models

    function(){                 // configure your application

 

})

 

steal関数にファイルパスと関数を渡しています。

このsteal関数は単純に、渡されたパスのファイル読み込み+渡された関数処理を行うだけです。

読み込み順序なんかがある場合は以下のように書きます。

steal(

// ファイルパスや関数・・・(1)

).then(

    // (1)が完了した後に実行される。

    // (1)と同じくファイルパスや関数を渡す ・・・(2)

).then(

    // (2)が完了した後に実行される。

    // (1)と同じくファイルパスや関数を渡す 

)

 

 

現状のsimple_memo.jsではCSSファイルやその他のJSファイルなんかを読み込んでいますね。

今回jQueryMobileを使うため、必要ファイルを追記します。

jQueryMobileのグローバル設定の為に、

1:jQuery本体の読み込み

2:jQueryMobileのグローバル設定用イベントを登録

3:jQueryMobileの読み込み

となるようにします。

 

steal(

    './simple_memo.css',           // application CSS file

    './models/models.js',       // steals all your models

    './fixtures/fixtures.js',   // sets up fixtures for your models

    'http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css',   // jQueyMobile用CSS

    'http://code.jquery.com/jquery-1.7.2.min.js',                        // jQuery本体

    function(){                 // configure your application

    }

).then(                                                                  // グローバル設定用イベント

    function(){

        $(document).bind("mobileinit", function(){

            $.mobile.loadingMessage = '読み込み中';

            $.mobile.pageLoadErrorMessage = '読み込みに失敗しました';

            $.mobile.dialog.prototype.options.closeBtnText = '閉じる';

            $.mobile.selectmenu.prototype.options.closeText= '閉じる';

            $.mobile.listview.prototype.options.filterPlaceholder = '検索文字列';

            $.mobile.page.prototype.options.backBtnText = '戻る';        

        });

    }

).then(

    'http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css',   // jQueryMobile本体

)

 

 

確認のためsimple_memo.htmlをブラウザで開いて、cssjsが読み込めているか確認しておきます。

 

<余談>

なお、simple_memo.html中の※1の部分ですが、これがないとdev.jsの読み込みエラーが発生します。

 

このエラーについてgoogle先生に聞いてみると、、、

http://forum.javascriptmvc.com/topic/integration-jquery-mobile-js-mvc

http://forum.javascriptmvc.com/topic/jquery-mobile-or-zeptojs

こんなのが。おんなじ症状っぽいですね。

何らかの条件下でjqueryMobileの初期化処理が行われるとエラーになるっぽい。

とりあえずdata-role="page"のエレメントを追加しておけば大丈夫だったんで、後で調査してみます。

 

長くなりそうなんで、続きは次回。