1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESP-WROOM-02 気象センサーの観測データをUDPパケットでブロードキャストする

Last updated at Posted at 2024-02-17

 前回 ESP-WROOM-02 DIP化キットに接続しているサーミスターの温度をアナログコンバーターで取得する方法を紹介しました。
ESP-WROOM-02 サーミスタの温度をアナログコンバーターで取得する (Qiita@pipito-yukio)
 今回は下記のようなESP気象センサーに搭載された2つのセンサーの観測データをWi-Fi経由で定期的(約10分間隔)にUDPパケットで内部ネットワークにブロードキャストするプログラムについて解説します。

ESP_sensorModuleOnly.jpg

搭載する機能

  • (1) 充電池駆動 (ニッケル水素電池 x 4: 4.8vをレギュレータで 3.3vに降圧)
  • (2) サーミスタによる外気温測定 (アナログコンバータMCP3002使用)
    ※実装方法の詳細については前回Qiita投稿記事をご覧ください。
  • (3) BME280 センサーによる室内気温・湿度・気圧の測定 (Adafruit ライブラリ使用)
  • (4) 2つのセンサーの測定値をUDPバケットとしてWi-Fi経由でブロードキャストする
  • (5) 指定時間ディープスリープする (10分 - 処理時間)

実装の元になった書籍の出典箇所

搭載する機能(4)と(5) の実装については、ほとんどが①の出典箇所からの流用になります。
ただし(5)の機能については ②の出典箇所から処理手順のヒントをいただきました。
※②ESP32の本なので使用する関数はESP02のものに置き換えしています。

  • ① 超特急Web接続! ESPマイコン・プログラム全集 [CD-ROM付き]
    2019年4月1日 第2版発行 CQ出版株式会社
    • 第4章 [2] Wi-Fiスイッチャ
      Wi-Fiネットワークの設定、UDPパケットのブロードキャスト
    • 第4章 Appendix 2 いつまでも動く無人IoTを作る ESPマイコンのケチケチ運転術
      ディープスリープによる間欠運転
  • ② IoT 開発スタートブック ESP32でクラウドにつなげる電子工作をはじめよう!
    2019年8月24日 初 版 第1刷発行 株式会社技術評論社
    • 第4章 より実用的なセンサ端末を作るー消費電力を下げバッテリーで動かしてみよう
      ディープスリープによる間欠運転

開発環境

  • PC: Ubuntu 22.04
  • ソース作成とビルド: PlatformIO IDE for VSCode
  • USBシリアルポートドライバーがインストールされていること
  • ユーザーにシリアルポートのアクセス権(dialou)が付与されていること

UDPパケット送出I/F

  • インターネットプロトコル: UDP (ボート番号: 2222)
  • 指定された内部ネットワークにブロードキャスト: 192.168.0.255
    受信するIPアドレスの機器 (192.168.0.1〜192.168.0.244)
  • 送出データ
    • 約10分間隔でパケットを送出
    • [データ形式] カンマ区切りのバイトデータ
    • [送信内容] デバイス名, 外気温, 室内気温, 室内湿度, 室内気圧
    • サーミスタ気温測定エラーの場合は、外気温に NaNを送出  

使用ライブラリ

  • BME280センサー: Adafruit BME280 Library

自作ライブラリ

  • MCP3002アナログコンバーター処理クラス

1. プロジェクト作成

[プロジェクト名] udpsrv_weather_sensors

1-0. PlatformIO プロジェクトファイル

platformio.ini
[env:esp_wroom_02]
platform = espressif8266
board = esp_wroom_02
framework = arduino
monitor_port = /dev/ttyUSB0
monitor_speed = 9600
upload_protocol = esptool
lib_deps = adafruit/Adafruit BME280 Library@^2.2.0

 PlatformIO Homeからライブラリを追加すると .pio直下にライブラリのソースとサンプルがインストールされます。

