非同期通信の手法として XmlHttpRequest の代替として FetchAPI が使われるそうです。

最終更新日:2023/2/6

AjaxWebserver機能で実現するにはXmlHttpRequestを使用するExampleが紹介されており、それが正だと信じていました。しかし、iOSで動かないことが判ったため、別の方法論の調査を始めたところ FetchAPI なるモノがあることが判りました。調査します。


https://werner.rothschopf.net/202003_arduino_webserver_post_en.htmの記事はArduinoで構築するWebserberについて書かれているのですが、GETを使うとパラメータを開示してしまうので POST を使いましょう と謳っています。そしてデータ読出しについてJavascriptを使った非同期通信をしている様ですが これが FETCHAPIなのかどうかが判断出来ません。
提供されたスケッチをPico+W5500で動く様に移植は出来ましたので、掲載しておきます。

このスケッチだとiOSでも動作しました。しかしm文字列処理が重いようでキビキビ動くというような感じではないです。

Test_webserver_fetch.ino

/*
 * 2023年2月6日 T.Wanibe
 *      このコードはFetchAPIのダンプルを検索していてヒットしたモノ Pico+W5500で動作する様に移植した
 *      https://werner.rothschopf.net/202003_arduino_webserver_post_en.htm
 *      のドキュメントを参照のこと
 * 最大1044480バイトのフラッシュメモリのうち、スケッチが66592バイト(6%)を使っています。
 * 最大262144バイトのRAMのうち、グローバル変数が7928バイト(3%)を使っていて、ローカル変数で254216バイト使うことができます。
 */
/* *******************************************************************
  A simple Arduino Ethernet web server.
  Fetch/API
  This version:
 Arduino String クラスの読み取りパラメータ (GET および POST) を使用しない 定義されていないページに注意する 
 ループをきちんと処理する favicon.ico を保持する Streamlib Fetch API を使用して SRAM を保存する
  description:
  - en: https://werner.rothschopf.net/202003_arduino_webserver_post_en.htm
  - de: https://werner.rothschopf.net/202001_arduino_webserver_post.htm
  credits:
  -
  by noiasca
  2020-11-24 17468/511
* *************************************************************** */
#include        <SPI.h>
#include        <Ethernet3.h>                                                   //<Ethernet.h>から変更しています。
#include        <StreamLib.h>                                                   // download from library manager
#define         BAUDRATE                115200
#define         NICReset                18
#define         NICInit                 19
#define         SPI_SCK                 2
#define         SPI_TX                  3
#define         SPI_RX                  4
#define         SPI_CS                  5
byte            mac[]           =       {0x00,0x08,0xDC,0x54,0x4D,0xDD};        //WIZNET
byte            ip[]            =       {192, 168, 0, 208};
EthernetServer  server(80);                                                     // Port 80 is HTTP port
byte            ledPin[]        =       {16,17,20,21,22,26,27,28};              // define the LED pins. Don't use 0 (RX), 1(TX), 10 (SS ETH), 11 (MOSI), 12(MISO), 13(CLK). Be carefull with 4(SS SD).
byte            analogPin[]     =       {26, 27, 28};                           // some analog pins to read.
/*
void printHardwareInfo(){
        if (Ethernet.hardwareStatus() == EthernetNoHardware) {
                Serial.println(F("Ethernet shield was not found"));
        }else if (Ethernet.hardwareStatus() == EthernetW5100) {
                Serial.println(F("W5100 detected"));
        }else{
                if (Ethernet.hardwareStatus() == EthernetW5200) {
                        Serial.println(F("W5200 detected"));
                }else if (Ethernet.hardwareStatus() == EthernetW5500) {
                        Serial.println(F("W5500 detected"));
                }
                if (Ethernet.linkStatus() == Unknown) {                                 // cable connection
                        Serial.println(F("Link status detection is only available with W5200 and W5500"));
                }else if (Ethernet.linkStatus() == LinkON) {
                        Serial.println(F("Link status: On"));             
                }else if (Ethernet.linkStatus() == LinkOFF) {
                        Serial.println(F("Link status: Off"));
                }
        }
}
*/
//------------------
void setup(){
        Serial.begin(BAUDRATE);
        Serial.println(F("\nsimple webserver"));
        for (int i = 0 ;i<8; i++) {
                pinMode(ledPin[i], OUTPUT);                   // Set the digital pins ready to write to
        }
        SPI.setSCK(SPI_SCK);
        SPI.setRX(SPI_RX);
        SPI.setTX(SPI_TX);
        SPI.setCS(SPI_CS);
        SPI.begin(); 
        //Ethernet3で使用可能なAPI
        pinMode(SPI_CS, OUTPUT);                                                //NIC_CS出力設定
        Ethernet.setCsPin(SPI_CS);                                              //NIC_CSアサイン
        Ethernet.setRstPin(NICReset);                                           //NIC_RSTアサイン
        //NICリセット処理
        pinMode(NICReset, OUTPUT);
        digitalWrite(NICReset, LOW);
        delay(10);                                                              //10msecパルス幅で初期化
        digitalWrite(NICReset, HIGH);
        Ethernet.begin(mac, ip);                        // Start the Ethernet shield:
        Serial.print(F("server available at http://"));
        Serial.println(Ethernet.localIP());
        //printHardwareInfo();
        server.begin();                                 // Start the webserver:
}
//---------------
void loop(){
        // listen for incoming clients
        checkForClient();
        // put your other main code here, to run repeatedly. avoid any delay()
}

