ArduinoをAJAXで制御してみました

最終更新2018年9月20日

IoTを語る上で、Arduinoの制御をブラウザから出来るようにしておくことは重要だと考えます。

Ajaxとは「Asynchronous JavaScript + XML」の略。つまり、「JavaScriptとXMLを使って非同期にサーバとの間の通信を行うこと。」としています。

ブラウザ表示はHTMLで記述し、表示内容を変化/遷移するにはページ全体をリロードするのが一般的です。Pageサイズがしれていればそんなに問題視しないのですが、変化が頻繁になると、これは大きな負荷になります。

実際、Arduino のEthernetスケッチですとWebServerのコードをご覧ください。この例だと6ch分のAD変換値をHTMLに記載したREFRESH時間毎に更新して更新表示しています。単に数値表示6ch分の更新ですが、サーバ側(Arduino)この処理はREFRESH毎に新規HTMLを作成して要求に応じています。
不特定多数のクライアントに対して対応するには、この方法はリーズナブルかと思うのですが、1対1の接続で遣り取りしている場合、文字列部分だけ書き直してもらえれば通信量が少なくて済みます。背景に画像なんか貼り付けてあるページでは当然です。

或いは要求処理なんかでも、https://ホスト名:ポート番号/パス?クエリー と云うようなフォームで処理したりします。

もっと効率的な方法論として、Ajax が挙がります。

Ajaxを支える仕組み として、

という要素の組み合わせで実現しています。

以下の記事が正しく示しています。
https://arduinomylifeup.com/arduino-web-server/

スケッチは以下の通りです

/*
 * 20180903
 * このコードはWiznet社が提供するExampleコードをレタッチしています。
 * CPU:MEGA2560
 * EthernetShield WizNet5100搭載のEthernetShield cs=10固定
 * LED8個を42〜49pinに割付る
 */
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#define SD_CS    4
#define EH_CS    10
// Enter a MAC address はスイッチサイエンスが提供した正規のモノ
const byte mac[]        =       {0x00,0x50,0xC2,0x97,0x2C,0x3D};
const byte ip[]         =       {192, 168, 0, 198};
const byte subnet[]     =       {255,255,255,0};
const byte gateway[]    =       {192,168,0,1};
// Initialize the Ethernet server library
// (port 80 is default for HTTP):
EthernetServer server(80);
File webPage;
int RED1        =       42;     //LED to turn on/off
int RED2        =       43;     //LED to turn on/off
int RED3        =       44;     //LED to turn on/off
int RED4        =       45;     //LED to turn on/off
int RED5        =       46;     //LED to turn on/off
int RED6        =       47;     //LED to turn on/off
int RED7        =       48;     //LED to turn on/off
int RED8        =       49;     //LED to turn on/off
String HTTP_req;                // stores the HTTP request
void setup()
{
        Serial.begin(115200);           // for debugging
        Ethernet.begin(mac, ip);        // initialize Ethernet device
        server.begin();                 // start to listen for clients
        //while (!Serial) {
        //        ; // wait for serial port to connect. Needed for native USB port only
        //}
        // initialize SD card
        Serial.println(F("Checking SD card is accessible..."));
        if (!SD.begin(SD_CS)) {
                Serial.println(F("ERROR - SD card initialization failed!"));
                return;                 // init failed
        }
        Serial.println(F("SUCCESS - SD card initialized."));
        Serial.print(F("Server is at ")); Serial.print(Ethernet.localIP()); 
        HTTP_req = "";
        //Set LED to output
        pinMode(RED1, OUTPUT);digitalWrite(RED1, HIGH);
        pinMode(RED2, OUTPUT);digitalWrite(RED2, HIGH);
        pinMode(RED3, OUTPUT);digitalWrite(RED3, HIGH);
        pinMode(RED4, OUTPUT);digitalWrite(RED4, HIGH);
        pinMode(RED5, OUTPUT);digitalWrite(RED5, HIGH);
        pinMode(RED6, OUTPUT);digitalWrite(RED6, HIGH);
        pinMode(RED7, OUTPUT);digitalWrite(RED7, HIGH);
        pinMode(RED8, OUTPUT);digitalWrite(RED8, HIGH);
}
void loop()
{
        // listen for incoming clients
        EthernetClient client = server.available();
        if (client) {
                Serial.println(F("new client"));
                // an http request ends with a blank line
                boolean currentLineIsBlank = true;
                while (client.connected()) {
                        if (client.available()) {
                                char c = client.read();         // read 1 byte (character) from client
                                                                // if you've gotten to the end of the line (received a newline
                                                                // character) and the line is blank, the http request has ended,
                                                                // so you can send a reply
                                if ( HTTP_req.length() < 80)
                                        HTTP_req += c;  // save the HTTP request 1 char at a time
                                if (c == '\n' && currentLineIsBlank) {
                                                                // send a standard http response header
                                        client.println(F("HTTP/1.1 200 OK"));
                                        client.println(F("Content-Type: text/html"));
                                        client.println(F("Connection: close"));
                                        client.println();
                                        // send web page
                                        if (HTTP_req.indexOf("ajaxrefresh") >= 0 ) {
                                                Serial.println(F("ajaxRequest."));
                                                ajaxRequest(client);  //update the analog values
                                                break;
                                        }else if (HTTP_req.indexOf("ajax_inputs") >= 0 ) {
                                                Serial.print(F("ledChangeStatus."));Serial.print(HTTP_req);
                                                ledChangeStatus(client); //change the LED state
                                                break;
                                        }else{
                                                webPage = SD.open("index.htm");        // open web page file
                                                if (webPage) {
                                                        while (webPage.available()) {
                                                                client.write(webPage.read()); // send web page to client
                                                        }
                                                        webPage.close();
                                                        Serial.println(F("WriteWebPage."));
                                                }
                                                break;
                                        }
                                        if (c == '\n') {
                                                // you're starting a new line
                                                currentLineIsBlank = true;
                                        } else if (c != '\r') {
                                                // you've gotten a character on the current line
                                                currentLineIsBlank = false;
                                        }
                                }
                        }
                }
                // give the web browser time to receive the data
                delay(1);
                // close the connection:
                client.stop();
                HTTP_req = "";
                Serial.println(F("client disconnected"));
        } // end if (client)
}
// send the state of the switch to the web browser
void ajaxRequest(EthernetClient client)
{
        for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
                int sensorReading = analogRead(analogChannel);
                client.print(F("analog input "));
                client.print(analogChannel);
                client.print(F(" is "));
                client.print(sensorReading);
                client.println(F("<br />"));
        }
}
void ledChangeStatus(EthernetClient client)
{
        int     LEDIndex,LEDState;
        LEDIndex        =       HTTP_req.charAt(20) - 48;
        LEDState        =       HTTP_req.charAt(22) - 48;
        //sscanf(HTTP_req,"ajax_inputs&LED%d=%d",LEDIndex,LEDState);
        Serial.print(F("LEDIndex="));Serial.print(LEDIndex);
        Serial.print(F("LEDState="));Serial.println(LEDState);
        if (LEDState == 1) {
                digitalWrite(LEDIndex+41, LOW);
                client.print(F("OFF"));
        }else if(LEDState == 0){
                digitalWrite(LEDIndex+41, HIGH);
                client.print(F("ON"));
        }else{
                ;
        }
}