~/udpsrv_weather_sensors/.pio/libdeps/esp_wroom_02/Adafruit BME280 Library$ tree
.
├── Adafruit_BME280.cpp
├── Adafruit_BME280.h
├── LICENSE
├── README.md
├── assets
│   └── board.jpg
├── examples
│   ├── advancedsettings
│   │   └── advancedsettings.ino
│   ├── bme280_unified
│   │   └── bme280_unified.ino
│   └── bme280test
│       └── bme280test.ino
└── library.properties

BME280ライブラリを使った処理は上記サンプル(bme280test.ino)をそのまま流用します。
※サンプルのコメントを除いたコードの抜粋を以下に示します

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

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#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

unsigned long delayTime;

void setup() {
    Serial.begin(9600);
    while(!Serial);    // time to get serial running
    Serial.println(F("BME280 test"));

    unsigned status;
    
    // default settings
    status = bme.begin();  
    // You can also pass in a Wire library object like &Wire2
    // status = bme.begin(0x76, &Wire2)
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("SensorID was: 0x"); Serial.println(bme.sensorID(),16);
        Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("        ID of 0x60 represents a BME 280.\n");
        Serial.print("        ID of 0x61 represents a BME 680.\n");
        while (1) delay(10);
    }
    
    Serial.println("-- Default Test --");
    delayTime = 1000;

    Serial.println();
}


void loop() { 
    printValues();
    delay(delayTime);
}


void printValues() {
    Serial.print("Temperature = ");
    Serial.print(bme.readTemperature());
    Serial.println(" °C");

    Serial.print("Pressure = ");

    Serial.print(bme.readPressure() / 100.0F);
    Serial.println(" hPa");

    Serial.print("Approx. Altitude = ");
    Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
    Serial.println(" m");

    Serial.print("Humidity = ");
    Serial.print(bme.readHumidity());
    Serial.println(" %");

    Serial.println();
}

1-1. ヘッダー定義

main.cpp
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "SimpleMCP3002.h"

1-2. 定数定義

  • データ送出間隔: 600秒 (10分)
  • ネットワーク情報
    • Wi-FiルータのSSIDとパスワード
    • ESP気象センサーに内部ネットワークの固定アドレスを付与 (例) 192.168.0.31
    • ゲートウェイ、ネットマスク、ブロードキャストアドレス
  • ESP気象センサーのデバイス名: "esp8266_1" ※気象テーブルの主キーを構成
  • アナログコンバーター用の各種定数
  • サーミスタの温度計算用各種定数
// Loop total delay time
const int32_t SLEEP_T = 600 * 1000000; // micro second, 600 sec (10 minute)

// Wi-Fi section
const char *SSID = "mywifi-router";
const char *PASS = "0123456789abc";
const IPAddress IP_DEVICE = IPAddress(192, 168, 0, 31);
const IPAddress IP_GATEWAY = IPAddress(192, 168, 0, 1);
const IPAddress IP_NETMASK = IPAddress(255, 255, 255, 0);
// UDP section
const char *SENDTO = "192.168.0.255"; // BroadCast
const uint16_t PORT = 2222;

// DEVICE NAME
const char *DEVICE = "esp8266_1,";

// MCP3002: channel=0,1
const uint8_t ADC_SAMPLES = 10;
const uint8_t CHANNEL_THERM = 0;
const float ADC_VREF = 3.3;
// Thermister section
const float THERM_V = 3.3;
const float THERM_B = 3435.0;
const float THERM_R0 = 10000.0;
const float THERM_R1 = 10000.0; // Divide register 10k
const float THERM_READ_INVALID = -9999.0;

1-3. オブジェクト生成

  • アナログコンバーター処理オブジェクト
  • BME280センサー処理オブジェクト
SimpleMCP3002 adc(ADC_VREF);
Adafruit_BME280 bme;

1-4. デバック用のADC取得配列出力関数

※ DEBUG ビルドの場合有効になる