server.ino

/* *****************************************************************
 *  Webserver - the basic functions
 * *****************************************************************
 */
//-------------------
void checkForClient(){
        EthernetClient client = server.available();
        if (client) {
                Serial.println(F("\n[server] client connected"));
                uint8_t i = 0;                                  // index / current read position
                const uint16_t buffersize = 100;                // size of read buffer (reads a complete line) (if larger than 255, modify i also!
                const uint16_t smallbuffersize = 30;            // a smaller buffer for results
                char lineBuffer[buffersize] {'\0'};             // buffer for incomming data
                char method[8];                                 // largest one 7+1. HTTP request methods in RFC7231 + RFC5789: GET HEAD POST PUT DELETE CONNECT OPTONS TRACE PATCH
                char uri[smallbuffersize];                      // the requestet page, shorter than smallbuffersize - method
                char requestParameter[smallbuffersize];         // parameter appended to the URI after a ?
                char postParameter[smallbuffersize];            // parameter transmitted in the body / by POST
                enum class Status {REQUEST, CONTENT_LENGTH, EMPTY_LINE, BODY};
                Status status = Status::REQUEST;
                const size_t MESSAGE_BUFFER_SIZE = 64;          // size of buffer for StreamLib
                char buffer[MESSAGE_BUFFER_SIZE];               // a buffer for the StreamLib
                BufferedPrint message(client, buffer, sizeof(buffer));          // the StreamLib object to replace client print
                while (client.connected()) {
                        while (client.available()) {
                                char c = client.read();
                                Serial.print(c);                                // Debug print received characters to Serial monitor
                                if ( c == '\n' ){
                                if (status == Status::REQUEST){                 // read the first line
                                        //Serial.print(F("lineBuffer="));Serial.println(lineBuffer);
                                        // now split the input
                                        char *ptr;
                                        ptr = strtok(lineBuffer, " ");          // strtok willdestroy the newRequest
                                        strlcpy(method, ptr, smallbuffersize);
                                        Serial.print(F("method=")); Serial.println(method);
                                        ptr = strtok(NULL, " ");
                                        strlcpy(uri, ptr, smallbuffersize);     // enthalt noch evtl. parameter
                                        if (strchr(uri, '?') != NULL){
                                                ptr = strtok(uri, "?");         // split URI from parameters
                                                strcpy(uri, ptr);
                                                ptr = strtok(NULL, " ");
                                                strcpy(requestParameter, ptr);
                                                Serial.print(F("requestParameter=")); Serial.println(requestParameter);
                                        }
                                        Serial.print(F("uri=")); Serial.println(uri);
                                        status = Status::EMPTY_LINE;            // jump to next status
                                }else if (status == Status::CONTENT_LENGTH){    // MISSING check for Content-Length
                                        status = Status::EMPTY_LINE;
                                }else if (status > Status::REQUEST && i < 2){   // check if we have an empty line
                                        status = Status::BODY;
                                }else if (status == Status::BODY){
                                        strlcpy(postParameter, lineBuffer, smallbuffersize);
                                        break; // we have received one line payload and break out
                                }
                                i = 0;
                                strcpy(lineBuffer, "");
                        }else{
                                if (i < buffersize){
                                        lineBuffer[i] = c;
                                        i++;
                                        lineBuffer[i] = '\0';
                        }
                        // MISSING wenn status 3 und content-length --> abbrechen.
                }
        }
        if (status == Status::BODY){                            // status 3 could end without linefeed, therefore we takeover here also
                strlcpy(postParameter, lineBuffer, smallbuffersize);
        }
        Serial.print(F("postParameter=")); Serial.println(postParameter);
        // Simple evaluation of postParameter from body
        // Post data looks like pinD2=On
        //文字列処理に注意
        if ( strncmp( postParameter, "pinD", 4) == 0 ) {
                byte index1     =       postParameter[4] - 0x30;            // Convert ascii to int
                byte index2     =       postParameter[5] - 0x30;
                byte index      =       index1 * 10 +   index2;
                Serial.println(index);
                if ( strncmp( postParameter + 6, "=On", 3) == 0 ) {
                        digitalWrite(index, HIGH);
                }else if ( strncmp( postParameter + 6, "=Off", 4) == 0 ) {
                        digitalWrite(index, LOW);
                }
        }
        // send back a response
        if (!strcmp(uri, "/"))                         // the homepage
                sendPage(message);
        else if (!strcmp(uri, "/1.htm"))
                sendPage1(message);
        else if (!strcmp(uri, "/1.js")) 
                sendJs(message);                                   // javascript calling JSON request
        else if (!strcmp(uri, "/1.json"))                    // send data as json
                sendJson(message);
        else if (!strcmp(uri, "/favicon.ico"))               // a favicon
                //sendFavicon(client);
                send204(message);                                // if you don't have a favicon, send a defined empty response
        else                                                 // if the page is unknown, send HTTP response code 404
                send404(message);
                message.flush();
                client.stop();
        }
        }
}
//----------------------
void sendPage(BufferedPrint &message){
        // Serial.println("[server] 200 response send");
        message.println(F("HTTP/1.0 200 OK\r\n"                 // \r\n header fields are terminated by a carriage return (CR) and line feed (LF) character sequence.
                        "Content-Type: text/html\r\n"           // The media type of the body of the request (used with POST and PUT requests).
                        "\r\n"                                  // a blank line to split HTTP header and HTTP body
                        "<!doctype html>\n"                     // the HTTP page itself
                        "<html lang='en'>\n"
                        "<head>\n"
                        "\t<meta charset='utf-8'>\n"
                        "\t<meta name='viewport' content='width=device-width'>\n"
                        "\t<title>Webserver as pin controller</title>\n"
                        "</head>\n"
                        "<body style='font-family:Helvetica, sans-serif'>\n"  // a minimum style to avoid serifs
                        "<h1>Webserver as pin controller</h1>\n"
                        "<p>Buttons turn pins on or off</p>\n"
                        "<form method='post' action='/' name='pins'>"));
        for (auto &pin : ledPin){
                //client.print(F("<p>"));
                message.print(pin);
                message.print(F(" <input name='pinD"));
                message.print(pin);
                message.print(F("' type='submit' value='On'>"));
                message.print(F("<input name='pinD"));
                message.print(pin);
                message.print(F("' type='submit' value='Off'>"));
                if (digitalRead(pin)) message.print(F(" active"));      message.print(F( "<br>\n"));
        }
        message.print (F("</form>\n"));
        message.print (F("</body>\n</html>"));
        message.flush();
}
//--------------------
void send404(BufferedPrint &message){
        // Serial.println("[server] response 404 file not found");
        message.println(F("HTTP/1.0 404 Not Found\r\n"
                    "Content-Type: text/plain\r\n"                      // we will send a simple plain text without html tags
                    "\r\n"
                    "File Fot Found"));
        message.flush();
}
//--------------------
void send204(BufferedPrint &message){
        // Serial.println("[server] response 204 no content");
        message.println(F("HTTP/1.0 204 no content\r\n"));  // no content
        message.flush();
}
//--------------------
void sendFavicon(EthernetClient &client){
        client.println("HTTP/1.0 204 no content\r\n");
}