HTMLの中身を示します。

赤字の数字を変更して更新時間を変更できます。

<HTML>
<HEAD>
  <META NAME="GENERATOR" CONTENT="Adobe PageMill 3.0J Win">
  <TITLE>Arduino Ajax LED Button Control</TITLE>
  <LINK REL="shortcut icon" HREF="favicon.ico">
  <SCRIPT>
		window.setInterval(function(){
			nocache = "&nocache=" + Math.random() * 1000000;
			var request = new XMLHttpRequest();
			request.onreadystatechange = function() {
				if (this.readyState == 4) {
					if (this.status == 200) {
						if (this.responseText != null) {
							document.getElementById("analoge_data").innerHTML = this.responseText;
						}
					}
				}
			}
			request.open("GET", "ajaxrefresh" + nocache, true);
			request.send(null);
		}, 1000);
		strLED = "";
		//var LED2_state = 0;
		function GetArduinoIO()
		{
			nocache = "&nocache=" + Math.random() * 1000000;
			var request = new XMLHttpRequest();
			request.onreadystatechange = function()
			{
				if (this.readyState == 4) {
					if (this.status == 200) {
						if (this.responseXML != null) {
							// XML file received - contains LED states
							var re;
							var num_LEDs;
							var i;
							var ledstr = "";
							
							re = this.responseXML.getElementsByTagName('LED');
							num_LEDs = re.length;
							
							for (i = 0; i < num_LEDs; i++) {
								ledstr = "LED" + (i + 1);
								if (re[i].childNodes[0].nodeValue === "checked") {
									document.getElementsByName(ledstr)[0].checked = true;
								}
								else {
									document.getElementsByName(ledstr)[0].checked = false;
								}
							}
						}
					}
				}
			}
			// send HTTP GET request with LEDs to switch on/off if any
			request.open("GET", "ajax_inputs" + strLED + nocache, true);
			request.send(null);
			setTimeout('GetArduinoIO()', 200);
			strLED = "";
		}
		// service LEDs when checkbox checked/unchecked
		function GetCheck(led_num_str, cb)
		{
			if (cb.checked) {
				strLED += ("&" + led_num_str + "=1");
			}
			else {
				strLED += ("&" + led_num_str + "=0");
			}
		}
	</SCRIPT>
