ESP32

DIY Cloud Weather Station with ESP32/ESP8266 (MySQL Database and PHP)

기하 2021. 8. 16. 16:12

클라우드 기상 관측소 대시보드를 구축하여

전 세계 어디에서나 ESP32 또는 ESP8266 센서 판독값을 시각화합니다. 

게이지와 테이블에 표시된 센서 데이터를 볼 수 있습니다. 

ESP32 또는 ESP8266은 데이터를 MySQL 데이터베이스에 삽입하기 위해

PHP 스크립트에 HTTP POST 요청을 보냅니다.

이전에는 센서 판독값을 데이터베이스에 저장하고 

자신의 서버를 사용하여 어디에서나 액세스할 수 있는 테이블 이나 차트 에 표시했습니다 . 

이제 몇 단계 더 나아가 웹 페이지에 정보를 추가하기로 결정했습니다.

 

최신 온도 및 습도 판독값과 정의할 수 있는 판독값의 양에서

최소, 최대 및 평균 판독값에 대한 일부 통계를 표시하기 위해

두 개의 게이지를 추가했습니다. 

 

또한 테이블에 모든 최신 판독값을 시각화하고 표시할 판독값 수를 선택할 수 있습니다.

이 프로젝트를 빌드하려면 다음 기술을 사용합니다.

  • Arduino IDE로 프로그래밍된 ESP32 또는 ESP8266
  • 호스팅 서버 및 도메인 이름
  • MySQL에 데이터를 삽입하고 웹 페이지에 표시하는 PHP 스크립트
  • 판독값을 저장할 MySQL 데이터베이스

 

0. 소스코드 다운로드

이 프로젝트에는 다음 파일이 필요합니다.

1. PHP 애플리케이션 및 MySQL 데이터베이스 호스팅

이 프로젝트의 목표는 ESP32 또는 ESP8266의 센서 판독값을 저장할 수 있는

고유한 도메인 이름과 호스팅 계정을 갖는 것입니다. 

자신의 서버 도메인에 액세스하여 전 세계 어디에서나 판독값을 시각화할 수 있습니다.

다음은 프로젝트 작동 방식에 대한 높은 수준의 개요입니다.

 

  1. 센서 판독값을 자체 서버로 보내는 ESP32 또는 ESP8266이 있습니다. 
    이를 위해 보드가 라우터에 연결되어 있습니다.
  2. 서버에는 MySQL 데이터베이스에 판독값을 저장할 수 있는 PHP 스크립트가 있습니다.
  3. 그런 다음 다른 PHP 스크립트가 게이지, 테이블 및 기타 모든 정보가 포함된 웹 페이지를 표시합니다.
  4. 마지막으로 자신의 도메인 이름에 액세스하여 전 세계 어디에서나 판독값을 시각화할 수 있습니다.

참고 : 당신은 또한 실행할 수있는 LAMP (리눅스, 아파치, MySQL은, PHP) 서버 

라즈베리 파이에 로컬 네트워크에서 데이터에 액세스 . 
그러나 이 자습서의 목적은 전 세계 어디에서나 액세스할 수 있는
자체 도메인 이름으로 판독값을 게시하는 것입니다. 
이를 통해 타사 IoT 플랫폼에 의존하지 않고도 ESP 판독값에 쉽게 액세스할 수 있습니다.

2. MySQL 데이터베이스 준비

호스팅 계정에 가입하고 도메인 이름을 설정 한 후 cPanel 또는 유사한 대시보드에 로그인할 수 있습니다. 그런 다음 다음 단계에 따라 데이터베이스, 사용자 이름, 암호 및 SQL 테이블을 만듭니다.

데이터베이스 및 사용자 생성

1. 검색창에 "database"를 입력하고 "MySQL Database Wizard"를 선택합니다.

2. 원하는 데이터베이스 이름을 입력합니다. 

제 경우 데이터베이스 이름은esp_data. 그런 다음 "다음 단계" 버튼을 누릅니다.

참고: 나중에 호스트가 제공하는 접두사와 함께 데이터베이스 이름을 사용해야 합니다

(위 스크린샷에서 내 데이터베이스 접두사는 흐릿함).

나는 그것을 다음과 같이 언급 할 것이다.example_esp_data 지금부터.

