M5Stack Core2 IoT開発キットをAzure IoT Hubに接続してBME280で測定した環境のグラフをみる。

M5Stack Core2 IoT開発キットをMicrosoft Azure IoT Hubに接続してみた。

M5Stack Core2 for AWS - ESP32 IoT開発キットを 買おうとしていたところで, 気づいたら売り切れていたので

image 代わりに買ってみたのがこのM5Stack Core2 IoT開発キット

“for AWS”でないから Microsoft Azule IoT Hubに接続してみる。

Microsoft Azule IoT

Azure IoT (モノのインターネット) は、何十億もの IoT 資産を接続、監視、制御する、Microsoft によって管理される一連のクラウド サービスです。

https://docs.microsoft.com/ja-jp/azure/iot-fundamentals/

M5Stack用のチュートリアルがあるんでこの通りに始めてみる。
Azure ESP32 IoT DevKit Get Started

What you need

  • A ESP32 device.
  • A computer running Windows 10 or macOS 10.10+.
  • An active Azure subscription.

M5Stack Core2 + BME280センサー + Windows 10 WSL + PlatformIO + VSCode の環境でやってみる。

開発環境に必要な拡張機能をインストール

ここでAzule IoT Workbench拡張をVSCodeにインストールする。

We recommend Azure IoT Device Workbench extension for Visual Studio Code to develop on the ESP32 devices.

以下の手順通りに Windowsインストーラ版のArduino IDEを入れた。(すでにインストール済みだった。)

Download and install Arduino IDE. It provides the necessary toolchain for compiling and uploading Arduino code.

Windows: Use Windows Installer version

続いて Visual Studio Codeをインストールする。(すでにインストール済みだった。)

ここまで, すでにインストール済みだったので何もしていない。
続いて Azule IoT Workbench拡張をVSCodeにインストールした。

結果VSCode拡張はこんな状態。(今回と関係ないものが多数あるけどね) vscode-extensions

“ファイル > ユーザー設定 > 設定”で出てくる検索ボックスに”arduino”といれて
“Arduino: Additional Urls”の”settings.jsonで編集”をクリックする。 vscode-settings-json

Auduinoパスの設定 vscode-settings-json

Open the project folder

サンプルいらないのでチュートリアルとは違って
“Azure IoT Device Workbench: Create Project…” を選択した。

プロジェクト名を決めて vscode04

Arduinoを選んで vscode05

Generic ESP32 boards with Azure IoT Hubを選ぶと vscode06

こうなる vscode07

Provision Azure service

チュートリアルに戻ってきて
vscode08

無料試用版 -> “Create Resource Group” を選んでリソースグループを作る。 vscode09

とりあえず西日本 vscode10

vscode11

Iot Hubを新規作成 vscode12

西日本 -> “F1:Free tire” vscode13

IoT Hub名を決める。 vscode14

ここでちょっとした待ち時間があるので休憩。

vscode15

続けてIoT Hubデバイスの設定 vscode16

vscode17

vscode18

Config Device Code

チュートリアル通りに進める。

“Copy device connection string.”のところで拡張機能がエラーを出してきたので, WebブラウザからAzure potalにはいってプライマリ接続文字列を確認した。

vscode19

“Azure IoT Device Workbench: Upload Device Code.”を実行するとM5Stack Core2に書き込まれて実行する。

なぜかVSCodeの出力が文字化けして読めなかったので, シリアルコンソールからM5Stackの出力を見る。 vscode20

WebブラウザからAzure potalを確認すると, この通りなので毎秒Azure IoT HubにM5Stackから送信されているのが確認できた。 vscode21

前回の温度/湿度/気圧の測定ソフトウェアをAzure Iot Hubに接続する

正常にAzure IoT Hubに接続できていることを確認できたので
前回の温度/湿度/気圧の測定ソフトウェア
Azure IoT Hubに接続してブラウザからグラフを見れるようにする。

前回の温度/湿度/気圧の測定ソフトウェアを編集する

src/main.cpp
/*
 * Copyright 2020 Akihiro Yamamoto
 */
#include <M5Core2.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "Esp32MQTTClient.h"

// from azure_iot.cpp
extern void azure_iot_InitWifi();
extern void azure_iot_setup();
extern void azure_iot_push(float temperature, float humidity, float pressure);

