PR

Arduino UNO R4 WiFiでセンサーの値をグラフ表示

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

Arduino UNO R4 WiFiでWiFiとSDを組み合わせ、SDカード上のHTMLを返すWebServerを構築し、取得したセンサーデータをChart.jsでブラウザーにグラフ化して表示する方法をまとめました。

WiFiライブラリでWebServerを実装する方法を下記記事にまとめています。

Arduino UNO R4 WiFiでWebServerを実装する

本記事はデータをグラフ表示するChart.jsを組み込んでWebServerで応答する応用例です。

SD CARD SHIELDはアイキャッチ画像のようにArduino UNOに差し込むだけで使用することができます。土壌センサーはMAKER SOIL MOISTURE SENSOR(Cytron製:秋月電子で購入)を使用しています。

以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

WebServerの全体構成

WebServerの全体構成
WebServerの全体構成

スマホはUNOR4-WiFiで実装したアクセスポイントに接続し、Google Chromeなどのブラウザーを使用して「192.168.11.1」にアクセスします。

ブラウザーはクライアントとして動作し、最初にGET / HTTPを送信します。UNOR4-WiFiはこの文字列を識別してSDカードからhtmlファイル(index.htm)を読み込んで応答します。

次にブラウザーはUNOR4-WiFiから取得したHTML内のスクリプトの読み込みのため、GET /chart.jsを送信します。UNOR4-WiFiはこの文字列を識別してSDカードからchart.jsのファイルを読み込んで応答します。chart.jsのファイルは数百kbyteなので読み込みに時間が必要です。

その後、JavaScriptファイル(main.js)を読み込んで応答します。一定周期でGET /dataが送信され、UNOR4-WiFiはセンサーのデータをJSON形式で応答します。

SDカードに保存するファイル
SDカードに保存するファイル

chart.jsのライブラリをダウンロードしてSDカードに保存します。記事作成時で最新のバージョンのリンクは下記のとおりです。下記リンクからchart.js-4.5.1.tgzをダウンロードして展開します。

Release v4.5.1 · chartjs/Chart.js · GitHub

展開したフォルダの階層を下ってdistフォルダにライブラリファイルがあります。chart.umd.jsやchart.umd.min.jsがありますが圧縮版であるchart.umd.min.jsを使用して読み込みサイズを短縮します。chart.jsやchart.min.jsはimport用のファイルなので使用できません。

ArduinoのSDカードライブラリはファイルの名前が8.3形式にする必要があるため、chart.umd.min.jsのファウル名を変更してSDカードに保存します。本記事では「chart.js」(任意でよい)に変更しています。import用のファイル名と同じなので間違えないように注意が必要です。

以下ではchart.jsを組み込むための処理を中心に説明しています。以下ではWiFiClientクラス、SDクラス、Fileクラスのメンバー関数を赤太文字で記載し、その他のメンバー関数は太文字で記載します。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

クライアントの接続と応答(chart.js)

WiFiClient client = server.available();
if (!client) return; //接続なしで抜ける

String req = client.readStringUntil('\r');
client.readStringUntil('\n');
Serial.println(req);

