반응형

이번 글에서는 아두이노 와이파이(wifi) 모듈 ESP-01을 이용하여  wifi AP를 설정하고 스마트폰과 wifi AP 연결을 통한 데이터 전송과  와이파이로 수신된 데이터를 시리얼 통신을 통해 아두이노에서 받고 코드를 실행하는 원격제어를 할 수 있도록 코딩해 보자.  

 

ESP-01 pin map

아두이노와 연결하기

 

TX -> 아두이노 RX

RX -> 아두이노 TX

VCC -> 아두이노 3.3V

CH_PD -> 아두이노 3.3V

GND -> 아두이노 GND

 

아두이노의 출력 핀 TTL 전압은 5V이고 ESP-01 모듈은 3.3V이다. 따라서 아두이노 TX핀에서 출력되는 신호 전압을 TTL 레벨 쉬프트를 통해 ESP-01 모듈의 RX 핀에 3.3V의 신호 전압으로 변경해주어야 하지만, 테스트상에서는 별 문제가 없어서 아래와 같이 TTL 레벨 쉬프트 없이 연결하였다. 이 연결 방식은 장시간 사용 시 ESP-01 모듈에 손상을 줄 가능성이 있다. 

 

장시간 사용할 예정이라면 아두이노 TTL 레벨 쉬프트를 검색하여 적용하기 바란다.

* TTL 레벨 쉬프트 연결도

참조 사이트

https://arduino.stackexchange.com/questions/44336/esp-01-adapter-and-logic-level-converter

 

연결을 하였다면 AT command를 통해 정상 작동 여부를 확인해 보자.

 

아래 파일을 아두이노에 업로드해준다.

Serial_basic_esp01.ino
0.00MB

#include <SoftwareSerial.h> 

#define rxPin 3 

#define txPin 2 

SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

 

void setup() {

  Serial.begin(115200);   //시리얼 모니터

  esp01.begin(115200);  //와이파이 시리얼

}

 

void loop() {

  if (esp01.available()) {       

    Serial.write(esp01.read());  //블루투스 측 내용을 시리얼 모니터에 출력

  }  

  if (Serial.available()) {         

    esp01.write(Serial.read());  //시리얼 모니터 내용을 블루투스 측에 쓰기

  }

}

 

ESP-01의 기본 baud rate는 115200이다. 현재 상태의 작동 여부를 확인하기 위해 하드웨어와 소프트웨어 시리얼 baud rate를  둘 다 115200으로 맞추어 주었다. 115200으로 해놓아도 AT command의 사용에는 지장이 없다.

 

아두이노 시리얼 모니터의 전송 옵션을 Both NL & CR, 보드 레이트를 115200으로 맞추어 주고 아두이노 시리얼 모니터 입력창에 AT를 입력하고 엔터를 친다. 

 

응답으로 OK가 들어온다면 기기의 정상작동과 연결이 잘 되었음을 확인할 수 있다. 아래 그림처럼 수신된 데이터에 읽을 수 없는 문자가 섞여 있을 수 있다. 하지만 OK만 확인된다면 문제는 없다.

 

 

 

아두이노에서 softwareSerial을 통해 안정적으로 사용하기 위해서는 baud rate를 9600으로 변경해야 한다.

ESP-01의 baud rate를 변경하는 방법으로는 기본 baud rate가 9600으로 설정된 펌웨어를 모듈에 업데이트하는 방법과 AT command를 사용하여 변경해주는 방법이 있는데 이 글에서는 AT command를 사용하여 진행하겠다. 펌웨어 업데이트에 관해서는 인터넷상에 관련 글들이 많이 있으므로 참조하길 바란다.

 

아두이노 시리얼 모니터 입력창에 AT+UART_DEF=9600,8,1,0,0 를 복사한 뒤 붙여 넣고 엔터를 쳐준다.

 

 

아래처럼 OK 응답이 표시된다면 ESP-01의 baud rate가 9600으로 변경된 것이다. 

 

 

ESP-01의 baud rate가 9600으로 변경되었으니 위의 코드의 baud rate를 115200에서 9600으로 아래 그림과 같이 변경해주고 업로드한다. 그리고 시리얼 모니터의 보드 레이트도 9600으로 변경한 뒤 AT를 입력하여 정상 작동 여부를 확인하면 준비는 끝나게 된다. 

 

 

확인이 되었으면 스케치 상 보드 레이트를 9600 상태에서 저장을 시켜놓자. 이제 보드 레이트 115200은 사용하지 않게 된다. 

 

ESP-01 wifi 모듈을 이용한 아두이노 원격제어의 방법은 아래와 같다.

 

ESP-01 모듈을 soft AP로 설정(공유기 기능)하여 스마트폰의 wifi가 AP의 ip로 연결할 수 있게 하고  또한, server로 설정하여 soft AP 통해 연결된 스마트폰이 server로 웹페이지를 요청할 수 있도록 하게 된다. 이렇게 함으로써, 만약 서버의 ip가 192.168.4.1로 설정되었을 경우, 스마트폰 웹브라우저 주소창에 http://192.168.4.1/on 입력하여 서버에 on 페이지에 대한 요청을 할 수 있게 된다. 이때 ESP-01 모듈은 들어온 웹페이지 요청 값을 시리얼 통신을 통해 아두이노에 전송하게 되고 아두이노는 수신된 웹페이지 요청 값을 코드에 따라서 원격제어에 필요한 on 만을 분리해내고 그 값에 해당하는 코드를 실행시켜 아두이노 원격제어를 구현하게 된다. 만약 on에 대한 응답 코드가 아두이노에 설정되어 있다면 아두이노는 시리얼 통신을 통해 ESP-01 모듈에 웹페이지 요청에 대한 응답을 보내라고 명령하게 되고 ESP-01 모듈은 그 명령에 따라 스마트폰에 응답을 보내게 된다. 이러한 과정을 통하여 스마트폰과 아두이노 간의 원격제어용 데이터 송신 및 수신이 이루어지게 된다.  

 

 

 

상기의 작업을 수행할 수 있도록 ESP-01를 설정하는데 필요한 AT command에 대해 살펴보자.

 

AT+RST                        // ESP-01 리셋

AT+CWMODE=2            // 1 = Station mode (client), 2 = AP mode (host), 3 = AP + Station mode

AT+CWSAP="esp01","1234test",5,3   // AT+CWSAP= softAP SSID, Password, channel id, ecn

                                                            ecn(보안설정) : 0 = OPEN, 2 = WPA_PSK, 3 = WPA2_PSK, 4 = WPA_WPA2_PSK

AT+CIFSR                     // AP IP 주소

AT+CIPMUX=1              // 0: Single connection, 1: Multiple connections (0 ~ 4), 서버 설정 시 "1" 강제 사항

AT+CIPSERVER=1,80     // AT+CIPSERVER= mode, port

                                       mode: 0: Delete server, 1: Create server, port number

 

 

상기의 AT command를 앞서 보드 레이트 변경 시와 같이 주석 앞부분을 복사하여 차례대로 아두이노 시리얼 모니터 입력창에 붙여 넣기 한 뒤 엔터를 치면 아래와 같이 ESP-01 모듈이 설정되는 것을 볼 수 있다. 

 

 

 

ESP-01 모듈의 soft AP의 기본 설정 ip 주소는 192.168.4.1이다. 현재 soft AP의 주소가 기본 설정값인 192.168.4.1 임을 확인할 수 있다.

 

 

스마트폰에서 wifi 장치를 켜게 되면 상기에서 설정한 대로 SSID "esp01"이 검색될 것이다. 클릭하고 설정해 놓은 비밀번호를 입력하고 연결해 보자. 연결 후 네트워크 설정 관리에 들어가 보면 스마트폰이 할당받은 ip 주소를 확인할 수 있다. 아두이노 원격제어에서 우리가 할당받은 ip주소를 사용할 일은 거의 없다. 단지, soft AP의 주소와 비슷하여 혼동을 방지하기 위해 확인이 필요한 것뿐이다.

 

  

 

 

스마트폰이 soft AP에 접속이 되면 아래와 같이 시리얼 모니터상에 connection id: 0이 연결됐다는 표시와 +IPD,0,459:GET /hi HTTP/1.1 수신 데이터가 표시되고, 접속장비와 soft AP의 ip 주소를 확인할 수 있고 데이터 수신 후에 id 0번의 연결을 끊은걸 확인할 수 있다.

 

 

데이터의 수신

+IPD,0,158:GET / HTTP/1.1  // +IPD,id,len:GET /data HTTP/1.1 

                                     // id: id no. of connection, len: data length, data: data received

 

"+IPD,0,158:GET /" 이하가 수신된 데이터이다. 수신 시에는 AT+CIPMUX=1 옵션 설정에 따라 멀티 커넥션 id 0부터 4까지 자동으로 할당되어 사용하게 되며 수신이 완료되면 자동으로 연결을 닫게 되는데 연결을 닫기 전에 수신되는 데이터는 다른 커넥션 id로 자동 할당되어 수신되는 걸 테스트를 통해 확인할 수 있었다. 

 

데이터의 전송

AT+CIPSEND=0,30

AT+CIPSEND=length    // normal send (single connection).

AT+CIPSEND=id,length  // normal send (multiple connection), id: ID no. of connection, length: data length, MAX 048 bytes

AT+CIPCLOSE=0          // id: ID no. of connection to close, 

                                       Close TCP or UDP connection.For multiply connection mode

 

 

ESP-01을 통해 데이터 송신 시에는 아두이노에서 시리얼 통신을 통해 상기의 데이터 전송 AT command를 사용하여 전송 데이터와 함께 보내야만 데이터가 스마트폰으로 전송된다. 시리얼 통신으로 연결된다는 것 자체가 데이터의 전송 속도에 제한을 걸게 되는데, 이 상황에 더불어 모듈이 AT command를 수신하고 작동하는 시간이 소요되게 되어 아두이노에서 어떤 작동의 응답으로 ESP-01 모듈의 wifi를 통해 데이터 전송할 경우 약 1초 에서 2초의 시간이 걸리게 된다. 이 얘기는 짧은 시간 동안 연속되는 데이터의 전송하는 데 있어서 문제를 야기하게 된다고 할 수 있다. 필자는 ESP-01의 이러한 데이터 전송 특성 때문에 발생하는 사용의 불편함(긴 응답 시간)에 따라 이 기능의 효용성이 낮다고 보아 아두이노에서 스마트폰으로 데이터를 전송하는 기능을 아예 사용하지 않기로 하였다. 

 

상기와 같이 아두이노 시리얼 모니터를 통해 ESP-01을 일일이 설정하는 것은 많이 불편하다. 코드상에서 아두이노 실행 시 자동으로 설정되도록 하는 코드를 인터넷에서 쉽게 찾아볼 수 있었고 이를 이용하기로 한다. 

 

주요 코드를 아래와 같이 코딩해 주었다.

 

String sendData(String command, const int timeout, boolean debug) {  // 스트링 타입 AT command 전송 함수

  String response = "";                     // 스트링 타입 지역변수 선언 및 초기화

  esp01.print(command);                  // ESP-01 모듈에 스트링 타입의 AT command 전송

  long int time = millis();                  

  while( (time+timeout) > millis()) {    // ESP-01 모듈의 명령 실행 후 응답에 필요한 시간이 경과되면

    while(esp01.available()) {              //  시리얼 버퍼에 ESP-01 모듈의 응답을 있을 경우

      char c = esp01.read();               // 1byte 데이터를 char타입 변수 c에 저장

      response+=c;                          // c에 저장된 문자를 문자열로 변경

    }

  }

  if(debug) Serial.print(response);       // debug가 참이면 시리얼 모니터에 출력

  return response;

}

 

전송 실행 함수: sendData(문자열 명령, 응답에 필요한 시간, 시리얼 모니터 출력 플래그);

 

sendData("AT+RST\r\n",2000,DEBUG);  //리셋

sendData("AT+CWMODE=2\r\n",1000,DEBUG); // configure as access point (working mode: AP+STA)

sendData("AT+CWSAP=\"ESP-01\",\"1234test\",11,3\r\n",1000,DEBUG); // join the access point

sendData("AT+CIFSR\r\n",1000,DEBUG); // get ip address

sendData("AT+CIPMUX=1\r\n",1000,DEBUG); // configure for multiple connections

sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); // turn on server on port 80

 

상기 코드 중 wifi 연결 시 비밀번호 연결을 비활성화하고자 한다면 아래처럼 3을 0으로 변경해주면 된다. 

sendData("AT+CWSAP=\"ESP-01\",\"1234test\",11,0\r\n",1000,DEBUG);

 

 

이제 수신 데이터를 필요한 값만 추출해내는 parsing에 대해 살펴보겠다. 

parsing을 하기 위해서는 String class를 활용해야만 한다.

상기 AT command 설정 테스트에서 데이터 수신이 +IPD,0,158:GET / HTTP/1.1 형식으로 들어온 걸 확인했었다.

만약 웹브라우저에서 http://192.168.4.1/hi라고 입력하면 +IPD,0,459:GET /hi HTTP/1.1 데이터가 수신될 것이다. 즉, 모든 데이터 수신에는 +IPD,0,158:GET / 형식의 코드가 선행된다.  데이터 수신의 확인을 위해 "+IPD,"를 이용하여 하기로 하자. 

 