#define LGFX_M5STACK_CORE2
#include <LovyanGFX.hpp>
static LGFX lcd;

static const uint8_t BME280_I2C_ADDRESS = 0x76;
static Adafruit_BME280 bme280;
static Adafruit_Sensor *bme280_temperature = bme280.getTemperatureSensor();
static Adafruit_Sensor *bme280_pressure = bme280.getPressureSensor();
static Adafruit_Sensor *bme280_humidity = bme280.getHumiditySensor();

const unsigned long background_color = 0x000000U;
const unsigned long message_text_color = 0xFFFFFFU;

#define INTERVAL 10000
static uint64_t send_interval_ms;

struct TempHumPres
{
  float temperature;
  float relative_humidity;
  float pressure;
};

void releaseEvent(Event &e)
{
}

struct TempHumPres sense()
{
  sensors_event_t temperature_event;
  sensors_event_t humidity_event;
  sensors_event_t pressure_event;
  //
  bme280_temperature->getEvent(&temperature_event);
  bme280_humidity->getEvent(&humidity_event);
  bme280_pressure->getEvent(&pressure_event);

  struct TempHumPres rv;
  rv.temperature = temperature_event.temperature;
  rv.relative_humidity = humidity_event.relative_humidity;
  rv.pressure = pressure_event.pressure;

  return rv;
}

void display(struct TempHumPres val)
{
  lcd.setCursor(0, 0);
  lcd.printf("温度 %7.2f ℃\n", val.temperature);
  lcd.printf("湿度 %7.2f %\n", val.relative_humidity);
  lcd.printf("気圧 %7.2f hPa\n", val.pressure);
}

void setup()
{
  M5.begin(true, true, true, true);

  M5.Buttons.addHandler(releaseEvent, E_RELEASE);

  //
  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_36);
  lcd.setTextColor(message_text_color, background_color);

  //
  while (!bme280.begin(BME280_I2C_ADDRESS))
  {
    lcd.printf("BME280センサが見つかりません。");
    while (1)
    {
      delay(10);
    }
  }
  //
  bme280_temperature->printSensorDetails();
  bme280_pressure->printSensorDetails();
  bme280_humidity->printSensorDetails();

  //
  azure_iot_setup();

  //
  struct TempHumPres val = sense();
  display(val);

  //
  send_interval_ms = millis();
}

void loop()
{
  M5.update();

  if ((int)(millis() - send_interval_ms) >= INTERVAL)
  {
    struct TempHumPres val = sense();
    display(val);
    //
    azure_iot_push(val.temperature, val.relative_humidity, val.pressure);
    send_interval_ms = millis();
  }
  else
  {
    Esp32MQTTClient_Check();
  }

  delay(10);
}
src/azure_iot.cpp
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.

#include <M5Core2.h>
#include <WiFi.h>
#include "AzureIotHub.h"
#include "Esp32MQTTClient.h"

#define DEVICE_ID "M5Stack-Core2-iot"
#define MESSAGE_MAX_LEN 256

// Please input the SSID and password of WiFi
const char *ssid = "************";
const char *password = "************";

/*String containing Hostname, Device Id & Device Key in the format:                         */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessKey=<device_key>"                */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessSignature=<device_sas_token>"    */
static const char *connectionString = "*************************************************************************************************************************************";

const char *messageData = "{\"DeviceId\":\"%s\", \"messageId\":%d, \"temperature\":%.2f, \"humidity\":%.2f, \"pressure\":%.2f}";

int messageCount = 1;
static bool hasWifi = false;
static bool messageSending = true;

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Utilities
static void InitWifi()
{
    Serial.println("Connecting...");
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
    hasWifi = true;
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
}

static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result)
{
    if (result == IOTHUB_CLIENT_CONFIRMATION_OK)
    {
        Serial.println("Send Confirmation Callback finished.");
    }
}

static void MessageCallback(const char *payLoad, int size)
{
    Serial.println("Message callback:");
    Serial.println(payLoad);
}

static void DeviceTwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char *payLoad, int size)
{
    char *temp = (char *)malloc(size + 1);
    if (temp == NULL)
    {
        return;
    }
    memcpy(temp, payLoad, size);
    temp[size] = '\0';
    // Display Twin message.
    Serial.println(temp);
    free(temp);
}

