WiFi遠隔操作Arduinoコマンドでブラウザベースのスマホ、PCリモートコントローラの紹介

M5StickC Plus
スポンサーリンク

WiFi通信を使用してブラウザベースで遠隔操作、リアルタイムデータ通信を行う方法をコピペ用サンプルプログラムを使って紹介します。

今回は「M5StickC Plus」を使ってますが他のマイコンボードでも基本は同じで、サーバー機能を利用して「JavaScript」の「fetch」を使うことでデータの送受信を行います。

まずはサーバーの「仕組み」についての紹介が必要と思いますので前置きが長いですが、サーバーについての紹介が不要な方は以下の目次から早速サンプルプログラムの動作確認をしてみてください。

1.動作原理

・概要

ブラウザベースのリモートコントローラの動作原理について紹介します。
以前に以下のリンクで「簡易IoT」の紹介をしました。

簡易IoT(稼働監視、遠隔操作)テストプログラム(ATOM LITE使用)の紹介
簡易ですが「IoT」やりましょう♪ IoTとは「Internet of Things」の略で「モノのインターネット」と呼ばれるものです。スマホから遠隔操作でLチカしたり、ボタンを押した回数やアナログ値の取得もできます。

この時もマイコンボード本体をサーバーに設定してブラウザボタンのリンク機能を利用することで遠隔操作を実現しましたが、リンクを送信する度にページが更新されてしまいます。

本体のデータを表示する時もブラウザページを1秒ごとに再読み込みすることで表示をしていました。
シンプルなページであれば小規模な構成で実現できるため、これはこれで便利です。

しかし、容量の大きなページの場合は表示が完了する前にまた再読み込みということになり実用的ではありません。
特にカメラの動画をモニター表示する場合には、頻繁にページが更新されるとまともに映像が見られません。

こうならないように今回はブラウザページの一部の情報(要素)だけを「JavaScript」で取得してデータ送信または更新することで、ページ全体を再読み込みすることなく遠隔操作、データ取得を行います。

これからサーバー機能を利用した遠隔操作の方法を紹介していきますが、単純な操作をするだけなのに回りくどい処理を行っているように思えるかもしれません。

今回はあくまで「サーバー機能を利用」した遠隔操作です。
これは本来のサーバーの利用目的とは違うかもしれませんが、使えるものは使っちゃいましょう♪

ただ自分で作っていても回りくどいと思うことが何度もありました(汗)。
まず先にサーバーの動作について理解しておくと「サーバー利用してるからしょうがないか」と思えると思うので、先にサーバーの動作について簡単に紹介しておきます。

・サーバーとは

プログラムをやっていると「サーバー」等のように、いろんな用語が出てきます。
これらを理解するには、まずその語源を知ることが手っ取り早いと思います。

「サーバー」とは英語で「server」と書きます。
でも、このまま翻訳しても「サーバ」と出てくるのがほとんどだと思います。

調べてみると「server」は「serve(仕える、尽くす)」+「er(~する人)」を組み合わせたもので「仕える者、尽くす者」といった意味のようです。

ここから「server」とはそれ単体で何かをするものでなく、求められたときに何かをしてくれるものだと想像されます。

実際にその通りで「サーバー」の動作は下画像のようになります。

サーバーについて

ここで「クライアント」という言葉が出てきます。
「クライアント」とは英語で「client」と書かれ「顧客、取引先、来訪者」と訳されます。

「サーバー」から見た「クライアント」とは、「サーバー」に対して何かを要求する私たちが操作するパソコンやスマホ、タブレット等のことを言います。

「サーバー」は「クライアント」の「要求」に対して忠実に「応答」を返します。
「要求」と「応答」を英訳すると

・要求:request(リクエスト)
・応答:response(レスポンス)

となります。

ということで「サーバー」とは「インターネット」経由で「クライアント」の「リクエスト」に対して忠実に「レスポンス」を返す「仕組み」の事を言います。

「リクエスト」に対して忠実に「レスポンス」を返しますが、逆に言えば「リクエスト」がなければ基本的に何もしません。
「サーバー」が「リクエスト」無しに自分から勝手に「レスポンス」としてデータを送信することはありません。

実際にWebページで何かを検索することに例えると、何も検索してないのに勝手にページが表示されたら困りますね。
この「仕組み」はセキュリティーを考えると非常に良くできた「仕組み」です。

「仕組み」なので「サーバー」は特別なものではなく、この「仕組み」を持ったパソコンです。
小規模であればマイコンボードにもこの「仕組み」を実装することができます。

今回はこの「サーバー」の「仕組み」を利用します。
ですので「サーバー」機能を持たせた「マイコンボード」本体の状態を知るためには「リクエスト」を送って「レスポンス」として応答を返してもらう必要があります。
本体のセンサが何かを検知したとしても自分からは知らせてくれません。

この辺りが回りくどく感じるかもしれませんが「サーバー」を利用するということはそういうことです。

この「仕組み」を使う利点もたくさんあって「サーバー」との通信は「WiFi通信」で行い、データの表示や操作はスマホやパソコンの「ブラウザ」を使用することができます。

「WiFi通信」も「ブラウザ」も身の回りに身近にあります。
スマホでもパソコンでも機種やメーカーが違ってもほぼ同じように動作します。

同じ「WiFi通信」ネットワーク内であればどんなに離れていても操作可能で、ラジコンやチルトパン付きの監視カメラもつくれますし、侵入検知や温度等のデータ収集もできます。

特別な端末やアプリを使用せずに手軽に実行できる遠隔操作の環境としては最も良い手段と思いますので便利に使っていきましょう。

2.動作紹介

今回準備したサンプルプログラムは以下のような動作になります。

プログラムを書き込むと「M5StickC Plus」に「IPアドレス」が表示されます。
これを同じネットワーク上に接続しているスマホやパソコンのブラウザを立ち上げてアドレスバーに入力してください。

ブラウザページのデータは「M5StickC Plus」内に文字列で埋め込んであるので「IPアドレス」を入力すると「サーバー」機能でブラウザページのデータが送信され下画像(スマホ画面)のように表示されます。

前回「簡易IoT」を紹介した時にプログラムが長すぎて「コピペ」の限界を感じました。
今回はその時より間違いなくプログラムは長くなるので、できる限りコンパクトに作成してます。
ですのでボタンは1個、表示データも1か所となってますが、拡張性は持たせてあるので必要な行を「コピぺ」で増やして編集していけばボタンや表示データを増やすことができます。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

「M5StickC Plus」本体の液晶画面に表示された「IPアドレス」の「192.168.0.31(お使いの環境によって変わります)」をスマホのアドレスバーに入力すると上画像のような画面が表示されます。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

スマホ画面の「ボタン0」を押すと「M5StickC Plus」本体のLED赤が点灯し、本体液晶画面の「LED_RED:」が1から0に変わります。
スマホ画面では「ボタン0」の色が緑色に変わります。


Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

「M5StickC Plus」本体正面のボタンを押すと本体のLED赤が点灯し、本体液晶画面の「LED_RED:」が1から0に変わります。
スマホ画面では「ボタン0」の色が緑色に変わります。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