3. 데이터베이스 사용자 이름을 입력하고 암호를 설정합니다. 

나중에 PHP 코드와 데이터베이스 연결을 설정하는 데 필요하기 때문에 이러한 세부 정보를 모두 저장해야 합니다.

 

새 데이터베이스와 사용자가 성공적으로 생성되었습니다. 나중에 필요하므로 이제 모든 세부 정보를 저장하십시오.

  • 데이터베이스 이름 : example_esp_data
  • 사용자 이름 : example_esp_board
  • 비밀번호 : 귀하의 비밀번호

SQL 테이블 생성

데이터베이스와 사용자를 생성한 후 cPanel 대시보드로 돌아가 "phpMyAdmin"을 검색합니다.

왼쪽 사이드바에서 데이터베이스 이름을 선택합니다. example_esp_data "SQL" 탭을 엽니다.

 

 

 

중요: 열었는지 확인하십시오.example_esp_data데이터 베이스. 그런 다음 SQL 탭을 클릭합니다. 
이 정확한 단계를 따르지 않고 SQL 쿼리를 실행하지 않으면
잘못된 데이터베이스에 테이블을 생성할 수 있습니다.

 

다음 스니펫에서 SQL 쿼리를 복사합니다.

CREATE TABLE SensorData (
    id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sensor VARCHAR(30) NOT NULL,
    location VARCHAR(30) NOT NULL,
    value1 VARCHAR(10),
    value2 VARCHAR(10),
    value3 VARCHAR(10),
    reading_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)

 

SQL 쿼리 필드(빨간색 사각형으로 강조 표시됨)에 붙여넣고 "이동" 버튼을 눌러 테이블을 만듭니다.

 

 

그런 다음 example_esp_data 안에 새로 생성된 SensorData테이블이 표시되어야 합니다.  

 

3. PHP 스크립트 HTTP POST
– MySQL 데이터베이스에서 데이터 수신 및 삽입

이 섹션에서는 ESP32 또는 ESP8266에서 들어오는 요청을 수신하고
데이터를 MySQL 데이터베이스에 삽입하는 역할을 하는 PHP 스크립트를 만들 것입니다.

cPanel과 함께 호스팅 제공업체를 사용하는 경우 "파일 관리자"를 검색할 수 있습니다.

 

그런 다음 public_html 옵션을 선택하고 "+ 파일" 버튼을 눌러 새 .php 파일 을 만듭니다 .

 

 

참고: 이 자습서를 따르고 있고 PHP 또는 MySQL에 익숙하지 않은 경우
이러한 정확한 파일을 만드는 것이 좋습니다. 
그렇지 않으면 다른 URL 경로와 함께 제공된 ESP 스케치를 수정해야 합니다.

다음 과 같은 정확한 이름과 확장자를 사용하여  esp-post-data.php을

/public_html에 만듭니다 .

 

새로 생성된 파일( esp-post-data.php )을 편집하고 다음 스니펫을 복사합니다.

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
  include_once('esp-database.php');

  // Keep this API Key value to be compatible with the ESP code provided in the project page. If you change this value, the ESP sketch needs to match
  $api_key_value = "tPmAT5Ab3j7F9";

  $api_key= $sensor = $location = $value1 = $value2 = $value3 = "";

  if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $api_key = test_input($_POST["api_key"]);
    if($api_key == $api_key_value) {
      $sensor = test_input($_POST["sensor"]);
      $location = test_input($_POST["location"]);
      $value1 = test_input($_POST["value1"]);
      $value2 = test_input($_POST["value2"]);
      $value3 = test_input($_POST["value3"]);

      $result = insertReading($sensor, $location, $value1, $value2, $value3);
      echo $result;
    }
    else {
      echo "Wrong API Key provided.";
    }
  }
  else {
    echo "No data posted with HTTP POST.";
  }

  function test_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data);
    return $data;
  }

 

4. 데이터베이스 기능을 위한 PHP 스크립트