if (esp01.find("+IPD,")) { // 만약 시리얼 버퍼에 "+IPD, "가 있으면, "+IPD,"가 확인될 경우 "+IPD,"는 삭제된다.

 

수신 데이터를 스트링 타입 income_wifi 변수에 저장해 주면 "0,158:GET / HTTP/1.1"라는 스트링이 저장된다.

income_wifi = esp01.readStringUntil('\r'); // 스트링 타입 income_wifi 변수에 '\r' 문자까지 저장 

                                                         웹브라우저 주소 끝에는 자동으로 '\r' 문자가 붙는다.

 

 

필요한 data는 "GET /" 이후에 들어오고 종료 문자 역할을 하는 "HTTP/1.1"의 앞 공백 문자에서 끝나게 된다. 

String class에서는 스트링 중의 일부를 떼어내어 저장할 수 있는 string.substring(start index, end index); 함수를 제공하고 있으며, 저장할 스트링의 시작 인덱스 번호와 종료 인덱스 번호를 필요로 한다. 이 함수를 이용하기 위해 "GET /"와 "HTTP/1.1"의 인덱스 번호를 알아내 보자.

 

 

income_wifi 스트링에서 "GET /"의 시작 인덱스 위치를 확인하는 코드는 income_wifi.indexOf("GET /")가 되고 "HTTP/1.1"의 시작 인덱스 위치를 확인하는 코드는 income_wifi.indexOf("HTTP/1.1")가 된다.

 

인덱스 번호를 사용하여 스트링에서 특정 위치의 스트링을 추출해내는 코드는 아래와 같게 된다. 

income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1")-1);

 

 

income_wifi.indexOf("GET /")는 "GET /"의 시작 인덱스 번호를 반환한다. 그러므로 필요한 위치의 인덱스 번호는  "GET /"의 글자 수 5를(공백 문자 포함) 더한 위치 값이 되게 된다. 

 

income_wifi.indexOf("HTTP/1.1")는 "HTTP/1.1"의 시작 인덱스 번호를 반환한다. 하지만 앞에 공백 문자(space)가 있으므로 -1을 해준 위치 값이 필요로 하는 위치 값이 되고, 상기의 코드에서는 data가 없으므로 두 위치 값은 같은 값일 것이다.

 

아래 코드를 아두이노에 업로드하고 스마트폰에서 soft AP에 접속해 보자.

Serial_basic_esp01_parsing.ino
0.00MB

#include <SoftwareSerial.h>

#define DEBUG true

 

String income_wifi = "";

 

SoftwareSerial esp01(2,3); 

 

void setup() {

  Serial.begin(9600);

  esp01.begin(9600); // your esp's baud rate might be different

  sendData("AT+RST\r\n",2000,DEBUG); // reset module

  sendData("AT+CWMODE=2\r\n",1000,DEBUG); // configure as access point (working mode: AP+STA)

  sendData("AT+CWSAP=\"ESP-01\",\"1234test\",11,0\r\n",1000,DEBUG); // join the access point

  sendData("AT+CIFSR\r\n",1000,DEBUG); // get ip address

  sendData("AT+CIPMUX=1\r\n",1000,DEBUG); // configure for multiple connections

  sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); // turn on server on port 80

}

 

void loop() {

  if (esp01.available()) { 

    if (esp01.find("+IPD,")) {

      income_wifi = esp01.readStringUntil('\r');

      String wifi_temp = income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1")-1);

      Serial.println(wifi_temp);

    }

  }

}

 

String sendData(String command, const int timeout, boolean debug) {

  String response = "";

  esp01.print(command); // send the read character to the esp01

  long int time = millis();

  while( (time+timeout) > millis()) {

    while(esp01.available()) {  // The esp has data so display its output to the serial window 

      char c = esp01.read(); // read the next character.

      response+=c;

    }

  }

  if(debug) Serial.print(response);

  return response;

}

 

 

스마트폰 웹브라우저 주소창에서 192.168.4.1/hi를 입력하고 연결을 클릭하면 아래와 같이 시리얼 모니터에 hi가 표시된 것을 볼 수 있다. 

 

 

앞에서 살펴본 AT command 설정 시에는 스마트폰 접속 시 관련 정보들이 시리얼 모니터에 출력되었었지만 지금은 data를 제외한 모든 값은 출력이 안되게 된다. 

 

이렇게 추출된 스트링 데이터를 이용하여 LED의 제어를 할 수 있게 된다. 

아래와 같이 아두이노 기본 LED를 웹브라우저를 통해 제어할 수 있도록 스트링 LED 제어 코드를 추가해 주었다. 

업로드하고 스마트폰 웹브라우저에 http://192.168.4.1/on을 입력하면 LED가 켜지는 것을 볼 수 있고 http://192.168.4.1/off를  입력하면 LED가 꺼지는 것을 볼 수 있다.  "on" 또는 "off"가 아닌 텍스트를 입력한다면 시리얼 모니터에 출력된다.

Serial_basic_esp01_string_LED.ino
0.00MB

#include <SoftwareSerial.h>

#define DEBUG true

#define ledPin 13

 

String income_wifi = "";

 

SoftwareSerial esp01(2,3); 

 

void setup() {

  Serial.begin(9600);

  esp01.begin(9600); // your esp's baud rate might be different

  pinMode(ledPin, OUTPUT);

  sendData("AT+RST\r\n",2000,DEBUG); // reset module

  sendData("AT+CWMODE=2\r\n",1000,DEBUG); // configure as access point (working mode: AP+STA)

  sendData("AT+CWSAP=\"ESP-01\",\"1234test\",11,0\r\n",1000,DEBUG); // join the access point

  sendData("AT+CIFSR\r\n",1000,DEBUG); // get ip address

  sendData("AT+CIPMUX=1\r\n",1000,DEBUG); // configure for multiple connections

  sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); // turn on server on port 80

}

 

void loop() {

  if (esp01.available()) { 

    if (esp01.find("+IPD,")) {

      income_wifi = esp01.readStringUntil('\r');

      String wifi_temp = income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1")-1);

      if(wifi_temp == "on"){  // wifi_temp는 지역변수로서 "if (esp01.find("+IPD,")){} 함수를 나가면 초기화됨.

        digitalWrite(ledPin, HIGH);

      }

      else if(wifi_temp == "off"){  

        digitalWrite(ledPin, LOW);  

      }

      else {

        Serial.println(wifi_temp); 

      }

    }

  }

}

 

String sendData(String command, const int timeout, boolean debug) {

  String response = "";

  esp01.print(command); // send the read character to the esp01

  long int time = millis();

  while( (time+timeout) > millis()) {

    while(esp01.available()) {  // The esp has data so display its output to the serial window 

      char c = esp01.read(); // read the next character.

      response+=c;

    }

  }

  if(debug) Serial.print(response);

  return response;

 

}

 

 

이제 웹브라우저를 이용하는 대신 안드로이드 앱을 이용하여 원격제어를 해보자.

 

arduino bluetooth controller PWM 안드로이드 앱의 wifi를 이용한 원격제어용 데이터 전송 프로토콜은 다음과 같이 코딩해 주면 된다. 

디지털 버튼 제어 프로토콜: "%%F0" + 스트링 값 + "%%F1"

                                    스트링 값 = "10" 버튼1 off, 스트링 값 = "11" 버튼1 on, "20" 버튼2 off, "21" 버튼2 on, ......

PWM 값 프로토콜: 1번 슬라이드 "%%F31" + 스트링 값 + "%%F1"

                           2번 슬라이드 "%%F32" + 스트링 값 + "%%F1"

                           3번 슬라이드 "%%F33" + 스트링 값 + "%%F1"

 

 

상기 예제 코드에서는 스트링을 parsing 할 때 시작 문자열로서 "GET /" 종료 문자열로서 "HTTP/1.1"를 사용했다. 이들을 상기 프로토콜에 적용하여 코드를 바꾸어 주었다. 또한 스트링으로 데이터를 한 번에 받기 때문에 "%%F1"와 "%%F0"가 스트링에 있다면 그 데이터의 검증이 완료된 것으로 볼 수 있다. 앞선 블루투스 원격제어에서는 검증 시 여러 단계가 필요했으나 스트링으로 데이터를 한꺼번에 받을 경우 코드가 간단해진다.  

