ArduinoをHTTPサーバーっぽくしてブラウザの相手をさせる

こんにちは。

FPGAボードとパソコンを通信させるにあたって、間にArduinoを挟むことにしました。

Arduinoにイーサネットのシールドを装着してPCとArduinoを接続。
ArduinoとFPGAボードはシリアル通信、という目論見です。

Arduinoの役割は、HTTPサーバっぽく振る舞ってブラウザの相手をすることです。
ブラウザから送信されてくるHTTPリクエストからFPGAに送信するデータを抜き出すわけです。

というわけで、以下のようなスケッチを作成してみました。

```C
#include <Arduino.h>
#include <WString.h>
#include <SPI.h>
#include <Ethernet.h>


#define LINE_BUF_SIZE (512)

byte mac_addr[] = {0x90, 0xA2, 0xDA, 0x00, 0x54, 0x65};
byte ip_addr[] = {192, 168, 11, 100};

EthernetServer server = EthernetServer(80);

String line_buf;
int delimiter = 0;
String key_values[20];
String callback;

int mode = 0;
int pulse_width = 0;
int pulse_interval = 0;
int pulse_cycle = 0;


void http_response_200(EthernetClient client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: application/javascript");
  client.println();
}


void http_response_404(EthernetClient client) {
  client.println("HTTP/1.1 404 Not Found");
  client.println("Content-Type: application/javascript");
  client.println();
}


void http_response_mode0(EthernetClient client) {

  Serial.println("# MODE 0: STOP");

  http_response_200(client);
  client.print(callback);
  client.println("({");
  client.println("  \"mode\": 0,");
  client.println("  \"param1\": \"param1\",");
  client.println("  \"param2\": \"param2\",");
  client.println("  \"param3\": \"param3\"");
  client.println("})");

  // シリアル通信でFPGAにパラメーターを渡す。
}


void http_response_mode1(EthernetClient client) {

  Serial.println("# MODE 1: CONTINUOUS PULSE");

  http_response_200(client);
  client.println();
  client.print(callback);
  client.println("({");
  client.println("  \"mode\": 1,");
  client.println("  \"param1\": \"param1\",");
  client.println("  \"param2\": \"param2\",");
  client.println("  \"param3\": \"param3\"");
  client.println("})");

  // シリアル通信でFPGAにパラメーターを渡す。
}


void http_response_mode2(EthernetClient client) {

  Serial.println("# MODE 2: SINGLE PULSE");

  http_response_200(client);
  client.print(callback);
  client.println("({");
  client.println("  \"mode\": 2,");
  client.println("  \"param1\": \"param1\",");
  client.println("  \"param2\": \"param2\",");
  client.println("  \"param3\": \"param3\"");
  client.println("})");

  // シリアル通信でFPGAにパラメーターを渡す。
}


void http_response_mode3(EthernetClient client) {

  Serial.println("# MODE 3: DOUBLE PULSE");

  http_response_200(client);
  client.print(callback);
  client.println("({");
  client.println("  \"mode\": 3,");
  client.println("  \"param1\": \"param1\",");
  client.println("  \"param2\": \"param2\",");
  client.println("  \"param3\": \"param3\"");
  client.println("})");

  // シリアル通信でFPGAにパラメーターを渡す。
}


void setup() {

  Ethernet.begin(mac_addr, ip_addr);
  server.begin();

  Serial.begin(9600);

  Serial.print("# IP address: ");
  Serial.println(Ethernet.localIP());
}


void loop() {

  EthernetClient client = server.available();

  if (client) {

    while (client.connected()) {

      if (client.available()) {

        char c = client.read();

        if (c != '\n' && line_buf.length() < LINE_BUF_SIZE) {

          line_buf += c;

        } else if (c == '\n') {

          line_buf.replace("GET /?", "");
          line_buf.replace(" HTTP/1.1", "");

          int i = 0;

          while (1) {

            delimiter = line_buf.indexOf('&');

            key_values[i] = line_buf.substring(0, delimiter);

            if (delimiter == -1) {
              break;
            }

            line_buf = line_buf.substring(delimiter + 1, line_buf.length());

            i++;

          }

          for (int i = 0; i < 20; i++) {

            if (key_values[i].startsWith("callback=")) {
              int pos = key_values[i].indexOf("=") + 1;
              callback = key_values[i].substring(pos);
            }

            if (key_values[i].startsWith("mode=")) {
              int pos = key_values[i].indexOf("=") + 1;
              mode = key_values[i].substring(pos).toInt();
            }

            if (key_values[i].startsWith("pulse_width=")) {
              int pos = key_values[i].indexOf("=") + 1;
              pulse_width = key_values[i].substring(pos).toInt();
            }

            if (key_values[i].startsWith("pulse_interval=")) {
              int pos = key_values[i].indexOf("=") + 1;
              pulse_interval = key_values[i].substring(pos).toInt();
            }

            if (key_values[i].startsWith("mrt3u_pulse_cycle=")) {
              int pos = key_values[i].indexOf("=") + 1;
              pulse_cycle = key_values[i].substring(pos).toInt();
            }

          }

          switch (mode) {

            case 0:
              http_response_mode0(client);
              break;

            case 1:
              http_response_mode1(client);
              break;

            case 2:
              http_response_mode2(client);
              break;

            case 3:
              http_response_mode3(client);
              break;

            default:
              http_response_404(client);
          }

          client.stop();

          line_buf = "";

        } else {
          client.flush();
        }
      }
    }
  }
}
```
ご覧の通り、**ArduinoとFPGAボードの通信部分はまだ出来ていません**。
それはこれから勉強して作成するつもりです。