void debugSerialOut(ReadData (&datas)[ADC_SAMPLES]) {
  int len = sizeof(datas) / sizeof(datas[0]);
  Serial.print("ReadData: [");
  for (int i = 0; i < len; i++) {
    ReadData rd = datas[i];
    char chrbuf[5];
    Serial.print("[0x");
    sprintf(chrbuf, "%x", rd.highByte);
    Serial.print(chrbuf);
    Serial.print(",0x");
    sprintf(chrbuf, "%x", rd.lowByte);
    Serial.print(chrbuf);
    Serial.print("]");
    if (i < ADC_SAMPLES) {
      Serial.print(",");
    }
  }
  Serial.print("]");
}

1-5. ADCサンプリング関数: 10回の平均値取得

float getSamplingAdcValue(uint8_t ch, SimpleMCP3002 &mcp) {
  int adcSamples[ADC_SAMPLES];
#if defined(DEBUG)
  ReadData datas[ADC_SAMPLES];
#endif
  int i;
  Serial.print("CH<");
  Serial.print(ch);
  Serial.print(">: [");
  for (i = 0; i < ADC_SAMPLES; i++) {
    adcSamples[i] = mcp.analogRead(ch);
#if defined(DEBUG)
    datas[i] = mcp.getReadData();
#endif
    Serial.print(adcSamples[i]);
    if (i < (ADC_SAMPLES - 1)) {
      Serial.print(",");
    }
    delay(10);
  }
  Serial.print("], mean adc: ");
  uint16_t adcTotal = 0;
  for (i = 0; i < ADC_SAMPLES; i++) {
    adcTotal += adcSamples[i];
  }
  uint16_t meanAdc = round(1.0 * adcTotal / ADC_SAMPLES);
  Serial.println(meanAdc);
#if defined(DEBUG)
  debugSerialOut(datas)
#endif
  return meanAdc;
}

1-6. サーミスタ気温取得関数

float getThermTemp() {
  double rx, xa, temp;
  uint16_t adcVal = getSamplingAdcValue(CHANNEL_THERM, adc);
  float outVolt = adc.getVolt(adcVal);
  Serial.print("Therm.outVolt: ");
  Serial.print(outVolt, 3);
  Serial.println(" V ");
  if (adcVal == 0 || outVolt < 0.01) {
    Serial.println("Thermister read invalid!");
    return THERM_READ_INVALID;
  }

  rx = THERM_R1 * ((THERM_V - outVolt) / outVolt);
  xa = log(rx / THERM_R0) / THERM_B;
  temp = (1 / (xa + 0.00336)) - 273;
  Serial.print("rx: ");
  Serial.print(rx);
  Serial.print(", xa: ");
  Serial.println(xa, 6);
  return (float)temp;
}

1-7. センサー測定値取得処理とUDPパケット生成・送出関数

  • サーミスタ気温取得
    ※取得処理の詳細と計算式については前回Qiita投稿をご覧ください。
  • BME280センサー測定値取得
    ※各測定値取得は上記サンプルコードそのまま流用
  • UDPパケットの生成と送出
    • WiFi用UDPオブジェクト生成
    • ポート番号設定
    • UDPパケットの開始設定
    • UDPパケット送出I/Fに沿って各測定値をカンマ区切りで設定
    • UDPパケットの送出
void measureSensors() {
  // Measure Thermister (Outdoor temparature)
  float thermTemp = getThermTemp();
  Serial.print("Therm Temp:");
  Serial.println(thermTemp, 1);
  // Wait BME280
  delay(50);

  // Measure BME280 senser (Room)
  float bmeTemp = bme.readTemperature();
  float humid = bme.readHumidity();             // %
  float pressure = bme.readPressure() / 100.0F; // hPa
  Serial.print("BME Temp:");
  Serial.println(bmeTemp, 1);

  // UDP Broadcast
  WiFiUDP udp;
  udp.begin(PORT);
  delay(100);
  udp.beginPacket(SENDTO, PORT);
  udp.print(DEVICE);
  if (thermTemp != THERM_READ_INVALID) {
    udp.print(thermTemp, 1);
  } else {
    udp.print(NAN);
  }
  udp.print(",");
  udp.print(bmeTemp, 1);
  udp.print(",");
  udp.print(humid, 1);
  udp.print(",");
  udp.print(pressure, 1);
  udp.endPacket();
  Serial.println("UDP End Packet.");
}

