M5Core2 + ENV II UNITで取得した値をPrometheusに送信する

ENV II UNITで取得した温度・湿度・気圧のデータをPrometheusに送信する方法を紹介します。

💡Prometheusサーバーに送信する方法(PUSH型)とPrometheusサーバーから取得する(PULL型)の2つの方法を紹介します。

目次

環境

  • Platform IO 6.1.3

ENV II UNITの値の取得

まずはENV II UNITから値を取得する方法を解説します。

必要なライブラリー

  • m5stack/M5Unit-ENV
  • adafruit/Adafruit Unified Sensor
  • adafruit/Adafruit BMP280 Library
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
lib_deps = 
	m5stack/M5Core2
	HttpClient
	WiFi
	
	adafruit/Adafruit Unified [email protected]^1.1.6
	adafruit/Adafruit BMP280 [email protected]^2.6.3
	m5stack/[email protected]^0.0.5

monitor_speed = 115200

プログラムの解説

  • include宣言
#include "M5_ENV.h"
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
  • 初期化
SHT3X sht30;
Adafruit_BMP280 bme;

float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;
  • bmeライブラリーはbeginが必要ですので、setup()内で以下を実行
while (!bme.begin(0x76))
{
}
  • センサー値の取得。気圧はPaで取得できるので、100分の1してhPaに変換しています。
pressure = bme.readPressure() / 100;

sht30.get();
tmp = sht30.cTemp;
hum = sht30.humidity;

ENV II UNITからの値取得は以上です。

💡サンプルコードはこちらです。

Prometheus remote write endpointへ送信する方法(サンプルプログラムの確認)

