Arduino Uno 事前検証過程(その3の1)でのEEPROM読み出し問題の回避策

暫定でEEPROMにログ保存して、読み出しを検証しようかとEEPROMをavrdudeコマンドで読みだそうとしたが、何をとち狂ったかFlashメモリの冒頭部分を読みだしていることに気が付いた。回避策もないようなので、Arduino スケッチ(Arduino に書き込むプログラム)とPowerShell実装の組み合わせで対処することにした。
 Arduinoの仕組みのせいか、なかなかめんどくさい。


目的

Arduino UNO 上の EEPROM 内容を確実に取得する。
avrdude(ブートローダ経由)では EEPROM/Flash の取り違えや挙動差があるため、
Arduino 自身に EEPROM を読ませて PC に転送する方式を採用する。


全体構成

  • Arduino 側
    EEPROM → Intel HEX 形式 → UART(USB CDC)送信
  • PC 側
    UART 受信 → 同期マーカー判定 → HEX ファイルとして保存

Arduino 側の動作原理

  1. EEPROM.read() を用いて EEPROM アドレス空間を順次走査
  2. 読み出したバイト列を Intel HEX レコードとして整形
  • アドレス
  • データ長
  • チェックサムを含む
  1. UART(Serial)へ ASCIIテキストとして出力

同期制御

  • データ送信の前後に明示的なマーカーを出力
EEPROM_BEGIN
:10....
:10....
...
:00000001FF
EEPROM_END

これにより PC 側は、

  • 受信開始点
  • 正常終了点
    を確実に判定できる。

再送設計

  • 送信処理は一定周期(例:20秒)で繰り返す
  • PC 側の起動タイミングや USB 再接続に依存しない

PC(PowerShell)側の動作原理

  1. System.IO.Ports.SerialPort で COM ポートをオープン
  2. 行単位(ReadLine())で受信
  3. 受信内容を以下のルールで処理
受信内容動作
EEPROM_BEGIN書き込み開始
: で始まる行HEX レコードとして保存
EEPROM_END書き込み終了・クローズ
  1. ファイルは スクリプトの配置ディレクトリに生成
    $PSScriptRoot を基準に絶対パス化)

この方式のポイント

  • EEPROM と Flash の経路を完全に分離
  • 読み出し主体は MCU 本体
  • PC 側は「単なる受信・保存」
  • ブートローダ/avrdude 非依存
  • テキスト転送
  • ロジアナ/ターミナルで可視
  • 途中破損の検出が容易
  • 再送前提設計
  • 信頼性は通信層ではなく運用で確保


Arduinoスケッチ(EEPROMreader.ino)

#include <EEPROM.h>

const unsigned long INTERVAL = 20000;

uint8_t checksum(uint8_t *buf, uint8_t len) {
uint16_t sum = 0;
for (uint8_t i = 0; i < len; i++) sum += buf[i];
return (uint8_t)(-sum);
}

void sendHexLine(uint16_t addr) {
uint8_t rec[5 + 16];
rec[0] = 16; // length
rec[1] = addr >> 8;
rec[2] = addr & 0xFF;
rec[3] = 0x00; // record type

for (int i = 0; i < 16; i++) {
rec[4 + i] = EEPROM.read(addr + i);
}

uint8_t cs = checksum(rec, 4 + 16);

Serial.print(':');
for (int i = 0; i < 4 + 16; i++) {
if (rec[i] < 0x10) Serial.print('0');
Serial.print(rec[i], HEX);
}
if (cs < 0x10) Serial.print('0');
Serial.println(cs, HEX);
}


void setup() {
Serial.begin(115200);
while (!Serial) { ; }
}

void loop() {
Serial.println("EEPROM_BEGIN");
for (uint16_t addr = 0; addr < 1024; addr += 16) {
sendHexLine(addr);
}
Serial.println(":00000001FF"); // EOF
Serial.println("EEPROM_END");

digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(200); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(INTERVAL);
}

PowerShell(eepromGet.ps1)

$port = New-Object System.IO.Ports.SerialPort COM4,115200,None,8,one
$port.ReadTimeout = 5000
$port.Open()

#$outFile = "eeprom.hex"
#$sw = New-Object System.IO.StreamWriter $outFile
$outFile = Join-Path $PSScriptRoot "eeprom.hex"
$sw = New-Object System.IO.StreamWriter($outFile)
$sw.AutoFlush = $true

Write-Host "Waiting for EEPROM_BEGIN..."

# ===== BEGIN待ち =====
while ($true) {
try {
$line = $port.ReadLine().Trim()
if ($line -eq "EEPROM_BEGIN") {
Write-Host "BEGIN received"
break
}
} catch {
# timeout → 何もしないで待ち続ける
}
}

# ===== データ受信 =====
while ($true) {
try {
$line = $port.ReadLine().Trim()

if ($line -eq "EEPROM_END") {
Write-Host "END received"
break
}

$sw.WriteLine($line)
Write-Host $line

} catch {
# timeout 継続
}
}

$sw.Close()
$port.Close()

Write-Host "DONE"

初期状態で得られる結果

eeprom.hex

:1000000000000000000000000000000000000000F0
:1000100000000000000000000000000000000000E0
:1000200000000000000000000000000000000000D0
:1000300000000000000000000000000000000000C0
:1000400000000000000000000000000000000000B0
:1000500000000000000000000000000000000000A0
:100060000000000000000000000000000000000090
:100070000000000000000000000000000000000080
:100080000000000000000000000000000000000070
:100090000000000000000000000000000000000060
:1000A0000000000000000000000000000000000050
:1000B0000000000000000000000000000000000040
:1000C0000000000000000000000000000000000030
:1000D0000000000000000000000000000000000020
:1000E0000000000000000000000000000000000010
:1000F0000000000000000000000000000000000000
:1001000000000000000000000000000000000000EF
:1001100000000000000000000000000000000000DF
:1001200000000000000000000000000000000000CF
:1001300000000000000000000000000000000000BF
:1001400000000000000000000000000000000000AF
:10015000000000000000000000000000000000009F
:10016000000000000000000000000000000000008F
:10017000000000000000000000000000000000007F
:10018000000000000000000000000000000000006F
:10019000000000000000000000000000000000005F
:1001A000000000000000000000000000000000004F
:1001B000000000000000000000000000000000003F
:1001C000000000000000000000000000000000002F
:1001D000000000000000000000000000000000001F
:1001E000000000000000000000000000000000000F
:1001F00000000000000000000000000000000000FF
:1002000000000000000000000000000000000000EE
:1002100000000000000000000000000000000000DE
:1002200000000000000000000000000000000000CE
:1002300000000000000000000000000000000000BE
:1002400000000000000000000000000000000000AE
:10025000000000000000000000000000000000009E
:10026000000000000000000000000000000000008E
:10027000000000000000000000000000000000007E
:10028000000000000000000000000000000000006E
:10029000000000000000000000000000000000005E
:1002A000000000000000000000000000000000004E
:1002B000000000000000000000000000000000003E
:1002C000000000000000000000000000000000002E
:1002D000000000000000000000000000000000001E
:1002E000000000000000000000000000000000000E
:1002F00000000000000000000000000000000000FE
:1003000000000000000000000000000000000000ED
:1003100000000000000000000000000000000000DD
:1003200000000000000000000000000000000000CD
:1003300000000000000000000000000000000000BD
:1003400000000000000000000000000000000000AD
:10035000000000000000000000000000000000009D
:10036000000000000000000000000000000000008D
:10037000000000000000000000000000000000007D
:10038000000000000000000000000000000000006D
:10039000000000000000000000000000000000005D
:1003A000000000000000000000000000000000004D
:1003B000000000000000000000000000000000003D
:1003C000000000000000000000000000000000002D
:1003D000000000000000000000000000000000001D
:1003E000000000000000000000000000000000000D
:1003F00000000000000000000000000000000000FD
:00000001FF

1回PowerShellを実行して一部しかデータ取得できなかった場合は、即座にもう一回実行すると、20秒後には全データを取得できます。

とりあえず、これで、avrdudeコマンドで読みだしたのとほぼ同じ内容が取れていると思います。 本番とほぼ同じデータ形式になるので、このデータで解析用の実装を作成しておけば、ほとんど改修なしで流用できるはずです。


関連記事

AE測定、長期間ロギングシステム構築 (その2) アナログ部 検証編 

的が遠いシステムを構築する場合、つまり多数のコンポーネントを組み合わせてシステムを構築するSI(システムインテグレーション)では、一歩一歩検証しながら作業を進めていく必要があります。そして、その検証には測定器や計測器(OSC)が必要です。そしてその妥当性を確認できる手法です。

 今回は特に、中古部品も活用して構築するので、その部品自体の妥当性も検証が必要でした、というか早めにやっておいてよかった。9VのACアダプタですが、以前に購入して使っていた変圧とプラグ切り替え機能付きのACアダプタを使う予定でした。しかし、事前にテスターで確認すると9Vに設定しても9Vにならないことが判明し、ACアダプタを追加調達しました。 切り替えで12Vになりっぱなしだったり、一瞬17Vになったり、他の部品を壊してしまう原因になっていたかもしれません。

検証用の機器

既存のデジタルマルチテスター:
  電圧や導通試験に使用。このテスター自体も年代物で怪しいところがあったので事前に電池などで妥当性検証しておきました。前述の、ACアダプタの問題はこのテスターで検証して確認しました。

新規オシロスコープ:
[112972]DSO Shell 200kHz (DSO150) 完成品
https://akizukidenshi.com/catalog/g/g112972/


  オシロスコープとしては激安です。どこまで使えるか分かりませんが調達してみました。別案として激安の中古品(オークション)という線もありましたが、今回は納期と妥当性を勘案して、この製品を採用してみました。

検証

検証過程で、いろいろ動作しない事象にぶつかり、修正を繰り返して、最後は下の画像や動画のとおりの信号検出の確認ができました。

まず、最初は、それらしい信号がでてこず、何度か確認と修正をしています。参考のため注意しておくべきチェック観点を書いておきます。
・基板への部品取り付けは、後戻りが大変なので十分に注意してやりましょう。
・配線、入力と出力が同じミニジャック
・配線、R,L2回路ある
・ICの向き。 ICソケットの向きは注意して半田付けしたが、IC挿すときも注意
・電源の入力などなど
・その他の接触不良

つぎに、それらしい信号が見えるようになったところで、不安定な信号を見つけてそれの対策をしました。接触不良が主な原因でした、なかでもオシロ付属のプローブが不安定な信号の主要因でした。仕方がないので、自前のBNCケーブル(無線用のもの)で、オシロと、アンプの出力を直結して対処しました。

 最後は、オシロの使い方ですね。 このオシロは、 安価なつくりに必要最小限の実装を詰め込んでいるので、ぱっと見操作が良くわからない。さらに説明書が簡単すぎですね。 この機種は初めてオシロを使う人にはわかりにくいかもしれません。オシロスコープとはどういうものかをそれなりの機種で理解したうえで(購入する)使うのが良いかもしれません。

検証動画

センサーを叩くと、検出した信号波形をオシロで捉えることができました。波形は約5kHzで、この素子の共振周波数とほぼ一致していました。

関連記事

AE測定、長期間ロギングシステム構築 (その1) アナログ部製作編 

AE測定、といっても….。狙いはAEつまり、100kHzレベルですが、なかなかそのレベルまでのものを用意するのは高額になってしまいます。そこで、できる限りそのレベルを目指した設計で、かつ低コストで実現できるシステムを目指すことにしました。
 高度なレベルと低コストをバランスを取って実現するために、「イベント信号波形の取得」ではなく「イベントが発生した時刻の取得」に特化することにしました。
もう一つ重要な要件として、「短時間で環境整備する必要がある」ということです。そこで、採用するシステム構成の方針を次のようにしました。

・比較的安価であること
・可能な限り、既製品を利用する
・短期間/短納期で入手できる部材を採用する
・検証しながら作業を進められるようにする
・既保有の部材や道具を利用する

採用部材(その1、アナログ部(プレアンプ))

直ぐに入手するために、国内に在庫があり即入手できるものから探しました。安価でも、中国からの船便となると2週間くらいかかってしまいます。そこで、30年ぶりくらいの部品通販での調達です。以前なら直接店舗で購入でしたが…。

■センサー

[104118]圧電スピーカー(圧電サウンダー)(13mm)PKM13EPYH4000-A0
https://akizukidenshi.com/catalog/g/g104118/
https://akizukidenshi.com/goodsaffix/murata-piezo-speaker-tape.pdf
10個買い

■アンプ

[112145]2回路入J-FET入力オペアンプ NJM072D  ←
https://akizukidenshi.com/catalog/g/g112145/

[112309]NJM4580DD使用ヘッドホンアンプキット
https://akizukidenshi.com/catalog/g/g112309/
このキットにNJM072D を置き換えて使う



[108853]3.5mmステレオミニプラグ⇔スクリュー端子台
https://akizukidenshi.com/catalog/g/g108853/

[108854]BNCオスコネクター⇔スクリュー端子
https://akizukidenshi.com/catalog/g/g108854/

■その他

電源は006Pの9電池でも動作するが、長期間ロギングの必要があるため、9VのACアダプターを使用します。
センサー、アンプ間の配線は既保有のアンテナ線などの廃材を使用します。半田やはんだごてなどは既保有なので省きます。

製作

とりあえず、アンプを製作、40年ぶりくらいのキットでの電子工作です。30分ほどの半田付け作業で完成しました。

左がセンサー代わりのピエゾです、とりあえず直結してます。アンプ基盤の右側が出力です。


以上で、アナログ部の製作は完了です。
そのあとは、アナログ部の動作検証を行う予定です。→(アナログ部検証編はこちら)そしてその後のAD変換でのデータロギングのプログミングを含む作業が待っています。多分これが一番大変でしょう。

関連記事

MCR-4TCでの温度測定について(Tタイプ熱電対使用、4か所測定)

前の記事に続いて、 MCR-4TCを使っての温度測定の話です。
2025/12/21から屋外測定を開始しました。(つぎも参考
4点の測定結果はつぎのとおり

T4
T4

昼間の時間帯の測定値ばらつきが大きく見えている部分がありますが、そのばらつきが大きいところは直射日光や風が当たっていた時間帯と思われます。全体的に想定通りの温度変化をしています。良い感じです。

次は、サンプルの測定結果です。1分に1回測定しています。以下はサンプルのCSVファイルです。 MCR-4TCで取得したファイルは、USB経由でWindows PCに回収できます。独自のバイナリ形式での保存ですが、Graphソフトで CSV形式での出力も可能です。次がそのファイルのサンプルです。


今後の測定分については、上記のようにzip化したCSVファイルをこの下に追加していく予定です。

20260112
20260112

関連記事

hsBoxで、ATOM Cam 2の映像をキャプチャしてNASに蓄積、さらにクラウドにアップロードする

先の記事「コンクリート劣化の原因調査」に関連して画像を撮影してアップロードする仕組みを構築しました。ここでは、構築までの手順を活用できる形で紹介していきます。事前準備がほぼ出来上がっているので意外と簡単に構築できました。

10分おきに自動更新している画像

※画像が更新されていない場合は、ブラウザキャッシュが効いていることがあります。F5キーで再読み込みするか、Ctrl+右クリックで画像を、別タブで開いてF5キーを押すと再読み込みされます。

キャッチ画像から作成した動画

動画化

このように、NASへの蓄積から動画生成や、クラウドへのアップロードも自動化できました。

使用した製品

10年前のノートPCで動いているhsBoxの他には、下の製品を使用して構築しています。

カメラ: アトムテック株式会社(ATOM tech Inc.) 製 ATOM Cam 2
https://www.atomtech.co.jp/products/atomcam2

NAS: BUFFALO 製 LS710D
https://www.buffalo.jp/product/detail/ls710d0101.html

camLS710D

実装方針、実装方法

できるだけ手間をかけず(つまりATOM Cam 2の改造など特殊なことはしない)、運用やhsBoxへの負荷も最低限になるように設計する。 ということで、hsBoxを使い10分間隔でrtspでATOM Cam2から画像を取得して、NASに保存、同時にクラウド上に送ることにしました。

事前準備

hsBoxに必要なライブラリが入っているかなど環境状況を確認します。 結果、必要なものはすべてこれまでの構築等でインストールされていることを確認しました。 インストールされていない場合インストールしてください。
※ 本記事内のRTSP URL、トークン、ユーザー名・パスワードはすべてダミーです。

■ATOM CAM2のrtspのURLを確認する
rtsp://<*user>:<*pass>@192.168.**.**/live ★


■hsBoxライブラリの確認
●FFmpeg (コマンドラインツール)
ffmpeg -version

●NFS / CIFS (NAS接続用ユーティリティ)
# インストールされているパッケージの確認
dpkg -l | grep -E 'nfs-common|cifs-utils'

●3. Python環境の確認
#hsBoxの制御コードを書くためのベースを確認します。
#Python はhsBoxで構築済みなので省きます。

●マウントポイントの作成
#NASの画像を置くためのディレクトリをhsBox内に作成します。
#すでに作っていれば不要
sudo mkdir -p /mnt/nas_cam

●手動マウント
マウントは他の記事(proxy設定)も参考にしてください

アトムテックの製品のうち、rtspに対応しているのは公式にはATOM Cam 2のみです。ATOM Cam SwingやATOM Cam GPTは対応していないようです。 また、最初のころのATOM Cam2はrtspは公開されていなかったと記憶しています。ファームウェアバージョンにも依存するようです。対応状況やrtspのURL確認方法などの詳細はアトムテックのサイトを確認してください。

事前準備が終わったら、手動で画像が取れるか確認してみましょう。

ffmpeg -rtsp_transport tcp -ss 00:00:01 -i "rtsp://**:**@192.168.**.**/live" -vframes 1 -q:v 2 -y test_out.jpg

*マークの箇所は環境に合わせて修正してください。

これで、画像が撮れれば問題ありません。取れなければIPが変わっていないかなどネットワーク的な問題を確認・解決する必要があります。

 IPは動的に変わるので、MACからIPを確認する実装を組み込みます。そこで使用するMACアドレスを確認します。

# arp -a |grep 192.168.**.**
? (192.168.**.**) at 7c:dd:e9:**:**:** [ether] on enp1s0

MACアドレスが確認できました。 ここまで準備できればほぼ完成です。

サンプル実装(hsBox側実装)

★印の箇所は環境に合わせて設定してください。
コードは、Windows、hsBox (Linux) 共用です。
※ 実運用では、環境変数や設定ファイルを用いてコード外に分離してください。

import os
import subprocess
import sys
import re
import base64
from datetime import datetime

# --- 設定項目 ---
# Windowsで試す際は、ここをカメラのIP等に書き換えてください
#RTSP_URL = "rtsp://**:**@192.168.*.*:554/live"
# 確認したMACアドレスをここに(ハイフンでもコロンでもOK)
TARGET_MAC = "7c-dd-e9-**-**-**" # ★要更新
RTSP_USER = "**" # ★
RTSP_PASS = "**" # ★
# 保存先設定
# 保存先設定
#SAVE_DIR = "/mnt/nas_cam" if os.name != 'nt' else r"C:\temp\atom"
SAVE_DIR = r"\\192.168.**.*****" if os.name == 'nt' else "/mnt/nas****" # ★
#
url = "https://******.jp/**/upload.php" # ★ クラウドアップロードURL
token = "your_secret_token" # ★
#認証の情報
user = "*****" # ★
pass = "*****" # ★
# -----------------------
BASE_DIR = SAVE_DIR

def get_next_serial(BASE_DIR ):
counter_file = os.path.join(BASE_DIR , "counter.txt")

# 現在の番号を読み込む
if os.path.exists(counter_file):
with open(counter_file, "r") as f:
try:
count = int(f.read().strip())
except:
count = 0
else:
count = 0

# 次の番号を決定(0-199のリング)
next_count = (count + 1) % 200

# 更新して保存
with open(counter_file, "w") as f:
f.write(str(next_count))

return f"{count:03d}.jpg" # 000.jpg 形式

def upload_to_xserver(local_file_path, serial_name):
# ユーザー名とパスワードを結合してBase64エンコード
user_pass = f"{user}:{pass}"
auth_encoded = base64.b64encode(user_pass.encode('ascii')).decode('ascii')
# curlで「サーバー側の名前」を指定して送る # ★
cmd = [
'curl', '-s', '-X', 'POST',
#'--user', f'{basic_user}:{basic_pass}', # ここで認証を通す # ★
#'-H', f'Authorization: Basic {auth_encoded}', # ヘッダーで認証を通す # ★
'-F', f'image=@{local_file_path};filename={serial_name}',
'-F', f'token={token}',
url
]
subprocess.run(cmd)



def capture_with_auto_management():
ip = get_current_ip(TARGET_MAC)
if not ip:
print("Error: Camera not found.")
sys.exit(1)

# 1. 日付ごとのディレクトリ名を生成 (例: 2025-12-23)
today_str = datetime.now().strftime("%Y-%m-%d")
daily_dir = os.path.join(BASE_DIR, today_str)

# 2. ディレクトリがなければ作成
if not os.path.exists(daily_dir):
os.makedirs(daily_dir, exist_ok=True)

# 3. ファイル名の生成 (例: 20251223_183000.jpg)
filename = datetime.now().strftime("%Y%m%d_%H%M%S.jpg")
save_path = os.path.join(daily_dir, filename)

# --- ffmpeg実行部 ---
rtsp_url = f"rtsp://{RTSP_USER}:{RTSP_PASS}@{ip}/live"
cmd = [
'ffmpeg', '-rtsp_transport', 'tcp', '-i', rtsp_url,
'-ss', '00:00:01', '-vframes', '1', '-q:v', '2', '-y', save_path
]

try:
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True, timeout=20)
print(f"Saved: {filename} (via {ip})")
serial_name = get_next_serial(BASE_DIR )
upload_to_xserver(save_path, serial_name)
except Exception as e:
print(f"FFmpeg failed: {e}")


