dinist

ARDUCAM ESP8266 UNO 보드에서 HTTPS POST 요청하기 본문

아두이노

ARDUCAM ESP8266 UNO 보드에서 HTTPS POST 요청하기

dinist 2020. 8. 11. 22:26

이전에 아두이노에서 수집한 값을 GCP Compute Engine의 CentOS의 DB에 저장하는 글을 작성했었다.

웹서버의 통신을 HTTP에서 HTTPS로 변경 한 이후 아두이노에서 데이터를 입력하려는데 데이터가 입력되지 않았다.

 

이전에 HTTP를 HTTPS로 Redirect하는 과정을 추가한 이후 아두이노에서 http요청을 하면 308 응답만 수신하고

Redirect를 하지 못한다.

 

그래서 주소를 https로 바꾸어봤더니 이번엔 400 응답이 온다.

될리가 없다. SSL이 적용된 사이트에 Plain HTTP 요청을 하는데..

 

해당 보드의 예제에 HTTPS통신 예제가 있었다. 그 코드를 참고하여 기존 코드를 수정했다.

 

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <DHT.h>
#include <WiFiClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <time.h>

#define SERVER_ADDR "YOUR SERVER ADDR"  // 서버의 주소
#define SERVER_URI "YOUR FILE PATH" // php파일 경로
#define DHTPIN 14         // DHT11 센서의 DATA를 연결한 DIGITAL 포트
#define DHTTYPE DHT11       // DHT11을 사용함을 명시
#define STASSID "YOUR SSID"   // WiFi SSID 입력
#define STAPSK  "YOUR PSK"   // WiFi 비밀번호 입력

const char fingerprint[] PROGMEM = "XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX";
// 서버의 fingerprint를 입력한다.
// PROGMEM은 데이터를 SRAM 대신 플래시 메모리에 저장하도록 한다.

DHT dht(DHTPIN, DHTTYPE);

struct tm *lc;          // 내가 원하는대로 날짜형식 작성을 위해 필요한 구조체

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);
  configTime(-(3600*9), 0, "1.kr.pool.ntp.org"); // 9시간 시차, 서머타임 적용 X

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi Connected! IP address   ");
  Serial.println(WiFi.localIP());
  
  Serial.println(F("Gather Temperature, Humidity, Concentrations Of Gases !"));
  Serial.println(F("Wait 60 Seconds...\n"));
  dht.begin();
}

String pluszero(int value){     // localtime을 통해 생성된 구조체의 값이 10 미만이면
  String out;           // 한자리 숫자로 나온다. 예를들어 9일이면 09가 아닌
  if(value < 10){         // 9로 나와서 한자리 숫자일 경우 앞에 0을 추가하는
    out.concat("0");        // 작업을 하는 함수이다.
    out.concat(String(value));
    return out;
  }else{
    out = String(value);
    return out;
  }
}

String calcdate(struct tm *t){        // 현재 시간을 DATETIME 형식으로 바꾸어
  String out = String((1900+t->tm_year)); // String 타입으로 return 한다.
  out.concat("-");
  out.concat(pluszero((t->tm_mon)+1));
  out.concat("-");
  out.concat(pluszero(t->tm_mday));
  out.concat(" ");
  out.concat(pluszero(t->tm_hour));
  out.concat(":");
  out.concat(pluszero(t->tm_min));
  out.concat(":");
  out.concat(pluszero(t->tm_sec));
  return out;
}