if (req.startsWith("GET /chart.js") ) {
    //chart.jsファイルの読み出し
    File myfile = SD.open("/chart.js");
    
    if (!myfile) {
      Serial.println("file doesn't exist.");
      return;
    }

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/javascript");
    client.println("Connection: close");
    client.println();

    uint8_t buf[256];
    while (true) {
      int n = myfile.read(buf, sizeof(buf));
      if (n <= 0) break;
      client.write(buf, n);
    }
    Serial.println("chart end");
    myfile.close();

client.stop();

1行目のavailable()関数でクライアント(ブラウザー)からの接続を待機します。接続要求があると4行目に進みます。

最初にクライアントからHTTPデータのリクエストがGET / HTTP/1.1のようにバージョン付きで送信されます。HTMLデータを読み込むと、chart.jsを読み込む処理に進みます。

HTTPの行末は改行コードのCRLF(\r\n)が付くため、readStringUntil()関数で文字列の先頭から\r\nまで読み込んでリクエストの内容を判断します。readStringUntil()関数は1文字しか指定できないため、CRLF(\r\n)の2文字を一度に処理できません。そのため先にCR(\r)までを読み、5行目でLF(\n)を読んでLFが残らないようにしています。

8行目はリクエストから取得した文字列がGET / chart.jsから始まっているかをstartsWith()関数で判定します。

10行目はSDカードのファイルを開いています。ファイルが存在しない場合は”HTTP/1.1 404 Not Found\r\n”でページが存在しないことをprintln()関数でブラウザーに通知します。

ブラウザーに最初にリクエストが正常に処理できたことを通知するため、HTTPのバージョン(HTTP/1.1)、ステータス(200 OK)を送ります。 200 OKはリクエストが正常に処理されたことを通知する応答です。

「Content-Type: application/javascript」は送るデータがJavaScriptであることを通知するヘッダーです。「Connection: close」はファイルを送信した後サーバー(UNOR4-WiFi)の接続を閉じる通知です。

UNOR4-WiFiで最後にstop()関数で切断をしますが、事前に接続を閉じる通知をしておくことでブラウザー側も切断処理にスムーズに進むため通信が安定します。ヘッダーの後は空行をいれてからchart.jsのデータを送信します。

22~27行目はchart.jsのデータをSDカードから読み出してブラウザーに送信します。chart.jsは圧縮版であっても204kByteのサイズがあるためすべて読み込むには時間が必要です。

1byte~数byteのデータを繰り返し送信するとTCPバッファが詰まってしまいページが表示されないなど影響が出てしまうことがあります。そのためwrite()関数を使って可能な限りデータをTCPバッファに詰め込んで(例では256byteで区切っている)ブラウザーに送信します。

ファイルの送信後はclose()関数でファイルを閉じ、クライアントの接続をstop()関数で切断します。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

HTMLファイルの生成

<head>
    <!-- Chart.js(SDカードに置いた UMD 版) -->
    <script src="chart.js"></script>
</head>
<body>
    <h2>最新のデータ
    <br>時刻:
        <code id="latest-time">----</code>
    <br>Value:
        <code id="latest-value">----</code>
    </h2>
    <div style="height: 400; width: 800px; margin: auto;">
        <canvas id="soil-chart" class="chart"></canvas>
    </div>
</body>

HTMLファイルのJavaScriptを使用してchart.jsを読み込む方法をソースコードから一部を抜粋して説明します。

3行目はHTMLが外部のJavaScript ファイル「chart.js」を読み込み、SDカードに保存しているchart.js(chart.umd.min.js)を読み込んでグラフ描画機能を使用できるようにします。

SDカードライブラリの制約により8.3形式を越えないようにファイル名を指定する必要があるためchart.jsにファイル名を修正して使用しています。

7~10行目は最新のデータのRTC時間とセンサーの値を表示します。初期の文字は「—」になりますが、後述のJavaScriptによって取得した値がid=”latest-time”と”latest-value”と連動して更新されます。

12~14行目は、幅800px・高さ400pxの中央配置されたコンテナを作り、その中にグラフ描画用のキャンバスを配置しています。Chart.jsのJavaScriptは、id=”sail-chart” のキャンバスにグラフを描写します。

広告
キャリア形成の不安を相談する!

データの更新

async function updateChart(){
    const res = await fetch('/data');
    const obj = await res.json();
    const times = obj.t;
    const values = obj.v;

    document.getElementById("latest-time").innerHTML = times[times.length -1];
    document.getElementById("latest-value").innerHTML = values[values.length -1];
    chart.data.labels = times;
    chart.data.datasets[0].data = values;
    chart.update();
}

updateChart()関数はfetch(“/data”)でUNOR4-WiFiに接続し、JSONデータを取得します。取得したJSONのtとvの値をHTML内のid=”latest-time”と”latest-value”の要素に表示します。(7~8行目)

updateChart()関数にasyncをつけると関数内でawaitが使えるようになります。JavaScriptのawaitは非同期処理が終わるまで、この関数の処理を一時停止する意味があります。ただし、Arduinoのdelay()関数のようにCPUを待機させるのではなく、関数の中だけを一時停止するだけで、ブラウザーの処理に影響は与えません。

fetch()関数はJavaScriptの標準関数で、指定したURLにGET リクエストを送り、UNOR4-WiFi(サーバー)から応答(レスポンス)を受け取るまでを自動で行います。2行目のawait fetch(“/data”)は通信でJSONデータを取得するのを待って次の行に進みます。

3行目のawait res.json()でJSONのデータを取得することができます。4~5秒目はJSONデータを構成しているオブジェクトをtimesとvaluesに展開しています。timesはセンサーから取得したときのRTC時刻でvaluesはセンサーの値です。これらのデータは最新のデータの更新及びグラフ描写に使用します。

9行目はグラフの横軸になるのでtimes(RTC時刻)を指定し、10行目は縦軸になるのでvalues(センサーの値)を指定しています。11行目はグラフの更新を行います。

PR:現役エンジニアによる学習サポートで即戦力のスキルを身に着ける:SHIFT TERAS CAMPUSプログラミングコース

JSONファイルを生成する

client.print("{\"t\":[");
for( i = 0; i < CHART_SZ; i++){
  sprintf(buf, "%02d:%02d", meas.t[rp].getMinutes(), meas.t[rp].getSeconds());
  str = "\"" + String(buf) + "\"";
  client.print(str);

  if( ++rp >= CHART_SZ ){
    rp = 0;
  }
  if (i < CHART_SZ - 1) client.print(",");
}

土壌センサーから取得したデータからJSONの形式に合わせて文字列を作成します。JSONはキーと値の組み合わせを{}で囲んだデータ形式です。キーは必ず” “で囲み値には数値や文字列が入ります。キーと値は:(コロン)でつなげてペアをつくり、ペアが複数あるときは,(カンマ)で区切ります。

String型のstrを準備して文字列を生成しながらクライアントに送信します。文字列を””で囲む必要があるためエスケープ文字をいれて\”t\”のように指定することで”t”を生成します。グラフで表示する点数の配列になるようにJSONデータ生成します。

3行目はRTC時刻を文字列に変換しています。この文字列を4行目のように文字を挿入して”12:30のようなJSONデータを生成します。

最終的に”t”:[“12:30”, “12:31″,・・・],”v”:[731,731,・・・]}のようにRTC時刻とセンサー値を含めた配列要素のJSONデータを生成してクライアントに送信します。