ボリュームを回すと本体液晶画面の「ADC:」表示のアナログ入力電圧が変化し、スマホ画面ではアナログ入力電圧の表示が変化します。


次にページが更新されていないことを確認します。
更新されていなければカメラの映像がリアルタイムで表示されるはずです。

おまけとして「M5CAMERA」の映像もブラウザページに表示できるようにしてあります。
「Arduino IDE」のスケッチ例でネットワークカメラに設定し、スマホ画面のテキストボックスに「http://カメラのIPアドレス/stream」と入力し「エンター」を押すと映像が表示されます。

以下が「M5CAMERA」の画像です。
液晶表示器SSD1306を使用してSSIDとIPアドレスが確認できるようにしたものです。

M5CAMERA 液晶表示器SSD1306で接続情報表示
M5CAMERA(OV2640)

「SSD1306 液晶表示器OLED」については以下のリンクで詳しく紹介しています。

SSD1306 液晶表示器OLEDの使い方、簡単2画面表示の方法も紹介
SSD1306はライブラリを準備して、I2C通信設定を行えば、簡単なコマンドで表示可能。ATOM LITEやESP32、Arduino等に液晶表示器を追加したい時に手軽に安価で実現できるのでとても便利です。
「M5CAMERA」についても紹介したかったのですが「M5Stack社」の公式ホームページからも消えていて生産中止のようです。
小型で扱いやすくて結構好きだったんですけど・・・後日「TIMER CAMERA」等を購入して使えるようになったらまた紹介したいと思います。
「Stream」表示のできるカメラをお持ちの方は試してみてください。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

カメラのストリーム画像表示アドレスを入力すると「M5CAMERA」の映像がブラウザ画面内に表示されます。
カメラの映像は「M5StickC Plus」を経由せずにスマホに直接表示されてます。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

スマホ側をカメラで写すと上画像のような不思議な映像がブラウザ上に表示されます。


Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

ブラウザの「ボタン0」を押している間、本体LEDは点灯し続け映像は途切れず表示されています。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

「M5StickC Plus」本体のボタンを操作しても同じ状態です。


今回のサンプルプログラムでは本体ボタンの代わりに外部センサ等の状態を検知したり、本体LEDの代わりに外付けのリレーを動作させて遠隔操作スイッチにすることも想定して、入出力端子も連動して使用できるようにしてあります。

動作確認のために外部センサ代わりにボタンスイッチを、外部リレーの代わりに抵抗内蔵LEDを使用して動作確認を行います。

M5StickC PlusのGroveコネクタ増設

追加ボタンの「デュアルボタンユニット」を接続するのにちょうどいいものがありました。
GROVE用 4ピン変換コネクタです。
詳細は下の「4.用意するもの」の部品リスト参照してください。

M5StickC PlusのGroveコネクタ増設

「M5StickC Plus」の「5V」と「GND」端子に合わせて差し込むと「G26」「G36/G25」が「Groveコネクタ」で使用できます。
ここに「デュアルボタンユニット」を接続します。


M5StickC PlusのGroveコネクタ増設

抵抗内蔵Lの青色LEDは上画像のように「3.3V」に「+」を「G0」に「ー」を接続します。

M5StickC PlusのGroveコネクタ増設

上画像のように赤色ボタンを押すと本体LED赤と連動して外付けLED青が点灯します。
スマホ画面では「ボタン0」が緑色になるのが確認できると思います。


M5StickC PlusのGroveコネクタ増設

「M5StickC Plus」の本体ボタンを押すと、本体LED赤と連動して、追加した抵抗内蔵LED青も点灯します。
スマホ画面では「ボタン0」が緑色になるのが確認できると思います。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作

全体の構成は上画像のようになります。
基本的な動作だけなら「M5StickC Plus」と「ボリューム」だけあれば大丈夫です。


ブラウザであればほとんどのブラウザで動作確認できます。
下画像は「Microsoft Edge」で表示したものです。

パソコンのブラウザの場合下画像右側のように「開発ツール」(Ctrl+Shift+i か右クリックで選択して開きます)のコンソールでログ情報が確認できます。

1秒ごとに本体情報(info)やアナログ入力値(v_in)、LEDの状態(LED_state)が「M5StickC Plus」から送信されて表示されます。

Arduinoコマンド使用でブラウザベースのWiFi遠隔操作
ブラウザで動作確認するにはそのブラウザが「JavaScript」の「fetch」に対応している事が条件です。
最近のものならほとんど対応しているようですが「Internet Explorer」では対応していないようです。また、バージョンの古いブラウザでも動作しない場合があります。
「簡易IoT」で使用した「iphone4s」の「Safari」もバージョンが古くて動作しませんでした。

3.用意するもの

用意するものは、スマホやパソコンと以下の部品です。
動作確認だけなら「マイコンボード(M5StickC Plus)」と「ボリューム」だけあれば出来ます。


スイッチは以下のボタンユニットにGrove変換コネクタの組み合わせで使用しています。

ほとんどがAmazonで購入できますが、抵抗内蔵LEDは「秋月電子通商」の方が必要な数量、色だけ購入できて送料(600円)を考えても別で購入するのがお得だと思います。

品名型式等購入先価格
抵抗内蔵LED抵抗内蔵5mmLED 5V
青:[ I-06247 ]
秋月電子通商1パック(10個)200円
1個25円

「M5StickC Plus」や「ボリューム」「スイッチ」「LED」については以下のリンクで詳しく紹介しています。

M5StickC Plusの使い方、初期設定、サンプルプログラム、M5StickCとの違い等を詳しく紹介
M5StickC PlusをArduino IDEやPlatformIOで使うための初期設定やサンプルプログラムでの動作確認の方法です。ビジュアルプログラミングのUiFlowの初期設定についても紹介します。
ボリューム(可変抵抗器)の使い方、つなぎ方、抵抗値計算等を回路図も使って詳しく解説
ボリュームというと音量調節のイメージですが、明るさや回転数等を調整するのにも使用されます。「抵抗(固定抵抗器)」が固定の抵抗値を持つのに対して「可変抵抗器」は抵抗値を調整することができます。このボリューム(可変抵抗器)について紹介します。
スイッチ(操作用)の使い方。身近にあるスイッチについて詳しく紹介
スイッチとは「開閉器」とも呼ばれ電気の通り道をつないだり開いたりするものです。 スイッチには人が操作して動作する「操作スイッチ」と、状況によって動作する「検知スイッチ(センサー)」があります。 ここでは「操作スイッチ」について紹介します。
LED(発光ダイオード)の使い方。Lチカでの動作確認方法
LEDが登場するまで光源としては白熱電球が主流でした。白熱電球は電気を熱に変えて光りますがLEDは+と-の電気が結合する時のエネルギーで光り、電気を直接光に変換するため発光効率が非常に良いです。

4.配線図

各部品を接続した配線図は下画像のようになります。

M5StickC Plus 遠隔操作配線

基本的な動作の確認は「M5StickC Plus」と「ボリュームユニット」だけ接続すれば大丈夫です。