/public_html 에 데이터베이스의 데이터 삽입 및 액세스를 담당 하는 새 파일을 만듭니다 . 
파일 이름: esp-database.php

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
  $servername = "localhost";

  // REPLACE with your Database name
  $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME";
  // REPLACE with Database user
  $username = "REPLACE_WITH_YOUR_USERNAME";
  // REPLACE with Database user password
  $password = "REPLACE_WITH_YOUR_PASSWORD";

  function insertReading($sensor, $location, $value1, $value2, $value3) {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "INSERT INTO SensorData (sensor, location, value1, value2, value3)
    VALUES ('" . $sensor . "', '" . $location . "', '" . $value1 . "', '" . $value2 . "', '" . $value3 . "')";

    if ($conn->query($sql) === TRUE) {
      return "New record created successfully";
    }
    else {
      return "Error: " . $sql . "<br>" . $conn->error;
    }
    $conn->close();
  }
  
  function getAllReadings($limit) {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit " . $limit;
    if ($result = $conn->query($sql)) {
      return $result;
    }
    else {
      return false;
    }
    $conn->close();
  }
  function getLastReadings() {
    global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit 1" ;
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function minReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT MIN(" . $value . ") AS min_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS min";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function maxReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT MAX(" . $value . ") AS max_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS max";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }

  function avgReading($limit, $value) {
     global $servername, $username, $password, $dbname;

    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
      die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT AVG(" . $value . ") AS avg_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS avg";
    if ($result = $conn->query($sql)) {
      return $result->fetch_assoc();
    }
    else {
      return false;
    }
    $conn->close();
  }
?>

 

파일을 저장하기 전에 수정해야 합니다. $dbname, $username  그리고 $password 

// Your Database name
$dbname = "example_esp_data";
// Your Database user
$username = "example_esp_board";
// Your Database user password
$password = "YOUR_USER_PASSWORD";

 

데이터베이스 이름, 사용자 이름 및 암호를 추가한 후

파일을 저장하고 이 자습서를 계속합니다. 

다음 URL 경로에서 도메인 이름에 액세스하려고 하면 다음이 표시됩니다.

http://example.com/esp-post-data.php

 

5. PHP 스크립트 – 게이지 및 테이블에 데이터베이스 판독값 표시

대시보드의 스타일을 지정하려면 esp-style.css 라는 이름을 지정하기 위해 CSS 파일도 추가해야 합니다 .

해당 CSS를 파일에 복사하고 저장합니다.

/**
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
**/
body {
    width: 60%;
    margin: auto;
    text-align: center;
    font-family: Arial;
    top: 50%;
    left: 50%;
}

@media screen and (max-width: 800px) {
    body {
        width: 100%;
    }
}

table {
    margin-left: auto;
    margin-right: auto;
}

div {
    margin-left: auto;
    margin-right: auto;
}

h2 { font-size: 2.5rem; }

.header {
	 padding: 1rem;
	 margin: 0 0 2rem 0;
	 background: #f2f2f2;
}

h1 {
    font-size: 2rem;
    font-family: Arial, sans-serif;
    text-align: center;
    text-transform: uppercase;
}

.content {
    display: flex;
}

@media screen and (max-width: 500px) /* Mobile */ {
    .content {
        flex-direction: column;
    }
}

.mask {
    position: relative;
    overflow: hidden;
    display: block;
    width: 12.5rem;
    height: 6.25rem;
    margin: 1.25rem;
}

.semi-circle {
    position: relative;
    display: block;
    width: 12.5rem;
    height: 6.25rem;
    background: linear-gradient(to right, #3498db 0%, #05b027 33%, #f1c40f 70%, #c0392b 100%);
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.semi-circle::before {
    content: "";
    position: absolute;
    bottom: 0;
    left: 50%;
    z-index: 2;
    display: block;
    width: 8.75rem;
    height: 4.375rem;
    margin-left: -4.375rem;
    background: #fff;
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.semi-circle--mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 12.5rem;
    height: 12.5rem;
    background: transparent;
    transform: rotate(120deg) translate3d(0, 0, 0);
    transform-origin: center center;
    backface-visibility: hidden;
    transition: all 0.3s ease-in-out;
}

.semi-circle--mask::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0%;
    z-index: 2;
    display: block;
    width: 12.625rem;
    height: 6.375rem;
    margin: -1px 0 0 -1px;
    background: #f2f2f2;
    border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%;
}

.gauge--2 .semi-circle { background: #3498db; }

.gauge--2 .semi-circle--mask { transform: rotate(20deg) translate3d(0, 0, 0); }

#tableReadings { border-collapse: collapse; }

#tableReadings td, #tableReadings th {
    border: 1px solid #ddd;
    padding: 10px;
}

#tableReadings tr:nth-child(even){ background-color: #f2f2f2; }

#tableReadings tr:hover { background-color: #ddd; }

#tableReadings th {
    padding: 10px;
    background-color: #2f4468;
    color: white;
}