1-8. セットアップ関数

 各種オブジェクトの初期化、測定値取得・UDPパケット送出、ディープスリープ迄の一連の処理を関数内で処理を完結させます。

1-8-1. 各種オブジェクトの初期化処理
  • アナログコンバーター処理オブジェクトの初期化
  • BME280センサー処理オブジェクトの初期化
    ※上記サンプルの初期化処理をそのまま流用
  • Wi-Fiネットワーク接続設定
  • アナログコンバーターの立ち上がりを待つ (50ミリ秒)
void setup() {
  unsigned long starttime = millis();
  Serial.begin(9600);
  Serial.println("Start.");

  adc.begin();
  bool status;
  status = bme.begin(0x76);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor!");
    while (1)
      ;
  }

  WiFi.mode(WIFI_STA);                            // Wi-Fi Station
  WiFi.config(IP_DEVICE, IP_GATEWAY, IP_NETMASK); //, IP_PRIMARY_DNS);
  WiFi.begin(SSID, PASS);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  Serial.println(WiFi.localIP());

  // Wait MCP3002
  delay(50);
  Serial.print("Setup complete: ");
  Serial.print((millis() - starttime) / 1000);
  Serial.println(" milli sec");
1-8-2. 測定値の送出とディープスリーブ
  • センサーから取得した測定値からUDPパケットを生成しネットワークにブロードキャスト
  • Deep Sleep モードに遷移する
    DeepSleep時間 = 10分 - (開始時間 + 処理時間)
  // Mesure Sensors
  measureSensors();
  delay(200); // UDP sending wait

  // Calc Deep sleep time
  uint64_t sleeptime = SLEEP_T - (millis() - starttime) * 1000;
  Serial.print("Deep sleep time: ");
  Serial.print(sleeptime / 1000);
  Serial.println(" milli sec");
  ESP.deepSleep(sleeptime, WAKE_RF_DEFAULT);
  while (1) {
    delay(100); // 100ms Deep sleep wait
  }
}

1-9. ループ関数は空 ※実装無し

void loop() {
}

2. ESP気象センサーの動作確認

Raspberry Pi Zero から UART接続で ESP気象センサーの動作を確認する

 下記は、センサーデータの取得と間欠運転 (Deep Sleep) が正常に動作していることを確認しているところ

2-1.シリアルモニタープログラム

python 仮想環境に pyserial ライブラリをインストールします

(py37_pigpio) $ pip install pyserial
SerialMonitor.py
import argparse
import time
# Adafruit-Blinkaをインストールするとpyserialもインストールされる
# Adafruitライブラリを入れていない場合は単独でインストールする (pip install pyserial)
import serial
from log import logsetting

"""
ESP気象測定モジュールのシリアル出力をモニタする
【条件】
raspi-config
 (1)シリアル通信: 有効
 (2)シリアルコンソール: 無効
【接続】 UART (Universal Asynchronous Receiver Transmitter)
[ラズパイ側]       [ESP-WROOM-02]
             pin          pin
 GPIO14(TxD) 10 <--> RXD  11
 GPIO15(RxD) 12 <--> TXD  12
【シリアルデバイス】
 OK: serial0: /dev/ttyAMA0  # Blutooth
(NG: serial1: /dev/ttyS0)
"""

logger = logsetting.create_logger("main_app")

SERIAL_DEV = "/dev/ttyAMA0"
DEFAULT_BAUD_RATE = 9600 # ESP側のレートに合わせる


def monitor(ser):
    while 1:
        line = ser.readline()        # bytes
        line = line.decode('ascii', errors='ignore')
        line = line.replace("\r\n", "")
        line.rstrip()
        logger.debug(line)
        time.sleep(1.0)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--baudrate", type=int, default=9600, choices=[9600, 115200], help="Baudrate (bps).")
    args = parser.parse_args()
    logger.info(args)

    ser = serial.Serial(SERIAL_DEV, args.baudrate) #, timeout=None)
    try:
        monitor(ser)
    except KeyboardInterrupt:
        ser.close()