5.サンプルプログラム(コピペ)

サンプルプログラムは以下になります。「コピペ」して書き込んでください。
(出来るだけ行数が少なくなるように書いてあるので見にくい部分があるかもしれません。)

※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

#include <M5StickCPlus.h>
#include <WiFi.h>       //WiFi接続用
#include <WebServer.h>  //サーバー設定用
// サーバー設定ポート80で接続
WebServer server(80);
// 端子割り付け
#define IN0 26  //入力端子
#define IN1 36
#define OUT0 0  //出力端子
#define ADC0 33 //アナログ入力端子
// 変数設定
float ad_val;   //アナログ入力値格納用
float v_in = 0; //アナログ入力電圧換算値
int btn_pw = 0; //電源ボタン状態取得
float battery;  //バッテリー残量表示用
int btn0_sig;   //ブラウザボタン0信号
// htmlブラウザ画面データ(文字列)----------------------------------------
// 「"」は「\"」に置き換え、htmlの改行は「\n」、コードの改行は「\」
String html = "\
<!DOCTYPE html><html lang=\"jp\"><head>\n\
  <meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\
  <title>REMOTE-CONTROLLER</title>\n\
<!-- CSS処理(ブラウザページ装飾)------------------------ -->\n\
  <style>\n\
    body{font-family: sans-serif; background-color: #22578b; max-width: 480px; margin: 0 auto; align-items: center;}\n\
    h1 {color:#ffffff; text-align: center; font-size: 28px; margin: 10px auto;}\n\
    div {display: flex; flex-direction: row; justify-content: center; margin-top: 10px;}\n\
    p {margin: 0px;}\n\
    td {padding: 0px 15px; width: 110px; color:#ffffff; text-align: center; font-size: 18px; width: auto;}\n\
    .btn {height: 70px; width: 100px; color: #555555; background-color: #dddde9; font-size: 18px; font-weight: bold; border-radius: 7%; margin: 0 10px; -webkit-appearance: none;}\n\
    .btn_on {background-color: springgreen;}\n\
  </style></head>\n\
<!-- html処理(ブラウザ表示)----------------------------- -->\n\
<body>\n\
  <h1>REMOTE-CONTROLLER</h1>\n\
  <div>\n\
    <!-- ボタンが複数の場合は以下をコピペで増やす。idのbtn番号変更 -->\n\
    <button class=\"btn\" id=\"btn0\">ボタン0</button>\n\
  </div>\n\
  <div> <table border = \"1\">\n\
    <!-- 表示内容が複数の場合は以下をコピペで増やす。idはJSONのkeyに変更 -->\n\
    <tr><td><p>アナログ<br>入力電圧</p></td> <td><span id=\"v_in\">0</span><span>V</span></td></tr>\n\
  </table> </div>\n\
<!-- 以下はおまけ、M5CAMERAの「IPアドレス:81/stream」の動画表示 -->\n\
  <div>\n\
    <form id=\"id_form1\" onsubmit=\"return startStream()\">\n\
      <input id=\"input_IP\" type=\"text\" placeholder=\"カメラIPアドレス\" />\n\
    </form>\n\
  </div>\n\
  <hr width='90%' />\n\
  <div id=\"output\"></div>\n\
<!-- JavaScript処理--------------------------------------- -->\n\
  <script type=\"text/javascript\">\n\
    let btn = [];\n\
    const btnOn = (i) => {  //ボタンON時処理(ボタン番号「i」ごとに分岐、複数可)\n\
      btn[i].classList.add('btn_on');\n\
      switch (i) {\n\
        case 0: getBtnOn(i); break;\n\
      }\n\
    }\n\
    const btnOff = (i) => {  //ボタンOFF時処理(ボタン番号「i」ごとに分岐、複数可)\n\
      btn[i].classList.remove('btn_on');\n\
      switch (i) {\n\
        case 0: getBtnOff(i); break;\n\
      }\n\
    }\n\
    for (let i = 0; i < 1; i++) { //ブラウザボタン状態取得(イベント処理、複数可)※i=btn番号\n\
      btn[i] = document.getElementById('btn' + i);\n\
      btn[i].addEventListener('touchstart', (e) => {e.preventDefault(); btnOn(i);} );\n\
      btn[i].addEventListener('mousedown', () => {btnOn(i);} );\n\
      btn[i].addEventListener('touchend', () => {btnOff(i);} );\n\
      btn[i].addEventListener('mouseup', () => {btnOff(i);} );\n\
    }\n\
    let get_data;\n\
    async function getData() {  //マイコンボード側JSONデータ取得(インターバル)\n\
      await fetch(\"/get/data\")\n\
        .then((response) => {if (response.ok) {return response.json();} else {throw new Error();} })\n\
        .then((json) => {\n\
          console.log(json);\n\
          get_data = json;\n\
          let el;\n\
          //以下に取得したデータごとに処理したい内容を記入\n\
          el = document.querySelector('#v_in'); //アナログ電圧表示要素取得(idはJSONのキー)\n\
          el.textContent = get_data.v_in;       //アナログ電圧表示更新\n\
          if (get_data.LED_state == 0) {btn[0].classList.add('btn_on');}  //LEDがONならブラウザボタン緑\n\
          else {btn[0].classList.remove('btn_on');}                       //LEDがOFFならブラウザボタン白\n\
        })\n\
        .catch((error) => console.log(error));\n\
    }\n\
    setInterval(getData, 1000); //インターバル設定(1秒ごとに本体データ取得)\n\
    \n\
    async function getBtnOn(i) {  //ブラウザボタンON時処理(複数可)\n\
      let link;\n\
      switch (i) {\n\
        case 0: link = \"/get/btn0_on\"; break; //ブラウザボタン番号(btn i)ごとに処理を分岐\n\
      }\n\
      await fetch(link)\n\
        .then((response) => { if (response.ok) {return response.text();} else {throw new Error();} })\n\
        .then((text) => { console.log(text) })\n\
        .catch((error) => console.log(error));\n\
    }\n\
    async function getBtnOff(i) { //ブラウザボタンOFF時処理(複数可)\n\
      let link;\n\
      switch (i) {\n\
        case 0: link = \"/get/btn0_off\"; break;  //ブラウザボタン番号(btn i)ごとに処理を分岐\n\
      }\n\
      await fetch(link)\n\
        .then((response) => { if (response.ok) {return response.text();} else {throw new Error();} })\n\
        .then((text) => { console.log(text) })\n\
        .catch((error) => console.log(error));\n\
    }\n\
    // M5CAMERA動画取得\n\
    const startStream = () => {\n\
      let target = document.getElementById(\"output\");\n\
      let stream = document.forms.id_form1.input_IP.value;\n\
      console.log(\"Start Stream:\" + stream);\n\
      stream = \"<img src= '\"+ stream + \"' width='90%' height='90%'>\";\n\
      target.innerHTML = stream;\n\
      return false;\n\
    }\n\
  </script></body></html>\n";