if (income_wifi.indexOf("%%F1") != -1) { // 만약 스트링 중에 "%%F1" 있고

if (income_wifi.indexOf("%%F0") != -1) { // "%%F0" 있으면 디지털 버튼 상태 값 검증이 되어 

String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F0")+4, income_wifi.indexOf("%%F1")); // 값 저장

 

if (income_wifi.indexOf("%%F31") != -1) { // 만약 스트링 중에 "%%F31" 있고

if (income_wifi.indexOf("%%F0") != -1) { // "%%F0" 있으면 슬라이드 1 PWM 값 검증이 되어 

String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F31")+5, income_wifi.indexOf("%%F1")); // 값 저장

 

 

기타 프로토콜에 해당되지 않는 텍스트는 앞선 parsing 방법을 사용한다. 

String wifi_temp = income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1")-1);

 

앞선 설명중 데이터 수신에서 멀티 커넥션 id에 관해 언급했었다. ESP-01 모듈은 데이터 수신 시 5개의 id를 사용하여 연결을 하게 되는데 그 id 번호는 0부터 4까지이다.  상기에 링크를 걸었던 ESP-01 모듈 AT command 참조 사이트에 의하면 만약 0 ~ 4까지 모두 사용하고 있는 상태에서 추가 연결이 있을 경우(id 값이 5가 될 경우) 모든 연결은 끊긴다고 한다. 필자가 확인해 본 결과 모두 끊기면서 더 이상 연결이 되지 않는다. 이런 상황이 되었을 경우 데이터의 송 수신이 안되게 되어 닫힌 멀티커넥션 기능을 열기 위해서는 리셋을 해주어야만 했다. 

 

 

추가로 설명하자면, 데이터 수신 시 id 0번부터 받게 되고 수신이 완료되면 id 0번의 연결을 끊는 것을 앞선 예제에서 확인했었다. id 0번의 연결이 끊기게 되면 이때 id 0번이 다시 데이터를 받을 수 있는 상태로 초기화 되게 된다. 만약 id 0번의 연결이 끊기 전에 데이터가 들어오게 된다면, 다음 id인 1번으로 넘어가게 되고 다시 id 0번이 끊겨 초기화가 되면 다시 id 0번으로 데이터를 수신하게 된다. 즉 연속된 데이터를 id 순서대로 받다가 앞선 번호에서 초기화가 되면 그 id를 우선 사용하게 된다. 수신되는 데이터 간격이 길어 id 0번이 수신 완료하고 초기화되었다면 계속에서 id 0번을 사용하게 된다.  문제는 id의 연결을 끊고 초기화시키는데 시간이 걸린다는 것이다. 데이터가 빠르게 연속으로 들어올 경우 id가 초기화되기 전에 id 0부터 id 4까지 모두 연결된 상태가 되어 모든 연결을 끊고 더 이상 데이터를 수신하지 못하는 현상이 발생하게 되었다. 

 

이를 해결하기 위해서 데이터를 읽는 시간 간격을 강제하고 활성화된 id를 AT command를 통해 강제로 끊는 코드를 아래와 같이 추가해 보았으나 그것 만으로는 충분하지 않았다. 

 

connectionId = income_wifi.charAt(0);

 

unsigned long int one_millis = 0;

 

void wifi_delay() { // 50 millis 마다 연결된 id 가 있으면 그 아이디를 종료시키고 데이터 수신확인

  if (millis() - one_millis > 50) {

    one_millis = millis();

    if (connectionId == '0') esp01.write("AT+CIPCLOSE=0\r\n");

    else if (connectionId == '1') esp01.write("AT+CIPCLOSE=1\r\n");

    else if (connectionId == '2') esp01.write("AT+CIPCLOSE=2\r\n");

    else if (connectionId == '3') esp01.write("AT+CIPCLOSE=3\r\n");

    else if (connectionId == '4') esp01.write("AT+CIPCLOSE=4\r\n");

    wifi_read();

  }

}

 

또 다른 문제로는 수신된 데이터 각각의 id로 분산되어 받게 될 경우 그 순서가 뒤바뀔 수 있다는 것이다. 한 개의 아이디로 연속된 데이터를 받는다면 순서는 뒤바뀌지 않는다. 모듈에서 멀티 커넥션 id 연결 시 데이터 수신 시점의 시간 값에 대한 검증은 하지 않는 것 같으며 마지막에 출력되어야 할 값이 그전 값보다 먼저 출력되는 현상을 여러 번 확인했다. 

 

사실 wifi 연결에 대한 응답 server의 구동에 있어 멀티 커넥션 자체가 다중 접속을 고려한 것이지 한 사용자의 연속된 데이터를 전송받기 위한 구성은 아닐 것이라 생각되며 웹페이지 요청 순서는 중요하지 않을 것이다. 

 

어찌 되었든지 간에 원격제어를 하기 위해서는 신뢰할 수 있는 값의 순서가 필요하고 이런 문제를 해결하기 위해서는 한 개의 id만을 사용하여 받을 수 있도록 스마트폰에서 데이터를 전송하는 간격을 늘려주는 방법 외에는 찾을 수가 없었다. 

 

ESP-01 wifi 모듈로 원격제어를 하는것은 블루투스 모듈의 사용에 비해 적당한 방법은 아니라고 생각이 되나 연결거리가 블루투스 보다 길어 또한 필요하게 된다. 

 

ESP-01 wifi 모듈의 특징을 이해하고 모듈의 성능에 맞게 필요 기능이 적용되도록 코딩해야 한다. 

1. 데이터 전송 시 시리얼 통신을 통하여 AT command를 매번 실행시켜주어야 한다. 

2. 서버로서 데이터 수신 시 연결 옵션으로 멀티 커넥션 사용이 강제되어 있고 수신에 사용된 id가 사용 중이거나 종료되지 않은 경우에 추가되는 요청은 다음 id가 요청받게 된다. 

3. 연결용 id 5개가 모두 연결된 상태에서 추가 요청이 올 경우 모두 닫힌다. -> 리셋해야만 다시 사용 가능해진다. 

4. 3번의 경우를 방지하기 위해 데이터 수신이 완료되면 반드시 id연결을 끊고 대기 상태로 하여야 한다. 

5. 연속되는 데이터를 받을 경우 id가 바뀌면서 데이터를 수신하게 되는데 그때 데이터 값의 순서가 바뀔 수 있다.

 

 

다음 편에 살펴볼 NODE MCU 1.0(ESP-12E, esp8266 칩셋)이나 ESP32 Dev Module의 경우 상기 ESP-01의 특징 중 2, 3, 5번은 동일한 거 같으나 모듈 자체에 wifi가 내장되어있어 ESP-01의 경우처럼 모듈과 wifi간 시리얼 통신을 사용하지 않을 뿐만 아니라 AT command도 사용하지 않게 되어 사용 id의 종료가 상대적으로 빠르다고 할 수 있다. 하지만 이 두 모델도 연속되는 빠른 데이터 수신 시 5번의 현상이 발견되었고 이를 해결하기 위해서 스마트 폰에서 데이터 전송 간격을 조정해주어야만 했다. 

스마트폰에서 이 두 모듈의 데이터 전송 간격은 ESP-01 모듈보다는 짧고 블루투스 모듈보다는 길게 된다.

arduino_remote_control_esp01.ino
0.01MB

위의 코드를 아두이노에 업로드한 뒤 스마트폰에서 ESP-01의 soft AP에 접속한다. 

 

arduino bluetooth controller PWM 안드로이드 앱을 실행한 뒤 wifi 아이콘을 클릭하고 ESP-01의 soft AP 192.168.4.1을 입력하고 esp01 체크 박스에 체크를 해준다. 앱에는 기본적으로 100 밀리 초마다 soft AP ip 주소로 응답 요청을 하도록 프로그램되어있다. 하지만 현재 ESP-01을 활용한 아두이노 스케치 코드에는 응답에 대한 어떠한 코드도 없으므로 스마트폰은 요청에 대한 응답을 받지 못하고 오류를 발생하게 된다. esp01 체크 박스에 체크를 해주면 100 밀리 초마다 soft AP ip 주소에 응답 요청을 하지 않게 되어 사용 가능하게 된다.  

 

arduino bluetooth controller PWM - 아두이노 원격제어 안드로이드 앱 버전 3.5 다운로드

arduino bluetooth controller PWM 매뉴얼

(*** 앱에서 와이파이 연결은 안드로이드8 [갤럭시S7]이하 버전에서만 작동합니다 ***)

(*** 앱에서 블루투스 연결은 안드로이드 10 이하 버전에서만 작동합니다 ***)

 

Confirm을 클릭한 뒤 버튼 1번을 클릭하면 아두이노 기본 LED가 켜지는 것을 볼 수 있고 버튼 2번 부터는 시리얼 모니터에 작동 상태가 표시된다. PWM 제어용 슬라이드는 천천히 움직일 경우 연속되는 데이터가 전송되고 빠르게 움직이면 최종 정지했을 때의 값만 전송된다.  

 

ESP-01 모듈 연결 시는 안드로이드 앱에서 wifi를 통해 연결되었다는 어떠한 표시도 볼 수 없다. 아두이노에서 스마트폰으로 응답을 주는 코드가 없기 때문이다. 

 

NODE MCU 1.0(ESP-12E, esp8266 칩셋)이나 ESP32 Dev Module의 경우 체크박스에 체크를 한 상태에서 Confirm을 클릭하게 되면 응답 요청을 하지 않게 되어 아두이노 측에서 입력된 값을 스마트폰에 출력을 하지 못하게 만든다. 아두이노 시리얼 모니터에 hello를 입력하면 스마트폰에 hello가 표시되어야 하나 표시가 되지 않는다. 이는 서버의 특징으로 스마트폰(클라이언트 또는 마스터)의 요청 시에만 서버(슬레이브)는 연결된 id를 통해 응답을 할 수 있기 때문에 요청이 없는 상태에서는 아두이노에서 스마트폰으로 데이터를 전송하지 못하게 된다.  또한 현재 연결되어 응답을 수신하고 있다는 표시도 할 수 없게 되어, 와이파이가 중간에 끊어져도 알 수 없게 된다. 하지만  ESP-01 모듈처럼 스마트폰에서 모듈의 제어를 하는 기능은 문제없이 사용 가능하다. 

 

NODE MCU 1.0(ESP-12E, esp8266 칩셋)이나 ESP32 Dev Module의 경우에는 esp01 체크 박스에 체크하지 않고 Confirm을 클릭해야만 wifi를 통해 연결되었을 때 응답을 수신하고 현재 연결된 상태라는 것을 표시하게 되고 만약, 가변저항이 연결되어 있다면 가변저항의 조정 상태를 슬라이드 바로 표시할 수 있게 된다.     

 

arduino_remote_control_esp01_pwm.ino
0.01MB

 

2019.11.22 추가

이제까지 ESP01의 soft AP에 연결하여 아두이노를 제어했다. 

ESP01을 station모드로 공유기에 연결하고 내부 네트워크를 통한 아두이노 원격 제어해보자. 

 

ESP01을 staion 모드로 공유기에 연결하면 공유기는 ESP01에 ip주소를 할당해 준다. 

이 주소를 통해 내부 네트워크용 안에서 원격제어를 할 수 있게 되는데 이를 위해서는 반드시 시리얼 모니터에 표시되는 할당받은 ip주소를 확인한 뒤에 연결해야만 한다. (공유기에서 ESP01에 지정 ip를 할당하는 설정을 할 수 없는 경우) 또는 한번 받은 ip 주소를 메모해 두었다가 다시 연결 시 그 주소를 입력하여 사용할 수도 있다. 

하지만 시리얼 모니터에서 ip주소를 확인하거나 ip주소가 변경되어 메모해둔 ip주소로 연결할 수 없는 경우도 발생할 수 있어 불편하다. 

 

상기 이유로 내부 네트워크 이용 원격제어가 불필요하다고 생각하여 설명하지 않았었는데, 공유기에 스마트폰이 연결된 상태에서는 soft AP를 이용한 원격제어처럼 와이파이를 따로 연결하지 않아도 되는 편리한 점 때문에 설명을 추가하게 되었다. 

 

 

 

ESP01을 station 모드로 공유기에 연결하기 위해서 아래와 같이 setup() 함수 코드를 변경해 주었다. 

 

void setup() {

  Serial.begin(9600);

  esp01.begin(9600); // your esp's baud rate might be different

  pinMode(ledPin, OUTPUT);

  sendData("AT+RST\r\n",2000,DEBUG); // reset module

  sendData("AT+CWMODE=1\r\n",1000,DEBUG); // configure as access point (working mode: AP+STA)

  sendData("AT+CWDHCP=1,1\r\n",1000,DEBUG);

  sendData("AT+CWJAP=\""+ ssid +"\",\""+pass+"\"\r\n",8000,DEBUG);

  sendData("AT+CIFSR\r\n",1000,DEBUG); // get ip address

  sendData("AT+CIPMUX=1\r\n",1000,DEBUG); // configure for multiple connections

  sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); // turn on server on port 80

}

 

아래 코드를 업로드하고 시리얼 모니터를 통해 공유기에서 할당받은 ip를 확인하고 ip 주소 192.168.4.1 대신 공유기에서 할당받은 ip를 입력하고 연결해 주면 된다. 

 

 

2021.03.06 추가 

일정 시간 경과 후 와이파이 연결 및 데이터 전송에 관한 테스트 

 

공유기를 통해 테스트하였습니다. 

 

공유기 아이디: skynet

공유기 비번: skynet00

 

스마트폰의 와이파이가 공유기 skynet에 연결된 상태에서 arduino bluetooth controller PWM 앱을 이용해 테스트했습니다. 

 

아두이노 코드

arduino_remote_control_esp01_station_softAP_revised.ino
0.01MB

 

#include <SoftwareSerial.h> 
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid = "skynet";    // your network SSID (name)
const String pass = "skynet00";         // your network password

const String ssid_AP = "ESP-01";    // your network SSID (name)
const String pass_AP = "1234test";         // your network password
String ip = "";
bool ipCheck = false;

#define DEBUG true
#define ledPin 13

uint8_t pin_val = 0; // 디지털 버튼 제어용 변수
uint16_t pwm1 = 0;   // pwm 제어용 변수
uint16_t pwm2 = 0;
uint16_t pwm3 = 0;
String text = "";
char connectionId; // 연결 id 저장 변수
String income_wifi = "";

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  pinMode(ledPin, OUTPUT);
  sendData("AT+RST\r\n",2000,DEBUG); // reset module
  sendData("AT+CWMODE=3\r\n",1000,DEBUG); // configure as access point (working mode: AP+STA)
  sendData("AT+CWDHCP=1,1\r\n",1000,DEBUG);
  sendData("AT+CWSAP=\""+ ssid_AP +"\",\""+pass_AP+"\",11,0\r\n",1000,DEBUG); // join the access point
  sendData("AT+CWJAP=\""+ ssid +"\",\""+pass+"\"\r\n",8000,DEBUG);
  sendData("AT+CIFSR\r\n",1000,DEBUG); // get ip address
  sendData("AT+CIPMUX=1\r\n",1000,DEBUG); // configure for multiple connections
  sendData("AT+CIPSERVER=1,80\r\n",1000,DEBUG); // turn on server on port 80
}
 
void loop() {
  wifi_delay();
  if(Serial.available() > 0){ // 아두이노 모듈 작동 확인
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
  }
}

unsigned long int one_millis = 0;

void wifi_delay() { // 100 millis
  if (millis() - one_millis > 50) {
    one_millis = millis();
    if (connectionId == '0') esp01.write("AT+CIPCLOSE=0\r\n");
    else if (connectionId == '1') esp01.write("AT+CIPCLOSE=1\r\n");
    else if (connectionId == '2') esp01.write("AT+CIPCLOSE=2\r\n");
    else if (connectionId == '3') esp01.write("AT+CIPCLOSE=3\r\n");
    else if (connectionId == '4') esp01.write("AT+CIPCLOSE=4\r\n");
    wifi_read();
  }
}

void wifi_read() {
  if (esp01.available()) { // check if the esp is sending a message
    if (esp01.find("+IPD,")) {
      income_wifi = esp01.readStringUntil('\r');
      Serial.println(income_wifi);
      connectionId = income_wifi.charAt(0);
      if (income_wifi.indexOf("%%F1") != -1) {
        if (income_wifi.indexOf("%%F0") != -1) {
          String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F0")+4, income_wifi.indexOf("%%F1"));
          pin_val = wifi_temp.toInt();
          Serial.println(pin_val);
          pin_control();
        }
        else if (income_wifi.indexOf("%%F31") != -1) {
          String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F31")+5, income_wifi.indexOf("%%F1"));
          pwm1 = wifi_temp.toInt();
          Serial.print("pwm1: "); Serial.println(pwm1);
        }
        else if (income_wifi.indexOf("%%F32") != -1) {
          String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F32")+5, income_wifi.indexOf("%%F1"));
          pwm2 = wifi_temp.toInt();
          Serial.print("pwm2: "); Serial.println(pwm2);
        }
        else if (income_wifi.indexOf("%%F33") != -1) {
          String wifi_temp = income_wifi.substring(income_wifi.indexOf("%%F33")+5, income_wifi.indexOf("%%F1"));
          pwm3 = wifi_temp.toInt();
          Serial.print("pwm3: "); Serial.println(pwm3); 
        }
      } else { // 텍스트 출력 코드
        String wifi_temp = income_wifi.substring(income_wifi.indexOf("GET /")+5, income_wifi.indexOf("HTTP/1.1"));
        Serial.println(wifi_temp);
      }
      income_wifi = "";
    }
  }
}
 
String sendData(String command, const int timeout, boolean debug) {
  String response = "";
  if (command.indexOf("CIFSR") != -1) ipCheck = true;
  else ipCheck = false;
  esp01.print(command); // send the read character to the esp01
  long int time = millis();
  while( (time+timeout) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window 
      char c = esp01.read(); // read the next character.
      response+=c;
    }
  }
  if (ipCheck) {
    ip = response.substring(response.indexOf("STAIP,")+7,response.indexOf("+CIFSR:STAMAC")-3);
  }
  if(debug) Serial.print(response);
  return response;
}

void pin_control() {
  if (pin_val != 0) {
    switch (pin_val) {
      case 11: digitalWrite(ledPin, true); // button 1 : on
                  break;
      case 10: digitalWrite(ledPin, false); // button 1 : off
                  break;
      case 21: Serial.println("button 2 : on");
                  break;
      case 20: Serial.println("button 2 : off");
                  break;
      case 31: Serial.println("button 3 : on");
                  break;
      case 30: Serial.println("button 3 : off");
                  break;
      case 41: Serial.println("button 4 : on");
                  break;
      case 40: Serial.println("button 4 : off");
                  break;
      case 51: Serial.println("button 5 : on");
                  break;
      case 50: Serial.println("button 5 : off");
                  break;
      case 61: Serial.println("button 6 : on");
                  break;
      case 60: Serial.println("button 6 : off");
                  break;
      case 71: Serial.println("button 7 : on");
                  break;
      case 70: Serial.println("button 7 : off");
                  break;
      case 81: Serial.println("button 8 : on");
                  break;
      case 80: Serial.println("button 8 : off");
                  break;
      case 91: Serial.println("button 9 : on");
                  break;
      case 90: Serial.println("button 9 : off");
                  break;  
      case 101: Serial.println("button 10 : on");
                  break;
      case 100: Serial.println("button 10 : off");
                  break;  
      case 111: Serial.println("button 11 : on");
                  break;
      case 110: Serial.println("button 11 : off");
                  break;  
      case 121: Serial.println("button 12 : on");
                  break;
      case 120: Serial.println("button 12 : off");
                  break;  
    }
    pin_val = 0;
  }
}

 

상기 코드 업로드 후 시리얼 모니터에서 공유기에서 할당해준 ip 주소 192.168.1.9 확인

 

 

스마트폰 와이파이 공유기 연결

 

arduino bluetooth controller PWM 앱에서 esp01 ip 주소 설정

 앱에서 텍스트 hi 전송 및 1번 버튼 온/오프 메시지

 데이터 수신 및 동작 확인 후 일정 시간 지난 뒤 다시 제어할 경우 가끔씩 esp01 모듈이 sleep 상태로 전환되어 있는 것처럼 첫 번째 데이터를 수신하지 못하는 경우를 확인하였습니다. 

모듈의 특성인지는 모르겠지만 앱에서 와이파이를 제어한다는 것이 인터넷 브라우저에서 제어하는 것과는 약간의 차이가 있게 됩니다. 웹브라우저는 서버에서 보내주는 "OK 200"확인 메시지를 받지 못하면 일정기간 또는 일정 횟수 동안 같은 메시지를 반복해서 보내게 되는데 앱에서는 한 번만 보내도록 되어 있어 발생하는 것으로 생각됩니다. 블루투스로 연결해서 사용할 경우에는 이러한 문제가 발생하지 않을 것입니다. 왜냐하면 블루투스는 상시 연결(BLE 제외)을 기준으로 하기 때문 일 것입니다.  

 

일정 시간 뒤 버튼을 눌러 제어를 하고자 했는데 esp01 모듈이 데이트를 받지 못하는 상황을 방지하기 위해서는 텍스트 입력창에 어떤 텍스트라도 입력하여 전 송한 뒤 esp01이 데이터를 확실하게 수신할 수 있도록 깨워주는(?) 선행 작업이 필요할 것으로 생각됩니다. 

 

 

 

 

 


2022년 5월 31일 업데이트

1. AT command의 딜레이 시간을 제거하고 회신 값에 따라 다음 코드가 실행되게 하여 오류 방지 및 setup 완료 시간 최적화

2. 공유기에 접속할 수 있도록 AT+CWJAP 추가

3. 공유기 연결 실패 시 재시도하도록 코드 추가

 위 사진의 ip 주소처럼 정상적으로 wifi 공유기에 연결되고 ip 주소를 할당받은 경우에는 컴퓨터의 웹브라우저 주소창에 해당 아이피와 테스트 문구 "192.168.1.12/hi"를 입력하여 테스트할 수 있다.

Serial_basic_esp01_parsing_update.ino
0.00MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[33] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("out")); break; }
  }
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

