Arduino I/O をスマートフォン(iPod touch)から操作する、WebSocket(socket.io + node.js) を使用して I/O 変化時にブラウザ画面を自動更新(その1)

 

Arduino をスマートフォンやWeb ブラウザから操作するアプリケーションの第2弾を紹介します。以前に紹介したWebアプリの機能に、 Arduino 上のI/O ポート値が変化したときに自動でブラウザ画面を最新の状態にするようにします。

以前に紹介したWeb アプリの説明は、Arduino をシリアルポート経由でサーバーに接続して、スマートフォン等の Web ブラウザから操作する機能については (1)Arduino を Web API 経由で操作 の記事と、(2) Arduino をスマートフォンから操作 の記事を参照してください。

前回紹介した Web アプリでは、複数のブラウザが同時に Arduino I/O を操作した場合に備えて “reload” ボタンをアプリのタイトルヘッダーにつけていましたが、今回のアプリでは Arduino がブラウザから操作されてI/O ポート値が変化すると、すべてのブラウザ画面が自動更新される様になるため “reload”ボタンが不要になります。

今回紹介するアプリケーションの動作画面(前回と比べると Reload ボタンがありません)

このWeb アプリを複数のPC やスマートフォンのブラウザから同時に操作すると、現在のI/O 値を示すチェックボックスの値が、リアルタイムに全てのブラウザ画面で同期させることができます。

これを実現するためには、以前に紹介したアプリに対して下記の機能の追加が必要になります。

(1) I/O チェックボックス操作時または Arduino I/O の値が変化した時に、最新の I/O 値をサーバーPC で検出・取得する

(2) Web アプリを実行する複数のブラウザ画面のコネクションを管理して、Arduino I/O が変化した時にサーバーPC から最新のI/O データを配信する

(3) Web アプリを実行しているブラウザが、最新の I/O データを受信したときにWeb アプリ上の GUI(チェックボックス) の状態を自動更新させる

最初に (1) について実現方法を検討します。

スマートフォンで実行する Web アプリが Arduino I/O を操作する時には、必ずサーバーPC のWeb API 経由で実行しますので、サーバーPC側で I/O の変化を知ることは可能です。ただ、この方法をとると Arduino をリセットした場合など、I/Oポート値がハード(Arduino)側のみの都合で変化した場合に、その状態をブラウザ側に通知する方法がなくなってしまいます。このため、今回のアプリでは Arduino 側で実行するスケッチプログラムで I/O の変化を検出して、最新の I/O データを Firmata プロトコルでシリアルでサーバーに通知する方法をとります。

下記に、Arduino で実行するスケッチプログラムを示します。この後の記事で説明するファイルを含めてここからダウンロードすることができます。

 

/*
 DigitalIOControl2.ino

 DigitalIOControl.ino から下記の機能を追加しています。

 (1) Arduino Digital I/O をPC 側から更新(変更)したときに、
     DIGITAL_PORTS_MESSAGE をPCに送信する

 (2) DIGITAL_PORTS_MESSAGE Firmata プロトコルを新規追加

 Firmata プロトコルで PC 側から Arduino digital I/O を操作するための機能です。
 初期化時に、Arduino pin#2 から pin#13 を digital OUTPUT モードに設定しています。

 ------------------
 サポートしている Firmata プロトコル
 * DIGITAL_MESSAGE
   標準プロトコルで定義されているプロトコル。
   PC から Arduinoボード側へのI/O ポート値設定のみに使用しています。

   -- digital I/O message --
   0  command (0x90) + port#
   1  port value bits 0-6  (LSB)
   2  port value bits 7-13 (MSB)

 * デジタルポートの現在の値を取得するための SYSEX コマンド DIGITAL_PORTS_QUERY と
   リプライDIGITAL_PORTS_REPLY

   -- query digital ports --
   0  START_SYSEX          (0xF0)
   1  query digital ports  (0x61)
   2  END_SYSEX            (0xF7)

   -- reply digital ports --
   0  START_SYSEX          (0xF0)
   1  reply digital ports  (0x62)
   3  port#0 bits 0-6      (LSB)
   4  port#0 bits 7-13     (MSB)
   ..
   5  port#n bits 0-6      (LSB)
   6  port#n bits 7-13     (MSB)
   7  END_SYSEX            (0xF7)

 * ピン番号と値を指定してポートを操作する SYSEX コマンド DIGITAL_WRITE_PIN
   -- write digital port pin --
   0  START_SYSEX          (0xF0)
   1  write digital pin    (0x63)
   2  pin number           (0x02-0x0D)
   1  pin value            (0/1)
   2  END_SYSEX            (0xF7)

 * DIGITAL_PORTS_MESSAGE
   I/O 値が更新された場合に、PC 側に最新ののI/O ポート値を送信する

   -- reply digital ports --
   0  START_SYSEX          (0xF0)
   1  digital ports message(0x64)
   3  port#0 bits 0-6      (LSB)
   4  port#0 bits 7-13     (MSB)
   ..
   5  port#n bits 0-6      (LSB)
   6  port#n bits 7-13     (MSB)
   7  END_SYSEX            (0xF7)

 このファイルは自由に使用することができます。内容を変更するなどをして利用することもできます。
 オールブルーシステムは、このファイルを使用したことによる損害等について一切保障できません。
                   2012 All Blue System     Satoshi Kimura
*/

#include <Firmata.h>

//  デジタルポート数の定義
//  ATmega328 を使用した Arduino ボードの場合は 2 になる
#define TOTAL_DIGITAL_PORTS             ((TOTAL_PINS - TOTAL_ANALOG_PINS + 7) / 8) 