Grafana Labs製のライブラリーを使用します。(こちら

必要なライブラリー

  • grafana/PrometheusArduino
  • grafana/PromLokiTransport
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
lib_deps = 
	m5stack/[email protected]
	[email protected]
	[email protected]

	grafana/[email protected]
	grafana/[email protected]

monitor_speed = 115200

プログラムの解説

サンプルがありますので、これをそのまま使います。

  • include

config.hにはWi-FiやPrometheusの送信先の値を設定します。

#include "config.h"
#include <PromLokiTransport.h>
#include <PrometheusArduino.h>
  • 初期化

サンプルでは、TimeSeriesとして、uptime_milliseconds_totalheap_free_bytesを送信します。2つの値を送信するので、WriteRequest reqの引数は2を指定しています。

PromLokiTransport transport;
PromClient client(transport);

// Create a write request for 2 series.
WriteRequest req(2);

// Define a TimeSeries which can hold up to 5 samples, has a name of `uptime_milliseconds`
TimeSeries ts1(5, "uptime_milliseconds_total", "{job=\"esp32-test\",host=\"esp32\"}");

// Define a TimeSeries which can hold up to 5 samples, has a name of `heap_free_bytes`
TimeSeries ts2(5, "heap_free_bytes", "{job=\"esp32-test\",host=\"esp32\",foo=\"bar\"}");
  • setup処理

Wi-Fi接続や、Prometheus送信先の設定を行います。

// Configure and start the transport layer
transport.setWifiSsid(WIFI_SSID);
transport.setWifiPass(WIFI_PASSWORD);
transport.setDebug(Serial);  // Remove this line to disable debug logging of the client.
if (!transport.begin()) {
    Serial.println(transport.errmsg);
    while (true) {};
}

// Configure the client
client.setUrl(URL);
client.setPath((char*)PATH);
client.setPort(PORT);
client.setDebug(Serial);  // Remove this line to disable debug logging of the client.
if (!client.begin()) {
    Serial.println(client.errmsg);
    while (true) {};
}

// Add our TimeSeries to the WriteRequest
req.addTimeSeries(ts1);
req.addTimeSeries(ts2);
req.setDebug(Serial);  // Remove this line to disable debug logging of the write request serialization and compression.
  • 送信処理

送信処理はバッチ送信する形のサンプルとなっており、5件データを一時蓄積した後、まとめて送信して言います。

if (!ts1.addSample(time, millis())) {
    Serial.println(ts1.errmsg);
}
if (!ts2.addSample(time, freeMemory())) {
    Serial.println(ts2.errmsg);
}
loopCounter++;

if (loopCounter >= 4) {
    // Send data
    loopCounter = 0;
    PromClient::SendResult res = client.send(req);
    if (!res == PromClient::SendResult::SUCCESS) {
        Serial.println(client.errmsg);
        // Note: additional retries or error handling could be implemented here.
        // the result could also be:
        // PromClient::SendResult::FAILED_DONT_RETRY
        // PromClient::SendResult::FAILED_RETRYABLE
    }
    // Batches are not automatically reset so that additional retry logic could be implemented by the library user.
    // Reset batches after a succesful send.
    ts1.resetSamples();
    ts2.resetSamples();
}

💡簡単に送信できるライブラリーが用意されています。

方法1:ENV II UNITの値をPrometheus remote write endpointへ送信

上記サンプルを改変し、ENV II UNITの値を送信します。

必要なライブラリー

  • m5stack/M5Unit-ENV
  • adafruit/Adafruit Unified Sensor
  • adafruit/Adafruit BMP280 Library
  • grafana/PrometheusArduino
  • grafana/PromLokiTransport
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
lib_deps = 
	m5stack/[email protected]
	[email protected]
	[email protected]
	
	m5stack/[email protected]

	adafruit/Adafruit Unified [email protected]
	adafruit/Adafruit BMP280 [email protected]

	grafana/[email protected]
	grafana/[email protected]

monitor_speed = 115200
  • 送信処理

PrometheusArduinoのサンプルをベースに以下のように修正します。

WriteRequest reqの引数を3(値の数)にします。第2引数はバッファーサイズの指定で、デフォルト値は512ですが、増やす必要がありますので、1024を指定します。
あとは、TimeSeriesを3つ定義し、WriteRequestに追加。ENV II UNITで取得した値をaddSampleして送信します。

WriteRequest req(3, 1024);

TimeSeries ts3(5, "m5core2_pressure",   "{job=\"unit ii\",host=\"m5core2\"}");
TimeSeries ts4(5, "m5core2_tmp",        "{job=\"unit ii\",host=\"m5core2\"}");
TimeSeries ts5(5, "m5core2_hum",        "{job=\"unit ii\",host=\"m5core2\"}");
req.addTimeSeries(ts3);
req.addTimeSeries(ts4);
req.addTimeSeries(ts5);
pressure = bme.readPressure() / 100;

sht30.get();
tmp = sht30.cTemp;
hum = sht30.humidity;

if (!ts3.addSample(time, pressure)) {
    Serial.println(ts3.errmsg);
}
if (!ts4.addSample(time, tmp)) {
    Serial.println(ts4.errmsg);
}
if (!ts5.addSample(time, hum)) {
    Serial.println(ts5.errmsg);
}

Prometheusサーバーを起動(Remote Write有効化)

Dockerを使用して構築しました。
Remote Writeを有効にするには、--web.enable-remote-write-receiver引数を指定します。
prometheus.ymlはデフォルト値のままでOKです。(未指定だとエラーとなりました。)

docker run --rm \
  -p 9090:9090 \
  -v `pwd`/prometheus.yml:/prometheus/prometheus.yml \
  prom/prometheus --web.enable-remote-write-receiver
  • prometheus.yml
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

💡ENV II UNITの値をPrometheusサーバーに送信できました。

方法2:ENV II UNITの値をPrometheusの/metricsエンドポイントで公開する

次の方法はM5Core2でWebサーバーを起動し、/metricsで公開します。こちらはPrometheusに特化したライブラリーは不要です。
また、Prometheus側から探しやすいように、mDNSを使用して名前解決ができるようにします。

必要なライブラリー

ENV II UNIT関連のみです

  • m5stack/M5Unit-ENV
  • adafruit/Adafruit Unified Sensor
  • adafruit/Adafruit BMP280 Library
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
lib_deps = 
	m5stack/M5Core2
	HttpClient
	WiFi
	
	adafruit/Adafruit Unified [email protected]^1.1.6
	adafruit/Adafruit BMP280 [email protected]^2.6.3
	m5stack/[email protected]^0.0.5

monitor_speed = 115200

プログラムの解説

  • Include

ENV II UNIT関連の他に、WebサーバーとmDNSに必要なものをInclude

#include <M5Core2.h>

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>

#include "M5_ENV.h"
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
  • 初期化

Webサーバーを定義します。9090は使用するポート番号です。

WebServer server(9090);

SHT3X sht30;
Adafruit_BMP280 bme;

float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;
  • setup (mDNS設定)

mDNSを有効化し、m5core2.localで検索できるようにするにはsetupで以下の処理を行います。

if (MDNS.begin("m5core2"))
{
Serial.println("MDNS responder started");
}
  • setup (Webサーバー)

/metricsへリクエストが来たら、handleRootメソッドが呼び出されるように指定した後、begin()します。

server.on("/metrics", handleRoot);
server.begin();

handleRootメソッドでは、ENV II UNITの値を取得し、HTTPレスポンスを作成し返却します。

pressure = bme.readPressure() / 100;

sht30.get();
tmp = sht30.cTemp;
hum = sht30.humidity / 100;

String metrics_p = "m5core2_pressure " + String(pressure);
String metrics_t = "m5core2_temperature " + String(tmp);
String metrics_h = "m5core2_humidity " + String(hum);
String metrics = metrics_p + "\n" + metrics_t + "\n" + metrics_h + "\n";

server.send(200, "text/plain", metrics);

Prometheusサーバーを起動(mDNSで名前解決させ、Prometheusからscrapeさせる)

オフィシャルのDocker Imageでは、mDNSが動作しないようですので、docker run時にホスト側で解決したIPアドレスを渡すことにしました。(ホスト側にavahi-utilsが必要となります)

docker run --rm \
  -p 9090:9090 \
  -v `pwd`/prometheus.yml:/prometheus/prometheus.yml \
  --add-host "m5core2.local:`avahi-resolve-host-name -n  m5core2.local | awk '{print $2}'`" \
  prom/prometheus
  • prometheus.yml
# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

  - job_name: 'prometheus_m5core2'
    static_configs:
    - targets: ['m5core2.local:9090']

💡ENV II UNITの値をPrometheusサーバーから取得できました。

参考

https://create.arduino.cc/projecthub/425297/webservers-on-esp32-edffef
https://github.com/m5stack/M5Core2/blob/master/examples/Advanced/WIFI/mDNS_Find/mDNS_Find.ino
https://github.com/m5stack/M5Unit-ENV/blob/master/examples/Unit_ENVII_M5Core2/Unit_ENVII_M5Core2.ino