<STYLE>
 .IO_box { float: left; margin: 0 20px 20px 0; border: 1px solid blue; padding: 0 5px 0 5px; width: 120px; } 
h1 { font-size: 120%; color: blue; margin: 0 0 10px 0; } 
h2 { font-size: 85%; color: #5734E6; margin: 5px 0 5px 0; } p, form, 
button { font-size: 80%; color: #252525; } 
.small_text { font-size: 70%; color: #737373; } 
</STYLE>
</HEAD>
<BODY onload="GetArduinoIO()">
<FORM ID="check_LEDs" NAME="LED_form1">
<H1><TABLE WIDTH="1149" BORDER="1" CELLSPACING="0" CELLPADDING="0">
  <TR>
    <TD WIDTH="100%" BGCOLOR="#00646e">
    &nbsp;</TD>
  </TR>
</TABLE></H1>
<BLOCKQUOTE>
  <H1>Analogue Values</H1>
  <P><div id="analoge_data">Arduino analog input values loading.....</div></P>
  <H1>Arduino Ajax MEGA 8 Output</H1>
  <div class="IO_box">
  <H2>LED Using Checkbox</H2>
  <P><INPUT TYPE="checkbox" NAME="LED1" VALUE="0" onclick="GetCheck('LED1', this)"
  />LED 1 (D42)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form2">
  <INPUT TYPE="checkbox" NAME="LED2" VALUE="0" onclick="GetCheck('LED2', this)"
  />LED 2 (D43)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form3">
  <INPUT TYPE="checkbox" NAME="LED3" VALUE="0" onclick="GetCheck('LED3', this)"
  />LED 3 (D44)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form4">
  <INPUT TYPE="checkbox" NAME="LED4" VALUE="0" onclick="GetCheck('LED4', this)"
  />LED 4 (D45)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form5">
  <INPUT TYPE="checkbox" NAME="LED5" VALUE="0" onclick="GetCheck('LED5', this)"
  />LED 5 (D46)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form6">
  <INPUT TYPE="checkbox" NAME="LED6" VALUE="0" onclick="GetCheck('LED6', this)"
  />LED 6 (D47)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form7">
  <INPUT TYPE="checkbox" NAME="LED7" VALUE="0" onclick="GetCheck('LED7', this)"
  />LED 7 (D48)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div><div class="IO_box"></P>
  <H2>LED Using Checkbox</H2>
  <P></FORM>
<FORM ID="check_LEDs" NAME="LED_form8">
  <INPUT TYPE="checkbox" NAME="LED8" VALUE="0" onclick="GetCheck('LED8', this)"
  />LED 8 (D49)<BR / CLEAR="ALL"><BR / CLEAR="ALL"></div></BLOCKQUOTE>
</FORM>
</BODY>
</HTML>

テンプレートをレタッチして問題となるケースはHTTPの遣り取りの中身をきちんと理解しないといけないことかと思います。
Arduinoはデバッグがなかなか難しいところがありますが、Print文でシリアルモニタに書き込むのがリーズナブルです。

今回の例では、

Serial.print(F("ledChangeStatus."));Serial.print(HTTP_req);

という扱いをして、ブラウザのEventがHTTP_req内にどのような文字列になっているかを確認します。
後は文字列を一生懸命弄るだけです。処理しやすい文字列にしなければいけないことも実際にやってみると判断出来ます。

上記コードですが、複数のクライアントが同じサーバにアクセスした場合の挙動を確認しました。

iPhoneからのアクセスでももちろん制御出来ました。Localの無線LAN接続ですが。LTE経由となるとちょっと工夫が要りますね。


戯言(nonsense)に戻る


免責事項

本ソフトウエアは、あなたに対して何も保証しません。本ソフトウエアの関係者(他の利用者も含む)は、あなたに対して一切責任を負いません。
あなたが、本ソフトウエアを利用(コンパイル後の再利用など全てを含む)する場合は、自己責任で行う必要があります。

本ソフトウエアの著作権はToolsBoxに帰属します。
本ソフトウエアをご利用の結果生じた損害について、ToolsBoxは一切責任を負いません。
ToolsBoxはコンテンツとして提供する全ての文章、画像等について、内容の合法性・正確性・安全性等、において最善の注意をし、作成していますが、保証するものではありません。
ToolsBoxはリンクをしている外部サイトについては、何ら保証しません。
ToolsBoxは事前の予告無く、本ソフトウエアの開発・提供を中止する可能性があります。

商標・登録商標

Microsoft、Windows、WindowsNTは米国Microsoft Corporationの米国およびその他の国における登録商標です。
Windows Vista、Windows XPは、米国Microsoft Corporation.の商品名称です。
LabVIEW、National Instruments、NI、ni.comはNational Instrumentsの登録商標です。
I2Cは、NXP Semiconductors社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。