こちらは ESP気象センサーのリリース時にラズパイで採取したログ出力 ※2021年09月12日

2021-09-12 16:24:46 INFO SerialMonitor.py(44)[<module>] Namespace(baudrate=9600)
2021-09-12 16:25:10 DEBUG SerialMonitor.py(36)[monitor] @i4dhdStart.
2021-09-12 16:25:14 DEBUG SerialMonitor.py(36)[monitor] 192.168.0.31
2021-09-12 16:25:15 DEBUG SerialMonitor.py(36)[monitor] Setup complete: 4 milli sec
2021-09-12 16:25:16 DEBUG SerialMonitor.py(36)[monitor] CH<0>: [512,512,512,513,512,513,513,512,512,513], mean adc: 512
2021-09-12 16:25:17 DEBUG SerialMonitor.py(36)[monitor] Therm.outVolt: 1.652 V 
2021-09-12 16:25:18 DEBUG SerialMonitor.py(36)[monitor] rx: 9980.47, xa: -0.000001
2021-09-12 16:25:19 DEBUG SerialMonitor.py(36)[monitor] Therm Temp:25.4
2021-09-12 16:25:20 DEBUG SerialMonitor.py(36)[monitor] BME Temp:25.9
2021-09-12 16:25:21 DEBUG SerialMonitor.py(36)[monitor] UDP End Packet.
2021-09-12 16:25:22 DEBUG SerialMonitor.py(36)[monitor] Deep sleep time: 595497 milli sec
2021-09-12 16:34:54 DEBUG SerialMonitor.py(36)[monitor] @ihIhdhStart.
2021-09-12 16:34:58 DEBUG SerialMonitor.py(36)[monitor] 192.168.0.31
2021-09-12 16:34:59 DEBUG SerialMonitor.py(36)[monitor] Setup complete: 4 milli sec
2021-09-12 16:35:00 DEBUG SerialMonitor.py(36)[monitor] CH<0>: [449,449,449,449,447,448,447,448,449,449], mean adc: 448
2021-09-12 16:35:01 DEBUG SerialMonitor.py(36)[monitor] Therm.outVolt: 1.445 V 
2021-09-12 16:35:02 DEBUG SerialMonitor.py(36)[monitor] rx: 12834.82, xa: 0.000073
2021-09-12 16:35:03 DEBUG SerialMonitor.py(36)[monitor] Therm Temp:19.0
2021-09-12 16:35:04 DEBUG SerialMonitor.py(36)[monitor] BME Temp:26.0
2021-09-12 16:35:05 DEBUG SerialMonitor.py(36)[monitor] UDP End Packet.
2021-09-12 16:35:06 DEBUG SerialMonitor.py(36)[monitor] Deep sleep time: 595494 milli sec

リリース後のメンテナンス状況

バッテリー駆動の連続稼働日数 (充電式EVOLTA4本)

  • 初回(2021年9月から): 約45日間動作
    ※試験運用なので電池切れでパケットがこなくなるまで稼働させた
    ※UDPパケットが15分以上遅延するとラズパイ側のシステムがGmailで遅延報告メールを送信するようにしている。
  • 2022年01月から本番運用開始
    ※2022年から2023年は約40日で交換 (2年間で約 22回交換)
  • 2024年02月現在: 35日で交換

本番運用時のトラブル対応

ラズパイからUART経由で ESP気象センサーに接続しシリアルログを確認する
Wi-Fiが原因のトラブルがありうるので UART経由で接続できるようにしておくことが重要

本番運用開始からのトラブル

  • 電池を交換してから数日しか立っていないのにUDPパケットが届かない
    ラズパイからUART接続しシリアルログを確認したところ正常に動作していた
    [原因] Wi-Fiルータの障害 ※ルータの再起動で解決

今回紹介した ESP気象センサーのソースコードは下記GitHubで公開しています。
GitHub pipito-yuko: home_weather_sensors/esp_wroom_02

platformio.ini
src/
  SimpleMCP3002.cpp
  SimpleMCP3002.h
  main.cpp
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?