// サーバーリクエスト時の処理関数 -------------------------------------------
// ルートアクセス時の応答関数
void handleRoot() {
  // htmlData(); //htmlデータ更新
  server.send(200, "text/html", html);              //レスポンス200を返しhtml送信
}
// エラー(Webページが見つからない)時の応答関数
void handleNotFound() {
  server.send(404, "text/plain", "404 Not Found!"); //エラー情報送信(text)
}
// ブラウザONボタン処理
void btn0On() {
  btn0_sig = 1;                                     //ブラウザボタン信号を1(ON)にする
  server.send(200, "text/plain", "ボタン0 ON");     //レスポンス200を返し情報送信(text)
}
// ブラウザOFFボタン処理
void btn0Off() {
  btn0_sig = 0;                                     //ブラウザボタン信号を0(OFF)にする
  server.send(200, "text/plain", "ボタン0 OFF");    //レスポンス200を返し情報送信(text)
}
// ブラウザへデータ送信(JSONファイル)-------------------------------------
void getData() {
  String data= "";
  //JSONフォーマット({ "key(項目)" : "value(値)" ~ ,"key(項目)" : "value(値)"})
  data += "{\"info\":\"";   data += "M5StackC Plus";       //本体情報
  data += "\",\"v_in\":\"";  data += v_in;                 //アナログ入力電圧
  data += "\",\"LED_state\":\"";  data += digitalRead(10); //本体LEDの状態
  data += "\"}";
  server.send(200, "text/plain", data);                    //JSONデータ送信実行
}
// WiFi接続処理 -----------------------------------------------------------
const char ssid[] = "自宅のWiFi接続先を記入";  //接続先SSID
const char pass[] = "接続先のパスワードを記入"; //接続先パスワード
void WiFiLocal() {
  WiFi.begin(ssid, pass);                 //WiFiローカル接続開始
  // WiFi接続完了待ち
  while (WiFi.status() != WL_CONNECTED) { //接続完了するまで繰り返す
    delay(500);                           //0.5秒待機
    Serial.print(".");                    //「.」シリアル出力
  }
  // 接続情報シリアル出力
  Serial.printf("\nSSID:%s\n", ssid);     //接続先SSID表示
  Serial.printf("PASS:%s\n", pass);       //接続パスワード表示
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());         //IPアドレス(配列)
  //ブザー処理
  M5.Beep.beep(); //ブザー初期値(周波数:4000, 発音時間:100ms)で鳴らす
}
// 初期設定 ----------------------------------------------
void setup() {
  M5.begin(); //(LCD有効, POWER有効, Serial有効)
  Serial.begin(9600); //標準のシリアル通信設定
  WiFiLocal();        //WiFi接続処理呼出し
  // サーバー設定
  server.on("/", handleRoot);           //ルートアクセス時の応答関数
  server.onNotFound(handleNotFound);    //Webページが見つからない時の応答関数
  server.on("/get/btn0_on", btn0On);    //ボタン0オン受信処理
  server.on("/get/btn0_off", btn0Off);  //ボタン0オフ受信処理
  server.on("/get/data", getData);      //ブラウザへのデータ送信処理
  server.begin();                       //Webサーバー開始
  // 入出力ピン設定
  // 入力設定
  pinMode(IN0, INPUT_PULLUP); //入力設定(プルアップ)
  pinMode(IN1, INPUT_PULLUP);
  // 出力設定
  pinMode(OUT0, OUTPUT);      //本体LED赤と連動
  pinMode(10, OUTPUT);        //本体LED赤
  pinMode(2, OUTPUT);         //本体ブザー
  digitalWrite(OUT0, HIGH);   //OUT0初期値OFF(HIGH)
  digitalWrite(10, HIGH);     //本体LED初期値OFF(HIGH)
  // アナログ入力設定
  pinMode(ADC0, ANALOG);      //アナログ入力
  // G36とG25は同時使用不可。使っていない方は以下のようにフローティング入力にする
  gpio_pulldown_dis(GPIO_NUM_25); //G25をフローティング入力に設定
  gpio_pullup_dis(GPIO_NUM_25);
  // 液晶表示初期設定
  M5.Lcd.fillScreen(BLACK); //背景色
  M5.Lcd.setRotation(1);    //画面向き設定(USB位置基準 0:下/ 1:右/ 2:上/ 3:左)
  M5.Lcd.setTextSize(1);    //文字サイズ(整数倍率)
  M5.Lcd.setTextFont(4);    //フォント 1(8px), 2(16px), 4(26px), 6(36px数字-.:apm), 7(7セグ-.:), 8(75px数字-.:)
}
// メイン -----------------------------------------
void loop() {
  M5.update();                //本体ボタン状態更新(ブザーも更新)
  server.handleClient();      //クライアント(ブラウザ)からのアクセス確認
  // 本体ボタン、外部ボタン、ブラウザボタン入力処理
    //ボタンA または 外部スイッチ赤 または ブラウザボタン0 が押されているなら
  if (M5.BtnA.isPressed() || digitalRead(IN0) == 0 || btn0_sig == 1) {
    digitalWrite(10, LOW);    //本体LED赤点灯
    digitalWrite(OUT0, LOW);  //OUT0出力(本体LED赤連動)
  } else {                    //そうでなければ
    digitalWrite(10, HIGH);   //本体LED赤消灯
    digitalWrite(OUT0, HIGH); //OUT0出力OFF(HIGH)
  }
  // アナログ入力処理
  ad_val = analogRead(ADC0);    //アナログ入力値を取得
  v_in = ad_val * (3.3 / 4095); //3.3Vへ換算
  // バッテリー残量(MAX約4.2V、限界電圧3V)パーセント換算表示
  battery = (M5.Axp.GetBatVoltage() - 3) * 90;  //バッテリー残量取得、換算
  if (battery > 100) { battery = 100; }         //換算値が100以上なら100にする
  // LCD表示処理
  M5.Lcd.setTextFont(4);  //フォント変更
  M5.Lcd.setCursor(5, 5);  M5.Lcd.setTextColor(CYAN, BLACK);    //SSID表示
  M5.Lcd.print("SSID : "); M5.Lcd.print(ssid);
  M5.Lcd.setCursor(5, 30); M5.Lcd.setTextColor(ORANGE, BLACK);  //IPアドレス表示
  M5.Lcd.print("IP        : "); M5.Lcd.print(WiFi.localIP());
  M5.Lcd.setCursor(5, 55);  M5.Lcd.setTextColor(RED, BLACK);    //本体LEDの状態表示
  M5.Lcd.printf("LED_RED   : %d", digitalRead(10));
  M5.Lcd.setCursor(5, 80); M5.Lcd.setTextColor(WHITE, BLACK);   //アナログ入力値表示
  M5.Lcd.printf("ADC : %01.2fv  ( %04.0f )", v_in, ad_val);
  M5.Lcd.setTextFont(2);  //フォント変更
  M5.Lcd.setCursor(200, 118); M5.Lcd.setTextColor(DARKGREY, BLACK); //バッテリー残量表示
  M5.Lcd.printf("%.0f%%    ", battery);
  delay(100);   //遅延時間(ms)
}