スポンサーリンク

Chart.jsの設定

window.onload = () => {
    const ctx = document.getElementById("soil-chart");

    chart = new Chart( ctx, {
        //以下にチャート表示するデータをセットしていく
        type: 'line',
        data: {
            labels: [],
            datasets: [{
            label: '土壌水分',
            data: [],
            }]
        },
        options: {
            scales: {
                x: {
                    ticks: {
                        display: true,
                        color:"black",
                    },
                },
                y: {
                    min: 0,
                    max:1000,
                    ticks: {
                        display: true,
                        color:"black",
                    },
                }
            }
       }
    });
    
    updateChart();
    setInterval(updateChart, 5000);
};

1行目のwindow.onload =()=>{・・・}は、ページの読み込みがすべて完了した後に{}内の処理を実行します。HTML要素やキャンバスが確実に準備された状態でJavaScriptを動かせるようにします。

2行目は、HTML のキャンバスをJavaScriptで取得する処理です。3行目の new Chart(ctx, {…})はキャンバスにChart.jsのグラフを描写するための 設定を行うオブジェクトを生成します。第1引数に描写するキャンバスを指定し、第2引数にグラフの種類やデータ・オプション(3行目以降)を追加します。

6行目はグラフの種類を指定します。土壌センサーの値を時系列に表示するため折れ線ブラフ(line)を指定します。他にはbar・pieなどがあります。