一方、これを書き込んだArduinoが相手をするブラウザにはこんなHTML+JavaScriptを用意しました。

![WS000015.JPG](https://qiita-image-store.s3.amazonaws.com/0/14792/950c55c9-cb32-fabe-0460-88917b39296e.jpeg)

```HTML
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>PULSE CONTROLLER v0.1</title>
    <link rel="stylesheet" href="./css/bootstrap.min.css">
    <script src="./js/jquery-3.1.1.min.js"></script>
    <script src="./js/bootstrap.min.js"></script>
    <style>
      body { padding: 20px; }
      .navbar { margin-bottom: 20px; }
      input[type="number"] {
          text-align: right;
          -moz-appearance:textfield;
      }
      input[type="number"]::-webkit-outer-spin-button,
      input[type="number"]::-webkit-inner-spin-button {
          -webkit-appearance: none;
          margin: 0;
      }
    </style>
  </head>
  <body>
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <span class="navbar-brand" href="#">PULSE CONTROLLER v0.1</span>
      </div>
    </nav>
    <div class="container">
      <div class="row">
        <div class="col-sm-4" id="continuous-pulse">
          <h2>Continuous pulse</h2>
          <div class="btn-group switch">
            <button class="btn btn-default">ON</button>
            <button class="btn btn-primary">OFF</button>
          </div>
          <hr>
          <div class="form-group">
            <label for="continuous-pulse-width">Pulse Width</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="250" id="continuous-pulse-width">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
          <div class="form-group">
            <label for="continuous-pulse-cycle">Pulse Interval</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="250" id="continuous-pulse-interval">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
        </div>
        <div class="col-sm-4" id="single-pulse">
          <h2>Single pulse</h2>
          <div class="btn-group switch">
            <button class="btn btn-default">ON</button>
            <button class="btn btn-primary">OFF</button>
          </div>
          <hr>
          <div class="form-group">
            <label for="single-pulse-width">Pulse Width</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="250" id="single-pulse-width">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
          <div class="form-group">
            <label for="single-pulse-cycle">Pulse Cycle</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="4000000" id="single-pulse-cycle">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
        </div>
        <div class="col-sm-4" id="double-pulse">
          <h2>Double pulse</h2>
          <div class="btn-group switch">
            <button class="btn btn-default">ON</button>
            <button class="btn btn-primary">OFF</button>
          </div>
          <hr>
          <div class="form-group">
            <label for="double-pulse-width">Pulse Width</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="250" id="double-pulse-width">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
          <div class="form-group">
            <label for="double-pulse-interval">Pulse Interval</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="3000" id="double-pulse-interval">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
          <div class="form-group">
            <label for="double-pulse-cycle">Pulse Cycle</label>
            <div class="input-group">
              <input type="number" class="form-control input-lg" value="4000000" id="double-pulse-cycle">
              <div class="input-group-addon">nano-sec.</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
  <script>
    +function ($) {
        'use strict';

        var IP_ADDR = '192.168.11.100';

        var request = {
            "url": "http://" + IP_ADDR,
            "params": {},
            "send": function() {
                console.log(this.params);
                var defer = $.Deferred();
                $.ajax({
                    type: "GET",
                    url: this.url,
                    data: this.params,
                    dataType: "jsonp",
                    success: defer.resolve,
                    error: defer.reject
                });
                return defer.promise();
            }
        }


        var $continuous_pulse_switch = $('#continuous-pulse .switch');
        var $continuous_pulse_switch_on = $continuous_pulse_switch.children().first();
        var $continuous_pulse_switch_off = $continuous_pulse_switch.children().last();

        var $single_pulse_switch = $('#single-pulse .switch');
        var $single_pulse_switch_on = $single_pulse_switch.children().first();
        var $single_pulse_switch_off = $single_pulse_switch.children().last();

        var $double_pulse_switch = $('#double-pulse .switch');
        var $double_pulse_switch_on = $double_pulse_switch.children().first();
        var $double_pulse_switch_off = $double_pulse_switch.children().last();


        $continuous_pulse_switch.bind('turnOn', function() {
            $continuous_pulse_switch_on.addClass('btn-primary').removeClass('btn-default');
            $continuous_pulse_switch_off.addClass('btn-default').removeClass('btn-primary');
        });

        $continuous_pulse_switch.bind('turnOff', function() {
            $continuous_pulse_switch_on.addClass('btn-default').removeClass('btn-primary');
            $continuous_pulse_switch_off.addClass('btn-primary').removeClass('btn-default');
        });

        $single_pulse_switch.bind('turnOn', function() {
            $single_pulse_switch_on.addClass('btn-primary').removeClass('btn-default');
            $single_pulse_switch_off.addClass('btn-default').removeClass('btn-primary');
        });

        $single_pulse_switch.bind('turnOff', function() {
            $single_pulse_switch_on.addClass('btn-default').removeClass('btn-primary');
            $single_pulse_switch_off.addClass('btn-primary').removeClass('btn-default');
        });

        $double_pulse_switch.bind('turnOn', function() {
            $double_pulse_switch_on.addClass('btn-primary').removeClass('btn-default');
            $double_pulse_switch_off.addClass('btn-default').removeClass('btn-primary');
        });

        $double_pulse_switch.bind('turnOff', function() {
            $double_pulse_switch_on.addClass('btn-default').removeClass('btn-primary');
            $double_pulse_switch_off.addClass('btn-primary').removeClass('btn-default');
        });


        $continuous_pulse_switch_on.on('click', function() {
            request.params = {
                'mode': 1,
                'pulse_width': get_continuous_pulse_width(),
                'pulse_interval': get_continuous_pulse_interval(),
                'pulse_cycle': 0
            };
            request.send().done(function(data) {
                console.log(data);
                $continuous_pulse_switch.trigger('turnOn');
                $single_pulse_switch.trigger('turnOff');
                $double_pulse_switch.trigger('turnOff');
            });
        });

        $continuous_pulse_switch_off.on('click', function() {
            request.params = {
                'mode': 0
            };
            request.send().done(function(data) {
                console.log(data);
                $continuous_pulse_switch.trigger('turnOff');
            });
        });

        $single_pulse_switch_on.on('click', function() {
            request.params = {
                'mode': 2,
                'pulse_width': get_single_pulse_width(),
                'pulse_interval': 0,
                'pulse_cycle': get_single_pulse_cycle()
            };
            request.send().done(function(data) {
                console.log(data);
                $continuous_pulse_switch.trigger('turnOff');
                $single_pulse_switch.trigger('turnOn');
                $double_pulse_switch.trigger('turnOff');
            });
        });

        $single_pulse_switch_off.on('click', function() {
            request.params = {
                'mode': 0
            };
            request.send().done(function(data) {
                console.log(data);
                $single_pulse_switch.trigger('turnOff');
            });

        });

        $double_pulse_switch_on.on('click', function() {
            request.params = {
              'mode': 3,
              'pulse_width': get_double_pulse_width(),
              'pulse_interval': get_double_pulse_interval(),
              'pulse_cycle': get_double_pulse_cycle(),
            };
            request.send().done(function(data) {
                console.log(data);
                $continuous_pulse_switch.trigger('turnOff');
                $single_pulse_switch.trigger('turnOff');
                $double_pulse_switch.trigger('turnOn');
            });
        });

        $double_pulse_switch_off.on('click', function() {
            request.params = {
                'mode': 0
            };
            request.send().done(function(data) {
                console.log(data);
                $double_pulse_switch.trigger('turnOff');
            });

        });


        var get_continuous_pulse_width = function() {
            return parseFloat($('#continuous-pulse-width').val());
        }

        var get_continuous_pulse_interval = function() {
            return parseFloat($('#continuous-pulse-interval').val());
        }

        var get_single_pulse_width = function() {
            return parseFloat($('#single-pulse-width').val());
        }

        var get_single_pulse_cycle = function() {
            return parseFloat($('#single-pulse-cycle').val());
        }

        var get_double_pulse_width = function() {
            return parseFloat($('#double-pulse-width').val());
        }

        var get_double_pulse_interval = function() {
            return parseFloat($('#double-pulse-interval').val());
        }

        var get_double_pulse_cycle = function() {
            return parseFloat($('#double-pulse-cycle').val());
        }


        request.params = {
            'mode': 0
        };
        request.send().done(function(data) {
            console.log(data);
        });

    }(jQuery);
  </script>
</html>
```

スタイルシートに[Bootstrap3](http://getbootstrap.com/)を、JavaScriptフレームワークには[jQuery](https://jquery.com/)を利用しているので、それぞれダウンロードして、HTMLのHEAD内に記載されているパスのとおりに配置しておく必要があります。

```
pulse_controller.html
├ css
│　 └ bootstrap.min.css
└ js
　　├ jquery-3.1.1.min.js
　　└ bootstrap.min.js
```

これをローカルの適当なフォルダーに放り込んで、HTMLファイルをダブルクリックしてブラウザで読み込みます。
上に貼り付けた画面が表示されるので、ON/OFFボタンを操作したりするとArduinoに設定値が送信されます。

シリアルモニタにログが出ます。

![WS000016.JPG](https://qiita-image-store.s3.amazonaws.com/0/14792/8b7232b3-c56a-5960-8f53-6cadf927ed74.jpeg)


以上のような感じです。

相変わらず動かすことを最優先でコードを書いているので、「ここはこう書いたほうがいいよ」などのご意見ございましたらコメントでアドバイスいただけますと幸甚です。

よろしくお願いします。