마지막으로 /public_html 디렉토리에 웹 페이지의 모든 데이터베이스 콘텐츠를 표시할 

또 다른 PHP 파일을 만듭니다 . 새 파일 이름: esp-weather-station.php

 

새로 생성된 파일( esp-weather-station.php )을 편집하고 다음 코드를 복사합니다.

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
-->
<?php
    include_once('esp-database.php');
    if ($_GET["readingsCount"]){
      $data = $_GET["readingsCount"];
      $data = trim($data);
      $data = stripslashes($data);
      $data = htmlspecialchars($data);
      $readings_count = $_GET["readingsCount"];
    }
    // default readings count set to 20
    else {
      $readings_count = 20;
    }

    $last_reading = getLastReadings();
    $last_reading_temp = $last_reading["value1"];
    $last_reading_humi = $last_reading["value2"];
    $last_reading_time = $last_reading["reading_time"];

    // Uncomment to set timezone to - 1 hour (you can change 1 to any number)
    //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time - 1 hours"));
    // Uncomment to set timezone to + 7 hours (you can change 7 to any number)
    //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time + 7 hours"));

    $min_temp = minReading($readings_count, 'value1');
    $max_temp = maxReading($readings_count, 'value1');
    $avg_temp = avgReading($readings_count, 'value1');

    $min_humi = minReading($readings_count, 'value2');
    $max_humi = maxReading($readings_count, 'value2');
    $avg_humi = avgReading($readings_count, 'value2');
?>

<!DOCTYPE html>
<html>
    <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">

        <link rel="stylesheet" type="text/css" href="esp-style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    </head>
    <header class="header">
        <h1> ESP Weather Station</h1>
        <form method="get">
            <input type="number" name="readingsCount" min="1" placeholder="Number of readings (<?php echo $readings_count; ?>)">
            <input type="submit" value="UPDATE">
        </form>
    </header>
<body>
    <p>Last reading: <?php echo $last_reading_time; ?></p>
    <section class="content">
	    <div class="box gauge--1">
	    <h3>TEMPERATURE</h3>
              <div class="mask">
			  <div class="semi-circle"></div>
			  <div class="semi-circle--mask"></div>
			</div>
		    <p style="font-size: 30px;" id="temp">--</p>
		    <table cellspacing="5" cellpadding="5">
		        <tr>
		            <th colspan="3">Temperature <?php echo $readings_count; ?> readings</th>
	            </tr>
		        <tr>
		            <td>Min</td>
                    <td>Max</td>
                    <td>Average</td>
                </tr>
                <tr>
                    <td><?php echo $min_temp['min_amount']; ?> &deg;C</td>
                    <td><?php echo $max_temp['max_amount']; ?> &deg;C</td>
                    <td><?php echo round($avg_temp['avg_amount'], 2); ?> &deg;C</td>
                </tr>
            </table>
        </div>
        <div class="box gauge--2">
            <h3>HUMIDITY</h3>
            <div class="mask">
                <div class="semi-circle"></div>
                <div class="semi-circle--mask"></div>
            </div>
            <p style="font-size: 30px;" id="humi">--</p>
            <table cellspacing="5" cellpadding="5">
                <tr>
                    <th colspan="3">Humidity <?php echo $readings_count; ?> readings</th>
                </tr>
                <tr>
                    <td>Min</td>
                    <td>Max</td>
                    <td>Average</td>
                </tr>
                <tr>
                    <td><?php echo $min_humi['min_amount']; ?> %</td>
                    <td><?php echo $max_humi['max_amount']; ?> %</td>
                    <td><?php echo round($avg_humi['avg_amount'], 2); ?> %</td>
                </tr>
            </table>
        </div>
    </section>