7行目のdataはlabelsとdatasetsを設定します。labels:[]はX軸に並ぶラベルで、空欄ですがJSONから取得したRTC時刻が入ります。datasets:[]はY軸の設定です。labelは凡例に表示される名前で、data:[]がY軸の値になります。これも空欄ですがJSONから取得したセンサーの値が入ります。

14行からはオプション設定です。X軸ではメモリ(ticks)を黒文字にする指定をしています。Y軸では値の範囲を0~1000に固定しています。

他にも設定がありますが、ソースコードの全体を参照してください。

34行目はグラフの描写を更新するため自作のupdateChart()関数を読み出しています。

35行目は5秒ごとに自作のupdateChart()関数を自動で実行するタイマです。UNOR4-WiFiが生成したJSONから定期的にセンサー値を取得し、Chart.js のグラフを更新するために使用します。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

動作確認

動作確認用の回路図
動作確認用の回路図

SD CARD SHILDをUNOR4-WiFiに挿入して動作確認を行います。土壌センサーはSD CARD SHILDのA0ピンと7ピンに配線していますがSD CARD SHILDはUNOR4-WiFiのピンを延長しているだけなので配線は同じです。

UNOR4-WiFiの電源を入れると土壌センサーの測定とクライアントからの接続を待機します。

RTC時刻はArduino UNO R4 WiFiのRTCを使用するで作成した自作の電文で変更できます。例えば RTC2026-05-25-01-20-30:30 の文字列を送信すると2026年5月25日(月)20時30分30秒になります。

スマホでアクセスポイントに接続し、Chromeなどのブラウザーで「192.168.11.1」を入力して接続します。HTMLファイルの内容が表示されますが、初期表示はSDカードからChart.jsを読み込むのに数十秒必要です。

シリアルモニターで読み込み時間を確認
シリアルモニターで読み込み時間を確認

GET /chart.js HTTP/1.1からchart endまでのタイムスタンプから読み込み時間を確認すると約24秒かかっています。一度読み込みに成功するとページをリロードしない限りJSONからデータを取得するだけの通信になるのでグラフの表示はスムーズにできるようになります。

ブラウザーにグラフを表示した結果
ブラウザーにグラフを表示した結果

UNOR4-WiFiで生成したJSONが展開されてブラウザーに最新のデータとグラフが表示されています。

グラフの値が変化するのを確認するために土壌センサーのプローブに濡れたスポンジを当てるとValueが500付近の値になりスポンジを外すと700付近の値に戻っている様子が更新されて表示されることが確認できました。

本記事ではグラフが更新される様子を確認するため土壌センサーの取得周期を1秒にしていますが、土壌の水分は頻繁に変化しないことを考慮すると10分や1時間周期の測定で十分だと思います。

SDカードに測定データを履歴として保存してSDカードから読み込んだデータをグラフ表示する仕組みにしてもよいと思います。

スポンサーリンク

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

UNOR4-WiFiのソースコード:

#include "RTC.h"
#include <SD.h>
#include "WiFiS3.h"

#define SD_CS 4
#define SOIL_DIS 7
#define MEAS_MAX 8
#define TIM_MEAS 100
#define TIM_SAVE 6000
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIM_RTC 100
#define RTC_READ 100
#define RING_SZ 128
#define TIM_RX_WAIT 5   //50ms
#define OFFSET 3
#define CHART_SZ 60

struct DIS_MEAN{
  uint8_t wp;
  uint32_t buf[MEAS_MAX];
  uint32_t humid;
};

struct RING_MNG{
  uint8_t wp;
  uint8_t rp;
  uint8_t buf[RING_SZ];
};

typedef struct{
  uint16_t wp;
  uint16_t rp;
  RTCTime t[CHART_SZ];
  uint32_t time[CHART_SZ];
  uint32_t value[CHART_SZ];
}DATA_RING;

