ラベル arduino の投稿を表示しています。 すべての投稿を表示
ラベル arduino の投稿を表示しています。 すべての投稿を表示

2025年9月22日月曜日

ESP32/arduino: AP(アクセスポイント)モードで WiFi設定を行う。

目的:

WiFi設定(接続先設定、IPアドレス 等)をスケッチには記述せず、スマホ等からAPモードでの接続で設定できる様にする。
"スマホからIPアドレス等のWiFi設定を行う”と同じ内容を LittleFs, WebServer を使用し、IDE2.x でコンパイルできるようにする。

背景:

以前 "スマホからIPアドレス等のWiFi設定を行う”でスケッチを作成したが、IDE2.x ではそのまま使用できなくなった。
この為、IDE2.x で使用できるように修正する必要があった。

動作概要:

・softAP で APモードでサーバ起動し、スマホ等から WiFi接続設定を行う。
 (APモードの時の ESP32 の SSID,パスワードは 固定値)
・設定したWiFi接続情報は、SPIFFS で 設定ファイルに保存する。
・設定ファイルが有り、接続先が設定されている場合は STA(ステーション)モードでサーバー起動する。
・STAモードで 接続できなかった場合は APモードでサーバー起動する。
・APモードのまま、メインの動作も可能。

接続手順:

  1. 最初は、接続設定がないため、ESP32 は AP モードで起動。
  2. スマホ等から、WiFi接続先として
    SSID  : ESP32_AP
    パスワード : password
    で接続できる。 
  3. 接続後、ブラウザで "192.168.4.1" にアクセスすると、下記設定画面が表示される。
  4. WiFi接続設定(SSID, PASSWORD, IPアドレス等) を設定し、STA MODE を選択して "設定"ボタンを押下すると、 ESP32 がリブートして 設定した WiFi 親機に接続する。
  5. スマホ等から、ブラウザで設定した IPアドレスにアクセスすると、下記 LED調光画面が表示される。
  6. LED 調光画面から、WiFi 設定画面 ボタンを押下すると再度 WiFiの設定画面に遷移する。
  7. WiFi 親機に接続できなかった場合は、再度 AP モードで立ち上がる。
  8. 設定画面で、AP MODE を選択した場合は、AP モードのまま、LED調光画面に遷移する。

修正点:

主な修正項目はIDE2.x 対応のための修正で、以下の通り。
  • SPIFFS から LittleFS に変更
  • WebServer を使用
  • led 制御 (ledcAttach) 変更
  • その他、記述の整理
修正前については "スマホからIPアドレス等のWiFi設定を行う”を参照。

スケッチ:

スケッチの詳細はコード内のコメント参照。

< WiFi 設定画面用 HTML >

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>
<style>
    #base         {font-size:16pt; text-align:center; width:600px; border:solid 4px 00000000; background-color:#c0c0c0; }
    #text_box     {height: 25px; font-size:12pt; float:left ; text-align:left; width:45%; }
    #input_box    {height: 25px; font-size:12pt; float:right; text-align:left; width:55%; }
    #ip_box       {height: 25px; font-size:12pt; float:right; text-align:left; width:15%; }
    #radio_box    {font-size:12pt; clear:both; margin : 0% 20% ; width : 60% ; }
    #button_box   {font-size:12pt; clear:both; margin : 0% 20% ; width : 60% ; }
    #foot         {font-size:16pt; clear:both;}
    input.val     {width: 90%;}
    input.ip      {width: 20%;}
    input.button  {margin:10px 10% ; width: 25%;}
    input.radio   {margin:10px 0px 0px 15% ; }
</style>
 
<title>設定画面</title></head>
 
<body>
<div id="base">
  <p>設定画面</p>
  <div id="text_box">
    <span> WiFi 接続先 SSID </span>
  </div>
 <form  method="get">
  <div id="input_box">
      <input class='val' type='text' name='ssid' id='ssid' value=$ssid>
  </div>
  <div id="text_box">
    <span> WiFi 接続先 PASSWORD </span>
  </div>
  <div id="input_box">
      <input class='val' type='text' name='pass' id='pass' value=$pass>
  </div>
  <div id="text_box">
    <span> WiFi 接続  IP アドレス </span>
  </div>
  <div id="input_box">
      <input class='ip' type='number' name='ip1' id='ip1' min=0 max=255 value=$ip1 >
      <input class='ip' type='number' name='ip2' id='ip2' min=0 max=255 value=$ip2 >
      <input class='ip' type='number' name='ip3' id='ip3' min=0 max=255 value=$ip3 >
      <input class='ip' type='number' name='ip4' id='ip4' min=0 max=255 value=$ip4 >
  </div>
  <div id="text_box">
    <span> WiFi 接続  サブネットマスク</span>
  </div>
  <div id="input_box">
      <input class='ip' type='number' name='sn1' id='sn1' min=0 max=255 value=$sm1 >
      <input class='ip' type='number' name='sn2' id='sn2' min=0 max=255 value=$sm2 >
      <input class='ip' type='number' name='sn3' id='sn3' min=0 max=255 value=$sm3 >
      <input class='ip' type='number' name='sn4' id='sn4' min=0 max=255 value=$sm4 >
  </div>
  <div id="text_box">
    <span> WiFi 接続  デフォルトゲートウェイ</span>
  </div>
  <div id="input_box">
      <input class='ip' type='number' name='gw1' id='gw1' min=0 max=255 value=$gw1 >
      <input class='ip' type='number' name='gw2' id='gw2' min=0 max=255 value=$gw2 >
      <input class='ip' type='number' name='gw3' id='gw3' min=0 max=255 value=$gw3 >
      <input class='ip' type='number' name='gw4' id='gw4' min=0 max=255 value=$gw4 >
  </div>
  <div id="text_box">
    <span> WiFi 接続  DNSアドレス</span>
  </div>
  <div id="input_box">
      <input class='ip' type='number' name='dns1' id='dns1' min=0 max=255 value=$dns1 >
      <input class='ip' type='number' name='dns2' id='dns2' min=0 max=255 value=$dns2 >
      <input class='ip' type='number' name='dns3' id='dns3' min=0 max=255 value=$dns3 >
      <input class='ip' type='number' name='dns4' id='dns4' min=0 max=255 value=$dns4 >
  </div>
  <div id="radio_box">
      <input class='radio' type='radio' name='wifi_stamode' id='rad_sta' value='sta' $checked_sta > STA MODE
      <input class='radio' type='radio' name='wifi_stamode' id='rad_ap' value='ap' $checked_ap > AP MODE
  </div>
  <div id="button_box">
    <!--
      <button class='button' type='submit' name='set' id='set' onclick="location.href='/conf'" value='設定'>設定</button>
      <a href="/conf" class='button'>設定</a>
    -->
      <input class='button' type='submit' name='set' id='set' value='設定'>
      <input class='button' type='submit' name='set' id='set' value='キャンセル'>
  </div>
 </form>
 <div id="foot">
   <span>$footer</span>
 </div>
</div>
</body>
</html>

 < LED調光用 HTML >

<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>
<style>
    #base       {font-size:16pt; text-align:center; width:400px; border:solid 4px #93ff93; background-color:#f0f0f0; }
    #text_box   {height: 30px; font-size:12pt; float:left ; text-align:left; width:45%;}
    #button_box {font-size:12pt; clear:both;}
    #foot       {font-size:16pt; clear:both;}
    input       {margin:8px;width:100px;}
    div         {font-size:16pt;text-align:center;}
</style>
 
<title>Color LED Controller</title></head>
 
<body>
<div id="base">
  <p>LED ON/OFF</p>
  <form method='get'>
    <input type='submit' name='on' value='ON' />
    <input type='submit' name='off' value='OFF' />
  </form>
 
  <form method="get">
    <div id="button_box">
      <input class='button' type='submit' name='config' id='config' value='WiFi設定画面'>
    </div>
   </form>
 <div id="foot">
   <span>$footer</span>
 </div>
</div>
</body>
</html>

 < スケッチ (メイン) >

