Arduino I/O を Web API から操作する

Arduino を Web から操作する方法について紹介します。この方法を応用することでスマートフォンや PDA はもちろん、クラウドのサーバーやWeb アプリケーションサーバーなどインターネットやイントラネットから自由に Arduino を操作できるようになります。例えば、アプリケーションサーバーでエラーが発生したときに、手元の Arduino に接続したブザーを鳴らしたり、スマートフォンからArduino に接続したリレーを操作してエアコンや警報器を操作できます。

今回は、Arduino の digital I/O ポートのみを使用しています。Arduino では標準ライブラリで提供されている Firmata プロトコルを使用したプログラムを動作させます。Arduino IDE のサンプルで提供されている幾つかのFirmata スケッチプログラムでは機能的に過不足がありますので、今回はdigital I/O リモート操作用のスケッチを新規に作成しました。このプログラムは PC からシリアルポート経由で受信した Firmata コマンドを実行して、I/O ポートの ON/OFF を切り替えたり、現在のI/Oポートの状態を PC 側に知らせることが出来ます。

Arduino で実行するスケッチは下記になります。後で説明する DeviceServer 側のスクリプトを含めて ここから ダウンロードすることができます。

 

/*
 DigitalIOControl.ino

 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)

 このファイルは自由に使用することができます。内容を変更するなどをして利用することもできます。
 オールブルーシステムは、このファイルを使用したことによる損害等について一切保障できません。
                   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

// 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]);
            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);
        }
    }
}

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);
    }
}

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

このスケッチでは Arduino で下記の Firmata コマンドの受付と実行を行います。

[DIGITAL_MESSAGE]

このコマンドはパラメータで指定されたポートにデータを出力します。指定したポート中の全ビットの値を一度に変更したい場合に使用します。

[DIGITAL_WRITE_PIN]

Arduino のピン番号と値を指定して、ポートに出力します。このコマンドは指定したピン番号の I/O ポートのみを更新して、指定したピン以外の I/O ポートに影響を与えません。

[DIGITAL_PORTS_QUERY]

Arduino ボードの現在の I/O ポートの値を全て取得します

それぞれのコマンドの詳細については、スケッチプログラムのコメント欄を参考にしてください。Firmata プロトコルは MIDI を元に作成されているため、データ部分は 7bit で収まる様に設計されています。そのため今回の様に 8 ビットデータを扱う場合には助長なフォーマット(LSB + MSB) になっている点に注意してください。

次に、Arduino に接続するシリアルポートの設定を DeviceServer 側で行います。ボーレートを スケッチプログラムで設定した 57600 に合わせて、タイプに FIRMATA を選択します。

Arduino が接続されている COM ポート番号は、実際に使用する環境に合わせてください。次に、Web サーバー機能を設定して HTTP プロトコルと Web API 機能を有効にします。

この例では、HTTPServer ポートをデフォルトの 80 (HTTP) から 8080 に変更しています。 “WebAPI を有効にする” にチェックを付けて HTTP プロトコル経由で DeviceServer 側のスクリプトを実行できるようにします。次に、DeviceServer 用に3つのスクリプトをスクリプトフォルダ(“C:\Program Files\AllBlueSystem\Scripts”)にコピーします。それぞれのスクリプトについて説明します。

* ARDUINO_IO_PUT.lua スクリプトファイル

file_id = "ARDUINO_IO_PUT"
log_msg("start..",file_id)

--[[
**************************************************************************************
Arduino デバイスの指定したI/O ポートの値を設定する

スクリプト起動時に渡されるパラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
port			Arduino port 番号										"1"

value			I/O ポートに設定する値(16進数2桁)						"FC"

ATmega328 を使用した Arduino ボードの場合には、 digital I/O port は 0,1 の2 つで
それぞれ、port0:PORTD, port1:PORTB(bit#0-bit#5) + (PORTC bit#0-#1)に対応しています。
詳しくは、Arduino IDE に同梱されている pins_arduino.h ファイルを参照して下さい。
**************************************************************************************
]]

-------------------------
-- パラメータチェック
-------------------------
if not (g_params["port"] and g_params["value"]) then
	log_msg("parameter error",file_id)
	error()
end

----------------------------------------------------------------------
-- Firmata digital I/O message 送信
----------------------------------------------------------------------
local com = "COM4"
local firmata_command = list_to_hex(bit_or(0x90,tonumber(g_params["port"])))
local send_data = firmata_command .. tbl_to_hex72({tonumber("0x" .. g_params["value"])},2)
local stat,rdata = serial_write(com,send_data)
if not stat then
	log_msg("serial_write() failed",file_id)
	error()
end

このスクリプトはパラメータで指定されたポート番号と値のデータを、Firmata プロトコル(DIGITAL_MESSAGE) で Arudino に送信して I/O ポートに出力します。スクリプトを配置すると直ぐに Web API でこのスクリプトを実行できますので、Web ブラウザから実行してみます。

URL パラメータにスクリプト名とスクリプトパラメータを指定しています。URL の ”/command/json/script” 部分が DeviceServer の Web API コマンドになります。この Web API コマンドは、URL パラメータで指定されたスクリプトを実行して実行結果(スクリプトリターンパラメータと実行結果ステータス)を JSON フォーマットで返します。name=ARDUINO_IO_PUT でサーバー側で実行するスクリプト名を指定しています。session=abc123 は DeviceServer 側で認証を行うためのセッショントークン文字列です。本来はログイン操作を行ってからその都度ユニークなセッションを使用するのですが、今回はサーバー起動時に作成した秘密のセッショントークンを使用してユーザー認証を省略しています。詳しくは ”2012/5/9 メールからFAX送信”の記事を参照して下さい。その他の URL パラメータ(port=0, value=FC)はそのまま、Lua のスクリプトパラメータに変換されてスクリプトに渡されます。Webブラウザでアクセス(HTTP GET)すると Arduino 側の I/O ポートが設定されて下記の様になります。

URLパラメータで指定したポートに I/O 値が出力されているのを確認することができます。

次に、ARDUINO_PIN_SET.lua スクリプトファイルを設定します。

file_id = "ARDUINO_PIN_SET"
log_msg("start..",file_id)

--[[
**************************************************************************************
Arduino デバイスの指定したI/O ピンの値を設定する

スクリプト起動時に渡されるパラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
pin			Arduino pin番号(2 から 13 までの整数)						"13"

value		pin に指定したI/Oポートに設定する値(0 または 1)				"1"

**************************************************************************************
]]

-------------------------
-- パラメータチェック
-------------------------
if not (g_params["pin"] and g_params["value"]) then
	log_msg("parameter error",file_id)
	error()
end

----------------------------------------------------------------------
-- Arduino pin 番号と値を指定してポート設定
----------------------------------------------------------------------
local com = "COM4"
local sysex_start = "F0"
local sysex_end = "F7"
local firmata_request = "63"
local pin = bit_tohex(tonumber(g_params["pin"]),2)
local val = bit_tohex(tonumber(g_params["value"]),2)
local send_data = sysex_start .. firmata_request .. pin .. val .. sysex_end

----------------------------------------------------------------------
-- リクエストフレーム送信
----------------------------------------------------------------------
local stat = serial_write(com,send_data)
if not stat then
	log_msg("serial_write() failed",file_id)
	error()
end

このスクリプトはURLパラメータで指定されたピン番号と値を、Firmata プロトコル(DIGITAL_WRITE_PIN) で Arduino に送信して I/O を設定します。スクリプト設定後、同様にWeb ブラウザで実行してみます。

URLパラメータ pin=13&value=1を指定することで、Arduino に標準で装備されているパイロットLED(pin#13, PORTB#5) を点灯させています。

ボードに装備されている LED が点灯するのを確認することができます。

次に、ARDUINO_IO_GET.lua スクリプトファイルを設定します。

file_id = "ARDUINO_IO_GET"
log_msg("start..",file_id)

--[[
**************************************************************************************
現在の Arduino デバイスの I/O 値を取得する

リターン時に返されるパラメータ
---------------------------------------------------------------------------------
キー値			値		            									値の例
---------------------------------------------------------------------------------
PORT_0			Arduino port0(8bit)の現在の値(16進数2桁)				"FC"
PORT_1			Arduino port1(8bit)の現在の値(16進数2桁)				"00"
..
PORT_n			Arduino portn(8bit)の現在の値(16進数2桁)				"00"

ATmega328 を使用した Arduino ボードの場合には、 digital I/O port は 0,1 の2 つで
それぞれ、port0:PORTD, port1:PORTB(bit#0-bit#5) + (PORTC bit#0-#1)に対応しています。
詳しくは、Arduino IDE に同梱されている pins_arduino.h ファイルを参照して下さい。
**************************************************************************************
]]

----------------------------------------------------------------------
-- Arduino digital I/O ポート取得 QUERY 送信と REPLY 受信
----------------------------------------------------------------------
local com = "COM4"
local sysex_start = "F0"
local sysex_end = "F7"
local firmata_request = "61"
local firmata_reply = "62"
local send_data = sysex_start .. firmata_request .. sysex_end

----------------------------------------------------------------------
-- 排他制御開始
-- 同じ Arduino デバイスに対して、I/O PORT QUERY コマンドが複数同時実行される場合に備えています。
-- 別のArduino デバイスを操作する場合や、同じ Arduino デバイスに対して異なる FIRMATA REPLY
-- コマンドバイト値を取得する場合には排他制御の必要はありません
----------------------------------------------------------------------
local cstat,handle = critical_section_enter("FirmataQueyOP" .. com,5000);
if not cstat then
	log_msg("critical_section_enter() failed",file_id)
	error()
end

----------------------------------------------------------------------
-- リクエストフレーム送信とリプライ受信
----------------------------------------------------------------------
local stat,rdata = serial_write(com,send_data,firmata_reply)

----------------------------------------------------------------------
-- 排他制御終了
----------------------------------------------------------------------
cstat = critical_section_leave(handle)
if not cstat then
	log_msg("critical_section_leave() failed",file_id)
	error()
end
if not stat then
	log_msg("serial_write() failed",file_id)
	error()
end

----------------------------------------------------------------------
-- 受信した FIRMATA フレームの内容をリターンパラメータに格納
----------------------------------------------------------------------
local data = hex72_to_tbl(string.sub(rdata,5,-3),2)
if data ~= nil then
	for key,val in ipairs(data) do
	 	if not script_result(g_taskid,"PORT_" .. tostring(key - 1),bit_tohex(val,2)) then error() end
	end
end

このスクリプトは Arduino にFirmata プロトコル(DIGITAL_PORTS_QUERY) を送信して現在の I/O 値を取得した後、Firmata プロトコル(DIGITAL_PORTS_REPLY) を使用してPC側に送信されてくるデータを受け取ります。受信したFirmata フレームデータを解析して現在の I/O データを取り出してスクリプトリターンパラメータにセットします。DeviceServer ではスクリプトリターンパラメータにキー名と値のペアを好きなだけ設定することができます。設定したリターンパラメータは、Web API で返される JSON 文字列中に ResultParams 配列として格納されます。また、Firmata プロトコルで Query-Reply タイプのコマンドを同一デバイスに対して複数同時に実行する場合に備えて、ここでは排他制御を行っています。

スクリプトファイル設定後、同様にWeb ブラウザで動作させてみます。

ブラウザでは、Arduino から返信された現在の I/O ポート値が JSON フォーマットで表示されているのを確認することができます。

今回の記事では、Arduino を Web API (URL リクエスト) 経由でアクセスしてポートの設定や値の取得が出来るようになるまでの説明を行いました。今後、この機能を使用してスマートフォン等から Arduino の I/O を操作するアプリを作成していきたいと思います。また応用として、 Arduino の I/O ポート数が足りなくなった場合には、複数の Arduino に今回のスケッチをロードして同時に操作することもできます。この場合には サーバー側のスクリプトのみを変更することで対応できます。今回紹介した Arduino に市販のリレーシールドなどを接続して、ネットワーク対応(Web API 対応) の I/O 装置を非常に安価に作成することができます。

ここで紹介した DeviceServer の機能は、ABS-9000 DeviceServer インストールキットをダウンロードして、直ぐに使用することができます。(デモライセンスが添付されていますので直ちに使用可能です)

それではまた。

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