uint32_t beforetimCnt = millis();
int16_t  timdataget;
int32_t  timsave;
DIS_MEAN soil;
int16_t timrtc;
int16_t timrcv = TIME_OFF;
char datestr[32];
char str[] = {"2025/09/22-00-21:30:30"};
char week[] = {"sun"};
uint8_t Rcvdata[sizeof(str) + OFFSET];
RING_MNG rcv;
RTCTime now;
DATA_RING meas;
String htmpath = "/index.htm"; //8.3形式
String jspath = "/main.js"; //8.3形式
String chartpath = "/chart.js"; //8.3形式
int status = WL_IDLE_STATUS;
WiFiServer server(80);
const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
const IPAddress ip(192,168,11,1); //IPアドレス

void mainTimer(void);
void mainApp(void);
void mainRtc(void);
void maiWifi(void);
void rcvdatechk(void);
void ReadPointerAdd(void);
bool chkdata(void);
void sendJson(WiFiClient &client);

void setup() {

  Serial.begin(115200);
  pinMode(SOIL_DIS,OUTPUT);

  RTC.begin();
  RTCTime init(3, Month::MAY, 2026, 9, 0, 30, DayOfWeek::SUNDAY, SaveLight::SAVING_TIME_INACTIVE );
  RTC.setTime(init);
  delay(500);
  Serial.println("Init Start!");

  if(!SD.begin(SD_CS)){
    Serial.println("initialization failed!");
    while (1);
  }  

  for( uint8_t i=0; i < 10; i++ ){
    timdataget = TIME_UP;
    timrtc = TIME_UP;
    mainRtc();
    mainApp();
    delay(1000);
  }

  WiFi.config(ip);
  status = WiFi.beginAP(ssid, pass);
  if (status != WL_AP_LISTENING) {
    Serial.println("Creating access point failed");
    // don't continue
    while (true);
  } 

  server.begin();
  Serial.println("Init End!");
}

void loop() {

  mainTimer();
  mainApp();
  mainWifi();
  mainRtc();
  rcvdatechk();
}

void mainWifi(void){

  WiFiClient client = server.available();
  if (!client) return;

  String req = client.readStringUntil('\r');
  client.readStringUntil('\n');
  Serial.println(req);

  if (req.startsWith("GET / HTTP")) {
    //HTMLファイルの読み出し
    File myfile = SD.open(htmpath);
    if (!myfile) {
      client.println("HTTP/1.1 404 Not Found\r\n");
      Serial.println("file doesn't exist.");
      return;
    }

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: text/html");
    client.println("Connection: close");
    client.println();

    while (myfile.available()){
      client.write(myfile.read());
    }

    myfile.close();
  }
  else if( req.startsWith("GET /data") ){
    //JSONファイルを送信
    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/json");
    client.println("Connection: close");
    client.println();
    sendJson(client);
  }
  else if (req.startsWith("GET " + jspath) ) {
    //JavaScriptファイルの読み出し
    File myfile = SD.open(jspath);

    if (!myfile) {
      Serial.println("file doesn't exist.");
      return;
    }

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/javascript");
    client.println("Connection: close");
    client.println();

    uint8_t buf[256];
    while (true) {
      int n = myfile.read(buf, sizeof(buf));
      if (n <= 0) break;
      client.write(buf, n);
    }

    myfile.close();
  }
  else if (req.startsWith("GET " + chartpath) ) {
    //chart.jsファイルの読み出し
    File myfile = SD.open(chartpath);
    
    if (!myfile) {
      Serial.println("file doesn't exist.");
      return;
    }

    client.println("HTTP/1.1 200 OK");
    client.println("Content-Type: application/javascript");
    client.println("Connection: close");
    client.println();

    uint8_t buf[256];
    while (true) {
      int n = myfile.read(buf, sizeof(buf));
      if (n <= 0) break;
      client.write(buf, n);
    }
    Serial.println("chart end");
    myfile.close();
  }
  else {
    client.println("HTTP/1.1 404 Not Found\r\n");
  }

  client.stop();
}