<?php
    echo   '<h2> View Latest ' . $readings_count . ' Readings</h2>
            <table cellspacing="5" cellpadding="5" id="tableReadings">
                <tr>
                    <th>ID</th>
                    <th>Sensor</th>
                    <th>Location</th>
                    <th>Value 1</th>
                    <th>Value 2</th>
                    <th>Value 3</th>
                    <th>Timestamp</th>
                </tr>';

    $result = getAllReadings($readings_count);
        if ($result) {
        while ($row = $result->fetch_assoc()) {
            $row_id = $row["id"];
            $row_sensor = $row["sensor"];
            $row_location = $row["location"];
            $row_value1 = $row["value1"];
            $row_value2 = $row["value2"];
            $row_value3 = $row["value3"];
            $row_reading_time = $row["reading_time"];
            // Uncomment to set timezone to - 1 hour (you can change 1 to any number)
            //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time - 1 hours"));
            // Uncomment to set timezone to + 7 hours (you can change 7 to any number)
            //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time + 7 hours"));

            echo '<tr>
                    <td>' . $row_id . '</td>
                    <td>' . $row_sensor . '</td>
                    <td>' . $row_location . '</td>
                    <td>' . $row_value1 . '</td>
                    <td>' . $row_value2 . '</td>
                    <td>' . $row_value3 . '</td>
                    <td>' . $row_reading_time . '</td>
                  </tr>';
        }
        echo '</table>';
        $result->free();
    }
?>

<script>
    var value1 = <?php echo $last_reading_temp; ?>;
    var value2 = <?php echo $last_reading_humi; ?>;
    setTemperature(value1);
    setHumidity(value2);

    function setTemperature(curVal){
    	//set range for Temperature in Celsius -5 Celsius to 38 Celsius
    	var minTemp = -5.0;
    	var maxTemp = 38.0;
        //set range for Temperature in Fahrenheit 23 Fahrenheit to 100 Fahrenheit
    	//var minTemp = 23;
    	//var maxTemp = 100;

    	var newVal = scaleValue(curVal, [minTemp, maxTemp], [0, 180]);
    	$('.gauge--1 .semi-circle--mask').attr({
    		style: '-webkit-transform: rotate(' + newVal + 'deg);' +
    		'-moz-transform: rotate(' + newVal + 'deg);' +
    		'transform: rotate(' + newVal + 'deg);'
    	});
    	$("#temp").text(curVal + ' ºC');
    }

    function setHumidity(curVal){
    	//set range for Humidity percentage 0 % to 100 %
    	var minHumi = 0;
    	var maxHumi = 100;

    	var newVal = scaleValue(curVal, [minHumi, maxHumi], [0, 180]);
    	$('.gauge--2 .semi-circle--mask').attr({
    		style: '-webkit-transform: rotate(' + newVal + 'deg);' +
    		'-moz-transform: rotate(' + newVal + 'deg);' +
    		'transform: rotate(' + newVal + 'deg);'
    	});
    	$("#humi").text(curVal + ' %');
    }

    function scaleValue(value, from, to) {
        var scale = (to[1] - to[0]) / (from[1] - from[0]);
        var capped = Math.min(from[1], Math.max(from[0], value)) - from[0];
        return ~~(capped * scale + to[0]);
    }
</script>
</body>
</html>

 

다음 URL 경로에서 도메인 이름에 액세스하려고 하면 다음이 표시됩니다.

http://example.com/esp-weather-station.php

 

브라우저에 빈 값이 있는 웹 페이지가 표시되면 모든 것이 준비되었음을 의미합니다. 

다음 섹션에서는 ESP32 또는 ESP8266의 데이터를 데이터베이스에 삽입하는 방법을 배웁니다.

 

6. ESP32 또는 ESP8266 준비

이 프로젝트는 ESP32 및 ESP8266 보드와 모두 호환됩니다. 

간단한 회로를 조립하고 제공된 스케치를 업로드하여 온도, 습도, 압력 등을

10분마다 데이터베이스에 업로드하기만 하면 됩니다.

 

 

필요한 부품

이 예에서는 BME280 센서에서 센서 판독값을 얻습니다. 

다음은 이 프로젝트의 회로를 구축하는 데 필요한 부품 목록입니다.

회로도

우리가 사용하는 BME280 센서 모듈은 I2C 통신 프로토콜을 통해 통신하므로

ESP32 또는 ESP8266 I2C 핀에 연결해야 합니다.

 

BME280 wiring to ESP32

ESP32 I2C 핀은 다음과 같습니다.

  • GPIO 22:  SCL(SCK)
  • GPIO 21:  SDA(SDI)

따라서 다음 회로도( BME280  ESP32 BME280 웹 서버가 있는 ESP32 가이드)에 
표시된 대로 회로를 조립합니다 .

 

 

 

ESP8266에 BME280 배선

ESP8266 I2C 핀은 다음과 같습니다.

  • GPIO 5  (D1): SCL(SCK)
  • GPIO 4  (D2): SDA(SDI)

ESP8266 보드를 사용하는 경우 다음 회로도와 같이 회로를 조립합니다( BME280이 포함된 ESP8266 가이드 참조 ).

 

 

ESP32/ESP8266 코드

Arduino IDE를 사용하여 ESP32/ESP8266을 프로그래밍할 것이므로

Arduino IDE에 ESP 애드온이 설치되어 있어야 합니다.

사용 중인 보드에 따라 다음 자습서 중 하나를 따르세요.

필요한 보드 애드온 및 라이브러리를 설치한 후 다음

코드를 Arduino IDE에 복사하되 아직 업로드하지 마십시오. 

작동하려면 몇 가지를 변경해야 합니다.

/*
  Rui Santos
  Complete project details at Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

*/

#ifdef ESP32
  #include <WiFi.h>
  #include <HTTPClient.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESP8266HTTPClient.h>
  #include <WiFiClient.h>
#endif

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

//Your Domain name with URL path or IP address with path
const char* serverName = "http://example.com/esp-post-data.php";

// Keep this API Key value to be compatible with the PHP code provided in the project page.
// If you change the apiKeyValue value, the PHP file /esp-post-data.php also needs to have the same key
String apiKeyValue = "tPmAT5Ab3j7F9";
String sensorName = "BME280";
String sensorLocation = "Office";

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;  // I2C
//Adafruit_BME280 bme(BME_CS);  // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);  // software SPI

// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastTime = 0;
// Timer set to 10 minutes (600000)
//unsigned long timerDelay = 600000;
// Set timer to 30 seconds (30000)
unsigned long timerDelay = 30000;

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  Serial.println("Connecting");
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());

  // (you can also pass in a Wire library object like &Wire2)
  bool status = bme.begin(0x76);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!");
    while (1);
  }
  
  Serial.println("Timer set to 30 seconds (timerDelay variable), it will take 30 seconds before publishing the first reading.");
}

void loop() {
  //Send an HTTP POST request every 10 minutes
  if ((millis() - lastTime) > timerDelay) {
    //Check WiFi connection status
    if(WiFi.status()== WL_CONNECTED){
      WiFiClient client;
      HTTPClient http;

      // Your Domain name with URL path or IP address with path
      http.begin(client, serverName);

      // Specify content-type header
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");

      // Prepare your HTTP POST request data
      String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName
                            + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature())
                            + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + "";
      Serial.print("httpRequestData: ");
      Serial.println(httpRequestData);

      // You can comment the httpRequestData variable above
      // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor)
      //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14";

      // Send HTTP POST request
      int httpResponseCode = http.POST(httpRequestData);

      // If you need an HTTP request with a content type: text/plain
      //http.addHeader("Content-Type", "text/plain");
      //int httpResponseCode = http.POST("Hello, World!");

      // If you need an HTTP request with a content type: application/json, use the following:
      //http.addHeader("Content-Type", "application/json");
      //int httpResponseCode = http.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}");

      if (httpResponseCode>0) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
      }
      else {
        Serial.print("Error code: ");
        Serial.println(httpResponseCode);
      }
      // Free resources
      http.end();
    }
    else {
      Serial.println("WiFi Disconnected");
    }
    lastTime = millis();
  }
}

 

네트워크 자격 증명 설정

네트워크 자격 증명으로 다음 줄을 수정해야 합니다.
SSID 및 암호. 코드는 어디를 변경해야 하는지 잘 설명되어 있습니다.

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

 

serverName 설정

ESP가 판독값을 자체 서버에 게시할 수 있도록 도메인 이름도 입력해야 합니다.

const char* serverName = "http://example.com/esp-post-data.php";

이제 보드에 코드를 업로드할 수 있습니다. 

ESP32 또는 ESP8266 보드 모두에서 바로 작동해야 합니다. 

코드가 어떻게 작동하는지 알고 싶다면 다음 섹션을 읽어보세요.

 

코드 작동 방식

이 프로젝트는 이미 꽤 길기 때문에

코드가 어떻게 작동하는지 자세히 다루지는 않겠지만 다음은 간단한 요약입니다.

  • 작동하도록 모든 라이브러리 가져오기
    (Arduino IDE에서 선택한 보드를 기반으로 ESP32 또는 ESP8266 라이브러리를 가져옴)
  • 변경하려는 변수를 설정합니다(apiKeyValue, sensorName, sensorLocation)
  • apiKeyValue 수정할 수 있는 임의의 문자열일 뿐입니다. 
    보안상의 이유로 사용되므로 API 키를 아는 사람만 데이터베이스에 데이터를 게시할 수 있습니다.
  • 디버깅 목적으로 직렬 통신 초기화
  • 라우터와 Wi-Fi 연결 설정
  • 판독값을 얻으려면 BME280을 초기화하십시오.

그런 다음, loop()  실제로 최신 BME280 판독값으로 10분마다 HTTP POST 요청을 하는 곳입니다.

/ Your Domain name with URL path or IP address with path
http.begin(serverName);

// Specify content-type header
http.addHeader("Content-Type", "application/x-www-form-urlencoded");

// Prepare your HTTP POST request data
String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName                      + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature())                      + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + "";

int httpResponseCode = http.POST(httpRequestData);

 

댓글을 달 수 있습니다.httpRequestData 모든 BME280 판독값을 연결하고 사용하는

위의 변수 httpRequestData 테스트 목적으로 아래 변수:

String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14";

데모

모든 단계를 완료한 후 ESP 보드가 일부 판독값을 수집하여 서버에 게시하도록 합니다.

 

모든 것이 정확하면 Arduino IDE 직렬 모니터에 다음과 같이 표시됩니다.

 

이 URL 경로에서 도메인 이름을 여는 경우:

http://example.com/esp-weather-station.php

데이터베이스에 저장된 최근 20개의 판독값이 표시되어야 합니다. 최신 온도 및 습도 판독값과 타임스탬프를 표시하는 두 개의 게이지가 있습니다.

웹 페이지를 새로고침하여 최신 판독값을 확인하십시오.

 

시각화할 판독값 수와 이러한 통계에 대한 판독값 수(최소, 최대 및 평균)를

입력할 수 있는 필드가 있습니다. 기본적으로 20으로 설정되어 있습니다.

예를 들어 30을 입력하고 업데이트 버튼을 누르면 웹 페이지가 모든 값을 업데이트하고

다시 계산하는 것을 볼 수 있습니다.

웹 페이지는 또한 모바일 반응형이므로 모든 장치를 사용하여 다음 항목에 액세스할 수 있습니다.

 

phpMyAdmin으로 이동하여 컴퓨터에 저장된 데이터를 관리할 수도 있습니다.

SensorData테이블. 삭제, 수정 등을 할 수 있습니다.

마무리

이 자습서에서는 전 세계 어디에서나 액세스할 수 있는

자체 서버 도메인의 데이터베이스에 센서 데이터를 게시하는 방법을 배웠습니다. 

이를 위해서는 자체 서버와 도메인 이름 이 필요합니다

 (또는 로컬 액세스를 위해 Raspberry Pi LAMP 서버를 사용할 수 있음 ).

 

웹 페이지 모양을 변경하고, 더 많은 기능( 예: 이메일 알림 )을 추가하고 ,

다른 센서의 데이터를 게시하고, 여러 ESP 보드를 사용하는 등의 작업을 수행하는 것이 좋습니다.

 

[출처번역인용] https://randomnerdtutorials.com/cloud-weather-station-esp32-esp8266/