def get_current_ip(mac):
"""MACアドレスから現在のIPアドレスを特定する (Win/Linux共用)"""
target_mac_raw = mac.replace(":", "").replace("-", "").lower()

# --- 1. ARPテーブルの強制更新 (Linuxのみ) ---
if os.name != 'nt':
subprocess.run(["ping", "-c", "2", "-b", "192.168.*.255"], # ★
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

try:
# ARPテーブルを取得
res = subprocess.check_output(["arp", "-a"]).decode("cp932" if os.name == 'nt' else "utf-8")

for line in res.splitlines():
# 解析中の行からも記号をすべて消す
line_raw = line.replace(":", "").replace("-", "").lower()

# 純粋な英数字の並びでマッチング
if target_mac_raw in line_raw:
# 行からIPアドレス部分だけを抜き出す
match = re.search(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", line)
if match:
return match.group(1)
except Exception as e:
print(f"Network scan error: {e}")
return None

if __name__ == "__main__":
capture_with_auto_management()

NASの指定ディレクトリの下に日付ディレクトリを作成し、その下にファイルを蓄積していきます。
ファイル自動削除の実装はありません。
必要に応じて、WindowsのエクスプローラーやNAS管理画面から手動で削除してください。

 

■サンプル実装(クラウド側実装)

※ 本PHPコードは動作確認用の簡易実装です。
※ 実運用では、IP制限、HTTPS、拡張子チェック等の追加を推奨します。

<?php
//---★環境に合わせてアクセス制限等を追加してください
$token = "your_secret_token"; // 簡易認証用
if ($_POST['token'] !== $token) exit("Access Denied");

$save_dir = "/cam_images/";

if (!is_dir($save_dir)) mkdir($save_dir, 0755);

if (isset($_FILES['image'])) {
// クライアントが指定したファイル名(001.jpg等)で保存
$file_name = basename($_FILES['image']['name']);
move_uploaded_file($_FILES['image']['tmp_name'], $save_dir . $file_name);

// 常に最新を確認するための固定名コピーも同時に行う
copy($save_dir . $file_name, $save_dir . "latest.jpg");
echo "OK";
}

アップロードされたファイルをディレクトリに保存します。 アップロードする際に000~199の番号をシリアルでつけてアップロードしてくるので、それを上書き保存します。あふれることはないので削除は不要です。
 同時に、”latest.jpg”にコピーします。 latest.jpgに常に最新の画像が保存されます。

あとはcronに設定を追加するだけで、画像を蓄積し続けます。
cron設定についてはhsBox本家サイトの記事(hsBoxで作る“LAN監視システム・アラート”)を参考にしてください。

追加記事

上の記事の実装で、後日検出した問題と暫定対処
https://mic.or.jp/info/2026/01/26/cam-3/

関連記事

https://mic.or.jp/info/2026/01/30/cam-4/

温度測定、3か月以上の長期間ロギング環境の整備に向けて事前準備

先の「コンクリート劣化の原因調査」の検証のためのデータ採取の1つとして温度の情報を収集蓄積する。 4ポイント(壁3点と気温)を収集することにした。 4か所を収集蓄積し、リアルタイムでクラウド等に上げる仕組みの構築は比較的簡単そうではあるが、20万円程度かかる見込みとなった。そこで、リアルタイムでのアップロードはあきらめ、逐次データ吸い上げでアップロードする方式として、後述の構成で、6万円程度で環境整備を行うことにした。

測定方式と使用機器

 下の写真の通りTタイプ熱電対(100m分を用意)を使用し、ロギングにはT&D MCR‑4TC日本の T&D Corporation:4 チャンネル 熱電対データロガー)を利用することにした。


MCR-4TCの実物は次の通り

とりあえず、1ch分熱電対を作成して、1日分の外気温を測定してみました。10秒間隔で測定しています。PCから設定できるものもありますが、基本は本体で設定操作が必要です。特に、測定開始の操作は本体側の操作が必要でした。 PCソフトの画面構成的にはPC操作で測定開始できそうに見えますが、MCR-4TCでは使えませんでした。

測定できそうな結果がえられたので、4ch分熱電対を作成しました。そして、下の写真のように氷水を使って0℃の測定精度チェックをしてみました。

下グラフは氷水を使ってのチェックしたときの結果です。コップ面に近い水は4℃程度までしか下がらない感じでした。中央の氷に囲まれた当たりがほぼ0℃に近い0.3℃くらいでした。

グラフの温度レンジが広すぎてあれですが、4chともにうまく測れています。測定点がここまで細かくとれると、この温度変化の動きでプラトー領域(※参考情報参照)を見ることで、凍結開始タイミングを確認できそうです。
MCR-4TCは1分間隔で4ch測定する場合、140日分ほどデータをロギングできるのでこの測定にはぴったりです。また、ロギングしながらデータを吸い上げることができるので、ほとんど切れ目なく測定し続けることができそうです。

参考情報

化学講座 第46回:凝固点降下
https://www.sidaiigakubu.com/examination-measure/chemistry/46/

冷却曲線と凝固点降下【高校化学】溶液の性質#6
https://www.youtube.com/watch?v=GKnf_UetCOg

10年越しの謎に挑む:なぜ壁は膨らんだのか?IT駆使で迫るコンクリート劣化の原因~この3年間の調査の締めくくりに向けて~

建築から約10年で徐々にコンクリート壁の表面にひび割れが入り膨らんできた。修復が必要だが、単純に表面を直しただけでは再発することが容易に想像できる。そこで、ダメージの根本原因の特定を目的として、原因についての仮説を立て妥当性の検討をした。残念ながら決め手となる証拠を得られず仮説の域を出ていない。そこで、明確に原因特定するためにITを駆使して温度やAEのデータを採取して検証することとした。

これまでの外観の変化、ストリートビュー&撮影記録より

2012年4月時点 (建築から3年後)↓ストリートビューより

2017年8月↓ストリートビューより

2018年4月↓ストリートビューより

2021年4月↓ストリートビューより

2024年11月↓ストリートビューより

2025年12月↓ 撮影

仮説と調査方法

壁面の中央からひび割れが入り始めて、左右上下に進行している状況と中央部を中心に膨らんでいることから凍結膨張が主要因と仮説を立てた。地震の影響も受けている可能性も考えられるが、地震であれば周辺部にも中央部と同様にひび割れがあってもおかしくはない。しかし、周辺部が起点のひび割れがないことから地震は主要因ではなく付加要因と推測した。
 凍結膨張(アイスレンズ)が発生する条件は、先人の研究結果(①凍結過程にある土中のアイスレンズ近傍の水分・熱移動、 ②土の凍上発生メカニズムについて、③土の凍上性評価手法に関する研究)などから、水分・温度・土粒子サイズの要因がそろうと発生することが知られている。本事例では水分と粒子サイズについては、過去の研究結果と合致している。3年前につぎの写真の通り床面に穿孔調査を行った。

土の粒子サイズは1mm以下を中心としている真砂土で、アイスレンズの影響を抑制できるほど大きいわけではない。また、一定期間穴に蓋をしていて、蓋を開けた直後の写真が次である。

蓋の内側には、多量の結露が発生していた。この結果から、多量の水分が存在していて、土は水分を透過するほど十分に大きいことが確認できた。これらより、水分と粒子サイズについては合致していると判断できる。

残るのは温度である。少なくとも外気温や一部の壁面は期間中に零下になったことが気象庁のデータおよび昨年計測した結果から確認できている。

推定される発生モデル
図1.推定される発生メカニズム


この発生モデルを確認するため、コンクリート壁内側の温度(複数個所)および外気温を測定する。また、内圧で破壊(コンクリート破砕音)を検知するAE(アコースティックエミッション)センサシステムを構築して破砕が発生するタイミングをとらえる方針です。 そして、外観の映像も記録していきます。

・撮影、動画化

上の通りカメラでの定点観測は2025/12/23より運用開始済みです。
画像をアップロードする仕組みを構築しました。自動アップロードしている画像が見れる構築方法を記載したページはこちらです。→「hsBoxで、ATOM Cam 2の映像をキャプチャしてNASに蓄積、さらにクラウドにアップロードする https://mic.or.jp/info/2025/12/25/cam/」
映像監視への、漏水状況の追加監視について2026/1/12
https://mic.or.jp/info/2026/01/13/cam-2/
温度測定点設置壁裏の温度測定準備完了。見えてきた「隙間」を内視鏡カメラ「YPC99」でどう捉えるか?
https://mic.or.jp/info/2026/01/28/temp-2/

・温度測定

4か所の温度をロギングする仕組みは用意できました。事前準備を行いました。→事前準備の結果はこちらです。 2025/12/21に4点測定を開始し、12/24に測定予定の場所の近くに仮配置しました。

4p
4p

上のように、4か所に温度を測定するための熱電対を設置し、ロギングを実施しています。この状況及び測定結果を公開するページを作成しました。温度測定結果のデータはこちらから。
温度測定点設置壁裏の温度測定準備完了。見えてきた「隙間」を内視鏡カメラ「YPC99」でどう捉えるか?

・AE測定

準備中です。 部材発注し、入手済みです。12/27時点でアナログ部分の製作と検証を完了しました。引き続きAD変換部の構築作業に着手しました。記事については随時公開していきます。 
AE測定、長期間ロギングシステム構築 (その1)
アナログ部製作編  [公開済み]https://mic.or.jp/info/2025/12/29/ae/
アナログ部検証編 [公開済み]https://mic.or.jp/info/2025/12/30/osc/
AD変換部 検討事前検証編 [公開済み]https://mic.or.jp/info/2026/01/02/ad-3/
信号検出実験編 [公開済み]https://mic.or.jp/info/2026/01/03/ae-4/
複数信号キャプチャ実験編 [公開済み]https://mic.or.jp/info/2026/01/04/2ch/
最終検証編 [公開済み]https://mic.or.jp/info/2026/01/09/ae-2/
運用開始編 [公開済み]https://mic.or.jp/info/2026/01/10/ae-3/

ロギングした結果は、温度測定結果と同様にアップしていく予定です。

まとめ

何からの結論が得られることを期待して進めていきます。それぞれのデータ採取に関するページまで公開してきました。 収集した結果からの総合的なまとめについては別途作成・公開予定です。
中間まとめ [公開済み]https://mic.or.jp/info/2026/02/01/damage-2/

その他

みつけた抑制策
[参考資料] 凍害抑制に関する研究(JST)
https://shingi.jst.go.jp/pdf/2022/2022_kansai_004.pdf

2025年12月22日で太陽光発電 通知メールがなくなる件。 代替として自前で収集した情報をLINEに通知してみた

いよいよ「太陽光発電、 通知メールがなくなる」。さてどうしようか、2025/12/07時点で、一番下に添付したメールが朝9:00ころに届いている。これを、代替できるLINE通知する仕組みを作ってみました。 12/14に取得した結果は次の通り、ほぼ同じ結果で十分な結果と言えるでしょう。

自作したLINEでの表示例 と【フロンティアモニター】のレポート
下記の通り、2025年12月13日の発電量をお知らせいたします。

発電量:7.52kWh
下記の通り、2025年12月13日の電力量をお知らせいたします。

発電量:7.52kWh
売電量:0.28kWh
買電量:53.99kWh
今月の目標売電量(223kWh)に対して、28%達成しました。
今月の目標消費電力量(1,106kWh)に対して、641kWh消費しました。
下記の通り、2025年12月07日-2025年12月13日の電力量をお知らせいたします。

発電量:96.83kWh
売電量:31.50kWh
買電量:281.26kWh
今月の目標売電量(223kWh)に対して、28%達成しました。
今月の目標消費電力量(1,106kWh)に対して、641kWh消費しました。


実装方針

・従来の通知メールに含まれる発電量等の計測値データはすべて含める
・通知メールの種類はいくつかあるが、元データは1つなので、引数で送信メッセージの内容を切り替える
・CRONで起動指定する

この方針で、ここまでで作成した実装をベースに集計+メッセージ送信するスクリプトを作成します。まずは、iPython上で動作確認して、hsBox上に移植しました。
以下のスクリプトを /home/hsbox/pyd/power_report.py に配置し、後述の設定を行いました。

スクリプト実装(例 ★印の箇所は要更新)

# power_report.py
import pandas as pd
from datetime import datetime, timedelta
import os
import sys
import argparse
import requests

# ==================== 設定エリア ====================
NAS_PATH = r"/mnt/nas<★NASマウント+パス>" #★
IFTTT_WEBHOOK_URL0 = "https://maker.ifttt.com/trigger/{event}/with/key/{your_key}"
IFTTT_EVENT_NAME = "******" # IFTTTで作ったイベント名★
your_key="********" # ←ここを自分のものに変更★
IFTTT_WEBHOOK_URL = IFTTT_WEBHOOK_URL0.replace("{your_key}", your_key)
# ====================================================


def send_line_message(title: str, body: str, timestamp: str):
payload = {
"value1": title,
"value2": body,
"value3": timestamp
}
url = IFTTT_WEBHOOK_URL.replace("{event}", IFTTT_EVENT_NAME)
try:
response = requests.post(url, headers={"Content-Type": "application/json"}, json=payload, timeout=10)
if response.status_code == 200:
print("LINE通知成功")
else:
print(f"エラー: {response.status_code}")
except Exception as e:
print(f"LINE通知失敗: {e}")

def load_day_data(target_date: datetime) -> pd.DataFrame:
date_str = target_date.strftime("%Y%m%d")
file_path = fr"{NAS_PATH}/power_{date_str}.parquet"
if not os.path.exists(file_path):
raise FileNotFoundError(f"データが見つかりません: {file_path}")
return pd.read_parquet(file_path)

def calc_daily_summary(df: pd.DataFrame, date_label: str):
# 数値化
df["value1"] = pd.to_numeric(df["value1"], errors="coerce") # 発電 kW
df["value2"] = pd.to_numeric(df["value2"], errors="coerce") # 買電 kW
df["value3"] = pd.to_numeric(df["value3"], errors="coerce") # 売電 kW
df["value4"] = pd.to_numeric(df["value4"], errors="coerce") / 6 # 消費 kWh(既に1時間積算値)

# 10分間隔と仮定して正確にkWh計算
interval_min = 10
hours_per_record = interval_min / 60.0

total_gen_kwh = df["value1"].mean() * len(df) * hours_per_record
total_buy_kwh = df["value2"].mean() * len(df) * hours_per_record
total_sell_kwh = df["value3"].mean() * len(df) * hours_per_record
total_use_kwh = total_buy_kwh + total_gen_kwh - total_sell_kwh # 電力収支で算出(最も正確)

return {
"date": date_label,
"gen": round(total_gen_kwh, 2),
"buy": round(total_buy_kwh, 2),
"sell": round(total_sell_kwh, 2),
"use": round(total_use_kwh, 2)
}

def calc_week_summary(days=7, da=-8 ):
"""過去days日分の集計(今日を除く昨日まで)"""
base_day = datetime.now().date()
results = []
for i in range(1+8+da, days + 1+8+da):
target_date = base_day - timedelta(days=i)
try:
df = load_day_data(target_date)
date_label = target_date.strftime("%m/%d")
summary = calc_daily_summary(df, date_label)
results.append(summary)
except Exception as e:
print(f"{target_date} のデータ読み込み失敗: {e}")

if not results:
return None

total_gen = sum(r["gen"] for r in results)
total_buy = sum(r["buy"] for r in results)
total_sell = sum(r["sell"] for r in results)
total_use = sum(r["use"] for r in results)

lines = [f"【過去{days}日間実績】"]
for r in reversed(results): # 古い順→新しい順
lines.append(f"{r['date']}: 発電{r['gen']} kWh")
lines.append("")
lines.append(f"合計発電量: {total_gen:.1f} kWh")
lines.append(f"合計買電量: {total_buy:.1f} kWh")
lines.append(f"合計売電量: {total_sell:.1f} kWh")
lines.append(f"合計消費量: {total_use:.1f} kWh")

return "\n".join(lines)

def main():
parser = argparse.ArgumentParser(description="電力データ集計&LINE通知")
parser.add_argument("--da", type=int, default=0, help="日付加算。-1=昨日, 0=今日, 1=明日…")
parser.add_argument("--ptn", type=str, default="f", choices=["p", "f", "w"],
help="p=発電量のみ, f=全項目, w=週間集計")
args = parser.parse_args()

# --- 対象日の決定 ---
base_date = datetime.now()
if args.da != 0:
base_date += timedelta(days=args.da)

# 今日の場合、現在時刻までのデータのみ読み込む(ファイルは当日分全部ある前提)
target_date = base_date.date()
date_label = base_date.strftime("%Y/%m/%d")

if args.ptn == "w":
message = calc_week_summary(7,args.da)
if message:
send_line_message("太陽光発電1週間データ",message,"-")
message=f"{message}\n"
else:
send_line_message("太陽光発電","週間集計データが取得できませんでした","-")
message="週間集計データが取得できませんでした"
print(message)
return
else:
try:
df = load_day_data(base_date)
except Exception as e:
send_line_message("太陽光発電",f"⚠️ {date_label} のデータがありません\n{e}","-")
message = f"⚠️ {date_label} のデータがありません\n{e}"
print(message)
return

summary = calc_daily_summary(df, date_label)

# --- メッセージ作成 ---
if args.ptn == "p":
message = f"☀️ {base_date.date()} 発電量\n{summary['gen']} kWh"
else: # f
message = f"⚡️ {base_date.date()} 電力実績\n" \
f"発電量 :{summary['gen']} kWh\n" \
f"買電量 :{summary['buy']} kWh\n" \
f"売電量 :{summary['sell']} kWh\n" \
f"消費電力量:{summary['use']} kWh"

send_line_message("太陽光発電データ",message,"-OK-")
print(message)

import sys
if "ipykernel_launcher.py" in sys.argv[0]:
# ここに実行したい引数を書く(好きな組み合わせでOK)
sys.argv = ["power_report.py"] # ← ptnなし → f 扱い(今日分フル)
#sys.argv = ["power_report.py", "--da", "-1"] # 昨日分フル
#sys.argv = ["power_report.py", "--ptn", "p"] # 今日の発電量だけ
# sys.argv = ["power_report.py", "--ptn", "w"] # 週間集計

if __name__ == "__main__":
main()
使用方法・CRON設定方法(CRON起動設定 例)
30 19 * * * /usr/bin/python3 /home/hsbox/pyd/power_report.py --da 0 --ptn p
19:30に当日の発電量を通知

0 9 * * * /usr/bin/python3 /home/hsbox/pyd/power_report.py --da -1 --ptn f
AM9:00に前日の発電量・売電量・買電量・消費電力を通知

1 9 * * 7 /usr/bin/python3 /home/hsbox/pyd/power_report.py --da -8 --ptn w
日曜日 AM9:00に前日までの7日間の発電量・売電量・買電量・消費電力を通知

IFTTTの設定

IFTTTでの設定は、随時更新されるため参考程度で見てください。詳細はWebhooksやLINEの項目を確認してください。 ※これは、2025/12/10時点の情報です。

Webhooksの「Your key」の確認方法

1. IFTTT にログイン

https://ifttt.com
にアクセスし、ログインします。

2. Webhooks サービスページへ移動

以下の公式ページを開く
👉 https://ifttt.com/maker_webhooks

3. 右上の「Settings」をクリック

画面右上に Settingsという青いボタンがあります。

4. “Webhooks Settings” の欄を確認

開いたページに以下のような記載があります:

URL
https://maker.ifttt.com/use/**********


この key(*******の部分) が個人専用の Webhooks Key です。
このkeyをスクリプト(power_report.py)のyour_keyに設定してください
IFTTTの Applet 作成例(※画面は2025年12月現在のものです)

Appletの名称は自分にわかりやすいように任意に設定してください。

イベント(THEN)に「Receive a web request」を選択し、Event Nameを設定します。このEvent Nameをスクリプト(power_report.py)内のIFTTT_EVENT_NAMEに設定します。

THAT(実行内容)に LINEの「Send message to self」を選択し、自分のLINE accountを設定します。 Messageは、上のように設定すれば、設定完了です。

この設定は、自分あてのメッセージ送信なので、このスクリプトに限らず、他のメッセージ送信にも応用できます。

以下、2025/12/07に届いた通知メール
件名:【フロンティアモニター】 12月06日 電力量レポート


内容:
**** 様

日頃より【フロンティアモニター】ホームエネルギーモニタリングサービスをご利用いただき、誠にありがとうございます。

【システム終了のお知らせ】
2025年12月22日(月)をもって本計測装置のサービスを終了いたします。
なお、システムの都合により、一部サービス終了のタイミングについては前後する可能性がございますので、ご承知おきください。
詳しくはお客様ご利用サイトのお知らせ欄をご覧ください。

本メール発信は、メールシステムメンテナンスにより、1日遅延する場合があります。メンテナンスの日程は、お客様ログイン画面の「お知らせ」欄に随時記載いたします。
メンテナンス時はご不便をおかけしますが、何卒ご承知おきくださいますようお願いいたします。

下記の通り、2025年12月06日の電力量をお知らせいたします。

発電量:19.98kWh
売電量:9.56kWh
買電量:37.58kWh

今月の目標売電量(223kWh)に対して、14%達成しました。
今月の目標消費電力量(1,106kWh)に対して、293kWh消費しました。

省エネ目標が01月01日から変更されておりません。

発電・消費電力量は季節ごとにかわりますので、目標も毎月更新されることをおすすめします。

日没後に送られてくる当日分発電量
**** 様

日頃より【フロンティアモニター】ホームエネルギーモニタリングサービスをご利用いただき、誠にありがとうございます。

【システム終了のお知らせ】
2025年12月22日(月)をもって本計測装置のサービスを終了いたします。
なお、システムの都合により、一部サービス終了のタイミングについては前後する可能性がございますので、ご承知おきください。
詳しくはお客様ご利用サイトのお知らせ欄をご覧ください。

本メール発信は、メールシステムメンテナンスにより、1日遅延する場合があります。メンテナンスの日程は、お客様ログイン画面の「お知らせ」欄に随時記載いたします。
メンテナンス時はご不便をおかけしますが、何卒ご承知おきくださいますようお願いいたします。

下記の通り、2025年12月06日の発電量をお知らせいたします。

発電量:19.98kWh















今後ともフロンティアモニターをよろしくお願いいたします。
★なお、お心当たりのない方は、お手数ではございますが、下記メールアドレスまでご連絡頂きますようお願いいたします。
★このメールは送信専用メールアドレスから配信しています。このまま返信いただいてもお答えできませんのでご了承ください。
-----------------------------------------------
ソーラーフロンティア株式会社
【フロンティアモニター】お客様サービスセンター
電話:0570-053115(受付時間:9:00-17:00)※日曜、祝祭日、メーデー、年末年始を除く
メール:information@solar-frontier.com
-----------------------------------------------

上の内容のうち、太字部分に相当する情報を、LINEで通知するようにしてみました。
参考にしてみてください。

※追記情報

LINEへの通知はIFTTTをPROアカウントで利用している場合でも件数制限があるようです。たぶん、月間50件くらいの制限だと思われます。通常はE-mailでの通知にして、頻度が少ない異常の検知をした場合にだけ使うようにするのがよさそうです。

関連記事

ソーラーフロンティア太陽光発電・発電量独自集積データ解析をiPythonで書いてみた

モニタリングを視覚化と、異常検知するアプローチを進めていきましょう。 まずは、自動化に向けて太陽光発電・発電量独自集積データ解析をiPythonで書いてみます。

先の記事までにデータ収集に成功し、ソーラーフロンティア太陽光発電のモニタリングサービスで収集した結果とデータ比較しほとんど一致していることを確認できています。ここからは、モニタリングを視覚化と、異常検知するアプローチを進めていきましょう。 まずは、自動化に向けて太陽光発電・発電量独自集積データ解析をiPythonで書いてみます。 → LINE通知はこちら

システム構成
fmget
ソーラフロンティアモニタ代替

ここからは、前にNASに保存した形式のデータを扱う前提で書いていきます。

元のデータは、10分毎の瞬間値データでした。1時間ごとに平均してkWhに変換すると、ソーラーフロンティアモニタリングサービスに蓄積されたデータとほとんど同じデータになります。※取得した瞬間値の取得タイミングが異なるので、雲がまばらにあるような瞬間値の変動が激しい天候の場合はずれが大きくなるでしょう。
 1日分の発電量を集計・グラフ化するipythonコードは次の通りです。

import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
from datetime import datetime

dt1 = "2025/12/02" #★参照したい日付 を指定
nas_path = r"<NASのパス名>" #★NASのパス 環境に合わせて指定


date_obj = datetime.strptime(dt1, "%Y/%m/%d")
date_with_slash = date_obj.strftime("%Y/%m/%d")
date_str = date_obj.strftime("%Y%m%d")

# --- ① parquet 読み込み(あなたのコード) ---
# ファイルパスを組み立て
file_path = fr"{nas_path}\power_{date_str}.parquet"
df = pd.read_parquet(file_path)
ttl=f"発電量推移 {date_with_slash}"

# --- ② Value1 を数値化(エラーを NaN に) ---
df["value1"] = pd.to_numeric(df["value1"], errors="coerce")

# --- ③ timestamp を datetime に変換 ---
df["timestamp"] = pd.to_datetime(df["timestamp"])

# --- ④ 時刻(Hour)を抽出 ---
df["hour"] = df["timestamp"].dt.hour

# --- ⑤ 1時間ごとの平均値を計算 ---
hourly_mean = df.groupby("hour")["value1"].mean()

# --- ⑥ 結果表示 ---
print(hourly_mean)
total_sum = df["value1"].sum()/6
print(total_sum)

# timestamp を x 軸として、そのままプロット
plt.figure(figsize=(14,4))
plt.plot(hourly_mean, linewidth=1)
plt.title(ttl)
plt.grid(True)
plt.tight_layout()
plt.show()

★印の箇所は、任意に変更してください。NASのパスは iPythonが動作しているマシン上から見た、データ保存のパスです。
\\192.168.1.50\share など

表示結果例

SolarPower

つぎは、月単位、年単位の集計値を出すコード書いてみましょう。それから、hsBoxで、スマートディスプレイやGoogleTVに表示させる仕組みに着手です。

関連記事

hsbox1.3で屋内のソーラーフロンティアホームサーバから直接発電量データ取得、データ検証編 まず2日分データで検証

hsbox でのソーラーフロンティアホームサーバーからのデータ収集の続きをしましょう。ホームサーバから直接取得する方法で、ホームエネルギーモニタリングサービス とほぼ一致するデータが取れたことを確認できました。


今回は、情報採取した2日分のデータと、「フロンティアモニター – ホームエネルギーモニタリングサービス -」のデータがどの程度一致しているか検証してみます。

取得データ比較結果

12/1と12/2の発電量の比較デーは以下の通りです。10分間隔で取得して1時間ごとに平均化しています。ほぼ同じ取得方法ですが、取得タイミングが微妙に違うので少しずれます。それでも、1日の発電量の一致度は、12/1は98.4%、 12/2は100.5%でした。十分満足できる結果でした。昨日公開した実装方法で発電量などのデータを取得できることを確認できました。

先のValue1が発電量のデータです。さらに2週間ほど並行してソーラーフロンティアに上がっているデータと一致しているか、詳細確認をしてみます。

検証環境

今回、データ取得に使用した環境は次の通りです。
フロンティアモニターホームサーバー
カーネルVer. 3.22
システムVer. 3.22
AD変換ボードVer. 2.00

hsBox
Version: 1.03.01.01, Build: 324

fmget
ソーラフロンティアモニタ代替

関連記事

https://www.frontier-monitor.com/persite/top