/* JSONデータの生成 */
void sendJson(WiFiClient &client){
  int16_t rp;
  uint16_t i;
  char buf[6];
  String str="";

  rp = meas.wp; //wp更新後なので最古のデータとなる
  client.print("{\"t\":[");
  for( i = 0; i < CHART_SZ; i++){
    sprintf(buf, "%02d:%02d", meas.t[rp].getMinutes(), meas.t[rp].getSeconds());
    str = "\"" + String(buf) + "\"";
    client.print(str);

    if( ++rp >= CHART_SZ ){
      rp = 0;
    }
    if (i < CHART_SZ - 1) client.print(",");
  }

  rp = meas.wp; //wp更新後なので最古のデータとなる
  client.print("],\"v\":[");
  for( i = 0; i < CHART_SZ; i++){
    client.print(meas.value[rp]);

    if( ++rp >= CHART_SZ ){
      rp = 0;
    }

    if (i < CHART_SZ - 1) client.print(",");
  }
  client.print("]}");

}

void mainApp(void){
  uint16_t sum;
  uint16_t i;

  if( timdataget == TIME_UP){
    timdataget = TIM_MEAS;
    soil.buf[soil.wp] = analogRead(A0);

    if(++soil.wp >= MEAS_MAX){
      soil.wp = 0;
    }

    sum = 0;
    for( i =0; i < MEAS_MAX; i++){
      sum += soil.buf[i];
    }
    soil.humid = sum >> 3;
    meas.t[meas.wp] = now;
    //meas.time[meas.wp] = now.getUnixTime();
    meas.value[meas.wp] = soil.humid;

    if( ++meas.wp >= CHART_SZ ){
      meas.wp = 0;
    }
  }
}
/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt >= BASE_CNT ){
    beforetimCnt = millis();

    if( timdataget > TIME_UP ){
      --timdataget;
    }

    if( timrtc > TIME_UP ){
      --timrtc;
    }

    if( timrcv > TIME_UP ){
      timrcv--;
    }
  }
}