// SYSEX コマンドの定義
#define DIGITAL_PORTS_QUERY      0x61
#define DIGITAL_PORTS_REPLY      0x62
#define DIGITAL_WRITE_PIN        0x63
#define DIGITAL_PORTS_MESSAGE    0x64

// デジタルポートが変更されかどうかを判断するたの値保存用
byte prev_pval[TOTAL_DIGITAL_PORTS];

// PC 側から I/O 値を操作したことを示すフラグ
// 0: I/O 操作なし
// 1: I/O 操作行った
// 2: I/O 値を強制的に PC に送信
volatile byte io_update;

// SYSEX コマンドの処理
void sysexCallback(byte command, byte argc, byte *argv)
{
    byte pval;
    switch(command) {
        case DIGITAL_PORTS_QUERY:
            Serial.write(START_SYSEX);
            Serial.write(DIGITAL_PORTS_REPLY);
            for (byte i=0; i<TOTAL_DIGITAL_PORTS; i++) {
                pval = readPort(i, 0xff);
                Serial.write(pval & B01111111);      // LSB
                Serial.write(pval >> 7 & B01111111); // MSB
            }
            Serial.write(END_SYSEX);
            break;
        case DIGITAL_WRITE_PIN:
            digitalWrite(argv[0],argv[1]);
    	    io_update = 1;
            break;
    }
}

void digitalWriteCallback(byte port, int value)
{
    byte currentPinValue;
    if (port < TOTAL_DIGITAL_PORTS) {
        for(byte i=0; i<8; i++) {
            currentPinValue = (byte) value & (1 << i);
            digitalWrite(i + (port*8), currentPinValue);
        }
    }
    io_update = 1;
}

void checkAndReportDigitalPorts(void){
    byte io_change = 0;
    byte current_pval[TOTAL_DIGITAL_PORTS];

    // 最新のデジタルポート値を読んで前回の値と比較
    for (byte p=0; p<TOTAL_DIGITAL_PORTS; p++) {
        current_pval[p] = readPort(p, 0xff);
        if(prev_pval[p] != current_pval[p]) io_change = 1;
        prev_pval[p] = current_pval[p];
    }

    // デジタルポート値が変化していた場合には最新のデジタルポート値を DIGITAL_PORTS_MESSAGE で送信する
    if((io_change) || (io_update == 2)) {
        Serial.write(START_SYSEX);
        Serial.write(DIGITAL_PORTS_MESSAGE);
        for (byte p=0; p<TOTAL_DIGITAL_PORTS; p++) {
            Serial.write(current_pval[p] & B01111111);      // LSB
            Serial.write(current_pval[p] >> 7 & B01111111); // MSB
        }
        Serial.write(END_SYSEX);
    }

    io_update = 0;
}

void setup()
{
    Firmata.setFirmwareVersion(0, 1);
    Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
    Firmata.attach(START_SYSEX, sysexCallback);
    Firmata.begin(57600);
    // pin#2-#13 のデジタルポートを OUTPUT モードに初期設定する
    for(byte i = 2;i <= 13;i++){
        pinMode(PIN_TO_DIGITAL(i), OUTPUT);
    }

    // ポート変化チェック用の変数を初期化
    for (byte p=0; p<TOTAL_DIGITAL_PORTS; p++) {
        prev_pval[p] = 0x0;
    }

    // 初期化時に最新のデジタルポート値をPCに送信
    io_update = 2;
}

void loop()
{
    // デジタルポート値が変化していた場合には最新のデジタルポート値をPCに送信
    if(io_update){
        checkAndReportDigitalPorts();
    }

    while(Firmata.available()) {
        Firmata.processInput();
    }
}

スケッチプログラムの殆どの部分は以前の記事で紹介したものと同じですが、Firmata プロトコルに下記のメッセージを追加しています。

* DIGITAL_PORTS_MESSAGE

Arduino の I/O 値が変化した場合などに、サーバーPC に最新の I/O ポート値を送信する。詳しいパケットの定義は、コメント部分を参照して下さい。

io_update 変数を使用して、サーバーPCに I/O データを通知するタイミングをコントロールしています。Web API 経由で Arduino 側の I/O を操作するときに実行される Firmata コマンドの DIGITAL_MESSAGE, DIGITAL_WRITE_PIN の処理ルーチン内で、io_update に 1 を代入して最新の I/O データ通知をリクエストします。実際に I/O 値が変化していたかどうかは、loop() 関数内で繰り返しコールされる CheckAndReportDigitalPorts() 関数で調べています。関数内では、前回サーバーPC に I/O 値を送信してから今回チェックした時までに I/O 値が変化していた場合には、Firmata プロトコル DIGITAL_PORTS_MESSAGE を使用して最新の I/O ポート値をサーバーPC に送信します。

また、Arduino リセット時には io_update に 2 を代入して、強制的に最新の I/O 値を DIGITAL_PORTS_MESSAGE パケットを使用してサーバーに送信します。

このスケッチをArduino にロードして実行します。Web API 経由で Arduino I/O を操作したときの DeviceServer で記録されたログは下記の様になります。下記のログは後の記事で紹介する予定のDeviceServer 側のスクリプト等を変更した後に取得しましたので、ログ中にはそれらのメッセージが混じっていますが今回の記事では無視して下さい。

 

Web アプリから I/O 操作を行った直後やリセット時に、DIGITAL_PORTS_MESSAGE パケットがサーバーに送信されているのを確認することが出来ます。

今回の記事では、Arduino 側のファームウエアの変更点についてのみ説明しました。ここまでの作業で Web API や Web アプリから Arduino I/O を操作すると、最新の I/O データ値がサーバーPC に送信されてくるようになりました。続きの作業は、後の記事で説明します….

それではまた。

 

コメントは受け付けていません。