Created
April 8, 2026 20:15
-
-
Save bakueikozo/84dc8fb3d59af18271abb9350d220901 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <Arduino.h> | |
| #include <HX711.h> | |
| #include <WiFi.h> | |
| #include <WebServer.h> | |
| static constexpr int kHx711DoutPin = 5; | |
| static constexpr int kHx711SckPin = 18; | |
| // WiFi(自宅/社内APのSSID/PASSに書き換え) | |
| static const char* kWifiSsid = "***"; | |
| static const char* kWifiPass = "***"; | |
| static HX711 scale; | |
| static WebServer server(80); | |
| // 1秒平均(生値)の直近約1分を保存 | |
| static constexpr uint32_t kBucketMs = 1000; | |
| static constexpr size_t kHistorySeconds = 60; | |
| static long g_history[kHistorySeconds] = {}; | |
| static size_t g_histWrite = 0; // 次に書く位置(リング) | |
| static size_t g_histCount = 0; // 溜まっている個数(最大60) | |
| // アルゴリズム検証用: 1秒平均の直近1時間(3600点)を保持 | |
| static constexpr size_t kAlgoHistorySeconds = 3600; | |
| static long g_algoHistory[kAlgoHistorySeconds] = {}; | |
| static size_t g_algoWrite = 0; | |
| static size_t g_algoCount = 0; | |
| static uint32_t g_bucketStartMs = 0; | |
| static int64_t g_bucketSum = 0; | |
| static uint32_t g_bucketN = 0; | |
| static void pushHistory(long v) { | |
| g_history[g_histWrite] = v; | |
| g_histWrite = (g_histWrite + 1) % kHistorySeconds; | |
| if (g_histCount < kHistorySeconds) g_histCount++; | |
| } | |
| static void pushAlgoHistory(long v) { | |
| g_algoHistory[g_algoWrite] = v; | |
| g_algoWrite = (g_algoWrite + 1) % kAlgoHistorySeconds; | |
| if (g_algoCount < kAlgoHistorySeconds) g_algoCount++; | |
| } | |
| struct AnalyzeParams { | |
| long thr = 500; // 安定判定: max-min <= thr | |
| long mthr = 200; // 安定区間の結合: mean差 <= mthr なら間のピークを無視して結合 | |
| size_t win = 10; // 判定窓(秒) | |
| size_t hold = 2; // 安定開始/復帰に必要な連続安定秒数 | |
| size_t seconds = 600; // 解析対象(直近N秒) | |
| }; | |
| static AnalyzeParams readAnalyzeParams() { | |
| AnalyzeParams p; | |
| if (server.hasArg("thr")) p.thr = server.arg("thr").toInt(); | |
| if (server.hasArg("mthr")) p.mthr = server.arg("mthr").toInt(); | |
| if (server.hasArg("win")) p.win = (size_t)server.arg("win").toInt(); | |
| if (server.hasArg("hold")) p.hold = (size_t)server.arg("hold").toInt(); | |
| if (server.hasArg("sec")) p.seconds = (size_t)server.arg("sec").toInt(); | |
| if (p.thr < 0) p.thr = -p.thr; | |
| if (p.mthr < 0) p.mthr = -p.mthr; | |
| if (p.win < 2) p.win = 2; | |
| if (p.hold < 1) p.hold = 1; | |
| if (p.seconds < 1) p.seconds = 1; | |
| if (p.seconds > kAlgoHistorySeconds) p.seconds = kAlgoHistorySeconds; | |
| return p; | |
| } | |
| static void handleRoot() { | |
| // CDNのChart.jsで簡単に折れ線表示 | |
| const char* html = R"HTML( | |
| <!doctype html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>HX711 raw (last 180s)</title> | |
| <style> | |
| body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 16px; } | |
| .row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; } | |
| button { padding: 8px 12px; } | |
| #wrap { max-width: 1000px; } | |
| #analysis { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; } | |
| #lists { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 8px; } | |
| table { border-collapse: collapse; width: 100%; } | |
| th, td { border: 1px solid #ddd; padding: 6px 8px; font-size: 12px; } | |
| th { background: #f6f6f6; text-align: left; } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> | |
| </head> | |
| <body> | |
| <div id="wrap"> | |
| <h2>HX711 raw(直近180秒の1秒平均)</h2> | |
| <div class="row"> | |
| <button id="reload">更新</button> | |
| <div id="meta"></div> | |
| </div> | |
| <canvas id="c"></canvas> | |
| <h3>解析(/analyze)</h3> | |
| <div class="row"> | |
| <div id="anMeta"></div> | |
| </div> | |
| <div id="analysis"></div> | |
| <div id="lists"> | |
| <div> | |
| <h3>安定区間リスト</h3> | |
| <div id="stableList"></div> | |
| </div> | |
| <div> | |
| <h3>ピークリスト</h3> | |
| <div id="peakList"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let chart; | |
| let yBase = null; // 初期値(最初に取得したraw) | |
| const SEC = 180; | |
| const ANALYZE_QUERY = `/analyze?thr=500&mthr=200&win=10&hold=2&sec=${SEC}`; | |
| function buildNullArray(n) { | |
| const a = new Array(n); | |
| for (let i = 0; i < n; i++) a[i] = null; | |
| return a; | |
| } | |
| function renderTable(targetId, headers, rows) { | |
| const el = document.getElementById(targetId); | |
| if (!rows.length) { | |
| el.innerHTML = '<div>(none)</div>'; | |
| return; | |
| } | |
| const thead = `<thead><tr>${headers.map(h => `<th>${h}</th>`).join('')}</tr></thead>`; | |
| const tbody = `<tbody>${rows.map(r => `<tr>${r.map(c => `<td>${c}</td>`).join('')}</tr>`).join('')}</tbody>`; | |
| el.innerHTML = `<table>${thead}${tbody}</table>`; | |
| } | |
| function ensureChart(labels, data) { | |
| const ctx = document.getElementById('c').getContext('2d'); | |
| if (!chart) { | |
| chart = new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels, | |
| datasets: [{ | |
| label: 'raw (avg/1s)', | |
| data, | |
| borderWidth: 2, | |
| pointRadius: 0, | |
| tension: 0.15 | |
| }, | |
| { | |
| label: 'stable mean', | |
| data: buildNullArray(data.length), | |
| borderWidth: 3, | |
| pointRadius: 0, | |
| tension: 0, | |
| borderColor: 'rgba(46, 125, 50, 0.9)' | |
| }, | |
| { | |
| type: 'scatter', | |
| label: 'peak max', | |
| data: [], | |
| pointRadius: 4, | |
| pointBackgroundColor: 'rgba(211, 47, 47, 0.95)' | |
| }, | |
| { | |
| type: 'scatter', | |
| label: 'peak min', | |
| data: [], | |
| pointRadius: 4, | |
| pointBackgroundColor: 'rgba(25, 118, 210, 0.95)' | |
| }] | |
| }, | |
| options: { | |
| animation: false, | |
| responsive: true, | |
| scales: { | |
| x: { title: { display: true, text: 'seconds ago' } }, | |
| y: { | |
| title: { display: true, text: 'raw' }, | |
| min: (yBase === null) ? undefined : (yBase - 50000), | |
| max: (yBase === null) ? undefined : (yBase + 50000) | |
| } | |
| } | |
| } | |
| }); | |
| } else { | |
| chart.data.labels = labels; | |
| chart.data.datasets[0].data = data; | |
| // overlayはloadAnalyzeで毎回作り直すので、ここではクリアだけしておく | |
| chart.data.datasets[1].data = buildNullArray(data.length); | |
| chart.data.datasets[2].data = []; | |
| chart.data.datasets[3].data = []; | |
| chart.options.scales.y.min = (yBase === null) ? undefined : (yBase - 50000); | |
| chart.options.scales.y.max = (yBase === null) ? undefined : (yBase + 50000); | |
| chart.update('none'); | |
| } | |
| } | |
| async function load() { | |
| const r = await fetch(`/series?sec=${SEC}`, { cache: 'no-store' }); | |
| const j = await r.json(); | |
| const labels = j.points.map(p => p.t); | |
| const data = j.points.map(p => p.v); | |
| document.getElementById('meta').textContent = | |
| `points=${j.points.length} age_sec=${j.age_sec}`; | |
| if (yBase === null && data.length) { | |
| yBase = data[data.length - 1]; | |
| } | |
| ensureChart(labels, data); | |
| } | |
| async function loadAnalyze() { | |
| // パラメータは必要ならここで変更(thr/win/hold/sec) | |
| const r = await fetch(ANALYZE_QUERY, { cache: 'no-store' }); | |
| const j = await r.json(); | |
| if (j.error) { | |
| document.getElementById('anMeta').textContent = `error=${j.error}`; | |
| document.getElementById('analysis').textContent = ''; | |
| return; | |
| } | |
| document.getElementById('anMeta').textContent = | |
| `stable=${j.count?.stable ?? 0} peaks=${j.count?.peaks ?? 0} (sec=${j.params?.sec})`; | |
| const lastStable = (j.stable && j.stable.length) ? j.stable[j.stable.length - 1] : null; | |
| const lastPeak = (j.peaks && j.peaks.length) ? j.peaks[j.peaks.length - 1] : null; | |
| const lines = []; | |
| if (lastStable) { | |
| lines.push(`last_stable: t=[${lastStable.t_start}..${lastStable.t_end}] mean=${lastStable.mean}`); | |
| } else { | |
| lines.push('last_stable: (none)'); | |
| } | |
| if (lastPeak) { | |
| lines.push(`last_peak: t=[${lastPeak.t_start}..${lastPeak.t_end}] max=${lastPeak.max} (t=${lastPeak.t_max}) min=${lastPeak.min} (t=${lastPeak.t_min})`); | |
| } else { | |
| lines.push('last_peak: (none)'); | |
| } | |
| document.getElementById('analysis').textContent = lines.join('\n'); | |
| // グラフに安定区間・ピークを重ね描き | |
| if (chart) { | |
| const labelToIndex = new Map(chart.data.labels.map((t, i) => [t, i])); | |
| const stableLine = buildNullArray(chart.data.labels.length); | |
| for (const s of (j.stable || [])) { | |
| const i0 = labelToIndex.get(s.t_start); | |
| const i1 = labelToIndex.get(s.t_end); | |
| if (i0 === undefined || i1 === undefined) continue; | |
| const a = Math.min(i0, i1); | |
| const b = Math.max(i0, i1); | |
| for (let i = a; i <= b; i++) stableLine[i] = s.mean; | |
| } | |
| chart.data.datasets[1].data = stableLine; | |
| const maxPts = []; | |
| const minPts = []; | |
| for (const p of (j.peaks || [])) { | |
| maxPts.push({ x: p.t_max, y: p.max }); | |
| minPts.push({ x: p.t_min, y: p.min }); | |
| } | |
| chart.data.datasets[2].data = maxPts; | |
| chart.data.datasets[3].data = minPts; | |
| chart.update('none'); | |
| } | |
| // リスト表示 | |
| const stableRows = (j.stable || []).map(s => [ | |
| `${s.t_start}..${s.t_end}`, | |
| String(s.mean), | |
| `${s.start}..${s.end}` | |
| ]); | |
| renderTable('stableList', ['t(sec ago)', 'mean', 'idx'], stableRows); | |
| const peakRows = (j.peaks || []).map(p => [ | |
| `${p.t_start}..${p.t_end}`, | |
| `max=${p.max} @t=${p.t_max}`, | |
| `min=${p.min} @t=${p.t_min}`, | |
| `${p.start}..${p.end}` | |
| ]); | |
| renderTable('peakList', ['t(sec ago)', 'max', 'min', 'idx'], peakRows); | |
| } | |
| async function refreshAll() { | |
| await load(); | |
| await loadAnalyze(); | |
| } | |
| document.getElementById('reload').addEventListener('click', refreshAll); | |
| refreshAll(); | |
| // 5秒ごとに「測定値+解析」を同期更新 | |
| setInterval(refreshAll, 5000); | |
| </script> | |
| </body> | |
| </html> | |
| )HTML"; | |
| server.send(200, "text/html; charset=utf-8", html); | |
| } | |
| static void handleData() { | |
| // JSON: { points: [ {t:-59, v:123}, ... {t:0, v:456} ], age_sec:0 } | |
| // tは「何秒前か」(0が最新) | |
| String out; | |
| out.reserve(4096); | |
| out += "{\"points\":["; | |
| const size_t n = g_histCount; | |
| // リングの読み出し(古い→新しい) | |
| const size_t start = (g_histWrite + kHistorySeconds - n) % kHistorySeconds; | |
| for (size_t i = 0; i < n; i++) { | |
| const size_t idx = (start + i) % kHistorySeconds; | |
| const long v = g_history[idx]; | |
| const long t = (long)i - (long)(n - 1); // -(n-1) ... 0 | |
| if (i) out += ","; | |
| out += "{\"t\":"; | |
| out += t; | |
| out += ",\"v\":"; | |
| out += v; | |
| out += "}"; | |
| } | |
| out += "],\"age_sec\":0}"; | |
| server.send(200, "application/json; charset=utf-8", out); | |
| } | |
| static void handleSeries() { | |
| // 解析/可視化用: 直近sec秒の1秒平均列(生値) | |
| size_t sec = 180; | |
| if (server.hasArg("sec")) sec = (size_t)server.arg("sec").toInt(); | |
| if (sec < 1) sec = 1; | |
| if (sec > kAlgoHistorySeconds) sec = kAlgoHistorySeconds; | |
| const size_t available = g_algoCount; | |
| const size_t n = (available < sec) ? available : sec; | |
| String out; | |
| out.reserve(8192); | |
| out += "{\"points\":["; | |
| const size_t start = (g_algoWrite + kAlgoHistorySeconds - n) % kAlgoHistorySeconds; | |
| for (size_t i = 0; i < n; i++) { | |
| const long v = g_algoHistory[(start + i) % kAlgoHistorySeconds]; | |
| const long t = (long)i - (long)(n - 1); // -(n-1) ... 0 | |
| if (i) out += ","; | |
| out += "{\"t\":"; | |
| out += t; | |
| out += ",\"v\":"; | |
| out += v; | |
| out += "}"; | |
| } | |
| out += "],\"age_sec\":0}"; | |
| server.send(200, "application/json; charset=utf-8", out); | |
| } | |
| // 直近N秒の1秒平均列(g_algoHistory)を解析して、安定区間と変動区間のピークを返す | |
| // 安定判定: | |
| // - 直近win点の max-min <= thr を「その秒は安定」とする | |
| // - その安定が hold 秒連続したら「安定区間開始」 | |
| // - 安定が崩れたら「変動区間」へ移行し、その間の max/min と出現位置を追跡 | |
| // - 再び hold 秒連続安定したら、その直前の変動区間のピークを確定し、新たな安定区間へ | |
| static void handleAnalyze() { | |
| const AnalyzeParams p = readAnalyzeParams(); | |
| const size_t available = g_algoCount; | |
| const size_t n = (available < p.seconds) ? available : p.seconds; | |
| if (n < (p.win + p.hold)) { | |
| server.send(200, "application/json; charset=utf-8", | |
| "{\"error\":\"not_enough_data\"}"); | |
| return; | |
| } | |
| // 古い→新しいの配列へ展開(最大3600) | |
| static long series[kAlgoHistorySeconds]; | |
| const size_t start = (g_algoWrite + kAlgoHistorySeconds - n) % kAlgoHistorySeconds; | |
| for (size_t i = 0; i < n; i++) { | |
| series[i] = g_algoHistory[(start + i) % kAlgoHistorySeconds]; | |
| } | |
| auto idxToT = [&](size_t idx) -> long { | |
| // 解析窓の中で「何秒前か」: oldest=-(n-1), newest=0 | |
| return (long)idx - (long)(n - 1); | |
| }; | |
| auto isStableAt = [&](size_t i) -> bool { | |
| // i を右端とするwin幅の安定判定(i >= win-1 が必要) | |
| long mx = series[i]; | |
| long mn = series[i]; | |
| for (size_t k = i + 1 - p.win; k <= i; k++) { | |
| const long v = series[k]; | |
| if (v > mx) mx = v; | |
| if (v < mn) mn = v; | |
| } | |
| return (mx - mn) <= p.thr; | |
| }; | |
| // stableSegments: [a,b] と mean | |
| static size_t segA[512]; | |
| static size_t segB[512]; | |
| static long segMean[512]; | |
| size_t segCount = 0; | |
| enum class State : uint8_t { SeekingStable, InStable, InUnstable }; | |
| State st = State::SeekingStable; | |
| size_t run = 0; | |
| size_t a = 0; | |
| int64_t sum = 0; | |
| size_t cnt = 0; | |
| for (size_t i = 0; i < n; i++) { | |
| const bool stf = (i >= (p.win - 1)) ? isStableAt(i) : false; | |
| if (stf) run++; else run = 0; | |
| if (st == State::SeekingStable) { | |
| if (stf && run == p.hold) { | |
| a = i - p.hold + 1; | |
| st = State::InStable; | |
| sum = 0; | |
| cnt = 0; | |
| for (size_t k = a; k <= i; k++) { | |
| sum += (int64_t)series[k]; | |
| cnt++; | |
| } | |
| } | |
| continue; | |
| } | |
| if (st == State::InStable) { | |
| if (stf) { | |
| sum += (int64_t)series[i]; | |
| cnt++; | |
| } else { | |
| if (segCount < 512) { | |
| segA[segCount] = a; | |
| segB[segCount] = i - 1; | |
| segMean[segCount] = (cnt > 0) ? (long)(sum / (int64_t)cnt) : 0; | |
| segCount++; | |
| } | |
| st = State::InUnstable; | |
| } | |
| continue; | |
| } | |
| // InUnstable | |
| if (stf && run == p.hold) { | |
| a = i - p.hold + 1; | |
| st = State::InStable; | |
| sum = 0; | |
| cnt = 0; | |
| for (size_t k = a; k <= i; k++) { | |
| sum += (int64_t)series[k]; | |
| cnt++; | |
| } | |
| } | |
| } | |
| if (st == State::InStable && segCount < 512) { | |
| segA[segCount] = a; | |
| segB[segCount] = n - 1; | |
| segMean[segCount] = (cnt > 0) ? (long)(sum / (int64_t)cnt) : 0; | |
| segCount++; | |
| } | |
| // meanがほぼ同じ安定区間は「同じ安定区間が揺れた」とみなして結合する | |
| // - abs(mean差) <= mthr なら結合(間のピークは「無かったことにする」) | |
| static size_t mA[512]; | |
| static size_t mB[512]; | |
| static long mMean[512]; | |
| size_t mCount = 0; | |
| for (size_t i = 0; i < segCount; i++) { | |
| if (mCount == 0) { | |
| mA[0] = segA[i]; | |
| mB[0] = segB[i]; | |
| mMean[0] = segMean[i]; | |
| mCount = 1; | |
| continue; | |
| } | |
| const long prevMean = mMean[mCount - 1]; | |
| const long curMean = segMean[i]; | |
| const long diff = (prevMean >= curMean) ? (prevMean - curMean) : (curMean - prevMean); | |
| if (diff <= p.mthr) { | |
| // 結合(平均は長さで重み付け) | |
| const size_t prevLen = (mB[mCount - 1] >= mA[mCount - 1]) ? (mB[mCount - 1] - mA[mCount - 1] + 1) : 0; | |
| const size_t curLen = (segB[i] >= segA[i]) ? (segB[i] - segA[i] + 1) : 0; | |
| const int64_t wsum = (int64_t)prevMean * (int64_t)prevLen + (int64_t)curMean * (int64_t)curLen; | |
| const size_t wlen = prevLen + curLen; | |
| mB[mCount - 1] = segB[i]; | |
| mMean[mCount - 1] = (wlen > 0) ? (long)(wsum / (int64_t)wlen) : prevMean; | |
| } else { | |
| if (mCount < 512) { | |
| mA[mCount] = segA[i]; | |
| mB[mCount] = segB[i]; | |
| mMean[mCount] = segMean[i]; | |
| mCount++; | |
| } | |
| } | |
| } | |
| String out; | |
| out.reserve(16384); | |
| out += "{"; | |
| out += "\"params\":{"; | |
| out += "\"thr\":"; out += p.thr; | |
| out += ",\"mthr\":"; out += p.mthr; | |
| out += ",\"win\":"; out += (long)p.win; | |
| out += ",\"hold\":"; out += (long)p.hold; | |
| out += ",\"sec\":"; out += (long)n; | |
| out += "},"; | |
| out += "\"stable\":["; | |
| for (size_t i = 0; i < mCount; i++) { | |
| if (i) out += ","; | |
| out += "{\"start\":"; | |
| out += (long)mA[i]; | |
| out += ",\"end\":"; | |
| out += (long)mB[i]; | |
| out += ",\"t_start\":"; | |
| out += idxToT(mA[i]); | |
| out += ",\"t_end\":"; | |
| out += idxToT(mB[i]); | |
| out += ",\"mean\":"; | |
| out += mMean[i]; | |
| out += "}"; | |
| } | |
| out += "],"; | |
| // 変動区間: stable[k] の後〜 stable[k+1] の前 → その間の max/min を「ピーク」として記録 | |
| out += "\"peaks\":["; | |
| size_t peakCount = 0; | |
| for (size_t k = 0; k + 1 < mCount; k++) { | |
| const size_t uStart = mB[k] + 1; | |
| const size_t uEnd = (mA[k + 1] > 0) ? (mA[k + 1] - 1) : 0; | |
| if (uStart >= n || uStart > uEnd) continue; | |
| long mx = series[uStart]; | |
| long mn = series[uStart]; | |
| size_t mxAt = uStart; | |
| size_t mnAt = uStart; | |
| for (size_t i = uStart; i <= uEnd; i++) { | |
| const long v = series[i]; | |
| if (v > mx) { | |
| mx = v; | |
| mxAt = i; | |
| } | |
| if (v < mn) { | |
| mn = v; | |
| mnAt = i; | |
| } | |
| } | |
| if (peakCount) out += ","; | |
| peakCount++; | |
| out += "{\"start\":"; | |
| out += (long)uStart; | |
| out += ",\"end\":"; | |
| out += (long)uEnd; | |
| out += ",\"t_start\":"; | |
| out += idxToT(uStart); | |
| out += ",\"t_end\":"; | |
| out += idxToT(uEnd); | |
| out += ",\"max\":"; | |
| out += mx; | |
| out += ",\"max_at\":"; | |
| out += (long)mxAt; | |
| out += ",\"t_max\":"; | |
| out += idxToT(mxAt); | |
| out += ",\"min\":"; | |
| out += mn; | |
| out += ",\"min_at\":"; | |
| out += (long)mnAt; | |
| out += ",\"t_min\":"; | |
| out += idxToT(mnAt); | |
| out += "}"; | |
| } | |
| out += "],"; | |
| out += "\"count\":{"; | |
| out += "\"stable\":"; | |
| out += (long)mCount; | |
| out += ",\"peaks\":"; | |
| out += (long)peakCount; | |
| out += "}"; | |
| out += "}"; | |
| server.send(200, "application/json; charset=utf-8", out); | |
| } | |
| void setup() { | |
| Serial.begin(115200); | |
| delay(500); | |
| scale.begin(kHx711DoutPin, kHx711SckPin); | |
| if (!scale.is_ready()) { | |
| Serial.println("HX711: 応答なし(配線・電源を確認)"); | |
| for (;;) { | |
| delay(1000); | |
| } | |
| } | |
| scale.tare(10); | |
| Serial.println("HX711: 準備完了(生値 raw を扱う)"); | |
| WiFi.mode(WIFI_STA); | |
| WiFi.begin(kWifiSsid, kWifiPass); | |
| Serial.print("WiFi: connecting"); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| delay(300); | |
| Serial.print("."); | |
| } | |
| Serial.println(); | |
| Serial.print("WiFi: connected, IP="); | |
| Serial.println(WiFi.localIP()); | |
| server.on("/", handleRoot); | |
| server.on("/data", handleData); | |
| server.on("/series", handleSeries); | |
| server.on("/analyze", handleAnalyze); | |
| server.begin(); | |
| Serial.println("HTTP: started (open '/' in browser)"); | |
| g_bucketStartMs = millis(); | |
| } | |
| void loop() { | |
| server.handleClient(); | |
| if (scale.is_ready()) { | |
| // 生値をできるだけ多く集めて、1秒ごとに平均を確定する | |
| const long raw = scale.read(); | |
| g_bucketSum += (int64_t)raw; | |
| g_bucketN++; | |
| } | |
| const uint32_t now = millis(); | |
| if ((uint32_t)(now - g_bucketStartMs) >= kBucketMs) { | |
| if (g_bucketN > 0) { | |
| const long avg = (long)(g_bucketSum / (int64_t)g_bucketN); | |
| pushHistory(avg); | |
| pushAlgoHistory(avg); | |
| Serial.print("raw_avg_1s: "); | |
| Serial.println(avg); | |
| } | |
| g_bucketStartMs += kBucketMs; | |
| g_bucketSum = 0; | |
| g_bucketN = 0; | |
| } | |
| delay(2); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment