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 を実行する。



2025年2月15日土曜日

windows : Windows11 24H2 アップデート で 起動できない件 (WD SN550)

現象:

24H2 に アップデートした後、Power ON で 起動が遅かったり、黒い画面のまま進まなくなったりすることが発生し、ついに 立ち上がらなくなった。
強制的に 電源ON/OFFを繰り返したところ、起動時に回復画面が表示されるが、何をしても回復できない状態。セーフモードも起動しない。

原因:

WD の SSD を使用しているため、これが原因と推定。 
 (参考 : https://support-jp.sandisk.com/app/answers/detailweb/a_id/52288/)

但し、使用している型番は SN550   WDS100T2B0C で、公表されている製品とは異なる。
(対象は 2TB の 製品のみ)
また、Western Digital Dashboard で確認しても FW は最新となっている。
以下の対処を行った結果、改善されている為、同じ問題と推定。
追記
下記 「HmbAllocationPolicy」の 値 '1' では現象はなくなっていない模様。
設定値を変えて、様子見中。

対処方法:

レジストリで HMB のサイズを Minimun に 設定。
( 「HmbAllocationPolicy」の 値を 1 )

設定後の HMB設定 の状態は 以下の通り。

 Attemped to Allocate : 0x337000 bytes
 Actually Allocated   : 0x337000 bytes
 Device Minimum       : 0x337000 bytes
 Device Preferred     : 0xC800000 bytes
 Policy Maximum       : 0x1FDB7000 bytes

HMB のサイズは、設定前は、推奨値 ( 0xC800000 bytes ) になっていた。
(23H2 の時は 0x4000000 byte (64MB) )

結果:

現状、問題は発生しなくなった。
しばらくは様子見。
追記
しばらく使用した後、シャットダウンが終了せず、強制終了後の立ち上げでPIN入力後に黒い画面のままとなる現象が発生。サインオフして再度サインイン したら立ち上がったが、現象はなくなっていない模様。
HmbAllocationPolicy の値を '0' にして 再度様子見。



2025年2月12日水曜日

raspberry pi : OS インストール(Raspberry Pi Imager, SSH, 無線LAN)

目的:

ラズベリーパイ に Raspberry Pi Imager を使用して Raspberry Pi OS をインストールする。
ラズベリーパイ 用には モニタ、キーボード、マウス、有線LAN は接続せず、 Windows PC から無線LANで SSH接続して行う。

手順:

Raspberry pi の モニタ、キーボード、マウス、有線LAN は 接続しない前提での インストール手順。
LAN は 無線LAN。
今回は、raspberry pi 5 に、64bit版 をインストール。

(1) imager のダウンロード

https://www.raspberrypi.com/software/(raspberry pi 公式 softwear ページ )
から 「Download for Windows」を押下して ダウンロードする。

(2) imager のインストール

ダウンロードした imager_*.*.*.exe  を 実行し、インストールを行う。
(今回は imager_1.8.5.exe)
インストール終了時「Run Raspberry Pi Imager」にチェックマークがついた状態で「Finish」を押下することで、SDカードへの OS書き込みを開始する。

(3) SDカードへの OS書き込み

Raspberry Pi Imager を実行し、各項目の設定を行う。
  • Raspberry Piデバイス
    Raspberry Pi の種類を選択する。(今回は Raspberry pi 5)
  • OS
    OS の種類を選択する。(今回は Raspberry pi OS (64-bit)
  • ストレージ
    書き込み用の MicroSDカードを挿入し、書き込み先を選択する。
設定すると、「次へ」のボタンが押下できるようになる為、押下する。
カスタマイズするかを聞かれる為、「設定を編集する」を押下する。
設定画面となる為、各項目の設定を行う。
  • 「一般」タブ
    ホスト名, ユーザ名, パスワード, WiFi設定, ロケール のチェック, 設定を行う。

  • 「サービス」タブ
    SSHを有効化する にチェックを入れる。
を設定し 「保存」 を押下し、「はい」を押下
ストレージに保存されているデータが削除されることの確認に対して 「はい」を押下すると、MicroSD への 書き込みが開始される。

(4) Power ON 

Raspberry Pi に MicroSDカードを挿入して電源を接続し、電源onする。

(5) SSH でLOGIN

raspberry pi に SSH で LOGIN する。
IPアドレスは DHCP で割り当てられた IP アドレスで 立ち上がるため、IPアドレスは推定で指定する。
 NetEnum 等で ネットワーク上に存在する IPアドレス (ping の通ったアドレス)  を調べ、アタリをつける。
SSH 接続時の ID,  パスワード は 設定した値を使用。
SSH 接続は、Windows 上 の端末から、
ssh IPアドレス
で行う。

(6) 初期設定

SSH で接続できたら、以下の設定を行う。
  • root の パスワードを設定
    sudo passwd root
    で、root の パスワードを 設定する。
  • パッケージの更新
    sudo apt update
    で、パッケージ管理ツールをアップデートする。
     (パッケージリストの更新)
    sudo apt upgrade
    で、アップグレードをする。(インストールされてるパッケージの更新)
  • ロケール設定 (日本語対応)
    sudo raspi-config
    で 以下のようにロケールの設定を行う。
    5 Localisation Options を選択して Return。
    L1 Locale  を選択して Return。
    en_GB.UTF-8 UTF8 でスペースキーを押下して '*' を外す。
    ja_JP.UTF-8 UTF-8 で スペースキーを押下して '*' を付け、Return。
    次画面で
    ja_JP.UTF-8 を選択して Return。
    設定が終わったら、再起動を行う。
    sudo reboot
  • IPアドレスの固定
    設定ファイルは /etc/NetworkManager/system-connections にある。
    ファウル名は preconfigured.nmconnection
    このファイルの [ipv4] セクションを変更する。
    sudo vi /etc/NetworkManager/system-connections/preconfigured.nmconnection
    変更前
    [ipv4]
    method=auto
    変更後例
    [ipv4]
    method=manual
    addresses=192.168.1.94/24  // IPアドレス
    gateway=192.168.1.1        // ルータのIPアドレス
    dns=8.8.8.8;8.8.4.4;       // DNS の IPアドレス
    
        ファイルを変更したら、設定を反映させるため、再起動する。
    sudo reboot
    設定を反映させるため、NetworkManagerの再起動 する。
    sudo systemctl restart NetworkManager
    SSH 接続たと端末が固まってしまう。raspberry pi の 再起動でも良い。
    sudo reboot
    再起動後、設定した アドレスで 接続する。