// +==========================================================================+
// |  WiFi Setting By AP mode                                                 |
// +==========================================================================+
#include <WiFi.h>                                // WiFi 使用の為
#include <WebServer.h>                           // WebServer 使用の為
#include <ArduinoJson.h>                         // JSOM 使用 の為
#include "FS.h"                                  // LittleFS 使用の為
#include "LittleFS.h"                            // LittleFs 使用に必要
//                                               //
#define DEBUG_HTML                               // Debug用 Serial 表示制御用
#define DEBUG                                    // Debug用 Serial 表示制御用
//                                               //
// +---------------------------------------------------------------------------+
// | WiFi 設定 他                                                              |
// +---------------------------------------------------------------------------+
//                                             //                             
// 動作モードフラグ ------------------------------------------------------------
bool stamode = true ;                          // WiFi STA 接続モード : true
bool sta_exec = false ;                        // WiFi STA接続モード実行中フラグ
bool config_mode = false ;                     // 初期設定モード ; true
bool config_exec = false ;                     // 初期設定実行中フラグ
char wifi_mode[16] = ""   ;                    // wifi mode
String foot_msg ="" ;                          // リブートメッセージ用
//                                             // 
// AP モード用 WIFI 設定 (AP : Access Point) -----------------------------------
const char      ap_ssid[] = "ESP32_AP";        // SSID for softAP                   
const char      ap_pass[] = "password";        // password for softAP
const IPAddress ap_ip(192, 168, 4, 1);         // IPアドレス for softAP
const IPAddress ap_subnet(255, 255, 255, 0);   // サブネットマスク for softAP
//                                             // 
// ST モード用 WIFI 設定 (ST : STation) ----------------------------------------
String    s_config ;                           // 設定ファイル用 String
char      st_ssid[128] = "" ;                  // SSID        for WiFi STA
char      st_pass[128] = "" ;                  // password    for WiFi STA
IPAddress st_ip(192,168,1,66) ;                // IP Address  for WiFi STA
IPAddress st_gw(192,168,1,1)  ;                // gate way    for WiFi STA
IPAddress st_sm(255,255,255,0) ;               // subnet mask for WiFi STA
IPAddress st_dns(192,168,1,1) ;                // dns address for WiFi STA
//                                             //
WebServer server(80);                          // WebServer オブジェクト生成
//                                             //
String    html_CONF ;                          // 設定用 HTML
String    html_MAIN ;                          // メイン HTML
//                                             //
// LED 調光処理用 ---------------------------------------------------------------
// -- for ledc                                 //
byte brightness = 0 ;                          // LED 明るさ設定 (0-255)
//                                             //
// +---------------------------------------------------------------------------+
// | ハンドル処理 : WEBからの要求に対する応答処理                              |
// +---------------------------------------------------------------------------+
// 受信した要求の内容を文字列に格納して返す      //
String get_client_msg() {                        //  受信した要求の内容を作成
  String s_req = "request param ----\n";         //    Clientからの要求
  s_req += "URI: " + server.uri() + "\n" ;       // 
  s_req += "Method: ";                           //
  s_req += (server.method() == HTTP_GET) ? "GET\n" : "POST\n"; //
  s_req += "Arguments: " + String(server.args()) + "\n" ; //
  for (uint8_t i = 0; i < server.args(); i++) {  //
    s_req += " " + server.argName(i) + ": " ;    //
    s_req += server.arg(i) + "\n" ;              //
  }                                              //
  s_req += "------------------ \n";              //
  Serial.print(s_req);                           // シリアルに表示
  return s_req ;                                 // 呼び出し元に文字列を応答
}                                                //                                                 
//                                               //
void handleRoot() {                              //メイン HTMLページを返す
  String s_wk ;                                  //
  Serial.println("-- handlRoot() ----") ;        //
  get_client_msg() ;                             // (debug) 受信メッセージ表示
  //                                             //
  if (server.hasArg("set")) {                    //                  
    proc_config() ;                              //  WiFi 設定用 ハンドル処理
    //                                           //
  } else {                                       //                       
    proc_main() ;                                //  Main処理用 ハンドル処理  
    //                                           //
  }                                              //
}                                                //
// +---------------------------------------------------------------------------+
// | arduino 初期化処理                                                        |
// +---------------------------------------------------------------------------+
void setup() {                                   //
  int res = 0 ;                                  // 結果格納用 (ワーク)
  //                                             //
  Serial.begin(115200);                          // シリアル 開始
  LittleFS.begin(true) ;                         // LittleFS 開始
  pinMode(2, OUTPUT);                            // set the LED pin mode
  delay(10) ;                                    //
  //                                             //
  // 初期設定用 HTML ファイルの読み込み ----------------------------------------
  Serial.println("") ;                           //
  Serial.println("-- start rd html_CONF file ---") ;  //
  res = rd_LittleFS("/Config.html",html_CONF) ;  // html_CONFにConfig.html を格納
  // 本体 HTML ファイルの読み込み ----------------------------------------------
  Serial.println("-- start rd html_MAIN file ---") ;  //
  res = rd_LittleFS("/main.html",html_MAIN) ;    // html_MAIN に main.html を格納
  // 初期設定ファイルを読み込み、グローバル変数に設定し、wifi,画面モードを決定 ---
  Serial.println("-- start rd_config --") ;      //
  res = rd_config() ;                            //
  //                                             //
  // wifiモード(stamode) に応じて AP か STA かを選択してサーバー起動 -------------
  if (stamode == false) {                        // APモードの場合softAP WiFi開始
    start_AP_server() ;                          // APモードでサーバー起動
    sta_exec = false ;                           //
  } else {                                       // STAモードの場合 Wifi 開始
    start_STA_server() ;                         // APモードでサーバー起動
    sta_exec = true ;                            //
  }                                              //
  //                                             //
  // 実行中のモード (設定かメインか) を設定 ------------------------------------
  if (config_mode) config_exec = true ;   // config_mode なら config_exec を '1'
  else config_exec = false ;                     //
  //                                             //
  Serial.println("Server start!");               //
  //                                             //
  // メイン処理用 初期化 ------------------------------------------------------
  ledcAttach(2,5000, 8);                         // attach pin 2 
  ledcWrite(0, 0);                               // initialize pin2 off     
  //                                             //
}                                                //
//                                               //
// +---------------------------------------------------------------------------+
// | arduino メインループ処理                                                  |
// +---------------------------------------------------------------------------+
void loop() {                                    //
  // HTML クライアント処理 ----------------------//-----------------------------
  server.handleClient();                         //
  //                                             //
}                                                //
//                                               //

 < スケッチ (WiFi設定用) >