上コードハイライト部のサーバー設定プログラムについては以下のリンクで詳しく紹介しています。

WiFi(ローカル接続、サーバー)で遠隔操作する方法(Arduinoプログラミング)
WiFiローカル環境に接続して、スマホから遠隔操作を行う方法を紹介します。遠隔操作は、サーバーのリクエスト(要求)に対してレスポンス(応答)を返すという動作を利用してプログラムを作成することで簡単に遠隔操作が実現できます。ATOM LITE

6.操作画面(ブラウザページ)プログラム(抜粋)

サンプルプログラムではブラウザページのデータは文字列としているため色分けがされず見にくいので、ブラウザページのデータだけ以下に抜粋しました。

このデータは「メモ帳」に「コピペ」で貼り付けて、拡張子「.txt」を「.html」に書き換えて保存して開くことで単体でもサンプルプログラムと同じページが表示されます。

単体で表示させた場合、表示はされてもデータの取得や操作はできません。(カメラモニターは機能します。)
これで操作できると便利なのですが、外部のプログラムからサーバーのデータが操作されるのはセキュリティ的に問題があります。
今回はサーバー機能を利用しているのでサーバー内にブラウザデータを置いてそれを使用することで、決められたアドレスにアクセスした時に、決められたデータのみ操作、確認することができます。

※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

<!DOCTYPE html>
<html lang="jp">
<head>
  <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>REMOTE-CONTROLLER</title>
