2020年5月2日土曜日

ESP32/arduino:クライアントのタイムアウト

目的:

WEBブラウザからの文字列受信にタイムアウト処理を追加する。

問題点:

「SPIFFSのテキストファイルをダウンロードする 」等 WEBサーバー動作で、クライアント接続後 に client.connected() のまま何も文字列を受信しなかった場合に while 文のループを抜けなくなってしまう。
 文字列受信開始後に 最終行(空行) を受信せずに client.available() == 0 となった場合も同様。( clientclient.connected() のまま で )

対策:

  一定時間 client.available() == 0 の状態が続いた場合、タイムアウトとして、ループを抜けて client.stop() を行う。タイムアウト時間は状況に応じて設定。

start_time = millis() ;         // 開始前の時刻を取得
while (client.connected()) {   
  if (client.available()) {

    start_time = millis() ;     // 文字列受信時の時刻に更新
      |
  } else {
    if ( (millis() - start_time) > 3000 ) {
      break ;                   // 3000 mS 受信がなければループを抜ける
    }
  }


例:

「SPIFFSのテキストファイルをダウンロードする 」の メインスケッチにタイムアウト処理を追加した例を示す。
(メインスケッチ以外のファイルは 「SPIFFSのテキストファイルをダウンロードする 」を参照 )


スケッチ (メイン)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <WiFi.h>                         // WiFi 使用の為
#include "FS.h"                           // SPIFFS 使用の為
#include "SPIFFS.h"                       // SPIFFS 使用の為
  
#define DEBUG_HTML                        // Debug用 Serial 表示制御用
#define DEBUG                             // Debug用 Serial 表示制御用
  
// AP モード用 WIFI 設定 ---------------------------------------------
const char ssid[] = "ESP32_AP";           // SSID for softAP
const char pass[] = "password";           // password for softAP
const IPAddress ip(192, 168, 4, 1);       // IPアドレス for softAP
const IPAddress subnet(255, 255, 255, 0); // サブネットマスク for softAP
  
// 動作モードフラグ ---------------------------------------------------
bool stamode = true ;                     // WiFi STA 接続モード : true
bool sta_exec = false ;                   // WiFi STA接続モード実行中フラグ
bool config_mode  = false ;               // 初期設定モード ; true
bool config_exec = false ;                // 初期設定実行中フラグ
  
// 設定ファイル用 -----------------------------------------------------
String s_config ;                         // 設定ファイル用 String
char wifi_ssid[128] = "" ;                // SSID        for WiFi STA
char wifi_pass[128] = "" ;                // password    for WiFi STA
IPAddress wifi_ip(192,168,1,66) ;         // IP Address  for WiFi STA
IPAddress wifi_gw(192,168,1,1)  ;         // gate way    for WiFi STA
IPAddress wifi_sm(255,255,255,0) ;        // subnet mask for WiFi STA
IPAddress wifi_dns(192,168,1,1) ;         // dns address for WiFi STA
char wifi_mode[16] = ""   ;               // wifi mode
String foot_msg ="" ;                     // リブートメッセージ用
 
#define CLIENT_TIMEOUT 3000               // クライアントタイムアウト (mS)
WiFiServer server(80);
  
String html_CONF ;                        // 設定用 HTML
String html_MAIN ;                        // メイン HTML
String html_resp ;
  
// LED 調光処理用 ----------------------------------------------------------------
// -- for ledc
byte brightness = 0 ;                     // LED 明るさ設定 (0-255)
  
// -----------------------------------------------------------------------------
//  arduino 初期化処理                                                        --
// -----------------------------------------------------------------------------
void setup() {
  int res = 0 ;                                // 結果格納用 (ワーク)
  
  Serial.begin(115200);                        // シリアル 開始
  SPIFFS.begin(true) ;                         // SPIFFS 開始
  
  // 初期設定用 HTML ファイルの読み込み ---------------------------------------
  res = rd_SPIFFS("/WiFi_Config.html",html_CONF) ;  // html_CONF に Config.html を格納
  
  // 本体 HTML ファイルの読み込み ----------------------------------------------
  res = rd_SPIFFS("/main.html",html_MAIN) ;         // html_MAIN に main.html を格納
    
  // レスポンス用HTML ファイルの読み込み ----------------------------------------
  res = rd_SPIFFS("/resp_cnfld.html",html_resp) ;   // html_resp に resp_confld.html を格納
    
  // 初期設定ファイルを読み込み、グローバル変数に設定し、wifi,画面モードを決定 ---
  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!");
  
  // メイン処理用 初期化 ------------------------------------------------------
  ledcSetup(0, 5000, 8);            // channel 0 周波数 5000 Hz, 8 bit
  ledcAttachPin(2,0);               // attach pin 2 to channel 0
  ledcWrite(0, 0);                  // initialize channel 0 to off    
  
  
}
// -----------------------------------------------------------------------------
  
  
// -----------------------------------------------------------------------------
//  arduino メインループ処理                                                  --
// -----------------------------------------------------------------------------
void loop() {
  WiFiClient client = server.available();
  String htmlwk = "" ;                            // html 用 ワーク変数
  String line   = "" ;                            // クライアントからの入力用
  int xhr = 0 ;                                   // xhr 要求フラグ
  unsigned long start_time = 0 ;                  // タイムアウト監視開始時間
  unsigned long wait_time = 0 ;                   // 開始/前回受信からの経過時間
 
  // HTML クライアント処理 ------------------------------------------------------
  if (client) {
    start_time = millis() ;                       // タイムアウト監視開始時間を更新
    Serial.println(" +++++++++++++++++ new client! +++++++++++++++++ ");
    while (client.connected()) {                  // クライアントから接続されたとき
      if (client.available()) {                   // 受信文字列がある事
        start_time = millis() ;                   // タイムアウト監視開始時間を更新
        line = client.readStringUntil('\n');      // 1行分取得
        # ifdef DEBUG_HTML                        //
          Serial.println(line) ;                  //
        # endif                                   //
        if (line.indexOf("GET /?") != -1) {       // GET 処理
          if (config_mode)                        // 初期設定用 フォームデータ処理
            set_form2param(line) ;                //   フォームデータを変数に格納
          else {                                  // メイン用 処理
            xhr = proc_main(line) ;               //   フォームデータのメイン処理
          }                                       //
        }                                         //
        // 最終行(空行)を受信した時 -------------------------------------------
        if (line.length() == 1 && line[0] == '\r'){  // 最終行判定
          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(client) ;            //     初期設定 HTML 送信
            } else {                              //   メイン画面なら
              send_MAIN_html(client) ;            //     メイン HTML 送信
            }                                     //    ( その後 リブート)
          } else {                                // sta_mode が変わらない場合
            if (config_mode) {                    //   次に初期設定を表示するなら
              send_CONF_html(client) ;            //     初期設定 HTML 送信
              config_exec = true ;                //
            } else {                              //   次にメイン画面なら
              if (xhr) {                          //     XHR 応答の場合
                send_resp_html(client) ;          //       XHR 応答 送信
              } else {                            //     HTML 送信なら
                send_MAIN_html(client) ;          //     メイン HTML 送信
                config_exec = false ;             //
              }                                   //
            }                                     //
          }                                       //
          # ifdef DEBUG_HTML                      //
            Serial.println("Send HTML") ;         //
          # endif                                 //
          break ;                                 // ループ終了
        }                                         //
        //-------------------------------------------------------------------
      } else {                                    // 文字列受信していない時
        wait_time = millis() - start_time ;       //   前回受信からの時間を取得
        if ((wait_time) > CLIENT_TIMEOUT) {       //   文字列受信タイムアウト判定
          Serial.print("タイムアウト :") ;        //  
          Serial.print(wait_time) ;               //
          Serial.println(" mS") ;                 //
          break ;                                 // ループ終了
        }                                         //
      }                                           //
    }
    // 接続が切れた場合 ------------------------------------------------------
    client.stop();
    Serial.println("client disonnected");
    Serial.println("----------------------------------------------------");
  }
  
  if (stamode != sta_exec) {                      // sta_mode が変わった場合
    Serial.println("------------------- リブートします------------------- ");
    delay(500);                                   //
    ESP.restart() ;                               // リブート
  } else {
  }
  // --------------------------------------------------------------------------
  
}

(行#31)
タイムアウト時間 の define 文

(行#96,97)
タイムアウト処理用ローカル変数

(行#101,105)
開始時、文字列受信時の 監視開始時間を取得

(行#151~158)
client.available() == 0 時の処理。
タイムアウト時間を超えていたら break ;を実行




0 件のコメント:

コメントを投稿