static int DeviceMethodCallback(const char *methodName, const unsigned char *payload, int size, unsigned char **response, int *response_size)
{
    LogInfo("Try to invoke method %s", methodName);
    const char *responseMessage = "\"Successfully invoke device method\"";
    int result = 200;

    if (strcmp(methodName, "start") == 0)
    {
        LogInfo("Start sending temperature and humidity data");
        messageSending = true;
    }
    else if (strcmp(methodName, "stop") == 0)
    {
        LogInfo("Stop sending temperature and humidity data");
        messageSending = false;
    }
    else
    {
        LogInfo("No method %s found", methodName);
        responseMessage = "\"No method found\"";
        result = 404;
    }

    *response_size = strlen(responseMessage) + 1;
    *response = (unsigned char *)strdup(responseMessage);

    return result;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Arduino sketch
void azure_iot_setup()
{
    Serial.println("ESP32 Device");
    Serial.println("Initializing...");

    // Initialize the WiFi module
    Serial.println(" > WiFi");
    hasWifi = false;
    InitWifi();
    if (!hasWifi)
    {
        return;
    }

    Serial.println(" > IoT Hub");
    Esp32MQTTClient_SetOption(OPTION_MINI_SOLUTION_NAME, "GetStarted");
    Esp32MQTTClient_Init((const uint8_t *)connectionString, true);

    Esp32MQTTClient_SetSendConfirmationCallback(SendConfirmationCallback);
    Esp32MQTTClient_SetMessageCallback(MessageCallback);
    Esp32MQTTClient_SetDeviceTwinCallback(DeviceTwinCallback);
    Esp32MQTTClient_SetDeviceMethodCallback(DeviceMethodCallback);
}

void azure_iot_push(float temperature, float humidity, float pressure)
{
    if (hasWifi)
    {
        if (messageSending)
        {
            // Send teperature data
            char messagePayload[MESSAGE_MAX_LEN];
            snprintf(messagePayload, MESSAGE_MAX_LEN, messageData, DEVICE_ID, messageCount++, temperature, humidity, pressure);
            Serial.println(messagePayload);
            EVENT_INSTANCE *message = Esp32MQTTClient_Event_Generate(messagePayload, MESSAGE);
            Esp32MQTTClient_Event_AddProp(message, "temperatureAlert", "true");
            Esp32MQTTClient_SendEventInstance(message);
        }
    }
}

Web アプリで Azure IoT Hub からのリアルタイム センサー データを視覚化する

今回のチュートリアルはこれ。
Web アプリで Azure IoT Hub からのリアルタイム センサー データを視覚化する

IoT ハブへのコンシューマー グループの追加

cli01

Web アプリの環境変数を構成する

WSL上のbashなので

bash
export IotHubConnectionString=YourIoTHubConnectionString
export EventHubConsumerGroup="consumer-group"

Web アプリの実行

実行そのまえに編集する。

public/js/chart-device-data.js
/* eslint-disable max-classes-per-file */
/* eslint-disable no-restricted-globals */
/* eslint-disable no-undef */
$(document).ready(() => {
  // if deployed to a site supporting SSL, use wss://
  const protocol = document.location.protocol.startsWith('https') ? 'wss://' : 'ws://';
  const webSocket = new WebSocket(protocol + location.host);

  // A class for holding the last N points of telemetry for a device
  class DeviceData {
    constructor(deviceId) {
      this.deviceId = deviceId;
      this.maxLen = 50;
      this.timeData = new Array(this.maxLen);
      this.temperatureData = new Array(this.maxLen);
      this.humidityData = new Array(this.maxLen);
      this.pressureData = new Array(this.maxLen);
    }

    addData(time, temperature, humidity, pressure) {
      this.timeData.push(time);
      this.temperatureData.push(temperature);
      this.humidityData.push(humidity);
      this.pressureData.push(pressure);

      if (this.timeData.length > this.maxLen) {
        this.timeData.shift();
        this.temperatureData.shift();
        this.humidityData.shift();
        this.pressureData.shift();
      }
    }
  }

  // All the devices in the list (those that have been sending telemetry)
  class TrackedDevices {
    constructor() {
      this.devices = [];
    }

    // Find a device based on its Id
    findDevice(deviceId) {
      for (let i = 0; i < this.devices.length; ++i) {
        if (this.devices[i].deviceId === deviceId) {
          return this.devices[i];
        }
      }

      return undefined;
    }

    getDevicesCount() {
      return this.devices.length;
    }
  }

  const trackedDevices = new TrackedDevices();

  // Define the chart axes
  const chartData = {
    datasets: [
      {
        fill: false,
        label: 'Temperature',
        yAxisID: 'Temperature',
        borderColor: 'rgba(255, 204, 0, 1)',
        pointBoarderColor: 'rgba(255, 204, 0, 1)',
        backgroundColor: 'rgba(255, 204, 0, 0.4)',
        pointHoverBackgroundColor: 'rgba(255, 204, 0, 1)',
        pointHoverBorderColor: 'rgba(255, 204, 0, 1)',
        spanGaps: true,
      },
      {
        fill: false,
        label: 'Humidity',
        yAxisID: 'Humidity',
        borderColor: 'rgba(24, 120, 240, 1)',
        pointBoarderColor: 'rgba(24, 120, 240, 1)',
        backgroundColor: 'rgba(24, 120, 240, 0.4)',
        pointHoverBackgroundColor: 'rgba(24, 120, 240, 1)',
        pointHoverBorderColor: 'rgba(24, 120, 240, 1)',
        spanGaps: true,
      },
      {
        fill: false,
        label: 'Pressure',
        yAxisID: 'Pressure',
        borderColor: 'rgba(24, 24, 240, 1)',
        pointBoarderColor: 'rgba(24, 24, 240, 1)',
        backgroundColor: 'rgba(24, 24, 240, 0.4)',
        pointHoverBackgroundColor: 'rgba(24, 24, 240, 1)',
        pointHoverBorderColor: 'rgba(24, 24, 240, 1)',
        spanGaps: true,
      }
    ]
  };

  const chartOptions = {
    scales: {
      yAxes: [{
        id: 'Temperature',
        type: 'linear',
        scaleLabel: {
          labelString: 'Temperature (ºC)',
          display: true,
        },
        position: 'left',
      },
      {
        id: 'Humidity',
        type: 'linear',
        scaleLabel: {
          labelString: 'Humidity (%)',
          display: true,
        },
        position: 'left',
      },
      {
        id: 'Pressure',
        type: 'linear',
        scaleLabel: {
          labelString: 'Pressure (hPa)',
          display: true,
        },
        position: 'right',
      }]
    }
  };

  // Get the context of the canvas element we want to select
  const ctx = document.getElementById('iotChart').getContext('2d');
  const myLineChart = new Chart(
    ctx,
    {
      type: 'line',
      data: chartData,
      options: chartOptions,
    });

  // Manage a list of devices in the UI, and update which device data the chart is showing
  // based on selection
  let needsAutoSelect = true;
  const deviceCount = document.getElementById('deviceCount');
  const listOfDevices = document.getElementById('listOfDevices');
  function OnSelectionChange() {
    const device = trackedDevices.findDevice(listOfDevices[listOfDevices.selectedIndex].text);
    chartData.labels = device.timeData;
    chartData.datasets[0].data = device.temperatureData;
    chartData.datasets[1].data = device.humidityData;
    chartData.datasets[2].data = device.pressureData;
    myLineChart.update();
  }
  listOfDevices.addEventListener('change', OnSelectionChange, false);

  // When a web socket message arrives:
  // 1. Unpack it
  // 2. Validate it has date/time and temperature
  // 3. Find or create a cached device to hold the telemetry data
  // 4. Append the telemetry data
  // 5. Update the chart UI
  webSocket.onmessage = function onMessage(message) {
    try {
      const messageData = JSON.parse(message.data);
      console.log(messageData);

      // time and either temperature or humidity are required
      if (!messageData.MessageDate || (!messageData.IotData.temperature && !messageData.IotData.humidity && !messageData.IotData.pressure)) {
        return;
      }

      // find or add device to list of tracked devices
      const existingDeviceData = trackedDevices.findDevice(messageData.DeviceId);

      if (existingDeviceData) {
        existingDeviceData.addData(messageData.MessageDate, messageData.IotData.temperature, messageData.IotData.humidity, messageData.IotData.pressure);
      } else {
        const newDeviceData = new DeviceData(messageData.DeviceId);
        trackedDevices.devices.push(newDeviceData);
        const numDevices = trackedDevices.getDevicesCount();
        deviceCount.innerText = numDevices === 1 ? `${numDevices} device` : `${numDevices} devices`;
        newDeviceData.addData(messageData.MessageDate, messageData.IotData.temperature, messageData.IotData.humidity, messageData.IotData.pressure);

        // add device to the UI list
        const node = document.createElement('option');
        const nodeText = document.createTextNode(messageData.DeviceId);
        node.appendChild(nodeText);
        listOfDevices.appendChild(node);

        // if this is the first device being discovered, auto-select it
        if (needsAutoSelect) {
          needsAutoSelect = false;
          listOfDevices.selectedIndex = 0;
          OnSelectionChange();
        }
      }

      myLineChart.update();
    } catch (err) {
      console.error(err);
    }
  };
});

ここまで準備ができたら実行する。

bash
npm install
npm start

Using IoT Hub connection string [HostName=************************************************************;SharedAccessKey=********************************************]
consumer-group
Using event hub consumer group [consumer-group]
Listening on 3000.
Successfully created the EventHubConsumerClient from IoT Hub event hub-compatible connection string.
The partition ids are:  [ '0', '1' ]
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":5,"temperature":24.26,"humidity":33.62,"pressure":1026.43},"MessageDate":"2021-01-03T16:05:09.582Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":6,"temperature":24.26,"humidity":33.66,"pressure":1026.49},"MessageDate":"2021-01-03T16:05:19.723Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":7,"temperature":24.25,"humidity":33.71,"pressure":1026.49},"MessageDate":"2021-01-03T16:05:29.874Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":8,"temperature":24.23,"humidity":33.69,"pressure":1026.51},"MessageDate":"2021-01-03T16:05:40.022Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":9,"temperature":24.21,"humidity":33.73,"pressure":1026.5},"MessageDate":"2021-01-03T16:05:50.178Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":10,"temperature":24.23,"humidity":33.7,"pressure":1026.51},"MessageDate":"2021-01-03T16:06:00.334Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":11,"temperature":24.21,"humidity":33.74,"pressure":1026.53},"MessageDate":"2021-01-03T16:06:10.475Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":12,"temperature":24.22,"humidity":33.73,"pressure":1026.51},"MessageDate":"2021-01-03T16:06:20.617Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":13,"temperature":24.21,"humidity":33.74,"pressure":1026.53},"MessageDate":"2021-01-03T16:06:30.784Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":14,"temperature":24.22,"humidity":33.76,"pressure":1026.55},"MessageDate":"2021-01-03T16:06:40.940Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":15,"temperature":24.23,"humidity":33.73,"pressure":1026.47},"MessageDate":"2021-01-03T16:06:51.083Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":16,"temperature":24.23,"humidity":33.71,"pressure":1026.52},"MessageDate":"2021-01-03T16:07:01.227Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":17,"temperature":24.22,"humidity":33.72,"pressure":1026.5},"MessageDate":"2021-01-03T16:07:11.386Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":18,"temperature":24.23,"humidity":33.73,"pressure":1026.46},"MessageDate":"2021-01-03T16:07:21.534Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":19,"temperature":24.23,"humidity":33.71,"pressure":1026.48},"MessageDate":"2021-01-03T16:07:31.676Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":20,"temperature":24.22,"humidity":33.73,"pressure":1026.51},"MessageDate":"2021-01-03T16:07:41.832Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":21,"temperature":24.23,"humidity":33.76,"pressure":1026.47},"MessageDate":"2021-01-03T16:07:51.981Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":22,"temperature":24.22,"humidity":33.74,"pressure":1026.46},"MessageDate":"2021-01-03T16:08:02.137Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":23,"temperature":24.24,"humidity":33.75,"pressure":1026.45},"MessageDate":"2021-01-03T16:08:12.277Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":24,"temperature":24.21,"humidity":33.74,"pressure":1026.47},"MessageDate":"2021-01-03T16:08:22.434Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":25,"temperature":24.2,"humidity":33.75,"pressure":1026.44},"MessageDate":"2021-01-03T16:08:32.582Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":26,"temperature":24.16,"humidity":33.84,"pressure":1026.44},"MessageDate":"2021-01-03T16:08:42.739Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":27,"temperature":24.17,"humidity":33.81,"pressure":1026.4},"MessageDate":"2021-01-03T16:08:52.897Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":28,"temperature":24.16,"humidity":33.88,"pressure":1026.44},"MessageDate":"2021-01-03T16:09:03.039Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":29,"temperature":24.15,"humidity":33.92,"pressure":1026.47},"MessageDate":"2021-01-03T16:09:13.195Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":30,"temperature":24.16,"humidity":33.91,"pressure":1026.47},"MessageDate":"2021-01-03T16:09:23.344Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":31,"temperature":24.15,"humidity":33.94,"pressure":1026.49},"MessageDate":"2021-01-03T16:09:33.500Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":32,"temperature":24.14,"humidity":33.97,"pressure":1026.42},"MessageDate":"2021-01-03T16:09:43.641Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":33,"temperature":24.14,"humidity":33.96,"pressure":1026.44},"MessageDate":"2021-01-03T16:09:53.797Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":34,"temperature":24.14,"humidity":33.96,"pressure":1026.45},"MessageDate":"2021-01-03T16:10:03.953Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":35,"temperature":24.13,"humidity":33.97,"pressure":1026.44},"MessageDate":"2021-01-03T16:10:14.110Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":36,"temperature":24.13,"humidity":33.99,"pressure":1026.43},"MessageDate":"2021-01-03T16:10:24.251Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":37,"temperature":24.13,"humidity":34,"pressure":1026.47},"MessageDate":"2021-01-03T16:10:34.408Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":38,"temperature":24.14,"humidity":34.03,"pressure":1026.47},"MessageDate":"2021-01-03T16:10:44.564Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":39,"temperature":24.17,"humidity":33.95,"pressure":1026.49},"MessageDate":"2021-01-03T16:10:54.705Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":40,"temperature":24.18,"humidity":33.92,"pressure":1026.46},"MessageDate":"2021-01-03T16:11:04.864Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":41,"temperature":24.22,"humidity":33.86,"pressure":1026.47},"MessageDate":"2021-01-03T16:11:15.004Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":42,"temperature":24.22,"humidity":33.87,"pressure":1026.48},"MessageDate":"2021-01-03T16:11:25.160Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":43,"temperature":24.23,"humidity":33.9,"pressure":1026.46},"MessageDate":"2021-01-03T16:11:35.307Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":44,"temperature":24.24,"humidity":33.85,"pressure":1026.45},"MessageDate":"2021-01-03T16:11:45.463Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":45,"temperature":24.26,"humidity":33.86,"pressure":1026.47},"MessageDate":"2021-01-03T16:11:55.604Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":46,"temperature":24.26,"humidity":33.84,"pressure":1026.45},"MessageDate":"2021-01-03T16:12:05.761Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":47,"temperature":24.27,"humidity":33.82,"pressure":1026.49},"MessageDate":"2021-01-03T16:12:15.911Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":48,"temperature":24.27,"humidity":33.81,"pressure":1026.45},"MessageDate":"2021-01-03T16:12:26.055Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":49,"temperature":24.27,"humidity":33.8,"pressure":1026.5},"MessageDate":"2021-01-03T16:12:36.211Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":50,"temperature":24.25,"humidity":33.83,"pressure":1026.49},"MessageDate":"2021-01-03T16:12:46.368Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":51,"temperature":24.26,"humidity":33.86,"pressure":1026.49},"MessageDate":"2021-01-03T16:12:56.516Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":52,"temperature":24.29,"humidity":33.82,"pressure":1026.49},"MessageDate":"2021-01-03T16:13:06.661Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":53,"temperature":24.27,"humidity":33.81,"pressure":1026.51},"MessageDate":"2021-01-03T16:13:16.817Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":54,"temperature":24.32,"humidity":33.75,"pressure":1026.5},"MessageDate":"2021-01-03T16:13:26.973Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":55,"temperature":24.31,"humidity":33.78,"pressure":1026.51},"MessageDate":"2021-01-03T16:13:37.129Z","DeviceId":"m5stack-bme280-device"}
Broadcasting data {"IotData":{"DeviceId":"M5Stack-BME280-device","messageId":56,"temperature":24.3,"humidity":33.8,"pressure":1026.52},"MessageDate":"2021-01-03T16:13:47.273Z","DeviceId":"m5stack-bme280-device"}

ブラウザからlocalhost:3000にアクセスすると graph01

グラフが激しく波打っているのは, 小数点以下の動きを視覚化しているから。
これだけ出来たから今日は終わり。