<!-- CSS処理(ブラウザページ装飾)------------------------ -->
  <style>
    body{font-family: sans-serif; background-color: #22578b; max-width: 480px; margin: 0 auto; align-items: center;}
    h1 {color:#ffffff; text-align: center; font-size: 28px; margin: 10px auto;}
    div {display: flex; flex-direction: row; justify-content: center; margin-top: 10px;}
    p {margin: 0px;}
    td {padding: 0px 15px; width: 110px; color:#ffffff; text-align: center; font-size: 18px; width: auto;}
    .btn {height: 70px; width: 100px; color: #555555; background-color: #dddde9; font-size: 18px; font-weight: bold; border-radius: 7%; margin: 0 10px; -webkit-appearance: none;}
    .btn_on {background-color: springgreen;}
  </style>
</head>
<!-- html処理(ブラウザ表示)----------------------------- -->
<body>
  <h1>REMOTE-CONTROLLER</h1>
  <div>
    <!-- ボタンが複数の場合は以下をコピペで増やす。idのbtn番号変更 -->
    <button class="btn" id="btn0">ボタン0</button>
  </div>
  <div> <table border = "1">
    <!-- 表示内容が複数の場合は以下をコピペで増やす。idはJSONのkeyに変更 -->
    <tr><td><p>アナログ<br>入力電圧</p></td> <td><span id="v_in">0</span><span>V</span></td></tr>
  </table> </div>
<!-- 以下はおまけ、M5CAMERAの「IPアドレス:81/stream」の動画表示 -->
  <div>
    <form id="id_form1" onsubmit="return startStream()">
      <input id="input_IP" type="text" placeholder="カメラIPアドレス" />
    </form>
  </div>
  <hr width='90%' />
  <div id="output"></div>
<!-- JavaScript処理--------------------------------------- -->
  <script type="text/javascript">
    let btn = [];
    const btnOn = (i) => {  //ボタンON時処理(ボタン番号「i」ごとに分岐、複数可)
      btn[i].classList.add('btn_on');
      switch (i) {
        case 0: getBtnOn(i); break;
      }
    }
    const btnOff = (i) => {  //ボタンOFF時処理(ボタン番号「i」ごとに分岐、複数可)
      btn[i].classList.remove('btn_on');
      switch (i) {
        case 0: getBtnOff(i); break;
      }
    }
    for (let i = 0; i < 1; i++) { //ブラウザボタン状態取得(イベント処理、複数可)※i=btn番号
      btn[i] = document.getElementById('btn' + i);
      btn[i].addEventListener('touchstart', (e) => {e.preventDefault(); btnOn(i);} );
      btn[i].addEventListener('mousedown', () => {btnOn(i);} );
      btn[i].addEventListener('touchend', () => {btnOff(i);} );
      btn[i].addEventListener('mouseup', () => {btnOff(i);} );
    }
    let get_data;
    async function getData() {  //マイコンボード側JSONデータ取得(インターバル)
      await fetch("/get/data")
        .then((response) => {if (response.ok) {return response.json();} else {throw new Error();} })
        .then((json) => {
          console.log(json);
          get_data = json;
          let el;
          //以下に取得したデータごとに処理したい内容を記入
          el = document.querySelector('#v_in'); //アナログ電圧表示要素取得(idはJSONのキー)
          el.textContent = get_data.v_in;       //アナログ電圧表示更新
          if (get_data.LED_state == 0) {btn[0].classList.add('btn_on');}  //LEDがONならブラウザボタン緑
          else {btn[0].classList.remove('btn_on');}                       //LEDがOFFならブラウザボタン白
        })
        .catch((error) => console.log(error));
    }
    setInterval(getData, 1000); //インターバル設定(1秒ごとに本体データ取得)
    
    async function getBtnOn(i) {  //ブラウザボタンON時処理(複数可)
      let link;
      switch (i) {
        case 0: link = "/get/btn0_on"; break; //ブラウザボタン番号(btn i)ごとに処理を分岐
      }
      await fetch(link)
        .then((response) => { if (response.ok) {return response.text();} else {throw new Error();} })
        .then((text) => { console.log(text) })
        .catch((error) => console.log(error));
    }
    async function getBtnOff(i) { //ブラウザボタンOFF時処理(複数可)
      let link;
      switch (i) {
        case 0: link = "/get/btn0_off"; break;  //ブラウザボタン番号(btn i)ごとに処理を分岐
      }
      await fetch(link)
        .then((response) => { if (response.ok) {return response.text();} else {throw new Error();} })
        .then((text) => { console.log(text) })
        .catch((error) => console.log(error));
    }
    // M5CAMERA動画取得
    const startStream = () => {
      let target = document.getElementById("output");
      let stream = document.forms.id_form1.input_IP.value;
      console.log("Start Stream:" + stream);
      stream = "<img src= '"+ stream + "' width='90%' height='90%'>";
      target.innerHTML = stream;
      return false;
    }
  </script>
</body>
</html>

7.ブラウザページの動作紹介

ブラウザページのデータをhtml、css、JavaScriptに分けてそれぞれの動作を紹介します。
(見やすくなるように改行して整列しなおしてます。)

・html(ボタン、データ表示)

「html」はブラウザページのベースとなる表示を行うプログラムでタグというものを使用します。
サンプルページから「html」部を以下に抜粋して紹介します。

<h1>REMOTE-CONTROLLER</h1>
<div>
  <!-- ボタンが複数の場合は以下をコピペで増やす。idのbtn番号変更 -->
  <button class="btn" id="btn0">ボタン0</button>
  <button class="btn" id="btn1">ボタン1</button>
</div>
<div>
  <table border = "1">
    <!-- 表示内容が複数の場合は以下をコピペで増やす。idはJSONのkeyに変更 -->
    <tr><td><p>アナログ<br>入力電圧</p></td> <td><span id="v_in">0</span><span>V</span></td></tr>
    <tr><td><p> 追加項目 </p></td> <td><span id=" 追加データID ">(更新データ)</span><span> 単位 </span></td></tr>
  </table>
</div>

3~5行目で「button」タグを使用してボタンを配置しています。
サンプルプログラムでは4行目の「ボタン0」だけですが、ボタンを追加する場合は5行目のように「コピペ」して追加してください。
追加したボタンには「id=”btn1″」のように「id」の「btn」番を連番で増やして変更してください。

9~11行目で「table」タグを使用してデータ表示を行うテーブル(表)を配置しています。
サンプルプログラムでは10行目の「アナログ入力電圧」だけですが、表示するデータを追加する場合は11行目のように「コピペ」して追加してください。
追加したテーブルでは(更新データ)部の「id」を、受信する「JSON」データの「key」に設定してください。(JavaScriptの「マイコンボードからのデータ取得」で詳しく紹介します。)

「JSON」とは「Java Script Object Notation」の頭文字をとったもので、JavaScriptのオブジェクトの書き方を元にしたデータの定義方法です。
オブジェクト(Object)は直訳すると、物、物体、対象、目標等多くの意味を持ちます。
一見わかりにくいですがJSONデータもオブジェクトですし、使い方によってはプログラムのコマンドや文字列、数字もオブジェクトといえます。
オブジェクトという言葉はよく出てきますが、深く考えるといろいろな情報が出てきて混乱するだけ時間の無駄と思います。
オブジェクトが出てきたらその周辺に書いてあるコマンドや数値のことだと思ってあまり気にせず、たくさんプログラムを書くことに集中しましょう。

・CSS(ページの装飾)

「css」は「html」で書かれたブラウザページの文字色やフォント、ボタン色、背景等の装飾や配置の指定を行うプログラムです。
サンプルページから「css」部を以下に抜粋して紹介します。

<style>
  body{font-family: sans-serif; background-color: #22578b; max-width: 480px; margin: 0 auto; align-items: center;}
  h1 {color:#ffffff; text-align: center; font-size: 28px; margin: 10px auto;}
  div {display: flex; flex-direction: row; justify-content: center; margin-top: 10px;}
  p {margin: 0px;}
  td {padding: 0px 15px; width: 110px; color:#ffffff; text-align: center; font-size: 18px; width: auto;}
  .btn {height: 70px; width: 100px; color: #555555; background-color: #dddde9; font-size: 18px; font-weight: bold; border-radius: 7%; margin: 0 10px; -webkit-appearance: none;}
  .btn_on {background-color: springgreen;}
</style>

「body」や「h1」「div」はこれらのhtmlタグへの指定を指し、これらのタグで囲まれたテキストに対して行う装飾を指定します。

「.btn」や「.btn_on」「.format」はhtmlのタグに設定した「クラス(class=” “)」を指し、この「クラス」を設定したタグに囲まれたテキストに対して行う装飾を指定します。

「div」等のタグはページ内で同じものが複数使用されますが、これらの一部のグループにだけに装飾をしたい場合は「クラス」を設定します。

その中でも特定の部分にだけ装飾をしたい場合はタグに「id」を設定します。
「id」の指定方法は以下のように「#」を付けて指定します。

#id名 { 装飾内容:数値等; }

「div」に指定している「display: flex;」は「Flexbox」の指定で、指定したタグを1つのボックスとして扱い、配置を指定できます。

今回はシンプルなページなので使うまでも無いのですが、複雑なページやスマホ用配置の「レスポンシブ」対応をする場合にはとても便利です。

「Flexbox」の理解には下画像の遊んで学べる「FLEXBOX FROGGY」をおすすめします。

Flexbox floggyの紹介

カエルを蓮の葉に配置することでクリアしていくゲームです。
以下のリンクからブラウザ上で遊べるので確認してみましょう。

Flexbox Froggy
A game for learning CSS flexbox
「クラス」と「id」の設定について、「クラス」は複数設定しても良いですが「id」は1つにしか指定してはいけません。

・JavaScript(ボタン操作イベント、fetch 非同期通信)

「JavaScript」はブラウザページに動きを持たせるためのプログラムです。
サンプルページから各動作ごとに「JavaScript」部を以下に抜粋して紹介します。

ボタン操作イベント(マウスクリック、タッチ)

「html」で記述した「ボタン(button)」には「JavaScript」で「イベント」を設定しています。

Webページでは「何かが起こった時に、何かを実行する」という動作をしたい時があります。
これを実現するために「html」で記述した要素(ボタンクやテキストボックス)にイベントを指定します。

「何かが起こった時に」が「イベント」で
「何かを実行する」が実行したい関数(イベントハンドラー)です。
let btn = [];
const btnOn = (i) => {  //ボタンON時処理(ボタン番号「i」ごとに分岐、複数可)
  btn[i].classList.add('btn_on');
  switch (i) {
    case 0: getBtnOn(i); break;
    case 1: getBtnOn(i); break;  //ボタンを増やした場合は追加(違う処理も指定可)
  }
}
const btnOff = (i) => {  //ボタンOFF時処理(ボタン番号「i」ごとに分岐、複数可)
  btn[i].classList.remove('btn_on');
  switch (i) {
    case 0: getBtnOff(i); break;
    case 1: getBtnOff(i); break;  //ボタンを増やした場合は追加(違う処理も指定可)
  }
}
for (let i = 0; i < 1; i++) { //ブラウザボタン状態取得(イベント処理、複数可)※i=btn番号
                              //ボタンを2個にした場合は i < 2 に変更する
  btn[i] = document.getElementById('btn' + i);
  btn[i].addEventListener('touchstart', (e) => {e.preventDefault(); btnOn(i);} );
  btn[i].addEventListener('mousedown', () => {btnOn(i);} );
  btn[i].addEventListener('touchend', () => {btnOff(i);} );
  btn[i].addEventListener('mouseup', () => {btnOff(i);} );
}

16~22行目でボタンにイベントを設定しています。
2行目、9行目からがイベント発生時に実行する関数(イベントハンドラー)です。

イベントの指定は以下のように行います。

変数(配列) = document.getElementById( id名 );
変数(配列).addEventListener(‘イベント‘, () => {実行する関数名(関数に渡したい値);} );

まず変数や配列にボタン(button)要素を取得します。
要素の取得には「document.getElementById」でボタンの「id名 」を指定します。

要素を取得した変数(配列)に対して「.addEventListener」を指定して「イベント」を設定していきます。
イベント」はたくさんの種類がありますが今回したものは以下のようになります。
 ・touchstart:タッチした時
 ・mousedown:マウスでクリックした時
 ・touchend:タッチした指を離した時
 ・mouseup:マウスのクリックボタンを離した時

イベント」が発生した時に実行する関数をアロー関数(関数の書き方)で以下のように指定しています。
 ・btnOn(i):ボタンをタッチまたはクリックしたときに実行する関数
 ・btnOff(i):ボタン上でタッチした指またはクリックしたボタンを離した時に実行する関数
 ※(i)でボタン番号を実行する関数に渡しています。
  これによって実行する関数側で(i)を使った処理や実行内容の分岐ができます。

ボタンを追加する場合
サンプルプログラムではボタンが1個ですが、ボタンの数を増やした時にまとめてイベントを設定できるように16行目のように「for文」を使用して記述しています。
htmlデータでボタン(button)を2個に増やした場合は「for文」の条件式の「i < 1」を「i < 2」に変更してください。
「btnOn / btnOff」関数内では「switch文」の「case」を追加します。
ボタンをクリックしてそのままスライドしてボタンの外で離すと「mouseup」が実行されずボタンはONのままになります。
対策として「mouseleave(マウスが要素から出た時)」を設定すると良いですが今回は省略してます。

マイコンボードからのデータ取得(fetch 非同期通信、インターバル動作)

マイコンボードのデータはJavaScriptの「インターバル」を使用して1秒ごとに「getData()」関数を実行し「fetch(非同期通信)」によってデータを「JSONファイル」形式で取得しています。

2行目の「async function getData()」内の「fetch」を使用してデータの送受信を行います。
async」は英単語「asyncronous」が由来で「非同期」という意味です。

async」を付けることで「非同期」で処理を行うことができます。

「非同期」の処理とは、処理の終了を待たずに次の処理を実行する事で、並行して実行する処理の事です。「非同期」で通信を行うことで、データを送信して受信データを待っている間に他の処理を実行する事が出来ます。

getData()」内では3行目の「 await fetch(“/get/data“) 」でサーバーへ「リクエスト」を送ります。
await」とは英単語で「待つ」という意味で「非同期通信」中に実行完了を待ちたい処理に書きます。

fetch」の前につけているのでデータを送信して他の処理を実行中でもデータを受信した時にこの部分の処理が実行されます。

let get_data;
async function getData() {  //マイコンボード側JSONデータ取得(インターバル)
  await fetch("/get/data")
    .then((response) => {if (response.ok) {return response.json();} else {throw new Error();} })
    .then((json) => {
      console.log(json);
      get_data = json;
      let el;
      //以下に取得したデータごとに処理したい内容を記入
      el = document.querySelector('#v_in'); //アナログ電圧表示要素取得(idはJSONのキー)
      el.textContent = get_data.v_in;       //アナログ電圧表示更新
      if (get_data.LED_state == 0) {btn[0].classList.add('btn_on');}  //LEDがONならブラウザボタン緑
      else {btn[0].classList.remove('btn_on');}                       //LEDがOFFならブラウザボタン白
      if (get_data.追加データid == 0) { btn[1].classList.add('btn_on'); }  //追加データONならブラウザボタン緑
      else { btn[1].classList.remove('btn_on'); }                         //追加データOFFならブラウザボタン白
    })
    .catch((error) => console.log(error));
}
setInterval(getData, 1000); //インターバル設定(1秒ごとに本体データ取得)
ボタンを追加した場合は「btn[1]」として14、15行目を追加することでボタンで操作する出力のON/OFF状態が確認できます。
「追加データid」は「html」のボタンに指定した「id」です。

サーバー側(M5StickC Plus)では以下の処理(サンプルプログラムより抜粋)が実行され「クライアント」へ「レスポンス」として「JSONファイル」を返します。
追加で送信したいデータがあればサンプルプログラムの「getData関数」に以下のコードの7行目のように追加します。

「JSONファイル」は以下のように「data」変数に文字列としてJSONフォーマットで準備します。

void getData() {
  String data= "";
  // JSONファイルフォーマット({ "key(項目)" : "value(値)" ~ ,"key(項目)" : "value(値)"})
  data += "{\"info\":\"";   data += "M5StackC Plus";         //本体情報
  data += "\",\"v_in\":\"";  data += v_in;                   //アナログ入力電圧
  data += "\",\"LED_state\":\"";  data += digitalRead(10);   //本体LEDの状態
  data += "\",\"追加データkey\":\"";  data += 追加データvalue; //追加データ
  data += "\"}";
  server.send(200, "text/plain", data);                      //JSONデータ送信実行
}
// サーバー設定
server.on("/get/data", getData);      //「/get/data」へのリクエストがあったら「getData」を実行

データの取得は「setInterval」を使用して1秒間隔で行います。
setInterval」の使い方は以下になります

setInterval( 実行したい関数,  実行間隔[ms]);

JSONファイルについて

取得するデータのJSONファイルについて紹介します。

「JSON」とは「Java Script Object Notation」の頭文字をとったもので、JavaScriptのオブジェクトの書き方を元にしたデータの定義方法です。

JSONファイルのフォーマット
{ “key“: “value“, “key“: “value” }

{ }波括弧で括った中に、” “で囲んで名前の「key」を指定し : で区切って値の「value」を書きます。
複数のデータを書く時は「,」カンマで区切って書いていきます。

key」とはデータにつける名前で、値の「value」を呼び出すためのキーワードとなります。
value」とは「key」に関連図けられたデータのことです。

JSONを使用すると、データの名前「key」を指定することで値「value」を呼び出すことができます。

サンプルプログラムで使用したJSONファイルは以下のように設定しています。
※以下のように改行して書くと見やすくなります。

{
   “info“: “M5StackC Plus”
   “v_in“: v_in
   “LED_state“: “digitalRead(10)” ,
   “追加取得データid“:”追加取得データ
}
※取得するデータを増やしたい時は「,」で区切って同じように書いていきます。
 「key」の部分は「追加取得データid」でなくてもいいですが、わかりやすいように同じにしてます。

この場合、「key」の “info” を指定することで「value」の “M5StackC Plus” という文字列を得ることができます。
v_in」を指定すると変数「v_in」に格納された値を得ることができます。

JSONと同じフォーマットで書かれていても、Pythonではディクショナリ(辞書、dict型)、Rubyではハッシュ、Golangではマップなどと言語によって呼び名は違いますので混乱しないようにしましょう。

ボタン操作時動作

ブラウザボタンのON/OFFでも「fetch」を使用して「リクエスト」を送ります。
ボタンONの場合について紹介しますがボタンOFFでも同じような動作です。

async function getBtnOn(i) {  //ブラウザボタンON時処理(複数可)
  let link;
  switch (i) {
    case 0: link = "/get/btn0_on"; break; //ブラウザボタン番号(btn i)ごとに処理を分岐
    case 1: link = "/get/btn1_on"; break; //ボタンを追加した場合はcase1を追加(btn番号は連番)
  }
  await fetch(link)
    .then((response) => { if (response.ok) {return response.text();} else {throw new Error();} })
    .then((text) => { console.log(text) })
    .catch((error) => console.log(error));
}
ボタンを追加した場合は5行目のように「/get/btn1_on」として「case 1」を追加します。

7行目で「fetch」を実行して「リクエスト」を送信します。
サーバー側(M5StickC Plus)でブラウザのボタン0が押されたことが確認されたら「レスポンス」としてtextデータ(ボタン0 ON)を返します。

ブラウザの開発ツール(ctr + shift +i で表示)のコンソールで「ボタン0 ON/ボタン0 OFF」が確認できます。(9行目の「console.log(text)」による)


サーバー側(M5StickC Plus)では以下の処理が実行されます。
ブラウザから「/get/btn0_on」にリクエストがあると以下コードの13行目で「btn0()関数」が呼び出されます。

「btn0()関数」では「btn0_sig」変数を1にして「クライアント」へ「レスポンス」として「textデータ」(ボタン0 ON)を返します。

// ブラウザONボタン処理
void btn0On() {
  btn0_sig = 1;                                     //ブラウザボタン信号を1(ON)にする
  server.send(200, "text/plain", "ボタン0 ON");     //レスポンス200を返し情報送信(text)
}
// ボタンを追加した場合は以下の処理を追加(btn1の番号は連番)
int btn1_sig;                                       //ボタンを追加した場合はbtn1_sig変数も追加
void btn1On() {
  btn1_sig = 1;                                     //ブラウザボタン信号を1(ON)にする
  server.send(200, "text/plain", "ボタン1 ON");     //レスポンス200を返し情報送信(text)
}
// サーバー設定
server.on("/get/btn0_on", btn0On);    //「/get/data0_on」へのリクエストがあったら「btn0On()」を実行
server.on("/get/btn1_on", btn1On);    //ボタンを追加した場合はbtn1(番号は連番)にして追加
ボタンを追加した場合は6~11行目のように「ボタン処理」と14行目のように「サーバー設定」をコピペで追加してbtn1に変更します。(変数「btn1_sig」も追加)

4行目の「server.send」で送信するデータはファイル形式ごとに「MIMEタイプ」というものを指定して送信します。
「MIMEタイプ」には以下表のようなものがあります。

ファイル形式一般的な拡張子MIMEタイプ
テキスト.txttext/plain
HTML文書.htm .htmltext/html
XML文書.xmltext/xml
JavaScript.jstext/javascript
VBScript.vbstext/vbscript
CSS.csstext/css
GIF画像.gifimage/gif
JPEG画像.jpg .jpegimage/jpeg
PNG画像.pngimage/png
CGIスクリプト.cgiapplication/x-httpd-cgi
Word文書.docapplication/msword
PDF文書.pdfapplication/pdf

8.おまけ:Webカメラで遠隔監視モニター

「M5CAMERA」の映像は「JavaScript」を使用してブラウザページに埋め込んでいます。

カメラのstream画像のアドレスを取得するために「html」では「form」タグを使用しています。
「input」タグでテキストボックスを作成して、そこにアドレスを入力してエンターを押すと「JavaScript」の「startStream()」が実行されます。

<div>
  <form id="id_form1" onsubmit="return startStream()" action="">
    <input class="input1" id="input_IP" type="text" value="" placeholder="カメラIPアドレス" />
  </form>
</div>
<hr width='90%' />
<div id="output"></div>

以下コードの「startStream()」では「html」で「id」を「output」に指定した「div」タグの要素を変数「target」に取得しています。
変数「stream」に「form」のテキストボックスに入力したstream画像のアドレスを取得して、このアドレスをリンク先とした「img」タグを使用した「html」で上書きします。

最後にstream画像を表示したい「div」タグの要素を取得した変数「target」に「innerHTML」を使用して変数「stream」の「html」データを書き出すことで表示しています。

const startStream = () => {
  let target = document.getElementById("output");
  let stream = document.forms.id_form1.input_IP.value;
  console.log("Start Stream:" + stream);
  stream = "<img src= '"+ stream + "' width='90%' height='90%'>";
  target.innerHTML = stream;
  return false;
}
今回使用した「M5CAMERA」は生産中止のようなので、新しいカメラを入手したらカメラの使い方も別記事で紹介したいと思います。

9.まとめ

WiFi通信を使用したブラウザベースの遠隔操作方法を紹介しました。
「サーバー」機能を利用しているのでWiFi環境があればスマホでもパソコンでもブラウザで表示させて操作できる遠隔操作モニタを作ることができます。(アクセスポイントに設定しても可)

モニタするブラウザのページ作成は「html」でベースを作り「css」で装飾や配置を行います。
配置の指定には「Flexbox」を使いましょう。
作ったページに「JavaScript」を使用して動きをつけていきます。

「サーバー」とは「インターネット」経由で「クライアント(スマホやパソコン等)」からの「リクエスト」に対して「レスポンス」を返す「仕組み」です。
「仕組み」なので「サーバー」は特別なものではなく、この「仕組み」を持ったパソコンです。
小規模であればマイコンボードにもこの「仕組み」を実装することができます。

「リクエスト」はサーバーへアドレスを送信するため「html」だけではリンク機能を使用することになります。
しかし、リンクを送信するとブラウザの表示は更新されてしまい、操作を頻繁に行いたい場合は毎回ページが更新されてしまうため実用的ではありません。

これを解決するために「JavaScript」の「fetch」を使用しました。
「fetch」を使用することで「リクエスト」としてリンク先のアドレスのみを送信できます。

データの送受信は「非同期通信」で行っているため処理が中断されることはありません。
「サーバー」側で処理が終わって「レスポンス」が帰ってきた時点で受信データを使用した処理を行うことができます。

今回はサーバー機能を利用して遠隔操作を実現しました。
サーバー本来の使い方では無いと思うので高速な操作には向きませんが、「IoT」でデータ収集や稼働監視、モニターカメラの操作をするのにはちょうど良いと思います。

ここまで長々と書きましたが私自身「html」や「JavaScript」を使いこなしているわけではありません。
ほぼ「コピペ」で作ってるのでググれない状況でプログラムを書いてと言われると困ってしまいます(汗)

今回の構成もググってあちこちから集めたプログラムに追加したり、余分なものを削除して作ってるので、消しちゃいけないものも消してるかもしれませんw(動いてるので良しとしてます)

動くことは間違いないので参考として紹介しました。
これを作ることが目的ではなくWiFi通信でブラウザで操作できるラジコンが作りたかっただけなのです。
遠回りしたようですが勉強になりました。

今後はこれを使っていろいろ作ってまた紹介したいと思います。

今回はデータの受信(GET)については紹介しましたが、データの送信(POST)までは紹介しきれませんでした。ブラウザデータを文字列として埋め込むにはこの辺りが限界かと・・・
SDカード搭載の物であれば「html」や「css」「JavaScript」ファイルをそのまま扱えるので、いつも「M5Stack GRAY」を使います。
残念ながら「GRAY」は生産中止のようなので今後は「CORE2」でしょうか。

「CORE2」については以下のリンクで詳しく紹介しています。

M5Stack CORE2について、デモ画面、基本仕様、端子配列(機能)等を詳しく紹介
CORE2について工場出荷時デモ画面の動作確認方法や外観紹介から基本仕様、端子配列(一覧表にまとめました)、端子機能等を詳しく紹介します。

コメント

タイトルとURLをコピーしました