String income_wifi = "";

void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    if (income_wifi.startsWith(F("+IPD,"))) {
      String wifi_temp = income_wifi.substring(income_wifi.indexOf(F("GET /"))+5, income_wifi.indexOf(F("HTTP/1.1"))-1);
      Serial.println(wifi_temp);
    }
  }
}

 

웹페이지를 통한 요청에 대해 응답을 하도록 코드를 작성해 주었다. 

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  esp01.print(Data);
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  disconnectID();
}

 

웹페이지 주소창에서 할당받은 아이피 주소에 on/off/message를 입력해 보자.

주소창에 192.168.1.12/on 입력후 회신받은 메세지
주소창에 192.168.1.12/off 입력후 회신받은 메세지
주소창에 192.168.1.12/Hello World! 입력후 회신받은 메세지
Serial_basic_esp01_parsing_response.ino
0.00MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[33] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

#define ledPin 13
 
void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("out")); break; }
  }
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

uint8_t connectionId = 255; // 연결 id 저장 변수
String Data = "";

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  esp01.print(Data);
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  disconnectID();
}

String income_wifi = "";
 
void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    if (income_wifi.startsWith(F("+IPD,"))) {
      income_wifi.remove(0,5);
      connectionId = income_wifi.charAt(0);
      int ed = income_wifi.lastIndexOf(F("HTTP/1.1"))-1;
      income_wifi.remove(ed);
      int st = income_wifi.indexOf(F("GET /"))+5;
      income_wifi.remove(0,st);
      Serial.println(income_wifi);
      if(income_wifi == F("on")){  
        digitalWrite(ledPin, HIGH);
        Data += F("<p>LED ON</p>");
      }
      else if(income_wifi == F("off")){  
        digitalWrite(ledPin, LOW);  
        Data += F("<p>LED OFF</p>");
      }
      else {
        income_wifi.replace("%20", " "); // URL Decoding 공백문자 변경
        Serial.println(income_wifi); 
        Data += F("<p>");
        Data += income_wifi;
        Data += F("</p>");
      }
      http_response();
    }
    income_wifi = "";
  }
}

 

웹페이지 응답 코드가 있는 아두이노 와이파이 제어에서 사용할 수 있는 연결 도우미 앱입니다. 

https://play.google.com/store/apps/details?id=com.tistory.postpop.MCUWiFi

 

MCUWiFi - Google Play 앱

스마트폰에서 아두이노 웹서버에 연결하기 위한 앱

play.google.com

특징:

1. 스마트폰에서 아두이노 웹서버에 연결할 때 에러 메세지 출력 및 연결 가이드 제시

2. 아두이노 웹서버의 html 페이지 연결시 스마트폰의 웹브라우저 대신 앱 내부 웹 뷰어를 통해 연결 및 표시

3. 앱 실행전 스마트폰의 와이파이 실행 유무에 따라 앱 종료시 와이파이 설정 페이지 이동 

자세한 설명은 아래 포스트를 참조하세요.

[Arduino] - 아두이노 와이파이 연결 도우미 앱

 

 

 

웹페이지를 이용하여 아두이노 우노의 기본 LED를 제어해 보자

 

웹페이지 코드

const char HEADER[] PROGMEM = // HTML HEADER
"<!doctype html><html><head>"
"<meta name='viewport'charset='utf-8'content='width=device-width,initial-scale=1,user-scalable=no'>"
"<style>.c{text-align:center;}div,input{padding:5px;font-size:1em;}input{width:95%;}"
"body{text-align:center;font-family:verdana;}"    
"button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
"</style></head><body><div style='text-align:center;display:inline-block;min-width:260px;'>";

const char FOOTER[] PROGMEM = "</div></body></html>\r\n"; // HTML FOOTER
const char BACK[] PROGMEM   = "<form method='get'action='ip'><button>Back</button></form><br/>"; 

void ipPage() {
  Data =  (const __FlashStringHelper *)HEADER; // 아두이노 PROGMEM 읽는 코드
  Data += F("<p>SSID: ");
  Data += ssid;
  Data += F("</p>");
  Data += F("<p>IP Address: ");
  Data += ip;  // 와이파이 공유기에서 할당받은 아이피 주소
  Data += F("</p>");
  Data += F("<form method='get'action='Button'><button>Button</button></form><br/>");
  Data += (const __FlashStringHelper *)FOOTER;
}

void buttonPage() {
  Data =  (const __FlashStringHelper *)HEADER;
  Data += F("<h3>Pin Control</h3>");
  Data += F("<p><button style='width:40%;background-color:#12cb3d'>ON</button>");
  Data += F("<button style='margin-left:10%;width:40%;background-color:#07510a'>OFF</button></p>");
  Data += F("<form method='get'action='button'>");  // 데이터 폼 시작
  Data += F("<input type='hidden'id='bt'name='bt'>");
  Data += F("<button style='background-color:#"); 
  if (digitalRead(ledPin)) Data += F("12cb3d"); // ON 버튼 색상
  else Data += F("07510a");                     // OFF 버튼 색상
  Data += F("'onclick='bt.value=");
  if (digitalRead(ledPin)) Data += 10;          // 버튼이 ON일경우 클릭시 OFF값 10전송
  else Data += 11;                              // 버튼이 OFF일경우 클릭시 ON값 11전송
  Data += F(";submit()'>Button 1</button><br /><br />"); // 데이터 폼의 값 전송
  Data += F("</form>");                         // 데이터 폼 종료
  Data += (const __FlashStringHelper *)BACK;
  Data += (const __FlashStringHelper *)FOOTER;
}

 

주소창에 192.168.1.12 입력
Button을 클릭했을때 들어가는 페이지

버튼 1을 클릭하면 아두이노 우노의 기본 LED가 켜지고 버튼 1이 ON색상으로 변경된 웹페이지를 받게 된다. 

 상기 시리얼 모니터에 표시된 AT+CIPSEND=1,912중 "912"가 웹페이지 전체 문자열 길이이고 이 중 Data 스트링 변수의 전체 길이는 912 - 19 = 893이다. 

스케치는 프로그램 저장 공간 12138 바이트(37%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 427바이트(20%)를 사용, 1621바이트의 지역변수가 남음.  최대는 2048 바이트. 

상기 코드를 업로드한 후에 아두이노 IDE에 현재 가용 메모리가 1621바이트 남아있다고 한다. 실제로는 아두이노 부팅 후 사용되는 함수 안의 스트링 지역 변수 등은 돌려받게 되지만 남은 메모리 1621바이트 전체를 사용할 수 없는 게 일반적이다. 변수에 공간을 할당하고 회수하는 데에 있어서 흔적들 또는 유실되는 영역들이 발생하므로 실제 가용 메모리에 대해서는 추정적이나 실험적으로 결정해야 한다. 버튼 웹페이지의 Data 문자열 길이가 현재보다 길어진다면 메모리 부족으로 인해 데이터가 일부만 전송될 수 있다. PROGMEM을 이용한 추가적인 페이지는 더 만들 수 있으나 상기 코드에서는 웹페이지의 Data  문자열 변수의  길이를 대략 900 이하로 맞추어 주어야 한다. 

Serial_basic_esp01_string_LED_webpage.zip
0.00MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[33] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

#define ledPin 13
 
void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("out")); break; }
  }
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

uint8_t connectionId = 255; // 연결 id 저장 변수
String Data = "";

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  esp01.print(Data);
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  disconnectID();
}

#include "webPage.h"

String income_wifi = "";
 
void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    if (income_wifi.startsWith(F("+IPD,"))) {
      income_wifi.remove(0,5);
      connectionId = income_wifi.charAt(0);
      int ed = income_wifi.lastIndexOf(F("HTTP/1.1"))-1;
      income_wifi.remove(ed);
      int st = income_wifi.indexOf(F("GET /"))+5;
      income_wifi.remove(0,st);
      Serial.println(income_wifi);
      if(income_wifi == F("on")){  
        digitalWrite(ledPin, HIGH);
        Data += F("<p>LED ON</p>");
      }
      else if(income_wifi == F("off")){  
        digitalWrite(ledPin, LOW);  
        Data += F("<p>LED OFF</p>");
      }
      else {
        if (income_wifi.indexOf(F("ip")) != -1) ipPage();
        else if (income_wifi.indexOf(F("Button")) != -1) buttonPage(); 
        else if (income_wifi.indexOf(F("button")) != -1) { 
          String temp = income_wifi.substring(income_wifi.indexOf('?')+4);
          uint8_t val = temp.toInt();
          if (val == 10) digitalWrite(ledPin, LOW);
          else digitalWrite(ledPin, HIGH);
          buttonPage(); 
        } else {
          Serial.println(F("enter"));
          if (income_wifi == "") ipPage();
          else {
            income_wifi.replace("%20", " "); // URL Decoding 공백문자 변경
            Serial.println(income_wifi); 
            Data += F("<p>");
            Data += income_wifi;
            Data += F("</p>");
          }
        }
      }
      http_response();
    }
    income_wifi = "";
  }
  if(Serial.available() > 0){ // 아두이노 모듈 작동 확인
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp.startsWith(F("AT"))) {
      temp.remove(0,3);
      Serial.println(temp);
      sendData(temp);
    } else if (temp == F("1")) {
      
    } 
  }
}

 

상기의 Data 문자열 변수의 길이를 줄여 메모리 문제를 해결하기 위해 boolean 변수 page를 설정하고 page 변수에 따라 웹페이지의 html header와 footer를 Data 문자열 변수에 저장하지 않고 PROGMEM에서 바로 읽어서 전송할 수 있도록 코드를 수정해 주었다. 

 

void http_response(bool page) {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  if (page) cip += Data.length()+19+sizeofHeader+sizeofFooter;
  else cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  if (page) {
    esp01.print((const __FlashStringHelper *)HEADER);
    esp01.print(Data);
    esp01.print((const __FlashStringHelper *)FOOTER);
  } else esp01.print(Data);
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  page = false;
  disconnectID();
}

 

Serial_basic_esp01_string_LED_webpage_revised.zip
0.00MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[33] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

#define ledPin 13

uint16_t sizeofHeader = 0;
uint16_t sizeofFooter = 0;
bool page = false;
String Data = "";
#include "webPage.h"
 
void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("out")); break; }
  }
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
  sizeofHeader = sizeof(HEADER)-1; // -1: '\0'
  sizeofFooter = sizeof(FOOTER)-1; // -1: '\0'
}

uint8_t connectionId = 255; // 연결 id 저장 변수

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  if (page) cip += Data.length()+19+sizeofHeader+sizeofFooter;
  else cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  if (page) {
    esp01.print((const __FlashStringHelper *)HEADER);
    esp01.print(Data);
    esp01.print((const __FlashStringHelper *)FOOTER);
  } else esp01.print(Data);
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  page = false;
  disconnectID();
}

String income_wifi = "";
 
void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    if (income_wifi.startsWith(F("+IPD,"))) {
      income_wifi.remove(0,5);
      connectionId = income_wifi.charAt(0);
      int ed = income_wifi.lastIndexOf(F("HTTP/1.1"))-1;
      income_wifi.remove(ed);
      int st = income_wifi.indexOf(F("GET /"))+5;
      income_wifi.remove(0,st);
      Serial.println(income_wifi);
      if (!income_wifi.startsWith(F("fav"))) { // favicon.ico 는 통과
        if(income_wifi == F("on")){  
          digitalWrite(ledPin, HIGH);
          Data += F("<p>LED ON</p>");
        } else if(income_wifi == F("off")){  
          digitalWrite(ledPin, LOW);  
          Data += F("<p>LED OFF</p>");
        } else {
          if (income_wifi.indexOf(F("ip")) != -1) ipPage();
          else if (income_wifi.indexOf(F("Button")) != -1) buttonPage(); 
          else if (income_wifi.indexOf(F("button")) != -1) { 
            String temp = income_wifi.substring(income_wifi.indexOf('?')+4);
            uint8_t val = temp.toInt();
            if (val == 10) digitalWrite(ledPin, LOW);
            else digitalWrite(ledPin, HIGH);
            buttonPage(); 
          } else {
            Serial.println(F("enter"));
            if (income_wifi == "") ipPage();
            else {
              income_wifi.replace("%20", " "); // URL Decoding 공백문자 변경
              Serial.println(income_wifi); 
              Data += F("<p>");
              Data += income_wifi;
              Data += F("</p>");
            }
          }
        }
        http_response();
      }
    }
    income_wifi = "";
  }
  if(Serial.available() > 0){ // 아두이노 모듈 작동 확인
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp.startsWith(F("AT"))) {
      temp.remove(0,3);
      Serial.println(temp);
      sendData(temp);
    } else if (temp == F("1")) {
      
    } 
  }
}

 

와이파이 매니저

게시글 [arduino] - 아두이노 - ESP01 와이파이 매니저, soft AP이용 공유기 연결용 아이디와 비밀번호 설정하기, wifimanager에서 구현한 와이파이 매니저를 WiFiEsp.h 라이브러리를 사용하지 않고 AT commnad를 사용하여 구현하였다. 

* 아두이노 IDE에서 와이파이 설정값 변경한 경우 EEPROM에 값 반영하는 방법

리부팅시 변경된 와이파이 설정값을 반영해주기 위해서는 시리얼 모니터에서 "1"을 입력하고 엔터를 쳐준 뒤에 아두이노를 리부팅 해준다. 

설정에 따라 공유기에 연결이 안된 경우

 

Wifi 설정 페이지 - 와이파이 연결유무, 아이디, 패스워드 설정
공유기 연결 설정
설정 저장 및 공유기 연결중 메세지
공유기 연결 후 할당받은 아이피 표시

웹페이지 코드

//web page String
const char HEADER[] PROGMEM = 
"<!doctype html><html><head>"
"<meta name='viewport'charset='utf-8'content='width=device-width,initial-scale=1,user-scalable=no'>"
"<style>.c{text-align:center;}div,input{padding:5px;font-size:1em;}input{width:95%;}"
"body{text-align:center;font-family:verdana;}"    
"button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
"</style></head><body><div style='text-align:center;display:inline-block;min-width:260px;'>";

const char FOOTER[] PROGMEM = "</div></body></html>\r\n"; 
const char BACK[] PROGMEM   = "<form method='get'action='ip'><button>Back</button></form><br/>"; 

void ipPage() {
  Data += F("<p>SSID: ");
  Data += ssid;
  Data += F("</p>");
  Data += F("<p>IP Address: ");
  Data += ip;
  Data += F("</p>");
  Data += F("<form method='get'action='Button'><button>Button Page</button></form><br/>");
  Data += F("<form method='get'action='Wifi'><button>Wifi Config</button></form><br/>");
  page = true;
}

void buttonPage() {
  Data += F("<h3>Pin Control</h3>");
  Data += F("<p><button style='width:40%;background-color:#12cb3d'>ON</button>");
  Data += F("<button style='margin-left:10%;width:40%;background-color:#07510a'>OFF</button></p>");
  Data += F("<form method='get'action='button'>");
  Data += F("<input type='hidden'id='bt'name='bt'>");
  Data += F("<button style='background-color:#"); 
  if (digitalRead(ledPin)) Data += F("12cb3d");
  else Data += F("07510a");
  Data += F("'onclick='bt.value=");
  if (digitalRead(ledPin)) Data += 10;
  else Data += 11;
  Data += F(";submit()'>Button 1</button><br /><br />");
  Data += F("</form>");
  Data += (const __FlashStringHelper *)BACK;
  page = true;
}

void wifiPage() {
  Data += (const __FlashStringHelper *)BACK;
  Data += F("<form method='get'action='wifi'>");
  Data += F("<input style='width:auto;'type='checkbox'name='use'"); 
  if (wifiRouter) Data += F("checked");
  Data += F("> connect To wifi Router<br/><br/>");
  Data += F("<input id='s'name='s'length='20'value='");
  Data += ssid;
  Data += F("'><br/>");
  Data += F("<input id='p'name='p'length='20'value='");
  Data += pass;
  Data += F("'><br/>");
  Data += F("<br/><button type='submit'>save</button></form>");
  page = true;
}

void messagePage() {
  if (wifiRouter) {
    Data += F("<p>SSID: ");
    Data += ssid;
    Data += F("</p>");
    Data += F("<p>PASS: ");
    Data += pass;
    Data += F("</p>");
    Data += F("<p>Credentials Saved.</p>"); 
    Data += F("<p>Trying to connect ESP01 to Router.</p>");
  }
  else Data += F("<p>disconnect from Router.</p>");
  Data += (const __FlashStringHelper *)BACK;
  page = true;
}

 

6.Serial_basic_esp01_LED_wifiManager.zip
0.00MB

#include <EEPROM.h>

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

bool wifiRouter = true;      // 공유기 연결 유무
char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

#define ledPin 13

uint16_t sizeofHeader = 0;
uint16_t sizeofFooter = 0;
String Data = "";
bool page = false;
#include "webPage.h"

void setWifi()  {
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("Set")); break; }
  }
  if (wifiRouter) sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  else sendData(F("CWMODE=2"));
  if (wifiRouter) sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  if (wifiRouter) {
    retry = 1;
    while(retry) { 
      sendData(addID(F("CWJAP="),ssid,pass)); 
      if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
      if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
    }
  }
  ipCheck(sendData(F("CIFSR"))); // get ip address
}

void wifi_eeprom_read() {  // 아이디 / 비밀번호 읽기
  for (int i = 0; i < 20; i++) ssid[i] = EEPROM.read(981 + i); // sizeof(ssid)-1
  for (int i = 0; i < 20; i++) pass[i] = EEPROM.read(1001 + i); // sizeof(pass)-1
}

void wifi_eeprom_write() { // 아이디 / 비밀번호 쓰기
  for (int i = 0; i < 20; i++) EEPROM.write(981 + i, ssid[i]); // sizeof(ssid)-1
  for (int i = 0; i < 20; i++) EEPROM.write(1001 + i, pass[i]); // sizeof(pass)-1
}
 
void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  if(EEPROM.read(1023) != 1) { 
    for (int i = 0 ; i < EEPROM.length() ; i++) EEPROM.write(i, 0); // EEPROM Clear
    EEPROM.write(1023, 1);   // eeprom 저장 플래그 
    EEPROM.write(980, wifiRouter);
    wifi_eeprom_write();  // 최초 초기화시 아이디 / 비밀번호 쓰기
  } else {
    wifiRouter = EEPROM.read(980);
    wifi_eeprom_read();  // 아두이노 리셋시 저장된 아이디 / 비밀번호 읽기
  }
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  setWifi();
  sizeofHeader = sizeof(HEADER)-1; // -1: '\0'
  sizeofFooter = sizeof(FOOTER)-1; // -1: '\0'
}

uint8_t connectionId = 255; // 연결 id 저장 변수

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  if (page) cip += Data.length()+19+sizeofHeader+sizeofFooter;
  else cip += Data.length()+19;
  cip += F("\r\n");
  Serial.println(cip);
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // +19
  if (page) {
    esp01.print((const __FlashStringHelper *)HEADER);
    esp01.print(Data);
    esp01.print((const __FlashStringHelper *)FOOTER);
  } else esp01.print(Data);
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  page = false;
  disconnectID();
}

String income_wifi = "";
 
void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    if (income_wifi.startsWith(F("+IPD,"))) {
      income_wifi.remove(0,5);
      connectionId = income_wifi.charAt(0);
      int ed = income_wifi.lastIndexOf(F("HTTP/1.1"))-1;
      income_wifi.remove(ed);
      int st = income_wifi.indexOf(F("GET /"))+5;
      income_wifi.remove(0,st);
      Serial.println(income_wifi);
      if (!income_wifi.startsWith(F("fav"))) { // favicon.ico 는 통과 
        bool wifiChanged = false;
        if(income_wifi == F("on")){  
          digitalWrite(ledPin, HIGH);
          Data += F("<p>LED ON</p>");
        } else if(income_wifi == F("off")){  
          digitalWrite(ledPin, LOW);  
          Data += F("<p>LED OFF</p>");
        } else {
          if (income_wifi.indexOf(F("ip")) != -1) ipPage();
          else if (income_wifi.indexOf(F("Button")) != -1) buttonPage(); 
          else if (income_wifi.indexOf(F("button")) != -1) { 
            String temp = income_wifi.substring(income_wifi.indexOf('?')+4);
            uint8_t val = temp.toInt();
            if (val == 10) digitalWrite(ledPin, LOW);
            else digitalWrite(ledPin, HIGH);
            buttonPage(); 
          } 
          else if (income_wifi.indexOf(F("Wifi")) != -1) wifiPage(); 
          else if (income_wifi.indexOf(F("wifi")) != -1) { // 
            ed = income_wifi.lastIndexOf('&');
            String temp = income_wifi.substring(ed+3);
            String check = "";
            if (temp.length() > 7) { // 비밀번호 길이가 8 이상인경우
              check = pass;
              if (temp != check) {
                wifiChanged = true;
                for (int i = 0; i < temp.length(); i++) pass[i] = temp[i];
                pass[temp.length()] = '\0';
              }
            }
            income_wifi.remove(ed);
            ed = income_wifi.lastIndexOf(F("s="));
            temp = income_wifi.substring(ed+2);
            Serial.println(temp);
            check = ssid;
            if (temp != check) {
              wifiChanged = true;
              for (int i = 0; i < temp.length(); i++) ssid[i] = temp[i];
              ssid[temp.length()] = '\0';
            }
            st = income_wifi.indexOf('?');
            temp = income_wifi.substring(st+1, st+4);
            bool oldWifiRouter = wifiRouter;
            if (temp == F("use")) wifiRouter = true;
            else wifiRouter = false;
            if (oldWifiRouter != wifiRouter) { EEPROM.write(980, wifiRouter); wifiChanged = true; }
            if (wifiChanged) { wifi_eeprom_write(); messagePage(); }
            else wifiPage(); 
          } 
          else {
            if (income_wifi == "") ipPage();
            else {
              income_wifi.replace("%20", " "); // URL Decoding 공백문자 변경
              Serial.println(income_wifi); 
              Data += F("<p>");
              Data += income_wifi;
              Data += F("</p>");
            }
          }
        }
        http_response();
        if (wifiChanged) setWifi();
      }
    }
    income_wifi = "";
  }
  if(Serial.available() > 0){ // 아두이노 모듈 작동 확인
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp.startsWith(F("AT"))) {
      temp.remove(0,3);
      Serial.println(temp);
      sendData(temp);
    } else if (temp == F("1")) {
      EEPROM.write(1023, 0);
    } else if (temp == F("2")) {
      Serial.println(ssid);
      Serial.println(pass);
    }
  }
}

 

웹페이지 로그인 

와이파이를 통해 아두이노를 원격 제어할 때 Soft AP(192.168.4.1)를 사용할 경우에는 Soft AP의 비밀번호를 설정함으로써 보안이 설정되는데 (휴대폰의 와이파이 연결에서 AP연결 시 비밀번호 입력 필요), 아두이노가 와이파이 공유기에 연결되고 IP를 할당받은 경우에는 공유기 네트워크 내에 연결된 누구라도 아두이노가 연결된 IP주소만 알면 연결 및 제어가 가능하게 된다. 이를 방지하기 위해 Cookie를 사용하여 웹 로그인 기능을 구현하였다. 원격제어는 한 명만 제어가 가능해야 하며, 따라서 멀티 로그인은 배제한다.  

1. 웹 로그인 사용 유무 설정

2. Soft AP(102.168.4.1)에 비밀번호가 설정되어 있는 경우에만 웹 로그인 활성화

     * 비밀번호는 8 문자 이상 입력해야 설정됨

3. 웹 로그인 사용 시 Soft AP(102.168.4.1)를 통한 연결은 AP연결 시 비밀번호를 입력하므로 로그인 확인 안 함.

4.  공유기 네트워크에 로그인되어있더라도 Soft AP(102.168.4.1) 및 다른 브라우저 로그인을 통한 연결 시 밀어내기 기능 - Soft AP 접속 우선 제어 권한

5. 웹 로그인 비밀번호는 Soft AP 비밀번호를 사용한다. 

웹로그인 페이지
웹로그인 비밀번호 입력
웹로그인 확인 후 페이지 표시
웹로그인 설정화면
웹로그인 시 시리얼모니터 표시

 

로그인 웹페이지 및 쿠기 생성 코드, 비밀번호 검증 코드

void loginPage() {
  if (connectedAP) {
    Data += F("<br />occupied connection by AP<br />");
  } else {
    Data += F("<form method='post'action='Login'>");
    Data += F("<input name='s'length='20'value='"); 
    Data += ssid_AP;
    Data += F("'><br />");
    Data += F("<input type='password'name='p'length='20'><br /><br />");
    Data += F("<button type='submit'>Login</button></form>");
  }
  page = true;
}

// login code
String cooValue = "u7Dv0";
uint8_t cooLength = 5;
bool newCoo = true;

String makeCooValue() {
  cooValue = "";
  uint8_t temp = 0;
  for (int i = 0; i < cooLength; i++) {
    temp = random(39, 123); //random(39, 123); // 39 ~ 122 pick
    if (temp == 47 || temp == 59 || temp == 60 || temp == 62 || temp == 92 || temp == 94 || temp == 96) temp = 41;
    cooValue += char(temp);
  }
  Serial.print(F("send coo: ")); Serial.println(cooValue);
  return cooValue;
}

bool checkLogin() {
  bool confirm = false;
  String tempId = "";
  tempId = esp01.readStringUntil('\n');
  while(1) { 
    tempId = esp01.readStringUntil('\n');
    if (tempId.startsWith(F("\r"))) { tempId = esp01.readStringUntil('\n'); break; }
  } 
  int index = tempId.lastIndexOf('&');
  String tempPass = tempId.substring(index+3);
  tempId.remove(index);
  index = tempId.lastIndexOf('?');
  tempId = tempId.substring(index+3); 
  Serial.print("tempPass: "); Serial.println(tempPass);
  Serial.print("tempId: "); Serial.println(tempId);
  if (tempId == ssid_AP && tempPass == pass_AP) { confirm = true; login = true; newCoo = true;}
  return confirm;
}

 

7.Serial_basic_esp01_LED_Login.zip
0.01MB

#include <EEPROM.h>

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

bool wifiRouter = true;      // 공유기 연결 유무
char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";
bool useLogin = true;
bool connectedAP = false;

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ip);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

#define ledPin 13

uint16_t sizeofHeader = 0;
uint16_t sizeofFooter = 0;
bool page = false;
bool login = false;
String Data = "";
#include "webPage.h"

void setWifi()  {
  sendData(F("RST")); // disconnect AP if connected
  unsigned long int time = millis();
  String temp = "";
  while (time+20000 > millis()) {
    temp = esp01.readStringUntil('\n');
    if (temp.indexOf(F("ready")) != -1) { Serial.println(F("Set")); break; }
  }
  if (wifiRouter) sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  else sendData(F("CWMODE=2"));
  if (wifiRouter) sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  if (wifiRouter) {
    retry = 1;
    while(retry) { 
      sendData(addID(F("CWJAP="),ssid,pass)); 
      if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
      if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
    }
  }
  ipCheck(sendData(F("CIFSR"))); // get ip address
}

void wifi_eeprom_read() {  // 아이디 / 비밀번호 읽기
  for (int i = 0; i < 20; i++) ssid[i] = EEPROM.read(981 + i); // sizeof(ssid)-1
  for (int i = 0; i < 20; i++) pass[i] = EEPROM.read(1001 + i); // sizeof(pass)-1
}

void wifi_eeprom_write() { // 아이디 / 비밀번호 쓰기
  for (int i = 0; i < 20; i++) EEPROM.write(981 + i, ssid[i]); // sizeof(ssid)-1
  for (int i = 0; i < 20; i++) EEPROM.write(1001 + i, pass[i]); // sizeof(pass)-1
}
 
void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  if(EEPROM.read(1023) != 1) { 
    for (int i = 0 ; i < EEPROM.length() ; i++) EEPROM.write(i, 0); // EEPROM Clear
    EEPROM.write(1023, 1);   // eeprom 저장 플래그 
    EEPROM.write(980, wifiRouter);
    EEPROM.write(979, useLogin);
    wifi_eeprom_write();  // 최초 초기화시 아이디 / 비밀번호 쓰기
  } else {
    useLogin = EEPROM.read(979);
    wifiRouter = EEPROM.read(980);
    wifi_eeprom_read();  // 아두이노 리셋시 저장된 아이디 / 비밀번호 읽기
  }
  if (pass_AP == "" || pass_AP.length() < 8) useLogin = false; // Soft AP의 비밀번호가 활성화되지않은 경우
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  setWifi();
  sizeofHeader = sizeof(HEADER)-1; // -1: '\0'
  sizeofFooter = sizeof(FOOTER)-1; // -1: '\0'
}

uint8_t connectionId = 255; // 연결 id 저장 변수

void disconnectID() {
  if (connectionId < 255) {
    String command = F("AT+CIPCLOSE=");
    command += connectionId-48;
    command += F("\r\n");
    esp01.print(command);
    connectionId = 255;
  }
}

void http_response() {
  String cip = F("AT+CIPSEND=");
  cip += connectionId-48;
  cip += ',';
  if (page) {
    uint8_t len = 50+4;
    if (!connectedAP && useLogin) {
      len += 4;
      if (login) {
        if (newCoo) len += cooLength;
      }
    }
    cip += Data.length()+len+sizeofHeader+sizeofFooter;
  } else cip += Data.length()+19;
  cip += F("\r\n");
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  if (page) {
    esp01.print(F("HTTP/1.1 200 OK\nContent-type:text/html\nSet-Cookie:")); // +52 -2 = 50
    if (!connectedAP && useLogin) {
      esp01.print(F("%%F5")); // +4
      if (login) {
        if (newCoo) esp01.print(makeCooValue()); // +cooLength
      }
    }
    esp01.print(F("\r\n\r\n")); // +8 -4 = 4 
    esp01.print((const __FlashStringHelper *)HEADER);
    esp01.print(Data);
    esp01.print((const __FlashStringHelper *)FOOTER);
  } else {
    esp01.print(F("HTTP/1.1 200 OK\r\n\r\n")); // 23 - 4 = +19
    esp01.print(Data);
  }
  time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { Serial.println(F("out")); break; }
  }
  Data = "";
  page = false;
  disconnectID();
}

String income_wifi = "";
 
void loop() {
  while (esp01.available()) { 
    income_wifi = esp01.readStringUntil('\n');
    bool parsing = false;
    if (income_wifi.startsWith(F("+"))) {
      if (income_wifi.startsWith(F("+IPD"))) parsing = true;
      else if (income_wifi.startsWith(F("+STA_CON"))) { connectedAP = true; Serial.println(F("connectedAP")); }
      else if (income_wifi.startsWith(F("+STA_DIS"))) { connectedAP = false; Serial.println(F("disconnectedAP")); }
    } 
    if (parsing) {
      income_wifi.remove(0,5);
      connectionId = income_wifi.charAt(0);
      int ed = income_wifi.lastIndexOf(F("HTTP/1.1"))-1;
      income_wifi.remove(ed);
      int st = income_wifi.indexOf(F("GET /"))+5;
      income_wifi.remove(0,st);
      Serial.println(income_wifi);
      if (!income_wifi.startsWith(F("fav"))) { // favicon.ico 는 통과 
        bool OK = false, rootPage = false; bool wifiChanged = false;
        if (useLogin) {
          if (connectedAP) {
            OK = true;
            if (login) { login = false; makeCooValue(); } // 로그인 밀어내기 old Cookie terminate
          } else {
            if (!login) { // 현재 로그인 상태가 아닌 경우
              if (income_wifi.indexOf(F("Logi")) != -1) {
                Serial.println("enter");
                rootPage = checkLogin(); // 비밀번호 검증 start sending Cookie
                Serial.println(rootPage);
              } 
            } else {
              newCoo = false; 
              if (income_wifi.indexOf(F("Logi")) != -1) { // 밀어내기 로그인
                rootPage = checkLogin();
              } else { 
                uint16_t len = 0;
                String temp = "";
                while(1) { 
                  temp = esp01.readStringUntil('\n');
                  len = temp.length();
                  if (temp.startsWith(F("Coo")) || len < 3) break; 
                } 
                if (len > 2) {
                  ed = temp.indexOf(F("%%F5"))+4;
                  if (ed != -1) {
                    temp = temp.substring(ed, ed+cooLength);
                    if (temp == cooValue) { OK = true; newCoo = true; }
                  } 
                }
                if (OK) { // 웹페이지 출력
                  if (income_wifi.indexOf(F("Logo")) != -1) { // Logout
                    login = false; OK = false; 
                    makeCooValue(); // cookie 값 변경
                  } 
                }
              }
            }
          }
        } else {
          OK = true;
        }
        if (OK) {
          if(income_wifi == F("on")){  
            digitalWrite(ledPin, HIGH);
            Data += F("<p>LED ON</p>");
          } else if(income_wifi == F("off")){  
            digitalWrite(ledPin, LOW);  
            Data += F("<p>LED OFF</p>");
          } else {
            if (income_wifi.indexOf(F("ip")) != -1) ipPage();
            else if (income_wifi.indexOf(F("Button")) != -1) buttonPage(); 
            else if (income_wifi.indexOf(F("button")) != -1) { 
              String temp = income_wifi.substring(income_wifi.indexOf('?')+4);
              uint8_t val = temp.toInt();
              if (val == 10) digitalWrite(ledPin, LOW);
              else digitalWrite(ledPin, HIGH);
              buttonPage(); 
            } 
            else if (income_wifi.indexOf(F("Wifi")) != -1) wifiPage(); 
            else if (income_wifi.indexOf(F("wifi")) != -1) { // 
              ed = income_wifi.lastIndexOf('&');
              String temp = income_wifi.substring(ed+3);
              if (temp == F("G=on")) { 
                useLogin = true; EEPROM.write(979, useLogin);
                income_wifi.remove(ed);
                ed = income_wifi.lastIndexOf('&');
                temp = income_wifi.substring(ed+3);
              } else { useLogin = false; EEPROM.write(979, useLogin); }
              String check = "";
              if (temp.length() > 7) { // 비밀번호 길이가 8 이상인경우
                check = pass;
                if (temp != check) {
                  wifiChanged = true;
                  for (int i = 0; i < temp.length(); i++) pass[i] = temp[i];
                  pass[temp.length()] = '\0';
                }
              }
              income_wifi.remove(ed);
              ed = income_wifi.lastIndexOf(F("s="));
              temp = income_wifi.substring(ed+2);
              Serial.println(temp);
              check = ssid;
              if (temp != check) {
                wifiChanged = true;
                for (int i = 0; i < temp.length(); i++) ssid[i] = temp[i];
                ssid[temp.length()] = '\0';
              }
              st = income_wifi.indexOf('?');
              temp = income_wifi.substring(st+1, st+4);
              bool oldWifiRouter = wifiRouter;
              if (temp == F("ROU")) wifiRouter = true;
              else wifiRouter = false;
              if (oldWifiRouter != wifiRouter) { EEPROM.write(980, wifiRouter); wifiChanged = true; }
              if (wifiChanged) { wifi_eeprom_write(); messagePage(); }
              else wifiPage(); 
            } else {
              if (income_wifi == "") ipPage();
              else {
                income_wifi.replace(F("%20"), F(" ")); // URL Decoding 공백문자 변경
                Serial.println(income_wifi); 
                Data += F("<p>");
                Data += income_wifi;
                Data += F("</p>");
              }
            }
          }
        } else {
          if (useLogin) {
            if (rootPage) ipPage();
            else loginPage();
          } else {
            if (income_wifi == "") ipPage();
            else {
              income_wifi.replace(F("%20"), F(" ")); // URL Decoding 공백문자 변경
              Serial.println(income_wifi); 
              Data += F("<p>");
              Data += income_wifi;
              Data += F("</p>");
            }
          }
        }
        http_response();
        if (wifiChanged) setWifi();
      }
    }
    income_wifi = "";
  }
  if(Serial.available() > 0){ // 아두이노 모듈 작동 확인
    String temp = Serial.readStringUntil('\n');
    Serial.println(temp);
    if (temp.startsWith(F("AT"))) {
      temp.remove(0,3);
      Serial.println(temp);
      sendData(temp);
    } else if (temp == F("1")) {
      EEPROM.write(1023, 0);
    } 
  }
}

 

ESP01 펌웨어 업로드하기

웹 로그인 구현 코드상에 Soft AP로 연결된 상태를 확인하는 코드는 아래와 같다. 

    if (income_wifi.startsWith(F("+"))) {
      if (income_wifi.startsWith(F("+IPD"))) parsing = true;
      else if (income_wifi.startsWith(F("+STA_CON"))) { connectedAP = true; Serial.println(F("connectedAP")); }
      else if (income_wifi.startsWith(F("+STA_DIS"))) { connectedAP = false; Serial.println(F("disconnectedAP")); }
    }

상기 코드는 ESP01 펌웨어에서 Soft AP에 연결/종료 이벤트가 발생하면 시리얼 통신을 통해 아두이노로 보내주는 메시지 "+STA_CONNECTED / +STA_DISCONNECTED"를 확인하고 현재 Soft AP에서 연결된 상태인지 아닌지를 결정하는 코드이다. 따라서 ESP01의 펌웨어 프로그램에서 해당 메시지를 정상적으로 보내주지 않는다면 웹로그인 기능은 작동할 수 없게된다. 일부 펌웨어 버전이 탑제된 ESP01은 Soft AP 연결/종료 이벤트 발생시 해당 메세지를 전송하지 않는 오류가 있다고 한다. 만약 Soft AP로 연결을 했는데 시리얼 모니터에 "connectedAP"라는 메시지가 표시되지 않는다면 최신 펌웨어를 ESP01에 업로드해야 한다. 

 

1. 아두이노에 EMPTY CODE 업로드

2. ESP01과 아두이노의 연결을 펌웨어 업로드용으로 변경 

ESP01 TX -> 아두이노 TX

ESP01 RX -> 아두이노 RX

ESP01 VCC -> 아두이노 3.3V

ESP01 CH_PD -> 아두이노 3.3V

ESP01 GND -> 아두이노 GND

ESP01 GPIO0 -> 아두이노 GND

ESP01 RST -> 아두이노 GND 연결 대기

ESP01 핀맵

 

3. ESP01 펌웨어 / 펌웨어 업로드 프로그램 다운로드 및 압축 풀기

 

펌웨어 업로드 프로그램

flash_download_tools_v3.6.5.zip
14.10MB

펌웨어 V1.7.5_1

ESP8266_NonOS_AT_Bin_V1.7.5_1.zip
1.89MB

https://www.espressif.com/en/products/sdks/esp-at/resource

 

Resources | Espressif Systems

*Note: When developing new products with ESP8266, it is recommended that you use ESP8266 AT Bin V2.0.0.0 and/or later versions. Previous AT versions were based on the ESP8266 NONOS SDK, to which no new features will be added in the future. ESP8266 AT Bin V

www.espressif.com

파일 경로상에 한글이 있으면 정상 작동하지 않으므로 다운로드한 펌웨어 / 업로드 프로그램은 C드라이브 또는 D드라이브에 복사하고 압축을 풀어준다. 

펌웨어 업로드 프로그램 경로
펌웨어 경로

4. ESP01 설치되어있는 펨웨어 지우기

4-1. flash_download_tools_v3.6.5.exe를 실행하고 ESP8266 DownloadTool을 선택한다.

4-2. DownloadTool에서 아두이노가 연결된 COM PORT를 설정해준다. 만약 아두이노 IDE의 시리얼 모니터가 열려있다면 닫아준다. 

아두이노 IDE의 COM 포트 확인

4-3. ESP01을 업로드 모드로 전환하고 지우기

ESP01의 RST핀에 연결해놓은 선을 아두이노 GND에 1초 정도 연결한 뒤 떼면 ESP01에 내장되어 있는 파란색 LED가 켜졌다 꺼지면서 업로드 모드로 전환된다. 파란색 LED가 켜졌다 꺼지지 않으면 업로드할 수 없다. ESP01과 아두이노의 연결이 잘못되었거나 아두이노에 EMPTY CODE가 업로드되지 않았을 수 있다. ERASE 버튼을 클릭한다.

 

정상적으로 펌웨어가 지워지면 아래와 같은 메시지와 함께 FINISH라고 표시된다. 

만약 아래 사진처럼 점선이 표시된다면 정상적으로 진행되지 않고 있다는 것이다. 이때는 STOP을 누르고 COM PORT를 확인하거나 "업로드 모드"로 전환하기를 다시 한 뒤 해본다. 펌웨어 지우기를 할 수 없다면, 펌웨어 업로드도 할 수 없게 된다. 

5. 펌웨어 설정 경로 확인하고 설정하기

D:\ESP8266_NonOS_AT_Bin_V1.7.5_1\ESP8266_NonOS_AT_Bin_V1.7.5\bin\at 경로의 README.md 파일을 메모장으로 열어보면 아래와 같은 경로 설정을 확인할 수 있다. 

### Flash size 8Mbit: 512KB+512KB
    boot_v1.2+.bin              0x00000
    user1.1024.new.2.bin        0x01000
    esp_init_data_default.bin   0xfc000
    blank.bin                   0x7e000 & 0xfe000

설정 방법에 따라 아래와 같이 순서대로 경로 창에 입력해 준다. 

1.D:\ESP8266_NonOS_AT_Bin_V1.7.5_1\ESP8266_NonOS_AT_Bin_V1.7.5\bin\boot_v1.7.bin을 선택하고 우측에 "0x00000"을 입력.

 2.D:\ESP8266_NonOS_AT_Bin_V1.7.5_1\ESP8266_NonOS_AT_Bin_V1.7.5\bin\at\512+512\user1.1024.new.2.bin을 선택하고 우측에 "0x01000"을 입력.

 

3.D:\ESP8266_NonOS_AT_Bin_V1.7.5_1\ESP8266_NonOS_AT_Bin_V1.7.5\bin\esp_init_data_default_v08.bin을 선택하고 우측에 "0xfc000"을 입력.

 

4.D:\ESP8266_NonOS_AT_Bin_V1.7.5_1\ESP8266_NonOS_AT_Bin_V1.7.5\bin\blank.bin을 선택하고 우측에 "0x7e000"을 입력.  그 아래 같은 파일을 한번 더 선택하고 우측에 "0xfe000"을 입력

 

입력한 다섯 개 항목의 체크박스에 체크를 해준다.

 

6. 설정이 완료됐으면 SPI SPEED: 40MHz, SPI MODE: QIO, FLASH SIZE: 8Mbit를 추가로 설정해주고

4-1 업로드 모드로 전환하기를 다시 실행한 뒤 "START" 버튼을 클릭

업로드 모드는 펌웨어 지우기 또는 펌웨어 업로드의 시도가 있은 후에는 그 성공 여부에 상관없이 초기화되므로 매번 업로드 모드로 전환한 후에 실행해야함. 

펌웨어 업로드가 완료되면 ESP01 모듈의 보드 레이트는 초기값인 115200으로 변경된다. 아두이노에서 사용할 수 있도록 보드 레이트를 9600으로 변경해주어야 한다. 

현재 아두이노에는 EMPTY CODE가 업로드 되어 있다. 이 상태에서 아두이노 IDE의 시리얼 모니터를 열고 보드 레이트는 115200, 전송옵션은 "Both NL & CR"로 설정한 뒤에 현재 아두이노 GND에 연결되어 있는 GPIO0핀의 연결을 해제한다.  GPIO0핀이 연결해제된 상태에서 RST핀을 아두이노의 GND핀에 연결했다 해제하면 시리얼 모니터에 아래와 같이 "ready"라는 메시지가 출력된다. 

시리얼 모니터 입력창에 "AT+UART_DEF=9600,8,1,0,0"를 입력하고 엔터를 치면 "OK"라는 메세지가 출력되고 ESP01의 보드레이트는 9600으로 변경된다.

 보드 레이트가 9600으로 변경되었으므로 시리얼 모니터의 보드 레이트를 9600으로 변경한 뒤 시리얼 모니터 입력창에 "AT"를 입력하고 엔터를 치면 "OK"라는 메시지를 확인 할 수 있다. 

이제 펌웨어 업로드와 보드 레이트 변경까지 완료 하였다. 아두이노와 ESP01의 연결을 이전으로(제어용) 변경해 준다.

 

 

 

 

기상청 및 오픈 웨더 맵에서 날씨 정보 받기

기상청에서 제공하던 RSS 날씨 정보 서비스가 폐지 되고 공공데이터 포털을 통한 API 접속(오픈웨더맵과 같은 방식)방식으로 변경되었습니다. API 기반 기상청 날씨 정보를 수신하고자 한다면 아래 포트스를 참조하시기 바랍니다. 

[Arduino] - 아두이노 - ESP01 모듈, 기상청 / 오픈웨더맵 API 날씨 정보 받기

 

 

기상청 :: 아래 기상청 날씨 정보 수신 코드는 RSS 방식일때 사용하는 코드입니다. 

스케치는 프로그램 저장 공간 13142 바이트(40%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 437바이트(21%)를 사용, 1611바이트의 지역변수가 남음.  최대는 2048 바이트.

Serial_basic_esp01_AT_weather_data_kor.ino
0.01MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";
String ap = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ap = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ap);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("CWQIF")); // disconnect AP if connected
  sendData(F("CWQAP")); // disconnect wifi if connected
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

// http://www.kma.go.kr/wid/queryDFSRSS.jsp?zone=1159068000
const char host[] PROGMEM = "www.kma.go.kr";
const char url[]  PROGMEM = "/wid/queryDFSRSS.jsp?zone=1159068000";

bool OK200 = false;
bool sentREQ = false;
bool gotWeather = false;

void get_weather() {
  if (!sentREQ) {
    Serial.println(F("Starting connection to server..."));
    bool connectOK = false;
    String cip = F("AT+CIPSTART=3,"); // connection ID 3 (ID: 0 ~ 4)
    cip += '"'; cip += F("TCP"); cip += '"';
    cip += ','; cip += '"';
    cip += (const __FlashStringHelper *)host;
    cip += '"'; cip += ',';
    cip += F("80"); // port number
    cip += F("\r\n");
    esp01.print(cip);
    unsigned long int time = millis();
    while (time+2000 > millis()) {
      cip = esp01.readStringUntil('\n');
      if (cip.indexOf(F("OK")) != -1) { connectOK = true; Serial.println(F("OK")); break; }
      else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
    }
    if (connectOK) {
      Serial.println(F("Connected to server"));
      String temp = F("GET ");
      temp += (const __FlashStringHelper *)url;
      temp += F(" HTTP/1.1\r\nHost: ");
      temp += (const __FlashStringHelper *)host;
      temp += F("\r\nConnection: close\r\n\r\n");
      cip = F("AT+CIPSEND=");
      cip += 3; // connection ID 3 (ID: 0 ~ 4)
      cip += ',';
      cip += temp.length();
      cip += F("\r\n");
      esp01.print(cip);
      time = millis();
      while (time+2000 > millis()) {
        cip = esp01.readStringUntil('\n');
        if (cip.indexOf(F("OK")) != -1) { Serial.println(F("OK")); break; }
        else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
      }
      esp01.print(temp);
      sentREQ = true;
      gotWeather = true;
    }
  }
}

// RSS 날씨 정보 저장 변수
// RSS 날씨 정보 저장 변수
String line0 = "";
String line1 = "";

uint8_t count = 0; // RSS 날씨 정보 분류 카운터
uint8_t tryCount = 0;

// 온도 저장 변수
float temp0;
float temp1;

void parsing() {
  String announce_time;
  int tm_start= line0.indexOf(F("<tm>")); // "<tm>"문자가 시작되는 인덱스 값('<'의 인덱스)을 반환한다. 
  int tm_end= line0.indexOf(F("</tm>"));  
  announce_time = line0.substring(tm_start + 4, tm_end); // +4: "<tm>"스트링의 크기 4바이트, 4칸 이동
  Serial.print(F("announce_time: ")); Serial.println(announce_time);
  String hour;
  int hour_start= line0.indexOf(F("<hour>"));
  int hour_end= line0.indexOf(F("</hour>"));
  hour = line0.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  String temp;
  int temp_start= line0.indexOf(F("<temp>"));
  int temp_end= line0.indexOf(F("</temp>"));
  temp = line0.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp0 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp0: ")); Serial.println(temp0);
  String wfEn;
  int wfEn_start= line0.indexOf(F("<wfEn>"));
  int wfEn_end= line0.indexOf(F("</wfEn>"));
  wfEn = line0.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line0 = ""; // 스트링 변수 line0 데이터 추출 완료 
  Serial.println();
  hour_start= line1.indexOf(F("<hour>"));
  hour_end= line1.indexOf(F("</hour>"));
  hour = line1.substring(hour_start + 6, hour_end);
  Serial.print(F("hour: ")); Serial.println(hour);
  temp_start= line1.indexOf(F("<temp>"));
  temp_end= line1.indexOf(F("</temp>"));
  temp = line1.substring(temp_start + 6, temp_end);
  Serial.print(F("temp: ")); Serial.println(temp);
  temp1 = temp.toFloat();   // 자료형 변경 String -> float
  Serial.print(F("temp1: ")); Serial.println(temp1);
  wfEn_start= line1.indexOf(F("<wfEn>"));
  wfEn_end= line1.indexOf(F("</wfEn>"));
  wfEn = line1.substring(wfEn_start + 6, wfEn_end);
  Serial.print(F("weather: ")); Serial.println(wfEn);
  line1 = ""; // 스트링 변수 line1 데이터 추출 완료 
}

bool header = false;

void loop() {
  if (!ip.startsWith(F("0"))) { // 공유기로부터 IP주소를 할당받은 경우
    if (!gotWeather) {
      if (tryCount < 3) { get_weather(); tryCount++; }
    }
  }
  if (sentREQ) { // 날씨 정보가 들어오는 동안
    if (!OK200) {
      if (esp01.find('+')) {
        line0 = esp01.readStringUntil('\n');
        if (line0.indexOf(F("200")) != -1) OK200 = true;
        line0 = "";
      } else { // gotWeather = true; -> 날씨 작업 종료 후 나머지 데이터 삭제
        line0 = esp01.readStringUntil('\n');
        if (line0.endsWith(F("D"))) { 
          if (line0.endsWith(F("CLOSED"))) sentREQ = false; // HTTP connection closed
        }
      }
    } else {
      if (!header) {
        line0 = esp01.readStringUntil('\n');
        if (line0.indexOf(F("<header>")) != -1) { header = true; line0 = ""; }
      } else {
        bool finish = false;
        while (esp01.available()) {
          char c = esp01.read();
          if (c == '<') count++;
          if (count <= 50) line0 += c;
          else if (count > 50 && count <= 90) line1 += c;
          else if (count > 90) { finish = true; break; }
        }
        if (finish) {
          Serial.println(F("Finished Reading"));
          Serial.println(line0);
          Serial.println(line1);
          parsing();
          gotWeather = true; // 날씨 작업 종료
          count = 0; header = false; OK200 = false; tryCount = 0; // 초기화 sentREQ = false; 
        }
      }
    }
  } else { // 날씨 이외의 경우
    if (esp01.available()) {
      line0 = esp01.readStringUntil('\n');
      Serial.println(line0);
    }
  }
}

 

OPEN WEATER MAP

스케치는 프로그램 저장 공간 10142 바이트(31%)를 사용. 최대 32256 바이트.
전역 변수는 동적 메모리 407바이트(19%)를 사용, 1641바이트의 지역변수가 남음.  최대는 2048 바이트.

Serial_basic_esp01_AT_weather_data_open.ino
0.01MB

#include <SoftwareSerial.h>
#define rxPin 3 
#define txPin 2 
SoftwareSerial esp01(txPin, rxPin); // SoftwareSerial NAME(TX, RX);

const String ssid_AP = "ESP01";    // your network SSID (name)
const String pass_AP = "12345678"; //"1234test";         // your network password

char ssid[21] = "skynet";    // "SK_WiFiGIGA40F7" your network SSID (name)
char pass[21] = "skynet00";  // "1712037218" your network password 

String ip = "";
String ap = "";

void ipCheck(String response) {
  int st, ed;
  if (response.indexOf(F("PIP")) != -1) {
    st = response.indexOf(F("PIP"))+5;
    ed = response.indexOf('"', st+1);
    ap = response.substring(st, ed);
    response.remove(0, ed+1);
    Serial.print(F("ap: ")); Serial.println(ap);
  }
  if (response.indexOf(F("AIP")) != -1) {
    st = response.indexOf(F("AIP"))+5;
    ed = response.indexOf('"', st+1);
    ip = response.substring(st, ed);
    Serial.print(F("ip: ")); Serial.println(ip);
  }
}

uint8_t retry = 1;

String sendData(String order) {
  String command = F("AT+");
  command += order;
  command += F("\r\n");
  esp01.print(command); // send the read character to the esp01
  unsigned long int time = millis();
  String response = "";
  while( (time+20000) > millis()) {
    while(esp01.available()) {  // The esp has data so display its output to the serial window
      char c = esp01.read(); // read the next character.
      response+=c;
    }
    if (response.indexOf(F("OK")) != -1) { retry = 0; Serial.println(F("OK")); break; } 
    else if (response.indexOf(F("FAIL")) != -1 || response.indexOf(F("ERROR")) != -1) { 
      if (response.indexOf(F("FAIL")) != -1) retry++;
      else if (response.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; } 
      Serial.print(command); Serial.println(F("Failed")); break; 
    }
  }
  return response;
}

String addID(String order, String ssid_AP, String pass_AP) {
  String temp = order+'"'+ssid_AP+'"'+','+'"'+pass_AP+'"';
  return temp;
}

void setup() {
  Serial.begin(9600);
  esp01.begin(9600); // your esp's baud rate might be different
  sendData(F("CWQIF")); // disconnect AP if connected
  sendData(F("CWQAP")); // disconnect wifi if connected
  sendData(F("CWMODE=3")); // configure as access point (working mode: AP+STA)
  sendData(F("CWDHCP=1,1"));
  sendData(addID(F("CWSAP="),ssid_AP,pass_AP)+','+String(10)+','+String(3)); 
  retry = 1;
  while(retry) { 
    sendData(addID(F("CWJAP="),ssid,pass)); 
    if (retry > 1) { Serial.print(F("Retrying to connect: ")); Serial.println(retry-1); }
    if (retry > 4) { Serial.println(F("connection failed")); retry = 0; }
  }
  sendData(F("CIPMUX=1")); // configure for multiple connections
  sendData(F("CIPSERVER=1,80")); // turn on server on port 80
  ipCheck(sendData(F("CIFSR"))); // // get ip address
}

String json_parser(String s, String a) { 
  String val;
  if (s.indexOf(a) != -1) {
    int st_index = s.indexOf(a);
    int val_index = s.indexOf(':', st_index);
    if (s.charAt(val_index + 1) == '"') {
      int ed_index = s.indexOf('"', val_index + 2);
      val = s.substring(val_index + 2, ed_index);
    }
    else {
      int ed_index = s.indexOf(',', val_index + 1);
      val = s.substring(val_index + 1, ed_index);
    }
  } 
  else {
    Serial.print(a); Serial.println(F(" is not available"));
  }
  return val;
}

// OPEN WEATHER MAP
// https://api.openweathermap.org/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY
const char host[] PROGMEM = "api.openweathermap.org";
const char url[]  PROGMEM = "/data/2.5/weather?q=Seoul,KR&APPID=YOUR_API_KEY"; // YOUR_API_KEY 수정

bool OK200 = false;
bool sentREQ = false;
bool gotWeather = false;

void get_weather() {
  Serial.println(F("Starting connection to server..."));
  bool connectOK = false;
  String cip = F("AT+CIPSTART=3,"); // connection ID 3 (ID: 0 ~ 4)
  cip += '"'; cip += F("TCP"); cip += '"';
  cip += ','; cip += '"';
  cip += (const __FlashStringHelper *)host;
  cip += '"'; cip += ',';
  cip += F("80"); // port number
  cip += F("\r\n");
  esp01.print(cip);
  unsigned long int time = millis();
  while (time+2000 > millis()) {
    cip = esp01.readStringUntil('\n');
    if (cip.indexOf(F("OK")) != -1) { connectOK = true; Serial.println(F("OK")); break; }
    else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
  }
  if (connectOK) {
    Serial.println(F("Connected to server"));
    String temp = F("GET ");
    temp += (const __FlashStringHelper *)url;
    temp += F(" HTTP/1.1\r\nHost: ");
    temp += (const __FlashStringHelper *)host;
    temp += F("\r\nConnection: close\r\n\r\n");
    cip = F("AT+CIPSEND=");
    cip += 3; // connection ID 3 (ID: 0 ~ 4)
    cip += ',';
    cip += temp.length();
    cip += F("\r\n");
    esp01.print(cip);
    time = millis();
    while (time+2000 > millis()) {
      cip = esp01.readStringUntil('\n');
      if (cip.indexOf(F("OK")) != -1) { Serial.println(F("OK")); break; }
      else if (cip.indexOf(F("ERROR")) != -1) { Serial.println(F("ERROR")); break; }
    }
    esp01.print(temp);
    sentREQ = true;
    gotWeather = true;
  }
}

// RSS 날씨 정보 저장 변수
String line = "";
uint8_t tryCount = 0;

void loop() {
  if (!ip.startsWith(F("0"))) { // 공유기로부터 IP주소를 할당받은 경우
    if (!gotWeather) {
      if (tryCount < 3) { get_weather(); tryCount++; }
    }
  }
  while (esp01.available()) { 
    line = esp01.readStringUntil('\n');
    Serial.println(line);
    if (sentREQ) {
      if (!OK200) {
        if (line.startsWith(F("+"))) {
          if (line.indexOf(F("200")) != -1) OK200 = true;
          else { OK200 = false; gotWeather = false; }
        }
      } else {
        if (line.startsWith(F("{"))) {
          Serial.println(line);
          Serial.println(F("weather data for parsing"));
          String description = json_parser(line, F("description"));
          Serial.print(F("description: ")); Serial.println(description);
          String pressure = json_parser(line, F("pressure"));
          Serial.print(F("pressure: ")); Serial.println(pressure);
          String wind = json_parser(line, F("speed"));
          Serial.print(F("wind: ")); Serial.println(wind);
          String Temp = json_parser(line, F("temp"));
          Serial.print(F("Temp: ")); Serial.println(Temp);
          line = "";
          gotWeather = true; // 날씨 작업 종료
          tryCount = 0; OK200 = false; sentREQ = false; // 초기화
        }
      }
    } else {
      Serial.println(line);
    }
  }
}

 

 2022년 12월 17일 최신 업데이트 : ESP01 원격 제어 기본코드

[Arduino] - Setting up Wi-Fi module ESP01 and basic code for Arduino remote control

 

최신 업데이트 유료앱(모든 안드로이드 버전 와이파이 연결 지원)

https://postpop.tistory.com/175

 

ADUCON - Arduino wireless remote control application

Arduino remote control app using a wireless module available in Arduino. Bluetooth 2.0 Classic / 4.0 BLE : HC-05, HC-06, HM-10, AT-09, BT05, ESP32, etc. Wi-Fi : ESP01, ESP8266 NodeMcu, ESP32, etc. https://play.google.com/store/apps/details?id=com.tistory.p

postpop.tistory.com

 

날씨 코드 설명 :

[arduino] - 아두이노 - ESP01 모듈, 기상청 RSS / 오픈 웨더 맵 API 날씨 정보 받기

 

 

참조사이트

http://allaboutee.com/2014/12/30/esp8266-and-arduino-webserver/ 

https://room-15.github.io/blog/2015/03/26/esp8266-at-command-reference/

 

관련 글

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 1편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 2편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 3편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 4편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 5편

[arduino] - 블루투스 4.0 BLE 이용 아두이노 및 ESP32 원격제어

 

[arduino] - 아두이노 - 시리얼통신 주요 함수와 예제, String class

[arduino] - 아두이노 - ESP01 wifi 모듈 무선 원격제어 그리고 시리얼 통신 - 6편

[arduino] - ESP8266 - NodeMcu 1.0 와이파이 이용 원격제어(soft AP, wifi)

[arduino] - ESP32 - Dev Module 와이파이 이용 원격제어(soft AP, wifi)

[arduino] - 아두이노 - 와이파이 모듈 ESP01, WiFiEsp.h 라이브러리 이용 웹페이지에서 디지털핀 원격제어

 

 

arduino bluetooth controller PWM - 아두이노 원격제어 안드로이드 앱 버전 3.5 다운로드

arduino bluetooth controller PWM 매뉴얼

 

 

+ Recent posts