void loop() {
  
  delay(60000);  // 측정 간 1분간의 지연을 설정한다.
  BearSSL::WiFiClientSecure client;
  client.setFingerprint(fingerprint);
  HTTPClient https;
  
  time_t now = time(nullptr);
  lc = localtime(&now);   // 현재 시간을 localtime을 통한 구조체 설정

  // 온도와 습도값을 읽는데 250ms가 소요된다.
  // 센서가 값을 읽을때 2초의 시간이 소요될것이다.
  float h = dht.readHumidity();
  
  // 섭씨온도값으로 온도를 수집한다. (기본값)
  float t = dht.readTemperature();
  
  //float f = dht.readTemperature(true); // 섭씨온도(oC)가 아닌 화씨온도(oF)사용시
                                         // (isFahrenheit = true) 설정을 해준다.

  // 읽기에 실패하였는지 확인하고 만약 그렇다면 빠르게 탈출한다. (빠른 재시작을 위해)
  if (isnan(h) || isnan(t)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  // 열 지수의 수집을 섭씨로 한다.
  float hic = dht.computeHeatIndex(t, h, false);
  
  float Sensor_Volt;
  float RS_gas;       // GAS의 RS값 저항 관련 값이라 생각하면 됨
  float ratio;        // RS_GAS/RS_air의 비율 값
  float R0 = 0.37;
  int sensorValue = analogRead(A0);
  Sensor_Volt = (float)sensorValue/1024*5.0;
  RS_gas = (5.0-Sensor_Volt)/Sensor_Volt;
  ratio = RS_gas / R0;

  // 온도 습도 영역
  Serial.print(F("Humidity: ")); Serial.print(h);
  Serial.print(F("%  Temperature: ")); Serial.print(t);Serial.print(F("°C "));
  Serial.print(F("Heat index: "));
  Serial.print(hic);
  Serial.println(F("°C"));
  // 가스 영역
  Serial.print(F("sensor_volt = "));
  Serial.print(Sensor_Volt);
  Serial.print(F(" RS_ratio = "));
  Serial.print(RS_gas);
  Serial.print(F(" Rs/R0 = ")); 
  Serial.print(ratio);    //ratio를 기준으로 삼고 ratio가 1밑으로 떨어지면 감지해야함
  Serial.println("");
  // 시간 영역
  Serial.print(F("기준 시간 : "));
  String date = calcdate(lc);
  Serial.println(date);
  Serial.println("");

  if ((WiFi.status() == WL_CONNECTED)) {

    Serial.print("[HTTP] 서버 연결을 시도합니다...\n");
    
    https.begin(client, "https://" SERVER_ADDR SERVER_URI);  // 요청을 보낼 URL 입력
    https.addHeader("Content-Type", "application/x-www-form-urlencoded");
    // POST 요청을 할때 전송 방식을 정한다.
    
    Serial.print("[HTTP] 수집한 값의 POST 요청을 시도합니다...\n");
    String POSTBODY = String("Humidity=");
    POSTBODY.concat(String(h));
    POSTBODY.concat(String("&Temperature="));
    POSTBODY.concat(String(t));
    POSTBODY.concat(String("&MQ5_Value="));
    POSTBODY.concat(String(ratio));
    POSTBODY.concat(String("&Time="));
    POSTBODY.concat(String(date));
    POSTBODY.concat(String("&HI="));
    POSTBODY.concat(String(hic));
    int httpsCode = https.POST(POSTBODY); // 위에 작성한 URL로 POST 요청을 보낸다.

    if (httpsCode > 0) {
      // HTTP 헤더를 전송하고 그에 대한 응답을 핸들링하는 과정
      Serial.printf("[HTTP] 응답 Code : %d\n", httpsCode);

      // HTTP 응답 200, 즉 정상응답이면 서버로부터 수신된 응답을 출력한다.
      if (httpsCode == HTTP_CODE_OK) {
        const String& payload = https.getString();
        Serial.print("서버로부터 수신된 응답 : ");
        Serial.println(payload);
        Serial.println("");
      }else{
        const String& payload = https.getString();
        Serial.println("정상 응답이 아닙니다.");
        Serial.print("서버로부터 수신된 응답 : ");
        Serial.println(payload);
        Serial.println("");
      }
    } else {    // 에러발생시 에러내용을 출력한다.
      Serial.printf("[HTTP] POST 요청이 실패했습니다. 오류 : %s\n", https.errorToString(httpsCode).c_str());
    }

    https.end();
  }
}

기존 코드와 차이가 있다면 헤더파일의 추가와 BearSSL을 사용한 WiFiClientSecure의 사용이다.

그리고 fingerprint도 추가되었다.

 

fingerprint는 해당 웹서버를 브라우저에서 접속하여 확인 하거나

 

echo -n | openssl s_client -connect 1.2.3.4:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ./server.pem
openssl x509 -noout -in server.pem -fingerprint -sha1

출처: https://firstboos.tistory.com/entry/인증서의-fingerprintsha1-sha256-확인 [散策 의 정리공간]

위 명령을 사용하여 확인한다.

확인한 fingerprint를 위 소스코드의 fignerprint에 채워 넣으면 된다. 인증서가 바뀌거나 갱신되면 fingerprint도 바뀌므로

바뀐 fingerprint를 다시 적용하여 아두이노에 업로드해야 한다.

 

이런게 번거롭고 귀찮거나 하기싫다면..

fingerprint 설정 없이 진행하는 방법은 있다.... 하지만

client.setfingerprint() 대신 client.setinsecure() 를 사용하면 된다. 하지만 함수명 그대로 insecure 상태가 된다.

상관없다면 setinsecure를 사용하여 fingerprint 설정 없이 https 통신을 할 수 있다.