// +===========================================================================+
// |  AP モード 用 処理                                                        |
// +===========================================================================+
// int rd_LiitleFs(String,String) : LittleFs から ファイルを 文字列に読み込む
// IPAddress stoip(String)        : 文字列を IPAddress に変換する
// void set_param(String,String) : 文字列を解析して 内部変数に変換してセットする
// void set_conf2param()      : 設定ファイル文字列を解析して内部変数にセットする 
// int  rd_config() : 初期設定ファイルから設定値を 設定ファイル用変数にセットする
// void start_AP_server()         : AP モード で サーバー開始
// void start_STA_server()        : STAtion モードでサーバー開始
// void dbgdisp_mode(String)      : デバッグ用 モードステータス表示
// void wr_config()               : 設定変数のファイルへの書き出し
// void set_form2param()          : クライアントからのデータを設定変数にセット
// void proc_config()             : クライアントからの要求処理
// void  send_CONF_html()         : 設定画面用 HTML の送信
//                                             // 
// +---------------------------------------------------------------------------+
// | LittleFs ファイルを String に読み込む                                     |
// +---------------------------------------------------------------------------+
int rd_LittleFS(String fname, String &sname) {   //
  File fp = LittleFS.open(fname,"r") ;           // ファイルをオープンする
  if (!fp) {                                     //  オープン失敗の場合
    Serial.print(fname) ;                        //
    Serial.println(" : file open error") ;       //
    return -1 ;                                  //    戻り値 -1
  }else {                                        //  オープン出来たら
    sname = fp.readString() ;                    //    String変数に読み込む
    fp.close() ;                                 //
  }                                              //
  return 0 ;                                     //  オープン出来た時のみ
}                                                //    戻り値 0
//                                               //
// +---------------------------------------------------------------------------+
// | 設定ファイルの内容を グローバル変数に 設定する                            |
// +---------------------------------------------------------------------------+
//                                             // 
// - 文字列 から IPAddress へ変換 ----------------------------------------------
IPAddress stoip(String &data) {                //
  IPAddress ip ;                               //
  String num = "" ;                            //
  int pos = 0 ;                                //
  while(data.charAt(pos) != '.') {             // 先頭から '.' までの文字を取得
    num += data.charAt(pos++) ;                //
  }                                            //
  ip[0] = num.toInt() ;                 // 数値に変換して 第1オクテット に 代入
  pos++ ;                                      // '.' の次から
  num="";                                      //
  while(data.charAt(pos) != '.') {             // '.' までの文字を取得
    num += data.charAt(pos++) ;                //
  }                                            //
  ip[1] = num.toInt() ;                  // 数値に変換して 第2オクテットに代入
  pos++ ;                                      // '.' の次から
  num="";                                      //
  while(data.charAt(pos) != '.') {             // '.' までの文字を取得
    num += data.charAt(pos++) ;                //
  }                                            //
  ip[2] = num.toInt() ;                        //
  pos++ ;                                      // '.' の次から
  num="";                                      // 
  while(data.charAt(pos) != '.') {         // '.' または 最後まで の 文字を取得
    num += data.charAt(pos++) ;                //
    if (pos > data.length()) break ;           //
  }                                            //
  ip[3] = num.toInt() ;                   // 数値に変換して 第4オクテットに代入
  //                                           //
  return ip ;                                  // IPAddress を戻り値とする
}                                              //
//                                             // 
// 設定用文字列を判定し、グローバル変数に設定 ---------------------------------
void set_param(String &name, String &data) {   //
  if (name.compareTo("st_ssid")==0) {          // 'st_ssid' の場合
    data.toCharArray(st_ssid,128) ;            //
  }                                            //
  if (name.compareTo("st_pass")==0) {          // 'st_pass' の場合
    data.toCharArray(st_pass,128) ;            //
  }                                            //
  if (name.compareTo("st_ip")==0) {            // 'st_ip' の場合
    st_ip = stoip(data) ;                      // 取得情報(文字列)をIPAddressに変換
  }                                            //
  if (name.compareTo("st_gw")==0) {            // 'st_gw' の場合
    st_gw = stoip(data) ;                      //
  }                                            //
  if (name.compareTo("st_sm")==0) {            // 'st_sm' の場合
    st_sm = stoip(data) ;                      //
  }                                            //
  if (name.compareTo("st_dns")==0) {           // 'st_dns' の場合
    st_dns = stoip(data) ;                     //
  }                                            //
  if (name.compareTo("wifi_mode")==0) {        // 'wifi_mode' の場合
    data.toCharArray(wifi_mode,16) ;           //
  }                                            //
}                                              //
// 設定ファイルの文字列を解析して内部変数にセットする -----------------------------------
void set_conf2param() {                        //
  int    pos = 0 ;                             //
  int    npos = 0 ;                            //
  String s_work ;                              //
  String s_name ;                              //
  String s_data ;                              //
//                                             //
// 記載内容を順次処理 ( 設定情報を取り出し、グローバル変数に設定する ) --------
  Serial.println("-- set config to param --") ; //
  while(1) {                                   //
    if (s_config.charAt(pos) == '/') {         // 先頭が '/' なら 次の行へ 
      npos = s_config.indexOf('\n',pos) ;      //
      pos = npos + 1 ;                         //
    } else {                                   //
      npos = s_config.indexOf(':',pos) ;       // ':' までの文字をs_name に取得
      if (npos == -1) break ;                  //   見つからなければ終了
      s_name = s_config.substring(pos,npos) ;  //
      s_name.trim() ;                          //
      pos = npos+1 ;                           // ポインタを ':' の次へ
      npos = s_config.indexOf('\n',pos) ;      // '\n' までの文字をs_data に取得
      if (npos == -1) npos = s_config.length() ;  //
      s_data = s_config.substring(pos,npos) ;  //
      s_data.trim() ;                          //
      pos = npos+1 ;                           //
      set_param(s_name,s_data) ;               // 取得した内容をグローバル変数に設定
    }                                          //
  }                                            //
}                                              //
//                                             //
// 初期設定ファイル をリードし、グローバル変数に値をセットする ----------------------
int rd_config() {                                //
  File fp       ;                                // 設定ファイル用ファイルポインタ
  int result = 0 ;                               // 戻り値
  String s_wd ;                                  //
  //                                             //
  if (LittleFS.exists("/config.txt")) {          // 設定ファイル存在確認
    // ファイルがあった場合 ---------------------//
    result = rd_LittleFS("/config.txt",s_config) ; // s_configにconfig.txtを格納
  } else {                                       //
    // ファイルが無かった場合 -------------------//
    Serial.println("設定ファイルなし") ;         // 無かったら、APモード
    result = -1 ;                                //
  }                                              //
  //                                             // 
  // ファイルが読み込めたら、グローバル変数にセットする
  if (result == 0) {                             //
    # ifdef DEBUG                                // デバッグ用表示
      Serial.println("-- s_config (config.txt) -- ") ;      //
      Serial.println(s_config ) ;                //
    # endif                                      //
    set_conf2param() ;                           // s_config の内容を変数に設定
  }                                              //
  // 初期設定ファイルの状態で wifiモード, 画面モードを設定 ----------------------
  if (result == 0) {                           // 変数に設定できた場合
    if (( strcmp(st_ssid,"") ==0) || (strcmp(st_pass,"") ==0)) {
      //                                       // ssid,pass 設定なければ
      stamode = false ;                        //   APモード
      if ( strcmp(wifi_mode,"ap") == 0)        // wifi_mode が "ap" なら
        config_mode = false ;                  //   メイン画面
      else                                     // wifi_mode が "ap" でなければ
        config_mode = true ;                   //   初期設定画面
    } else {                                   // ssid,pass の設定があれば、
      config_mode = false ;                    //   メイン画面
      if ( strcmp(wifi_mode,"ap") == 0)        // wifi_mode が "ap" なら
        stamode = false ;                      //   APモード
      else                                     // "ap" でなければ
        stamode = true  ;                      //   STAモード
    }                                          //
  } else {                                     // 変数に設定できなかった場合
    stamode = false ;                          //   APモード
    config_mode  = true ;                      //   初期設定画面
  }                                            //
  //                                           // 
  s_wd = (stamode ? "true" : "false" ) ;       //
  Serial.println("set stamode = " + s_wd) ;    //
  s_wd = (config_mode ? "true" : "false" ) ;   //
  Serial.println("set config_mode = " + s_wd) ;//
  return result ;                              //
}                                              //
//                                             //
// +---------------------------------------------------------------------------+
// | アクセスポイント モードで サーバーを起動                                  |
// +---------------------------------------------------------------------------+
void start_AP_server() {                       // AP モード で サーバー開始
  Serial.println(" AP Server exec") ;          //
  WiFi.softAP(ap_ssid, ap_pass);               // SSIDとパスの設定
  delay(100);                                  // delayが必要
  WiFi.softAPConfig(ap_ip, ap_ip, ap_subnet);  // IP adr,gateway,submskの設定
  IPAddress myIP = WiFi.softAPIP();            // WiFi.softAPIP()でWiFi起動
  //                                           // 
  server.on("/", HTTP_GET, handleRoot);        // メインHTML の設定
  //                                           // 
  server.begin();                              // サーバーを起動
}                                              //
//                                             //
// +---------------------------------------------------------------------------+
// | ステーションモードで サーバーを起動                                       |
// +---------------------------------------------------------------------------+
void start_STA_server() {                            //
  int lpcnt = 0 ;                                    // ループカウント
  int lpcnt2 = 0 ;                                   // ループカウント2
  //                                                 //
  WiFi.config(st_ip, st_gw, st_sm, st_dns);          // Set fixed IP address
  delay(10) ;                                        //
  WiFi.begin(st_ssid, st_pass);                  // wifi 開始
  lpcnt = 0 ;                                        // 
  lpcnt2 = 0 ;                                       //
  while (WiFi.status() != WL_CONNECTED) {            // 接続確認
      lpcnt += 1 ;                                   //
      if (lpcnt > 4) {                               //
        WiFi.begin(st_ssid, st_pass);            // 4回目(2秒) で再度開始指示
        lpcnt = 0 ;                                  //
        lpcnt2 += 1 ;                                //
      }                                              //
      if (lpcnt2 > 5) {                              // 2秒x5 接続できなければ、
        stamode = false ;                            // APモードにして
        ESP.restart() ;                              // 再起動
      }                                              //
      Serial.print(".");                             //
      delay(500);                                    //   0.5秒毎にチェック
  }                                                  //
  //                                                 // 
  server.on("/", HTTP_GET, handleRoot);              // メインHTML の設定
  //                                                 // 
  server.begin();                                    // サーバー開始
}                                                    //
//                                                   //
// +---------------------------------------------------------------------------+
// | デバッグ表示用                                                            |
// +---------------------------------------------------------------------------+
void dbgdisp_mode(String prefix) {                    //
  String s_wk ;                                       //
  # ifdef DEBUG_HTML                                  // デバッグ用表示
    s_wk = (sta_exec ? "true" : "false" ) ;           //
    Serial.println(prefix + " sta_exec = " + s_wk) ;  //
    s_wk = (stamode ? "true" : "false" ) ;            //
    Serial.println(prefix + " stamode  = " + s_wk) ;  //
    s_wk = (config_mode ? "true" : "false" ) ;        //
    Serial.println(prefix + " cfg_mode = " + s_wk) ;  //
    s_wk = (config_exec ? "true" : "false" ) ;        //
    Serial.println(prefix + " cfg_exec = " + s_wk) ;  //
  # endif                                             //
}                                                     //
//                                                    //
// +--------------------------------------------------------------------------+
// | クライアントからのフォームデータを変数にセットし、設定ファイルに書き出す |
// +--------------------------------------------------------------------------+
//                                                    //
// -- グローバル変数の内容を設定ファイルに書き出す  
void wr_config() {                                    //
  char s_work[128] ;                                  //
  File fp ;                                           //
//                                                    //
  // String に 変数の内容を書き込む ------------------//-----------------------
  sprintf(s_work,"st_ssid : %s\n",st_ssid) ;          //
  s_config = String(s_work) ;                         //
  sprintf(s_work,"st_pass : %s\n",st_pass) ;          //
  s_config += String(s_work) ;                        //
  sprintf(s_work,"st_ip   : %3d.%3d.%3d.%3d\n",       //
              st_ip[0],st_ip[1],st_ip[2],st_ip[3]) ;  //
  s_config += String(s_work) ;                        //
  sprintf(s_work,"st_sm   : %3d.%3d.%3d.%3d\n",       //
              st_sm[0],st_sm[1],st_sm[2],st_sm[3]) ;  //
  s_config += String(s_work) ;                        //
  sprintf(s_work,"st_gw   : %3d.%3d.%3d.%3d\n",       //
              st_gw[0],st_gw[1],st_gw[2],st_gw[3]) ;  //
  s_config += String(s_work) ;                        //
  sprintf(s_work,"st_dns  : %3d.%3d.%3d.%3d\n",       //
          st_dns[0],st_dns[1],st_dns[2],st_dns[3]) ;  //
  s_config += String(s_work) ;                        //
  sprintf(s_work,"wifi_mode : %s\n",wifi_mode) ;      //
  s_config += String(s_work) ;                        //
  //                                                  //
  // 設定ファイルに書き込む --------------------------//------------------------
  fp = LittleFS.open("/config.txt",FILE_WRITE) ;      // 設定ファイルを開く
  if (!fp) {                                          //
    Serial.println(" 設定ファイル オープンエラー !!") ;  //
  } else {                                            //
    // 初期設定ファイル書き込み -------------------------------------------------
    if(fp.print(s_config)) {                          // ファイルに書き込み
      Serial.println("s_config written") ;            //   終了メッセージ
    } else {                                          //
      Serial.println("s_config write error !!") ;     //   失敗メッセージ
    }                                                 //
    fp.close() ;                                      // ファイルクローズ
  }                                                   //
  //                                                  //
}                                                     //
// クライアントからのデータを設定変数にセットする ----------------------------- 
void set_form2param() {                               //
  String s_work ="" ;                                 //
//                                                    //
  // クライアントからのデータを グローバル変数にセットする。
  if (server.arg("set") == "設定") {          //
    if (server.hasArg("ssid")) {              //   受信メッセージを表示
      s_work = server.arg("ssid") ;           //   ssid に続く文字列を取得
      s_work.toCharArray(st_ssid,128) ;       //   変数にセット
    }                                         //
    if (server.hasArg("pass")) {              //   受信メッセージを表示
      s_work = server.arg("pass") ;           //   pass に続く文字列を取得
      s_work.toCharArray(st_pass,128) ;       //   変数にセット
    }                                         //
    if (server.hasArg("ip1")) {               //   受信メッセージを表示
      s_work = server.arg("ip1") ;            //   ip1 に続く数値を変数にセット
      st_ip[0] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("ip2")) {               //   受信メッセージを表示
      s_work = server.arg("ip2") ;            //   ip1 に続く数値を変数にセット
      st_ip[1] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("ip3")) {               //   受信メッセージを表示
      s_work = server.arg("ip3") ;            //   ip1 に続く数値を変数にセット
      st_ip[2] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("ip4")) {               //   受信メッセージを表示
      s_work = server.arg("ip4") ;            //   ip1 に続く数値を変数にセット
      st_ip[3] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         // 
    if (server.hasArg("gw1")) {               //   受信メッセージを表示
      s_work = server.arg("gw1") ;            //   ip1 に続く数値を変数にセット
      st_gw[0] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("gw2")) {               //   受信メッセージを表示
      s_work = server.arg("gw2") ;            //   ip1 に続く数値を変数にセット
      st_gw[1] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("gw3")) {               //   受信メッセージを表示
      s_work = server.arg("gw3") ;            //   ip1 に続く数値を変数にセット
      st_gw[2] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("gw4")) {               //   受信メッセージを表示
      s_work = server.arg("gw4") ;            //   ip1 に続く数値を変数にセット
      st_gw[3] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("sm1")) {               //   受信メッセージを表示
      s_work = server.arg("sm1") ;            //   ip1 に続く数値を変数にセット
      st_sm[0] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("sm2")) {               //   受信メッセージを表示
      s_work = server.arg("sm2") ;            //   ip1 に続く数値を変数にセット
      st_sm[1] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("sm3")) {               //   受信メッセージを表示
      s_work = server.arg("sm3") ;            //   ip1 に続く数値を変数にセット
      st_sm[2] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("sm4")) {               //   受信メッセージを表示
      s_work = server.arg("sm4") ;            //   ip1 に続く数値を変数にセット
      st_sm[3] = s_work.toInt()  ;            //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("dns1")) {              //   受信メッセージを表示
      s_work = server.arg("dns1") ;           //   ip1 に続く数値を変数にセット
      st_dns[0] = s_work.toInt()  ;           //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("dns2")) {              //   受信メッセージを表示
      s_work = server.arg("dns2") ;           //   ip1 に続く数値を変数にセット
      st_dns[1] = s_work.toInt()  ;           //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("dns3")) {              //   受信メッセージを表示
      s_work = server.arg("dns3") ;           //   ip1 に続く数値を変数にセット
      st_dns[2] = s_work.toInt()  ;           //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("dns4")) {              //   受信メッセージを表示
      s_work = server.arg("dns4") ;           //   ip1 に続く数値を変数にセット
      st_dns[3] = s_work.toInt()  ;           //   ip1 に続く数値を変数にセット
    }                                         //
    if (server.hasArg("wifi_stamode")) {      //   stamode set 
      s_work = server.arg("wifi_stamode") ;   //   ip1 に続く数値を変数にセット
      s_work.toCharArray(wifi_mode,16) ;      //   変数にセット
      if (s_work == "sta") {                  //   設定値により、モードをセット
        stamode = true ;                      // 
        config_mode = false ;                 // 
      } else {                                // 
        stamode = false ;                     // 
        config_mode = false ;                 // 
      }                                       //
    }                                         //
    // グローバル変数を 設定ファイルに書き出す 
    wr_config() ;                           // 設定を初期設定ファイルに書き出し
  } else {                                    //
    config_mode = false ;                     //
  }                                           //
  //                                          //
  dbgdisp_mode("set") ;                       //
}                                             //
//                                            //
// +--------------------------------------------------------------------------+
// | CONF_HTML 送信                                                           |
// +--------------------------------------------------------------------------+
void  send_CONF_html() {
  String htmlwk ;
 
  // 変数置換え処理 ------------------------------------------------------------
  htmlwk = html_CONF ;
  htmlwk.replace("$ssid",String(st_ssid)) ;
  htmlwk.replace("$pass",String(st_pass)) ;
  htmlwk.replace("$ip1",String(st_ip[0])) ;
  htmlwk.replace("$ip2",String(st_ip[1])) ;
  htmlwk.replace("$ip3",String(st_ip[2])) ;
  htmlwk.replace("$ip4",String(st_ip[3])) ;
  htmlwk.replace("$sm1",String(st_sm[0])) ;
  htmlwk.replace("$sm2",String(st_sm[1])) ;
  htmlwk.replace("$sm3",String(st_sm[2])) ;
  htmlwk.replace("$sm4",String(st_sm[3])) ;
  htmlwk.replace("$gw1",String(st_gw[0])) ;
  htmlwk.replace("$gw2",String(st_gw[1])) ;
  htmlwk.replace("$gw3",String(st_gw[2])) ;
  htmlwk.replace("$gw4",String(st_gw[3])) ;
  htmlwk.replace("$dns1",String(st_dns[0])) ;
  htmlwk.replace("$dns2",String(st_dns[1])) ;
  htmlwk.replace("$dns3",String(st_dns[2])) ;
  htmlwk.replace("$dns4",String(st_dns[3])) ;
  if (stamode) {                                // ステーションモード時
    htmlwk.replace("$checked_sta","checked") ;  // 
    htmlwk.replace("$checked_ap","") ;          //
  } else {                                      // アクセスポイントモード時
    htmlwk.replace("$checked_sta","") ;         //
    htmlwk.replace("$checked_ap","checked") ;   //
  }
  htmlwk.replace("$footer",foot_msg ) ;
  // --------------------------------------------------------------------------
  server.send(200, "text/html", htmlwk);        // HTMLを送信
}
//                                             //
// +---------------------------------------------------------------------------+
// | 設定用 ハンドル処理 : WEBからの要求に対する応答処理                       |
// +---------------------------------------------------------------------------+
void proc_config() {                           //メイン HTMLページを返す
  Serial.println("-- config ---------") ;      //
  //                                           //
  set_form2param() ;                           //
  //                                           //
  dbgdisp_mode("   ") ;                        //
  //                                           //
  if (stamode != sta_exec) {                   // sta_mode が変わった場合
    if (strcmp(wifi_mode,"ap") == 0){          //  リブートメッセージを設定
      foot_msg = "アクセスポイントモードに移行します。<br>" ;
      foot_msg += "この画面を閉じてアクセスポイントに接続して下さい。" ;
    }else{                                     //
      foot_msg = "ステーションモードに移行します。<br>"; 
      foot_msg += "この画面を閉じて 設定したアドレスに接続して下さい。" ;
    }                                          //
    if (config_exec) {                         //   初期設定中なら
      send_CONF_html() ;                       //     初期設定 HTML 送信
    } else {                                   //   メイン画面なら
      send_MAIN_html() ;                       //     メイン HTML 送信
    }                                          //    ( その後 リブート)
  } else {                                     // sta_mode が変わらない場合 
    if (config_mode) {                         //   次に初期設定を表示するなら
      send_CONF_html() ;                       //     初期設定 HTML 送信
      config_exec = true ;                     //
    } else {                                   //   次にメイン画面なら
       send_MAIN_html() ;                      //     メイン HTML 送信
       config_exec = false ;                   //
    }                                          // 
  }                                            //
  if (stamode != sta_exec) {                   // sta_mode が変わった場合
    Serial.println("------------------- リブートします------------------- ");
    delay(500);                                // 
    ESP.restart() ;                            // リブート
  }                                            //
}                                              //
 

 < スケッチ (LED調光用) >

// +===========================================================================+
// |  メイン処理                                                               |
// +===========================================================================+
// void send_MAIN_html()       // メインHTML を送信する
// void proc_main()            // メインHTML用ハンドル処理
// +---------------------------------------------------------------------------+
// | HTML 送信処理                                                             |
// +---------------------------------------------------------------------------+
// - CONF_HTML 送信                              //
void send_MAIN_html() {                          //
  String htmlwk ;                                //
  //                                             //
  htmlwk = html_MAIN ;                           // htmlwk に HTML をコピー
  htmlwk.replace("$footer",foot_msg ) ;          // 変数を値に置き換える
  //                                             //
  server.send(200, "text/html", htmlwk);         // HTMLを送信
  //                                             //
}                                                //
//                                               // 
void proc_main() {                               //
  if (server.hasArg("on")) {                     //                  
    brightness +=25 ;                            // 明るさを +25 
    Serial.print("brightness value : ");         //
    Serial.println(brightness);                  //
    // -- for ledc                               //
    ledcWrite(2, brightness) ;                   // チャネル#0 に Duty値設定
  } else if (server.hasArg("off")) {             //
    brightness = 0 ;                             // 明るさを "0" 
    Serial.print("brightness value : ");         //
    Serial.println(brightness);                  //
    // -- for ledc                               //
    ledcWrite(2, brightness) ;                   // チャネル#0 に Duty値設定
  } else if (server.hasArg("config")) {          //
    config_mode = true ;                         //
  } else {                                       //
  }                                              //
  dbgdisp_mode("   ") ;                          //
  if (config_mode) {                             // 次に初期設定を表示するなら
    send_CONF_html() ;                           //   初期設定 HTML 送信
    config_exec = true ;                         //
  } else {                                       //   次にメイン画面なら
     send_MAIN_html() ;                          //     メイン HTML 送信
     config_exec = false ;                       //
  }                                              // 
  send_CONF_html() ;                             //     初期設定 HTML 送信
}                                                //

2025年9月21日日曜日

ESP32/arduino : VSCode + arduino-cli + neovim の インストール (WSL)

目的:

arduino IDE2 では 外部エディタを使用できない。
コードの編集等で Vim を使用したいため、VSCode + arduino-CLI + neovim での環境を構築してみる。

概要:

VSCode に 拡張機能として VSCode neovim, Remode Development, Arduino Community Edition を インストールし、WSL から起動する。
これにより、arduino IDE2 で 作成したソースコードをそのまま使用することができる。
VSCode は Windows側にインストールし、WSL から Windows版 VSCode を呼び出す。
VSCode には Linux版もあるが、Windows版を呼び出すのが推奨とのこと。

インストール手順

インストールは以下の通り行う。
Windows 側
  1. Windows に vscode をインストールする。
    https://code.visualstudio.com/download
    から Windows 版をダウンロードしてインストール
    以下の拡張機能をインストールする。
    拡張機能のインストールは VSCode 画面 左側のアイコンの 拡張機能 をクリックし、上側にある検索窓にキーワードを入力して探す。
    1. VSCode neovim
    2. Remote Development
WS側
  1. WSL に arduino-CLI インストール
    curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
    で ~/bin に arduino-cli が置かれる。
    この arduino-cli を /usr/local/bin に移動(コピー) する。
    ( または ~/bin に PATH を通す )
  2. WSL から vscodeを起動し、arduino の 拡張機能をインストールする。
    code .
    で Windows版 VSCode が起動する。
    拡張機能 として Arduino Community Editionをインストールする。
  3. Arduino 拡張機能の設定。
    Ctrl + , を押下し、検索窓に arduino を入れ、以下の項目を設定
    • Arduino: Use Arduino CLI
      チェックをつける
    • Arduino: Path
      arduino-cli を インストールしたパスを 設定
      /usr/local/bin
    • Arduino: Command Path
      arduino-cli
    • Arduino: Disable Itelli Sense Auto Gen
      チェックをつける
      ( つけなくてもいいかも )
    • Arduino: Additional Urls
      サードパーティのボードマネージャの URL
      ( 今回は 空のまま )

VSCode 実行

  1. Arduino をUSB 接続、WSL に接続ポートを認識させる。
    "arduino IDE の インストール (WSL)" 参照
    Windows power shell で
    "usbipd list"で 接続された USB のBUSID を確認し、"usbipd attach --wsl --busid <BUSID>" で WSL に接続する。
  2. arduino スケッチのあるディレクトリで VSCode を起動する。
    ボードマネージャ、シリアルポート を設定し、コンパイル(Verify), アップロード (upload) を行う。 ctrl + shift + P で 以下の設定やコマンドの実行を行う。
    • Arduino: Board Manager
      ボードマネージャの選択を行う。
      ESP32 の場合、
      esp32: by Espressif Systems をインストールする。
    • Arduino: Board Config
      ボードの設定を行う。
      ESP32 の場合、
      Selected Bord: で ESP32 Dev Modele (esp32)を選択する。
    • Arduino: Select Serial Port
      arduino を接続したポートを選択する。
      WSL にポートが認識されていないと、選択肢に出てこない。
    • Arduino: Verify
      スケッチのコンパイルを行う。
      エディタ右上にある ボタン (Arduino : Verify) からも可能
    • Arduino: Upload
      コンパイル結果のアップロードを行う。
      エディタ右上にある ボタン (Arduino : Upload) からも可能
    • Arduino:Open Serial Monitor
      ボーレートの選択を行うと画面下部にシリアルモニタの画面が開く。
      画面下部のパネルで シリアルモニタを選択しても良い。
      画面下部のパネルがない場合、右上の パネルの切り替えボタンを押下。
★コードの表示画面での #include エラー
コードの表示画面で #include が エラー になる。
(下部に赤い波線が付き、ソースファイルを開けません となる)
そのままでも コンパイル(Verify) は可能であるが、検出しないように以下のように設定。
  1. Intelli Sense Engine を Rag Parser に設定
    Ctrl + , を押下し、検索窓に Intelli を入れ、以下の項目を設定
    C_Cpp: Intelli Sense Engine
     :Rag Parser を選択
  2. 1. で 波線が消えず、青くなった場合、include パス の設定を行う
    カーソルを #include の行に移動すると、左側にランプマークが付く。
    左クリックして"browse.path" 設定の編集 を選択。
    "パスを含める" の項目に、include するヘッダファイルのパスを追加する。
    例:
    ${workspaceFolder}/**
    ~/.arduino15/packages/esp32/hardware/esp32/3.3.0/cores/esp32/
    ~/.arduino15/packages/esp32/hardware/esp32/3.3.0/libraries/**
    
  3. これで include の エラーは消える。 ( .vscode/c_cpp_properties.json に パス等が追加される )

★LittleFs への アップロード
LittleFs への アップロードは VSCode arduino-CLI からはできない。
このため、LittleFS へのアップロードは Arduino IDE2.x を使用する様にした。
 VScode + arduino-CLI とArduino IDE2.x は 同時に起動しても問題なさそう。
尚、アップロードは VSCode の シリアルポートは 閉じた状態で Auduino IDE2.x からアップロードを行う。

2025年8月11日月曜日

ESP32/arduino : LittleFS の ファイルの ダウンロード

目的:

ESP32 の ファイルシステム (LittleFS) にあるファイルをダウンロードする 。

概要:

LittleFSにファイルが格納された状態でダウンロード用スケッチを書き込み、WEB経由でファイルをPC等にダウンロードする。
制限事項として、
 ファイルはカレントディレクトリのみの対応。
 ファイルの最大数は 50 固定
としている。

WEB画面サンプル

スケッチコード:

以下にスケッチのコードを示す。
// +==========================================================================+
// |  LittleFS data download                                                  |
// +==========================================================================+
#include <WiFi.h>                                //
#include <WebServer.h>                           // WebServer 使用の為
#include <ArduinoJson.h>                         // JSOM 使用 の為
#include "FS.h"                                  // LittleFS 使用に必要
#include "LittleFS.h"                            // LittleFS 使用に必要
//                                               //
// +--------------------------------------------------------------------------+
// | WiFi 設定 他                                                             |
// +--------------------------------------------------------------------------+
const char* ssid = "XXXXXXXXXXXXXXX";            // WiFi AP  SSID
const char* password = "xxxxxxxxxxxxx";          // WiFi AP  パスワード
//                                               //
IPAddress ip(192, 168, 1, 32);                   // ESP32 IPアドレス
IPAddress gateway(192,168, 1, 1);                //       ゲートウェイアドレス
IPAddress subnet(255, 255, 255, 0);              //       サブネットマスク
IPAddress DNS(192, 168, 1, 91);                  //       DNS アドレス
//                                               //
WebServer server(80);                            // WebServer オブジェクト生成
//                                               //
// +--------------------------------------------------------------------------+
// | グローバル変数                                                           |
// +--------------------------------------------------------------------------+
String filelist[50] ;                            // ファイルリスト 最大 50
int fileCount = 0 ;                              // ファイル数 カウント用
//                                               //
// +--------------------------------------------------------------------------+
// | Main HTML , JAVA Script                                                  |
// +--------------------------------------------------------------------------+
const char* htmlPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    .filelist{width:400px;}
    input {margin:4px;width:100px;}
    div {font-size:16pt;text-align:center;width:350px;}
  </style>
  <title>LittleFS ファイルリスト</title>
</head>
<body>
  <h3>LittleFS dataリスト</h3>
      <div>
        <form method='get'>
          <input type='button' name='btn1' value='更新' onclick='loadList()' >
          <input type='button' name='btn2' value='ダウンロード' onclick='getSelVal()'>
          <input type='button' name='btn3' value='削除' onclick='removeFile()'>
        </form>
      </div>
  <select class="filelist" id="listBox" size="5" ></select>

  <script>
    // ファイルリストを取得 ---------------------
    async function loadList() {                  // ファイルリスト取得
      const response = await fetch("/list");     // fetch で リストをjsonで 取得
      const data = await response.json();        // json の解釈
      const listBox = document.getElementById("listBox");  // listBox オブジェクト取得
      listBox.innerHTML = "";                    // ListBox を一度クリア
      data.filelist.forEach(item => {            // ListBox にファイルリストを 格納
        const option = document.createElement("option");
        option.text = item;
        listBox.add(option);
      });
    }
    // ファイルを取得 ---------------------------
    async function getSelVal() {                 //  選択したリストをダウンロードする
      // リストの選択した値からファイル名を取得する
      const selectElement = document.getElementById("listBox");  // listBox の要素を取得
      const selectedValue = selectElement.value ;                // 要素の値を取得
      if (selectedValue != "") {                                 // 要素が選択されている場合のみ実行
        const selectedVArry = selectedValue.split("\/");         // 値を "/" で分割
        const selectedfname= selectedVArry[selectedVArry.length - 1];  // 分割した最後の値(ファイル名)を取得
        // ファイル名の内容を要求し、ダウンロードする
        try {
          const response = await fetch("/data?file=" + selectedValue) ;  // GET /data?file=<ファイル名> 発行
          const bdt = await response.blob() ;                    // 応答を blob として受信して bdt に格納
          const url = window.URL.createObjectURL(bdt) ;          // blobのダウンロード先URLを生成
          const a = document.createElement('a') ;                // a タグを生成
          a.download = selectedfname ;                           // download属性にファイル名を設定
          a.href = url ;                                         // href属性に URLを設定
          a.click() ;                                            // a タグをクリックしてダウンロード実行
          window.URL.revokeObjectURL(url) ;                      // url オブジェクトの開放
        } catch (error) {                                        // ファイル取得できなかったときの処理
          alert("file read error") ;                             //
        }
      }
    } 
    // ファイルを削除 ---------------------------
    async function removeFile() {                                //
      // リストの選択した値からファイル名を取得して、削除要求を発行する
      const selectElement = document.getElementById("listBox");  // listBox の要素を取得
      const selectedValue = selectElement.value ;                // 要素の値を取得
      if (selectedValue != "") {                                 // 要素が選択されている場合のみ実行
        const response = await fetch("/del?file=" + selectedValue) ; // GET /del?file=<ファイル名> 発行
        loadList() ;                                             // ファイルリスト取得
      }                                                          //
    }
    // 最初にファイルリストを取得する                            //
    loadList() ;                                                 //
  </script>
</body>
</html>
)rawliteral";
//                                               //
// +--------------------------------------------------------------------------+
// | ハンドル処理 : WEBからの要求に対する応答処理                             |
// +--------------------------------------------------------------------------+
void handleRoot() {                              //メイン HTMLページを返す
  get_client_msg() ;                             // *- デバッグ用
  if (server.hasArg("btn2")) {                   // |    受信メッセージを表示
    Serial.println("  hit btn2 ") ;              // |
  }                                              // +---------------
  server.send(200, "text/html", htmlPage);       // HTMLを送信
}                                                //
// ファイルのリストを作成して送信                //
void handleList() {                              //ファイルリスト要求の応答
  DynamicJsonDocument doc(512);                  // JSON データ用変数
  JsonArray arr = doc.createNestedArray("filelist");// JSON配列を作成(キー:filelist)
  Serial.println("Client access /list ") ;       // デバッグ用表示
  listDir("/") ;                                 // ファイルリスト取得
  for (int i = 0; i < fileCount; i++) {          //   ファイルリストを 
    arr.add(filelist[i]);                        //   JSON配列に追加
  }                                              //
  String json;                                   // JSON 文字列変数
  serializeJson(doc, json);                      // JSONを文字列に変換
  Serial.println("json : ") ;                    // +- デバッグ用表示
  Serial.println(json) ;                         // +----------------
  server.send(200, "application/json", json);    // JSONデータを送信
}                                                //
// 選択されたファイルのデータを送信              //
void handleFileData() {                          // ファイル送信要求の応答
  String sdt = "Client access /data \n" ;        // +- デバッグ用表示
  sdt += get_client_msg() ;                      // |
  Serial.println(sdt) ;                          // +----------------
  String fname = server.arg("file") ;            // file の値(ファイル名)取得
  Serial.println("fname : " + fname ) ;          // +- デバッグ用表示
  File fp = LittleFS.open(fname,"r") ;           //  ファイルをオープンする
  if (!fp) {                                     //  オープン失敗の場合
    server.send(404, "text/plain", "File not found"); //  
    return ;                                     //
  }                                              //
  server.streamFile(fp, "application/octet-stream"); //  ファイル送信
  fp.close();                                    // ファイルをクローズ
}                                                //
// 選択されたファイルのデータを削除              //
void handleFileDel() {                           //
  String sdt = "Client access /del \n" ;         // +- デバッグ用表示
  sdt += get_client_msg() ;                      // |
  Serial.println(sdt) ;                          // +----------------
  String fname = server.arg("file") ;            // file の値(ファイル名)取得
  Serial.println("fname : " + fname ) ;          // +- デバッグ用表示
  String res = del_LittleFS(fname) ;             // ファイルを削除
  server.send(200, "text/plain",res) ;           // 結果を送信
}                                                //
// URLが不明な場合にメッセージを送信             //
void handleNotFound() {                          // 不明なURL受診時の処理
  String message = "File Not Found\n\n";         //   接続先のURLが 無い場合の
  message += get_client_msg() ;                  //   メッセージ作成
  server.send(404, "text/plain", message);       // メッセージを WEB  に送信
}                                                //
// 受信した要求の内容を文字列に格納して返す      //
String get_client_msg() {                        //  受信した要求の内容を作成
  String s_req = "request param ----\n";         //    Clientからの要求
  s_req += "URI: " + server.uri() + "\n" ;       // 
  s_req += "Method: ";                           //
  s_req += (server.method() == HTTP_GET) ? "GET\n" : "POST\n"; //
  s_req += "Arguments: " + String(server.args()) + "\n" ; //
  for (uint8_t i = 0; i < server.args(); i++) {  //
    s_req += " " + server.argName(i) + ": " ;    //
    s_req += server.arg(i) + "\n" ;              //
  }                                              //
  s_req += "------------------ \n";              //
  Serial.print(s_req);                           // シリアルに表示
  return s_req ;                                 // 呼び出し元に文字列を応答
}                                                //                                                 
// *--------------------------------------------------------------------------+
// |  処理関数 : arduino (ESP32) 内部処理用関数                               |
// +--------------------------------------------------------------------------+
//                                               //
void listDir( const char *dirname) {             // ファイルリストの取得
  Serial.printf("Listing directory: %s\r\n", dirname); //
  File root = LittleFS.open(dirname);            // 指定ディレクトをオープン
  if (!root) {                                   //
    Serial.println("- failed to open directory");//
    return;                                      //
  }                                              //
  if (!root.isDirectory()) {                     // ディレクトリか判定
    Serial.println(" - not a directory");        //   ディレクトリでなければ
    return;                                      //    戻る
  }                                              //
  //                                             //
  fileCount = 0 ;                                // ファイル数カウント 初期化
  File file = root.openNextFile();               // 最初のファイルをオープンする
  while (file) {                                 //   ファイルがある限りループ
    if (file.isDirectory()) {                    //   ディレクトリか判定
      Serial.print("  DIR : ");                  //   シリアルに情報表示
      Serial.println(file.name());               //
    //if (levels) {                              //   指定階層まで再帰的に
    //  listDir(file.path()) ;                   //   リストを取得
    //}                                          //     * 非対応
    } else {                                     //   ファイルの場合の処理
      Serial.print("  FILE: ");                  //    +--シリアルに情報表示
      Serial.print(fileCount);                   //    |
      Serial.print("  ");                        //    |
      //Serial.print(file.name());               //    |
      Serial.print(file.path());                 //    |
      Serial.print("\tSIZE: ");                  //    |
      Serial.println(file.size());               //    +--------------------
      //filelist[fileCount] = String(file.path()) + file.name() ; // リストにファイル名を追加
      filelist[fileCount] = file.path();         // リストにファイル名を追加
      fileCount ++ ;                             //    ファイル数をインクリメント
      if (fileCount <50) {                       // 
        file = root.openNextFile();              //    次のファイルをオープン
      } else {                                   //
        break ;                                  //
      }                                          //
    }                                            //
  }                                              //
}                                                //
//                                               //
String del_LittleFS(String fname) {              // LittleFS のファイル削除
  String result ;                                //
  Serial.println(" ------ Check_file ----------") ; //
  if (LittleFS.exists(fname)) {                  // fname ファイル存在確認
    Serial.println(fname + " 有り") ;            // あった場合の表示
  } else {                                       //
    Serial.println(fname + " なし") ;            // 無かった場合の表示
  }                                              //
  if (LittleFS.remove(fname)) {                  // fname ファイル削除
     Serial.println(fname + " 削除成功") ;       // 成功時の表示
     result = "success" ;                        //
  } else {                                       //
     Serial.println(fname + " 削除失敗") ;       // 失敗時の表示
     result = "failure" ;                        //
  }                                              //
  return result ;                                // 
}                                                //
// *--------------------------------------------------------------------------+
// |  初期設定                                                                |
// +--------------------------------------------------------------------------+
void setup()                                     //
{                                                //
  Serial.begin(115200);                          // シリアル出力開始
  WiFi.config(ip, gateway, subnet, DNS);         // ESP32 WiFi 設定
  delay(10);                                     //
  Serial.println();                              // デバッグ用出力
  Serial.print("Connecting to ");                // :
  Serial.println(ssid);                          // :
  WiFi.begin(ssid, password);                    // WiFi 開始
  while (WiFi.status() != WL_CONNECTED) {        // WiFi 接続待ち
      delay(500);                                //
      Serial.print(".");                         //
  }                                              //
  Serial.println("");                            // デバッグ用出力
  Serial.println("WiFi connected.");             // 接続先の IP address を
  Serial.println("IP address: ");                // シリアルに出力
  Serial.println(WiFi.localIP());                //
  //                                             //
  // ハンドル処理の登録                          //
  server.on("/", HTTP_GET, handleRoot);          // メインHTML の設定
  server.on("/list", handleList);                // ファイルリスト要求の応答
  server.on("/data", handleFileData) ;           // ファイル送信要求の応答
  server.on("/del", handleFileDel) ;             // ファイル削除要求の応答
  server.onNotFound(handleNotFound) ;            // 存在しない URL の場合
  //                                             //
  // WEBサーバー開始                             //
  server.begin();                                // WiFi サーバ 開始
  if (!LittleFS.begin(true)) {                   // LittleFS 開始
    Serial.println("LittleFS のマウントに失敗") ; //
    return ;                                     //
  }                                              //
}                                                //
//                                               //
// +--------------------------------------------------------------------------+
// | メインループ                                                             |
// +--------------------------------------------------------------------------+
void loop() {                                    //
  // クライアント処理                            //
  server.handleClient();                         //
  //                                             //
}                                                //
//                                               //

処理概要:

処理の概要は以下の通り。
  • WEB画面
    「更新」,「ダウンロード」,「削除」 のボタンを持ち、それぞれクリックされたら javascript を呼び出す。 また、リストボックスを設置する。
  • JavaScript
    各ボタンに対応する function を持つ。
    loadList : 更新時、ファイルリストを ESP32 へ要求し、結果(json) をリストボックスに表示する。
    getSelVal : ダウンロード時、選択されているファイル名のファイルを ESP32 へ要求し、結果(BSOB)をPC にダウンロードする。
    removeFile : 削除時、選択されているファイル名のファイルの削除を ESP32 へ要求し、終了後、LoadList を実行する。
  • ハンドル処理
    WEBクライアントからの各要求に対する応答処理を行う。
    handlRoot() : main HTML の送信を行う。
    handleList() : ファイルリスト要求に対する応答。
    LittleFS のファイルリストを作成し、JSON形式でWEBクライアントに送信する。

    handleFileData() : ファイル要求に対する応答。
    要求された LittleFS のファイルを server.streamFile でWEBクライアントに送信する。

    handleFileDel() : 削除要求に対する応答。
    要求された LittleFS のファイルを削除し、結果(成否 の文字列) をWEBクライアントに送信する。

    handleNotFound() : 不明なURL受診時の処理。
    クライアントからの受信内容をWEBクライアントに 404 で送信する。

  • 内部処理
    listDir() : LittleFS のファイルリストを作成する。
    del_LittleFS() : LittleFS から指定したファイルを削除する。
各ハンドル処理を setup() で登録し、loop() で server.handleClient() を呼び出す。
その他、詳細は スケッチコード内のコメントを参照。

2025年7月26日土曜日

ESP32/arduino : WebServer.h を使用した WEBサーバー処理

目的:

WebServer クラスを使用した WEBサーバー処理を行う。
以前作成した "POST受信" のスケッチを WebServer.h をインクルードして作り直す。

WebSerberクラス使用の概要 (変更点など):

WebSerberクラスを使用することによる変更点は以下の通り。
  1. オブジェクトの生成
    以前は WiFiServer で生成していたが、WevServer で生成する。 WebServer server(80) ;
  2. ハンドル処理
    WiFiServerでは loop 内で クライアントから受信した文字列を解析していた。
    WebSerberクラスでは、クライアントからの要求に対する処理(ハンドル処理)関数を作成し、setup 内で server.on で呼び出す。
    ハンドル処理はアクセスされた URL, GET や POST の種別毎 等に作成する。
    server.on の書式は
    server.on(<URL>,<GET/POST種別等>,ハンドル処理関数) ;
    例 : server.on("/",HTTP_GET,handleROOT) ;
    第2引数 (HTTP_GET等) は 省略可能。
  3. HTML 送信
    WiFiServerでは、client.println 等で文字列を送信していたが、
    WebSerberクラスでは server.send で 送信する。
    server.send の書式は
    server.send(<ステータスコード>,<タイプ>,<内容>)
    例 : server.send(200,"text/html",htmlPage)
     htmlPage : HTML内容(コンテンツ)
    server.send は 各ハンドル処理がら呼び出す。
  4. loop()内処理
    loop()関数内では
    server.hanggleClient() ;
    を記述するのみ。

スケッチ例

"POST受信" を変更した例を以下に示す。
// +==========================================================================+
// |  WiFi LED ON/OFF TEST ( GET / POST )                                     |
// +==========================================================================+
                                                 //
#include <WiFi.h>                                // WiFi 用
#include <NetworkClient.h>
#include <WebServer.h>                           // Wevサーバー 用
                                                 //
// ----------------------------------------------------------------------------
// 各種設定                                                                  --
// ----------------------------------------------------------------------------
const char* ssid = "xxxxxxxxxxxxxxx";            // WiFi AP  SSID
const char* password = "xxxxxxxxxxxxx";          // WiFi AP  パスワード
                                                 //
String html;                                     // HTML 格納用
                                                 //
IPAddress ip(192, 168, 1, 32);                   // ESP32 IPアドレス
IPAddress gateway(192,168, 1, 1);                //       ゲートウェイアドレス
IPAddress subnet(255, 255, 255, 0);              //       サブネットマスク
IPAddress DNS(192, 168, 1, 90);                  //       DNS アドレス
                                                 //
WebServer server(80);                            // オブジェクトの生成
                                                 //
// ----------------------------------------------------------------------------
// メイン HTML                                                               --
// ----------------------------------------------------------------------------
const char* htmlPage = R"rawliteral(
  <!DOCTYPE html><html lang='ja'>
    <head><meta charset='UTF-8'>
      <style>input {margin:8px;width:100px;}
        div {font-size:16pt;text-align:center;width:250px;border:solid 4px #93ff93;}
      </style>
      <title>Color LED Controller</title>
    </head>
    <body>
      <div><p>LED ON/OFF</p>
        <form method='get'>
          <input type='submit' name='btn1' value='GET_ON' />
          <input type='submit' name='btn1' value='GET_OFF' />
        </form>
        <form method='post'>
          <input type='submit' name='btn2' value='POST_ON' />
          <input type='submit' name='btn2' value='POST_OFF' />
        </form>
      </div>
    </body> 
  </html>
)rawliteral";
                                                 //
// ----------------------------------------------------------------------------
// HTMLページ レスポンス処理                                                 --
// ----------------------------------------------------------------------------
void handleGET() {                               // "get"を受信した場合の処理
  String sdt ="---------------\n" ;              //
  for (uint8_t i = 0; i < server.args(); i++) {  // 受信した name と value を
    sdt += " " + server.argName(i) + ": " ;      // 表示
    sdt += server.arg(i) + "\n";                 //
  }                                              //
  sdt += "---------------" ;                     //
  Serial.println(sdt) ;                          //
  Serial.println("") ;                           //
  if (server.arg("btn1") == "GET_ON") {          // 受信した値を判定して
    Serial.println(" led on ");                  // LED を ON/OF
    digitalWrite(2, HIGH);                       //     LED on
  } else if (server.arg("btn1") == "GET_OFF") {  //
    Serial.println(" led off ");                 //
    digitalWrite(2, LOW);                        //     LED off
  }                                              //
  server.send(200, "text/html", htmlPage);       // メインHTML を送信
}                                                //

void handlePOST() {                              //
  String sdt ="---------------\n" ;              //
  for (uint8_t i = 0; i < server.args(); i++) {  // 受信した name と value を
    sdt += " " + server.argName(i) + ": " ;      // 表示
    sdt += server.arg(i) + "\n";                 //
  }                                              //
  sdt += "---------------" ;                     //
  Serial.println(sdt) ;                          //
  Serial.println("") ;                           //
  if (server.arg("btn2") == "POST_ON") {         // 受信した値を判定して
    Serial.println(" led on ");                  // LED を ON/OFF
    digitalWrite(2, HIGH);                       //     LED on
  } else if (server.arg("btn2") == "POST_OFF") { //
    Serial.println(" led off ");                 //
    digitalWrite(2, LOW);                        //     LED off
  }                                              //
  server.send(200, "text/html", htmlPage);       // メインHTML を送信
}                                                //

void handleNotFound() {                          //
  String message = "File Not Found\n\n";         // 接続先のURLが 無い場合の
  message += "URI: ";                            // 処理
  message += server.uri();                       //
  message += "\nMethod: ";                       //
  message += (server.method() == HTTP_GET) ? "GET" : "POST"; //
  message += "\nArguments: ";                    //
  message += server.args();                      //
  message += "\n";                               //
  for (uint8_t i = 0; i < server.args(); i++) {  //
    message += " " + server.argName(i) + ": " ;  //
    message += server.arg(i) + "\n" ;            //
  }                                              //
  server.send(404, "text/plain", message);       // メッセージを WEB  に送信
}                                                //
                                                 //
// ----------------------------------------------------------------------------
// 初期設定                                                                  --
// ----------------------------------------------------------------------------
void setup()                                     //
{                                                //
    Serial.begin(115200);                        // シリアル出力開始
    pinMode(2, OUTPUT);                          // LED 用 PIN 設定
                                                 //
    WiFi.config(ip, gateway, subnet, DNS);       // ESP32 WiFi 設定
    delay(10);                                   //
    Serial.println();                            // デバッグ用出力
    Serial.print("Connecting to ");              // :
    Serial.println(ssid);                        // :
    WiFi.begin(ssid, password);                  // WiFi 開始
                                                 //
    while (WiFi.status() != WL_CONNECTED) {      // WiFi 接続待ち
        delay(500);                              //
        Serial.print(".");                       //
    }                                            //
    Serial.println("");                          // デバッグ用出力
    Serial.println("WiFi connected.");           // 接続先の IP address を
    Serial.println("IP address: ");              // シリアルに出力
    Serial.println(WiFi.localIP());              //
                                                 //
    server.on("/", HTTP_GET ,handleGET)  ;       // GET  の設定
    server.on("/", HTTP_POST,handlePOST) ;       // POST の設定
    server.onNotFound(handleNotFound) ;          // 存在しない URL の場合
    server.begin();                              // WiFi サーバ 開始
                                                 //
}                                                //
                                                 //
// ----------------------------------------------------------------------------
// メインループ                                                              --
// ----------------------------------------------------------------------------
void loop(){                                     //
  server.handleClient();                         // クライアント処理
                                                 //
}                                                //
スケッチの説明は、スケッチ内のコメントを参照。
WebServer 使用 への変更に伴い、HTML の書き方も変更した。
文字列 (String) へ 格納する際、 R"rawliteral( と )rawliteral"; で囲む。
こうすることで、改行などのエスケープ文字を そのまま記述可能になる。

2025年7月17日木曜日

ESP32/arduino : LittleFS アップローダーを使用する

目的:

ESP32 の ファイルシステム (LittleFS) にファイルをアップロードする為のアップローダーをインストールする 。
ここでは、WSL にインストールした Linux用 arduino IDE 2.xx にインストールを行う。

手順:

  1. Uploader のインストール
    1. arduino IDE 停止
      arduino IDE が動作していたら停止する。
      "ファイル" - "停止"
    2. Uploader の ダウンロード
      アップローダーのダウンロードページ
      https://github.com/earlephilhower/arduino-littlefs-upload
      の「Installation」 にある "VSIX file" をクリック すると、
      uploader (arduino-littlefs-upload-x.x.x.vsix) がある。
      これをクリックしてダウンロードする。
    3. アップローダ の格納
      arduino IDE の コンフィグレーションディレクトリ (~/.ardinoIDE) の下に plusins ディレクトリを作る。
      ダウンロードしたファイルを ~/.arduinoIDE/plugins/ の下に格納する。
    4. arduino 起動
      arduino IDE を起動する。

  2. アップロード
    1. シリアルモニタが開いていれば閉じる。
    2. スケッチのあるフォルダに "data" フォルダを作成し、その下にアップロードするファイルを格納する。
    3. Ctrl+Shift+Pでコマンドパレットを開き、「Upload LittleFS to Pico/ESP8266/ESP32」を選択する。
    アップロードすると、フラッシュメモリの中身は"data"フォルダの内容に置き換わる。

    なので、ファイルを消去する場合は、"data"フォルダから消去して再度アップロードすれば消える。

  3. アップロード確認
    ファイルシステムにあるファイルのリストを取得するスケッチを以下に示す。

    #include <LittleFS.h>                          //                              ;
                                                   //                              ;
    void setup() {                                 //
      File root ;                                  // root Dirctory  定義
      File file ;                                  // 
                                                   //
      Serial.begin(115200);                        // シリアル 開始
      Serial.println("") ;                         //
      Serial.println(" ---- File List ------") ;   //
                                                   //                              ;
      // LittleFS テスト ------------------------- //
      if (!LittleFS.begin(true)) {                 // LittleFS 開始
        Serial.println("LittleFS のマウントに失敗") ; //
        return ;                                   //
      }                                            //
                                                   //                              ;
      root = LittleFS.open("/");                   // root ディレクトリオープン
      if (!root) {                                 //
        Serial.println("root dir オープン失敗") ;  //
        return;                                    //
      }                                            //
                                                   //                              ;
      if (!root.isDirectory()) {                   // ディレクトリ判定
        Serial.println("/ はディレクトリでない!!"); //
        return;                                    //
      }                                            //
                                                   //                              ;
      file = root.openNextFile();                  // ファイルをオープン 
      while (file) {                               // ファイルがある限りループ 
        Serial.print("  FILE: ");                  //
        Serial.print(file.name());                 // ファイル名を表示
        if (file.isDirectory()) {                  // ディレクトリなら、"DIR"を表示
          Serial.print("  DIR");                   //
        }                                          //
        Serial.print("  SIZE: ");                  // ファイルサイズを表示
        Serial.println(file.size());               //
        file = root.openNextFile();                //
      }                                            //
    }                                              //
                                                   //                              ;
    void loop() {                                  // loop は未使用
    }                                              //
                                                   //                              ;
    

    実行結果


2025年7月14日月曜日

ESP32/arduino : LittleFS を使用する

目的:

arduino IDE でこれまで使用してきた SPIFFS が 非推奨となり、SPIFFS アップローダ も IDE 2.xx では 使用できない。
この為、LittleFS を使用して ファイルの生成, リード 等の記述を試した。

SPIFFS から LittleFS への移行 :

SPIFFS から LittleFS へ書き換える場合の変更箇所としては、主に
  1. include file
    SPIFFS.h → LittleFS.h
  2. コード
    主に 以下の様に "SPIFFS" を "LittleFS" に 書き換えれば 動作する。
    SPIFFS.begin  → LittleFS.begin
    SPIFFS.exists → LittleFS.exists
    SPIFFS.open   → LittleFS.open
    SPIFFS.remove → LittleFS.remove

サンプルコード

以下にサンプルコートを表示する。
サンプルコードでは、
ファイルの存在確認を行い、
あればファイルを1行づつ読み込んで行数をカウントする。
その後、ファイルに1行書き足して終了。
実行する(リセットする)たび、ファイルの内容が 1行づつ増えていく。

コードの説明は、サンプルコード内の コメントを参照。

#include <Arduino.h>                           //
#include "FS.h"                                // LittleFS 使用に必要
#include <LittleFS.h>                          // LittleFS 使用に必要

void setup() {                                 //  
  File datafile ;                              // File 定義
  String buf = ""   ;                          // 文字列変数 定義
  String lbuf = ""  ;                          // 文字列変数 定義
  int n = 0 ;                                  //
                                               //
  Serial.begin(115200);                        // シリアル 開始
  Serial.println("") ;                         //
  Serial.println(" ------ Test Start ----------") ; //
                                               //
  // LittleFS テスト ------------------------- //
  if (!LittleFS.begin(true)) {                 // LittleFS 開始
    Serial.println("ファイルシステムのマウントに失敗") ; //
    return ;                                   //
  }                                            //
                                               // 
  // ファイルの存在チェック ------------------ // 
  Serial.println(" ------ Check_file ----------") ; //
  if (LittleFS.exists("/data_1.txt")) {        // data_1.txt ファイル存在確認
    Serial.println("data_1.txt 有り") ;        // あった場合の表示
    // ファイルのリード -----------------------//
    datafile = LittleFS.open("/data_1.txt","r") ; 
                                             // ファイルをリードモードでオープン
    if (!datafile) {                           //
      Serial.println("datafile open error !!"); // オープン失敗時の表示
    } else {                                   //
      //  ファイルを1行づつリード
      n = 0 ;                                  //
      while (datafile.available()) {           // リードできる文字がある間ループ
        lbuf = datafile.readStringUntil('\n') ;  // ファイルを1行リード
        Serial.println(lbuf) ;                 // リードした内容の表示
        n++ ;                                  // 行数をカウント
        buf += lbuf + "\n" ;                   // buf に追加
      } ;                                      //
      Serial.println("line no. :" + String(n)) ; // リードした  行数の表示

      datafile.close() ;                       // ファイルをクローズ
    }                                          //
  } else {                                     //
    Serial.println("data_1.txt なし") ;        // 無かった場合の表示
  }                                            //
                                               //
  // ファイルのライト -------------------------//
      Serial.println(" ------ File W/R Test Start ----------") ; //
  datafile = LittleFS.open("/data_1.txt","w") ; 
                                             // ファイルをライトモードでオープン
  if (!datafile) {                             // 
    Serial.println("datafile open error !! ") ; // オープン失敗時の表示
  } else {                                     // 
    buf += "data add line #" + String(n) + "\n" ;  // 文字列を追加
    Serial.println("   write data :") ;        // 書き込む内容を表示
    Serial.print(buf) ;                        //
    Serial.println("") ;                       //
    datafile.print(buf) ;                      // data_1.txt に書き込み
    datafile.close() ;                         // ファイルをクローズ
  }                                            // 
                                               // 
  // ファイルのリード -------------------------//
  Serial.println("   read check  --- ") ;      //
  datafile = LittleFS.open("/data_1.txt","r") ; 
                                               // ファイルをリードモードでオープン
  if (!datafile) {                             //
    Serial.println("datafile open error !!"); // オープン失敗時の表示
  } else {                                     //
    buf = datafile.readString() ;              // ファイルをリード
    Serial.print(buf) ;                        // リードした内容の表示
    Serial.println("") ;                       //
    datafile.close() ;                         // ファイルをクローズ
  }                                            //
                                               //
  Serial.println(" ------ File W/R Test End ----------") ; //

  // ファイルの削除 , 削除して終わる場合は 以下のコメントアウトを外す
  //if (LittleFS.remove("/data_1.txt")) {        // data_1.txt ファイル存在確認
  //   Serial.println("data_1.txt 削除成功") ;   // 成功時の表示
  //} else {                                     //
  //   Serial.println("data_1.txt 削除失敗") ;   // 失敗時の表示
  //}                                            //
  // ------- ここまで ---------------------------//
}
// loop() は 未使用 -----------------------------
void loop() { }                                // 処理なし

実行結果 (シリアル出力)



2025年7月11日金曜日

ESP32/arduino : arduino IDE の インストール (WSL)

目的:

arduino IDE を WSL(debian) にインストールする。
Windows 版 よりも WSL の方が、コンパイル等が早い模様。

手順:

  • USBデバイスを WSL で使用できるようにする。
    WSL から Windowsに接続されたUSB デバイスを直接アクセスすることができない。
    このため、オープンソースの usbipd-win プロジェクトをインストールする必要がある。

    1. Windows に uspipd をインストールする。
      usbipd-win プロジェクトの最新リリース ページ (https://github.com/dorssel/usbipd-win/releases ) から
      usbipd-win_*.*.*_x64.msi をダウンロードし、実行する。
    2. WSLコマンドラインが開かれている状態で Arduino デバイス を USB に接続する。
    3. 管理者権限で PoweerShell を立ち上げる
    4. USBデバイスを確認する。
      usbipd list
      で USB の情報を確認する。
      上の例では対象デバイスは BUSID 6-4 (VID:PID = 0403:6001) で Shared となっているが、Not shared であれば、
      usbipd bind --busid <BUSID> を実行する。
      実行後、usbipd list で Shared になっていることを確認する。

    5. WSL に USBデバイスを接続する。
      WSL の ターミナルが開いた状態で PowerShell (管理者権限でなくても可) で 以下を実行。
      usbipd attach --wsl --busid <BUSID>
    6. WSL で USBデバイスを確認する。
      WSL に usbutils がインストールされていなければ インストールする。
      sudo apt install usbutils

      lsusb を実行し、 接続したデバイスが 見えることを確認する。
      上の例では ID 0403:6001 が 対象デバイス。

  • arduino ide をインストールする
    1. libfuse2 のインストールを行う。
      arduino IDE の インストールには libfuse2 がインストールされている必要がある。
      sudo apt install libfuse2
      自環境ではすでにインストール済であった。(いつ入れたのか ? )
    2. ファイルのダウンロード
      1. alduino 公式ページ (alduino ide) から "Linux AppImage 64 bits(X86-64)" をダウンロードする。
      2. ダウンロードしたファイルを WSL の /home/<user>/Arduino に 格納する。
        ( 格納場所は 任意の場所で可 )
      3. 格納したファイルに実行権限があるか確認し、なければ 実行権限を付ける。
    3. arduino 実行, 設定
      1. arduino 実行
        格納した arduino-ide_2.3.6_Linux_64bit.AppImage を実行する。
      2. ボードマネージャの追加
        (1) ファイル - 基本設定 で 「追加のボードマネージャ」に以下の URL を追加する
        https://espressif.github.io/arduino-esp32/package_esp32_index.json
        (2) ツール - ボード:"******"- ボードマネージャ をクリック。
        (3) ボードマネージャ の 検索ボックス に "ESP32" を入力。
        (4) esp32 by Expressif System を インストールする。
        (5) ツール - ボード - esp32 で、 ESP32 Dev Module を選択
      3. ポートの選択
        ツール - ポート で USB接続ポートを選択

  • 次回以降の arduino 起動
    • USBデバイス の抜き差し
      USBデバイスを抜き差しした場合やWSLを再起動した場合は、再度 WSL に USBデバイスを認識させる必要がある。
      WSLのターミナルが開いた状態でPowerShell(管理者権限でなくても可) で usbipd attach を実行。
      usbipd attach --wsl --busid <BUSID>
      尚、接続するUSBボートが変わった場合、 BUSID が変わる為、usbipd list で BUSID を確認する。
    • arduino IDE の起動
      arduino-ide_2.3.6_Linux_64bit.AppImage を実行する。



2020年10月25日日曜日

ESP32/arduino : WEBから制御可能なNゲージ用 PWM制御パワーパック_改4

目的:

Nゲージ用 PWM制御パワーパックの改良/機能追加として、以下を行う。
(1) フォトリフレクタ TPR-105 による車両検出

追加機能:

レイアウトの主要地点に TPR-105 を設置し、車両検出を行う。
自動運転時、車両検出 (通過検出) により次の動作(設定)へ移行する。

 

車両検出ハードウェア:

 PWPK ハードウェア(回路) は こちら
 車両検出回路 は こちら

ESP32 に TPR-105 を接続できる端子が余っていない為、I2C接続の I/Oエキスパンダ(MCP23017) を使用して TPR-105 を接続している。
MCP23017 の使い方は こちら を参照。
TPR-105 の使い方は こちら を参照。
 

WEB画面:

車両検出対応の為、PWPK設定画面 ( 自動運行の設定画面 ) を下の様に変更。

 "設定更新契機" で、次の設定を開始するタイミングを
・時間経過
・地点通過
・スイッチ押下
から選択する。
地点通過で車両検出を行う。

その他の画面は WEBから制御可能なNゲージ用 PWM制御パワーパック_改3 と同じ。


スケッチ等:

スケッチ等は こちらのファイルを参照。(zipファイル)
スケッチ等の構成は、改3 と同様。
詳細はコメントを参照。
 

HTML     :   PWPK MAIN HTML (pwpk.html)

PWPK設定 HTML   (pwpk_conf.html)
PWPK XHR 応答 HTML (pwpk_resp.html)
WiFi設定 HTML (wifi_conf.html)
WiFi XHR 応答 HTML (wifi_resp.html)
スケッチ :    MAIN (pwpk_5.ino : 変数定義, setup, loop 関数)
WIFI設定 (wifi_config.ino : 主に WiFI 設定用 の関数)
PWPK処理 (pwpk.ino : 主に PWPK処理、PWPK 設定用 の関数)



車両位置検出概要:

車両位置検出は、以下の変数を使用する。

det_ariv : 到達フラグ。検出位置に到達したことを示す。定期割込み開始時にクリア。
pnt_extno : 車両位置。
最後に到達した位置を示す。初期値は 0xff

pnt_arrive[8] : 検出位置毎の到達フラグ。
検出位置に到達した事を示す。
検出位置 に 到達した時 '1', 処理終了か、別検出位置到達で '0'

pnt_mask[8] : 検出位置毎のマスクフラグ。
検出位置到達後、一定期間、到達の検出を行わない様にする
検出位置 に 到達した時 '1', 一定期間経過経過後に '0'

pnt_time[8] : 検出位置毎の到達時刻。
検出位置 に 到達した時の時刻を記憶する。


車両位置検出の処理の概要は以下の通り。
 (実際の処理は、スケッチ 参照)
  • 車両の位置情報取得
    1. 各検出位置毎に 到達後の経過時間をチェックし、一定時間経過していればマスクフラグを解除する。
    2. MC23017の割込み状態を取得 ( INTFA, INTCAPA をリードする )。
    3. 到達フラグをクリア。
    4. 割込み発生時、各変数更新。
      1. 到達位置を取得。
        マスクされていない位置に到達した時、到達フラグをセットして到達した位置を取得。
      2. 各変数更新。
        到達した位置の場合、到達フラグ(pnt_arrive[n]),マスクフラグ(pnt_mask[n]) をセットし、, 到達時刻(pnt_time[n])を更新。
        到達した位置ではない場合は、到達フラグ(pnt_arrine[n]) をクリア。

  • 自動運転制御中の 車両位置検出処理
    1. 自動運転開始時、全検出位置の変数を初期化する。
    2. 設定更新契機が地点通過の場合、待っている位置の到達フラグを確認。
      到達フラグが立っていた場合、自動運転設定の更新フラグをONし、全検出位置の到達フラグをクリア。

また、パワーパックの制御(定期割込み処理) の 概要は以下の通り。

  • 操作盤情報取得 : 操作盤の状態を取得し、状態により制御設定値を更新する。
    (Local(制御盤操作) 時)
    制御盤のスイッチ状態を取得する。
    (設定モード時)
    READYスイッチが押下ならWiFi再起動
    (通常モード時)
    出力用ボリューム値取得
    (制御盤操作許可時)
    (走行中ではない時)
    進行方向スイッチをチェックし、変化したらフラグをセット
    READYスイッチをチェックし、押下ならLED常時点灯設定を現在の反対にセット
    STARTスイッチをチェックし、押下ならスタートフラグをセット
    自動走行でない場合は、PWMduty値にボリューム値から計算した値を設定
    (制御盤操作不許可時)
    PWMduty値にOFF状態の値を設定
    ボリュームの値が'0'なら制御盤操作を許可にする。
    (Remote(WEB からの操作) 時)
    READYスイッチをチェックし、押下ならLocalにする。
  • 車両位置情報取得 : センサ()の状態を取得し、車両の位置情報を更新する。
    車両位置検出マスクフラグ更新
    MCP23017割込み状態取得(車両位置検出)
    車両位置検出用変数更新
  • 自動運転制御 : 自動運転設定により、制御盤設定値を自動で更新する。
    発車スイッチチェック
    (発車スイッチがONの時)
    (自動運行実行中ではない時)
    自動運行開始要求をON
    (自動運行実行中の時)
    (発車スイッチ待ちの時)
    設定更新要求をON
    (発車スイッチ待ちではない時)
    停車要求ON
    自動運転開始
    (自動運転開始要求がONの時)
    運転設定No.を1にし、各パラメータをセットする
    加減速要求ON
    通過位置情報をクリア
    ステータスLED値をNo.に設定して点灯ON
    自動運転中フラグをON
    開始時の時刻を取得
    自動運行中の処理
    (自動運転中フラグがONの時)
    (設定更新契機が時間経過の時)
    開始からの時間が運転設定時間以上なら設定更新要求ON
    (設定更新契機が地点通過の時)
    設定地点の到達フラグがONなら設定更新フラグをONし、全到達フラグをクリア
    (設定更新要求ONの時)
    次の有効な設定No.を探す。
    有効な設定No.が無い場合は停車要求をON
    (停車でない場合)
    設定を更新。
    ステータスLED値をNo.に設定して点灯ON
    (加速要求がONの時)
    (PWMduty値が目標値でない場合)
    PWMduty値に加速度設定値を加減算する
    (PWMduty値が目標値の場合)
    加速要求をOFFする
    (停車要求の時)
    (PWMduty値が停車時のdutyより大きい場合)
    PWMduty値から加速度設定値を減算する
    (PWMduty値が停車時のduty以下の場合)
    停車要求をOFFする
    自動運転中フラグをOFFする
    ステータスLED値を停車時の値に設定して点灯ON
    (自動運転中フラグがOFFの時)
    (停車時設定変化時)
    ステータスLED値を停車時の値に設定して点灯ON
    走行中フラグ更新
    PWMdutyの値から走行中の判定をしてフラグをON/OFF
    操作盤制御:更新された制御盤設定値により、走行設定を行う。
    進行方向設定
    ステータスLED制御
    PWMduty設定
    デバッグ用シリアル出力
    デバッグ用にシリアル画面に各種情報を出力する。