2015年11月2日 星期一

ESP8266 0.9.2.2 版韌體多重連線時 TCP 連線失敗問題

前天在撰寫自己的 AT 指令 WiFi 函數時發現, 如果設定為多重連線, 即 AT+CIPMUX=1 時, TCP 連線就無法建立, 這是在 0.9.2.2 版韌體上測試時發現的, 參考 :

# 撰寫 Arduino 的 ESP8266 WiFi 函式

為了確定是否跟 MODE 為 1 或 3 有關, 今天又在快完成的 WiFi 函數的基礎上再進行測試. 結果 : 與工作模式無關, 只要是多重連線, TCP 連線就會失敗. 測試程式如下 :

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠
String ssid="EDIMAX-tony";  //無線基地台識別
String pwd="blablabla";  //無線基地台密碼

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Set Mode=1 : " + set_mode(1));
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=0 : " + set_mux(0));
  Serial.println("Mux : " + get_mux());
  Serial.println("Get AP : " + get_ap());    
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp("www.google.com",80));
  Serial.println("Send GET : " + send_data("GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp("184.106.153.149",80));
  Serial.println("Send GET : " + send_data("GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));     
  }

void loop() {
  if (sSerial.available()) {  //若軟體串列埠 RX 有收到來自 ESP8266 的回應字元
    Serial.write(sSerial.read());  //在串列埠監控視窗顯示 ESP8266 的回應字元
    }
  if (Serial.available()) {  //若串列埠 RX 有收到來自 PC 的 AT 指令字元 (USB TX)
    sSerial.write(Serial.read());  //將 PC 的傳來的字元傳給 ESP8266
    }
  }

String get_ESP8266_response() {  //取得 ESP8266 的回應字串
  String str="";  //儲存接收到的回應字串
  char c;  //儲存接收到的回應字元
  while (sSerial.available()) {  //若軟體序列埠接收緩衝器還有資料
    c=sSerial.read();  //必須放入宣告為 char 之變數 (才會轉成字元)
    str.concat(c);  //串接回應字元
    delay(10);  //務必要延遲, 否則太快
    }
  str.trim();  //去除頭尾空白字元
  return str;
  }

String get_version() {
  sSerial.println("AT+GMR");  //取得韌體版本
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_baud() {
  sSerial.println("AT+CIOBAUD?");  //取得傳送速率
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));}
  }

String get_ip() {
  sSerial.println("AT+CIFSR");  //取得 ESP8266 IP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_mode() {
  sSerial.println("AT+CWMODE?");  //取得工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mode(byte mode) {
  sSerial.println("AT+CWMODE=" + String(mode));  //設定工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1 || str.indexOf("no change") != -1) {return "OK";}
  else {return "NG";}
  }

String get_mux() {
  sSerial.println("AT+CIPMUX?");  //取得連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mux(byte mux) {  //0=single, 1=multiple
  sSerial.println("AT+CIPMUX=" + String(mux));  //設定連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String get_ap() {
  sSerial.println("AT+CWJAP?");  //取得連線之AP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String joint_ap(String ssid, String pwd) {
  sSerial.println("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"");  //連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(7000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String quit_ap() {
  sSerial.println("AT+CWQAP");  //離線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String start_tcp(String address, byte port) {
  sSerial.println("AT+CIPSTART=\"TCP\",\"" + address + "\"," + String(port));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("Linked") != -1) {return "OK";}
  else {return "NG";}
  }

String send_data(String s) {
  String s1=s + "\r\n";  //務必加上跳行
  sSerial.println("AT+CIPSEND=" + String(s1.length()));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf(">") != -1) {  //收到 > 開始傳送資料
    sSerial.println(s1); //傳送資料
    sSerial.flush();  //等待序列埠傳送完畢
    delay(7000);
    str=get_ESP8266_response();  //取得 ESP8266 回應字串
    if (str.indexOf("+IPD") != -1) {return "OK";}  //傳送成功會自動拆線
    else {  //傳送不成功須自行拆線
      close_ip();  //關閉 IP 連線
      return "NG";
      }
    }
  else {  //傳送不成功須自行拆線
    close_ip();  //關閉 IP 連線
    return "NG";
    }
  }

String close_ip() {
  sSerial.println("AT+CIPCLOSE");  //關閉 IP 連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

其輸出如下 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Set Mode=1 : OK
Mode : 1
Mux : 0
Set Mux=0 : OK
Mux : 0
Get AP : "H30-L02-webbot"
Get IP : 192.168.43.151
Connect Google : OK  (成功)
Send GET : OK
Connect Thingspeak : OK  (成功)
Send GET : OK

可見在 mode=1 (station), mux=0 (single) 時, TCP 連線可正常建立. 接下來測試 mode=1 (station), mux=1 (multiple) 情況, 下面只列測試程式, 函式不再列出 :

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Set Mode=1 : " + set_mode(1));   //Station mode
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=1 : " + set_mux(1));  //多重連線
  Serial.println("Mux : " + get_mux());
  Serial.println("Get AP : " + get_ap());    
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp("www.google.com",80));
  Serial.println("Send GET : " + send_data("GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp("184.106.153.149",80));
  Serial.println("Send GET : " + send_data("GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));      
  }

其輸出為 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Set Mode=1 : OK
Mode : 1
Mux : 0
Set Mux=1 : OK
Mux : 1
Get AP : "H30-L02-webbot"
Get IP : 192.168.43.151
Connect Google : NG (失敗)
Send GET : NG
Connect Thingspeak : NG (失敗)
Send GET : NG

可見只要一改到多重連線, TCP 連線就無法順利建立了. 接下來要測試是否 mode 會影響 TCP 連線, 程式如下 :

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Set Mode=3 : " + set_mode(3));   //Station+AP mode
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=0 : " + set_mux(0));  //單一連線
  Serial.println("Mux : " + get_mux());
  Serial.println("Get AP : " + get_ap());    
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp("www.google.com",80));
  Serial.println("Send GET : " + send_data("GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp("184.106.153.149",80));
  Serial.println("Send GET : " + send_data("GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));      
  }

其輸出如下 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Set Mode=3 : OK
Mode : 3
Mux : 0
Set Mux=0 : OK
Mux : 0
Get AP : "H30-L02-webbot"
Get IP : 192.168.4.1
Connect Google : OK (成功)
Send GET : OK
Connect Thingspeak : OK  (成功)
Send GET : OK

可見在模式 3, 單一連線情況下, TCP 連線沒問題. 若改為多重連線呢? 程式如下 :

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Set Mode=3 : " + set_mode(3));   //Station+AP mode
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=1 : " + set_mux(1));  //多重連線
  Serial.println("Mux : " + get_mux());
  Serial.println("Get AP : " + get_ap());    
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp("www.google.com",80));
  Serial.println("Send GET : " + send_data("GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp("184.106.153.149",80));
  Serial.println("Send GET : " + send_data("GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));      
  }

輸出如下 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Set Mode=3 : OK
Mode : 3
Mux : 0
Set Mux=1 : OK
Mux : 1
Get AP : "H30-L02-webbot"
Get IP : 192.168.4.1
Connect Google : NG  (失敗)
Send GET : NG
Connect Thingspeak : NG (失敗)
Send GET : NG

由上面測試可知,0.9.2.2 版韌體在多重連線下, TCP 連線無法建立, 與模式是 Station 或 Sattion + AP 無關. Why? 難道是與韌體版本有關嗎? 下次有空再來測試看看 0.9.5 版.

2015-11-09 補充 :

原因找到了, 今天在測試 WeeESP8266 函式庫時發現, 原來是在多重連線模式下,必須指定代表連線之通道編號 (0~4) 才行. 參考 :

# ITEAD WeeESP8266 函式庫測試

因此我加寫了 start_tcp() 與 send_data() 這兩個函式的多型, 即多一個通道 id 參數來處理 :

#include <SoftwareSerial.h>
SoftwareSerial sSerial(10,11); //(RX,TX) 與 ESP8266 介接的軟體串列埠
String ssid="H30-L02-webbot";  //無線基地台識別
String pwd="blablabla";  //無線基地台密碼

void setup() {
  sSerial.begin(9600);  //設定軟體序列埠速率 (to ESP8266)
  Serial.begin(9600);  //設定軟體序列埠速率 (to PC)
  Serial.println("*** SoftSerial connection to ESP8266 ***");
  Serial.println("Firmware version : " + get_version());
  Serial.println("Set Mode=3 : " + set_mode(3));   //Station+AP mode
  Serial.println("Mode : " + get_mode());
  Serial.println("Mux : " + get_mux());
  Serial.println("Set Mux=1 : " + set_mux(1));  //多重連線
  Serial.println("Mux : " + get_mux());
  Serial.println("Get AP : " + get_ap());  
  Serial.println("Get IP : " + get_ip());
  Serial.println("Connect Google : " + start_tcp(0,"www.google.com",80));
  Serial.println("Send GET : " + send_data(0,"GET /"));
  Serial.println("Connect Thingspeak : " + start_tcp(0,"184.106.153.149",80));
  Serial.println("Send GET : " + send_data(0,"GET /update?api_key=NO5N8C7T2KINFCQE&field1=28.00&field2=82.40&field3=81.00"));    
  }

void loop() {
  if (sSerial.available()) {  //若軟體串列埠 RX 有收到來自 ESP8266 的回應字元
    Serial.write(sSerial.read());  //在串列埠監控視窗顯示 ESP8266 的回應字元
    }
  if (Serial.available()) {  //若串列埠 RX 有收到來自 PC 的 AT 指令字元 (USB TX)
    sSerial.write(Serial.read());  //將 PC 的傳來的字元傳給 ESP8266
    }
  }

String get_ESP8266_response() {  //取得 ESP8266 的回應字串
  String str="";  //儲存接收到的回應字串
  char c;  //儲存接收到的回應字元
  while (sSerial.available()) {  //若軟體序列埠接收緩衝器還有資料
    c=sSerial.read();  //必須放入宣告為 char 之變數 (才會轉成字元)
    str.concat(c);  //串接回應字元
    delay(10);  //務必要延遲, 否則太快
    }
  str.trim();  //去除頭尾空白字元
  return str;
  }

String get_version() {
  sSerial.println("AT+GMR");  //取得韌體版本
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_baud() {
  sSerial.println("AT+CIOBAUD?");  //取得傳送速率
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));}
  }

String get_ip() {
  sSerial.println("AT+CIFSR");  //取得 ESP8266 IP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") == -1) {return "NG";}
  else {return str.substring(0,str.indexOf("\r\n"));}
  }

String get_mode() {
  sSerial.println("AT+CWMODE?");  //取得工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mode(byte mode) {
  sSerial.println("AT+CWMODE=" + String(mode));  //設定工作模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1 || str.indexOf("no change") != -1) {return "OK";}
  else {return "NG";}
  }

String get_mux() {
  sSerial.println("AT+CIPMUX?");  //取得連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String set_mux(byte mux) {  //0=single, 1=multiple
  sSerial.println("AT+CIPMUX=" + String(mux));  //設定連線模式
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String get_ap() {
  sSerial.println("AT+CWJAP?");  //取得連線之AP
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {
    return str.substring(str.indexOf(":")+1,str.indexOf("\r\n"));
    }
  else {return "NG";}
  }

String joint_ap(String ssid, String pwd) {
  sSerial.println("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"");  //連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(7000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String quit_ap() {
  sSerial.println("AT+CWQAP");  //離線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

String start_tcp(String address, byte port) {
  sSerial.println("AT+CIPSTART=\"TCP\",\"" + address + "\"," + String(port));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("Linked") != -1) {return "OK";}
  else {return "NG";}
  }

String start_tcp(byte id, String address, byte port) {
  sSerial.println("AT+CIPSTART=" + String(id) + ",\"TCP\",\"" + address + "\"," + String(port));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("Linked") != -1) {return "OK";}
  else {return "NG";}
  }

String send_data(String s) {
  String s1=s + "\r\n";  //務必加上跳行
  sSerial.println("AT+CIPSEND=" + String(s1.length()));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf(">") != -1) {  //收到 > 開始傳送資料
    sSerial.println(s1); //傳送資料
    sSerial.flush();  //等待序列埠傳送完畢
    delay(7000);
    str=get_ESP8266_response();  //取得 ESP8266 回應字串
    if (str.indexOf("+IPD") != -1) {return "OK";}  //傳送成功會自動拆線
    else {  //傳送不成功須自行拆線
      close_ip();  //關閉 IP 連線
      return "NG";
      }
    }
  else {  //傳送不成功須自行拆線
    close_ip();  //關閉 IP 連線
    return "NG";
    }
  }

String send_data(byte id,String s) {
  String s1=s + "\r\n";  //務必加上跳行
  sSerial.println("AT+CIPSEND=" + String(id) + "," + String(s1.length()));
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf(">") != -1) {  //收到 > 開始傳送資料
    sSerial.println(s1); //傳送資料
    sSerial.flush();  //等待序列埠傳送完畢
    delay(7000);
    str=get_ESP8266_response();  //取得 ESP8266 回應字串
    if (str.indexOf("+IPD") != -1) {return "OK";}  //傳送成功會自動拆線
    else {  //傳送不成功須自行拆線
      close_ip();  //關閉 IP 連線
      return "NG";
      }
    }
  else {  //傳送不成功須自行拆線
    close_ip();  //關閉 IP 連線
    return "NG";
    }
  }

String close_ip() {
  sSerial.println("AT+CIPCLOSE");  //關閉 IP 連線
  sSerial.flush();  //等待序列埠傳送完畢
  delay(1000);
  String str=get_ESP8266_response();  //取得 ESP8266 回應字串
  if (str.indexOf("OK") != -1) {return "OK";}
  else {return "NG";}
  }

這裡因通道 id 為 byte 類型, 因此務必用 String() 函數將其字串化後再串接, 其輸出如下 :

*** SoftSerial connection to ESP8266 ***
Firmware version : 0018000902
Set Mode=3 : OK
Mode : 3
Mux : 1
Set Mux=1 : OK
Mux : 1
Get AP : "H30-L02-webbot"
Get IP : 192.168.4.1
Connect Google : OK
Send GET : OK
Connect Thingspeak : OK
Send GET : OK

這個問題終於解決了. 


沒有留言 :