server_fetch.ino

/* *****************************************************************
 *   Webserver - page update with Fetch/API
 * *****************************************************************
 */
//-------------------
void sendPage1(BufferedPrint &message){
        // Serial.println("[server] 200 response send");
        message.print(F("HTTP/1.0 200 OK\r\n"                 // \r\n header Fields are terminated by a carriage return (CR) and line feed (LF) character sequence.
                        "Content-Type: text/html\r\n"         // The media type of the body of the request (used with POST and PUT requests).
                        "\r\n"                                // a blank line to split HTTP header and HTTP body
                        "<!doctype html>\n"                   // the HTTP page itself
                        "<html lang='en'>\n"
                        "<head>\n"
                        "\t<meta charset='utf-8'>\n"
                        "\t<meta name='viewport' content='width=device-width'>\n"
                        "\t<script src='/1.js'></script>\n"
                        "\t<title>Webserver as pin controller</title>\n"
                        "</head>\n"
                        "<body style='font-family:Helvetica, sans-serif'>\n"  // a minimum style to avoid serifs
                        "<main>\n"
                        "<h1>Arduino Webserver - Fetch Example</h1>\n"));
        message.print(F("<h2>Uptime</h2>\n"
                        "<p>Webserver running since <span id='ss'>"));
        message.print(millis() / 1000);
        message.print(F("</span> seconds</p>\n"));
        message.print(F("<p><span id='hour'></span> <span id='min'></span> <span id='sec'></span></p>\n"));
        message.print(F("<h2>Analog Pins</h2>\n"));
        message.print(F("<p>"));
        for (size_t i; i < sizeof(analogPin); i++){
                message.print(F("Analog pin "));
                message.print(analogPin[i]);
                message.print(F(" = <span id='analog"));
                message.print(i);
                message.print(F("'>"));
                message.print(analogRead(analogPin[i]));
                message.print(F("</span><br>\n"));
        }
        message.print (F("</p>\n"));
        message.print(F("<h2>Digital Pins</h2>\n"));
        message.print(F("<p>"));
        for (size_t i; i < sizeof(ledPin); i++){
                message.print(F("Digital pin "));
                message.print(ledPin[i]);
                message.print(F(" = <span id='digital"));
                message.print(i);
                message.print(F("'>"));
                message.print(digitalRead(ledPin[i]));
                message.print(F("</span><br>\n"));
        }
        message.print (F("</p>\n"));
        message.print (F("</main>\n</body>\n</html>"));
        message.flush();
}
//-------------------
void sendJs(BufferedPrint &message){
        message.print(F("HTTP/1.0 200\r\n"
                        "Content-Type: application/javascript\r\n"
                        "Cache-Control: public, max-age=2678400\r\n"
                        "\r\n"));
        message.print(F("const url ='1.json';\n"
                        "function renew(){\n"
                        " document.getElementById('ss').style.color = 'blue'\n"
                        " fetch(url)\n" // Call the fetch function passing the url of the API as a parameter
                        " .then(response => {return response.json();})\n"
                        " .then(jo => {\n"
                        "   document.getElementById('sec').innerHTML = Math.floor(jo['ss'] % 60); \n"
                        "   for (var i in jo)\n"
                        "    {if (document.getElementById(i)) document.getElementById(i).innerHTML = jo[i];}\n"
                        // add other fields here (e.g. the delivered JSON name doesn't fit to the html id
                        // finally, update the runtime
                        "   if (jo['ss'] > 60) { document.getElementById('min').innerHTML = Math.floor(jo['ss'] / 60 % 60);}\n"
                        "   if (jo['ss'] > 3600) {document.getElementById('hour').innerHTML = Math.floor(jo['ss'] / 3600 % 24);}\n"
                        "   if (jo['ss'] > 86400) {document.getElementById('day').innerHTML = Math.floor(jo['ss'] / 86400 % 7);}\n"
                        "   if (jo['ss'] > 604800) {document.getElementById('week').innerHTML = Math.floor(jo['ss'] / 604800 % 52);}\n"
                        "   document.getElementById('ss').style.color = 'black';\n"
                        " })\n"
                        " .catch(function() {\n"
                        "  document.getElementById('ss').style.color = 'red';\n"
                        " });\n"
                        "}\n"
                        "document.addEventListener('DOMContentLoaded', renew, setInterval(renew, "));
        message.print(F("5000"));  // the update interval in milliseconds
        message.print(F("));"));
        message.flush();
}
//-------------------
void sendJson(BufferedPrint &message){
        //void getJson(EthernetClient &client)
        // send the data as JSON to the web browser
        message.print(F("HTTP/1.0 200\r\n"
                        "Content-Type: application/json\r\n"
                        "\r\n"
                        "{\"ss\":"));
        message.print(millis() / 1000);                              // timestamp in seconds
        for (size_t i = 0; i < sizeof(ledPin); i++){
                message.print(F(",\"digital"));
                message.print(i);
                message.print(F("\":"));
                message.print(digitalRead(ledPin[i]));
        }
        for (size_t i = 0; i < sizeof(analogPin); i++){
                message.print(F(",\"analog"));
                message.print(i);
                message.print(F("\":"));
                message.print(analogRead(analogPin[i]));
        }
        message.println(F("}"));
        message.flush();
}

戯言(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社の登録商標です。
その他の企業名ならびに製品名は、それぞれの会社の商標もしくは登録商標です。
すべての商標および登録商標は、それぞれの所有者に帰属します。