/* メイン処理関数 */
void mainRtc(void){

  while(Serial.available()){
    rcv.buf[rcv.wp] = Serial.read();

    if( ++rcv.wp >= RING_SZ ){
      rcv.wp = 0;
    }
  }

  if( timrtc == TIME_UP ){
    timrtc = TIM_RTC;
    RTC.getTime(now);
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

/* 受信データから時刻データを生成 */
void rcvdatechk(void){
  int8_t  rxsz;
  uint8_t allsz;
  uint8_t rp = rcv.rp;  
  uint8_t i;
  uint8_t dat[3];

  if( timrcv == TIME_UP){
    timrcv = TIME_OFF;
    ReadPointerAdd();    
  }

  rxsz = rcv.wp - rcv.rp; //受信データ数の算出
    
  if( rxsz < 0 ){
    rxsz = rxsz + sizeof(rcv.buf);
  }
            
  if( rxsz == 0 ){
    timrcv = TIME_OFF;
  }
  else{
    if( timrcv == TIME_OFF ){
      timrcv = TIM_RX_WAIT;
    }

    if( rxsz >= 3 ){
      for( i = 0; i < 3; i++){//データサイズ算出のため仮おき
        dat[i] = rcv.buf[ rp ];
        if(++rp >= sizeof(rcv.buf) ) rp = 0;
      }

      if( dat[0] == 'R' && dat[1] == 'T' && dat[2] == 'C'){
        allsz = sizeof(str) + OFFSET - 1;
        
        if( rxsz >= allsz){
          timrcv = TIME_OFF;
                          
          for(i=0; i < sizeof(Rcvdata); i++){
            Rcvdata[i] = 0;
          }
                          
          for( i=0; i < allsz; i++ ){
            Rcvdata[i] = rcv.buf[rcv.rp];
            ReadPointerAdd();
          }

          if( chkdata() == false ){
            Serial.println("RTCSET NG!");
          }
        }
      }
    }
  }
}

/* 読み込み位置の更新 */
void ReadPointerAdd(void){
    
  if(++rcv.rp >= sizeof(rcv.buf) ){
    rcv.rp = 0;
  }
}

/* 時刻のチェック */
bool chkdata(void){
  int d;
  Month m;
  int y;
  int h;
  int min;
  int s;
  DayOfWeek dotw;
  SaveLight su;
  bool ret = false;

  //RTC2025-08-10-00-12-00-00
  y = strtol((const char*)&Rcvdata[3], NULL, 10);
  m = (Month)(strtol((const char*)&Rcvdata[8], NULL, 10)-1);
  d = strtol((const char*)&Rcvdata[11], NULL, 10);
  dotw = (DayOfWeek)strtol((const char*)&Rcvdata[14], NULL, 10);
  h = strtol((const char*)&Rcvdata[17], NULL, 10);
  min = strtol((const char*)&Rcvdata[20], NULL, 10);
  s = strtol((const char*)&Rcvdata[23], NULL, 10);
  
  if( y >= 2000 && y < 2100 ){
    if( m >= Month::JANUARY && m <= Month::DECEMBER ){
      if( d >= 1 && d <= 31 ){
        if( h >= 0 && h <= 23 ){
          if( min >= 0 && min <=59 ){
            if( s >= 0 && s <= 59 ){
              if( dotw >= DayOfWeek::SUNDAY && dotw <= DayOfWeek::SATURDAY ){
                RTCTime setT(d, m, y, h, min, s, dotw, SaveLight::SAVING_TIME_INACTIVE);
                RTC.setTime(setT);
                Serial.println("RTCSET OK!");
                ret = true;
              }
            }
          }
        }
      }
    }
  }

  return ret;
}

htmlファイル(index.htm)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>土壌センサーの動作確認</title>

    <!-- Chart.js(SDカードに置いた UMD 版) -->
    <script src="chart.js"></script>

    <!-- 自作の JavaScript(fetch + Chart.js 設定) -->
    <script src="main.js" defer></script>
</head>
<body>
    <h1>Maker soil Moisture sensor</h1>
    <h2>最新のデータ:
        <code id="latest-time">----</code>
        <code id="latest-value">----</code>
    </h2>

    <div style="height: 900px; width: 800px; margin: auto;">
        <canvas id="soil-chart" class="chart"></canvas>
    </div>
</body>
</html>

JavaScriptファイル(main.js)

let chart;

async function updateChart(){
    const res = await fetch('/data');
    const obj = await res.json();
    const times = obj.t.map(t => new Date(t * 1000));
    const values = obj.v;

    document.getElementById("latest-time").innerHTML = times[times.length -1];
    document.getElementById("latest-value").innerHTML = values[values.length -1];
   
    chart.data.labels = times;
    chart.data.datasets[0].data = values;
    chart.update();
}

/* Chartのグラフを生成 */
window.onload = () => {
    const ctx = document.getElementById("soil-chart");

    chart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [{
                label: '土壌水分',
                data: [],
                borderColor: 'blue', //線の色
                backgroundColor: 'red', //下を塗りつぶした時の色
                fill: false, //プロット点から下を塗りつぶす
                borderWidth: 1, //線の幅
                pointRadius: 1, //プロットした点の大きさ
                pointHoverBorderWidth: 1, //プロットした点を選択した時に大きく表示するサイズ
                pointStyle: 'triangle', //プロットした点の表示(△)
                tension: 0.3 //線の伸縮
            }]
        },
        options: {
            responsive: true,
            plugins: { 
                title: {
                    display: true,
                    text: '土壌水分の変化',
                }
            },
            scales: {
                x: {
                    type: 'time',
                    time: {
                        unit: 'minute'
                    }
                },
                y: {
                    min: 0,
                    max: 1000,
                }
            }
        }
    });
    
    updateChart();
    setInterval(updateChart, 5000);
};

ソースコードの内容をコピーしてファイルをSDカードに作成指定使用してください。ファイル名は任意でもよいですが、8.3形式のファイル名にする必要があります。

Chart.jsは冒頭で紹介しているリンクから取得してSDカードに保存してください。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Raspberry Pi Picoで学べるソフト開発

PR:外資系・IT業界などハイクラスの転職に強い【AXIS Agent】

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました