AE測定、長期間ロギングシステム構築 (その3の2) AD変換部 事前検証編

前回までで、センサーから信号をとりだすアナログ部分の仮構築が完了しました。ここからは、取り出したAE信号をAD変換してロギングする部分を構築していきます。
前出(アナログ部製作編)の採用するシステム構成の方針により、ロギングシステムの中核につぎのパーツを採用しました。

AD変換&ロギング部

[107385]Arduino Uno Rev3
https://akizukidenshi.com/catalog/g/g107385/

事前検討方針

 ドキュメントレベルの調査結果から、次の課題があることを確認しました。
・USBケーブル挿抜で再起動される(運用上、 通常ロギング時は単体で動作し、データ吸い上げ時だけUSB接続する運用にする必要がある)
・再起動でRAMはリセットされ、EEPROMはリセットされないが書き込み回数上限制限がある(カウンタ等の頻繁に更新される変数保存ができない)
・EEPROMの容量が小さい(長期間ロギングのため、できるだけ大きいデータ領域を確保したい)

これらの問題を、解決する必要があります。そこで2つめと3つめの課題は、次回検討するプログラミング実装レベルで解決することにしました。ここでは、USBケーブル挿抜に関るる挙動の確認と対策、そして再起動されていないかどうかのについて確認する方法を検討ことにしました。

Arduino IDE をインストールするPCのスペック確認

Arduino IDE に必要なスペックを確認しておく。Windnows 10以降のPCならほぼ大丈夫。
問題なければ、インストール。
Arduino 公式ソフトウェアページ

動作確認

PCにArduinoを繋ぎ、デバイスマネージャーの「ポート(COMとLPT)」に「Arduino Uno」が表示されるか確認する。

USBを挿す前は Port のさきに COM3などが見えていましたが、 USBをさすとPortがグレーアウトされました。
Board: ”Arduino Uno” → Arduino AVR Boardsで Arduino Unoを選択しました。
→ これでは、まだ認識していないようです。
USBを一度抜挿して確認、これでも場が認識していないようだが、 再度開きなおすと
下のキャプチャのようにCOM4(Ardunio Uno)が表示される。

最初の動作確認

出荷時は、次のサンプルが動いているようです。
サンプルを開く:
「ファイル」→「スケッチ例」→「01.Basics」→ 「Blink」

delay(100);  など、任意の時間に変更して、
再度、画面左上の 「→(書き込み)」ボタン を押すと、コードを書き込んで点滅間隔がかわる。

Arduino Unoの動作状況確認

Arduino Unoは基本1方向らしい、吸出しなどはavrdude コマンドらしい。

“C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avrdude.exe” -C “C:\Program Files (x86)\Arduino\hardware\tools\avr\etc\avrdude.conf” -v -p atmega328p -c arduino -P COM4 -b 115200 -U flash:r:backup.hex:i

ユーザーパスにインストールしていたので、avrdude.exeは次のパスにありました。
C:\Users\<ユーザー名>demo\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17\bin

パラメータを確認
> .\avrdude.exe -?
Usage: avrdude.exe [options]
Options:
-p <partno> Required. Specify AVR device.
-b <baudrate> Override RS-232 baud rate.
-B <bitclock> Specify JTAG/STK500v2 bit clock period (us).
-C <config-file> Specify location of configuration file.
-c <programmer> Specify programmer type.
-D Disable auto erase for flash memory
-i <delay> ISP Clock Delay [in microseconds]
-P <port> Specify connection port.
-F Override invalid signature check.
-e Perform a chip erase.
-O Perform RC oscillator calibration (see AVR053).
-U <memtype>:r|w|v:<filename>[:format]
Memory operation specification.
Multiple -U options are allowed, each request
is performed in the order specified.
-n Do not write anything to the device.
-V Do not verify.
-u Disable safemode, default when running from a script.
-s Silent safemode operation, will not ask you if
fuses should be changed back.
-t Enter terminal mode.
-E <exitspec>[,<exitspec>] List programmer exit specifications.
-x <extended_param> Pass <extended_param> to programmer.
-y Count # erase cycles in EEPROM.
-Y <number> Initialize erase cycle # in EEPROM.
-v Verbose output. -v -v for more.
-q Quell progress output. -q -q for less.
-l logfile Use logfile rather than stderr for diagnostics.
-? Display this usage.

avrdude version 6.3-20190619, URL: <http://savannah.nongnu.org/projects/avrdude/>

■flash(プログラム領域)の吸い上げ の試行
※参考にする場合はパス(特に赤字)を環境に合わせてください。

.\avrdude.exe "-CC:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf" -v -p atmega328p -c arduino "-PCOM4" -b115200 -U flash:r:backup.hex:i

avrdude.exe: Version 6.3-20190619
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2014 Joerg Wunsch

System wide configuration file is "C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf"

Using Port : COM4
Using Programmer : arduino
Overriding Baud Rate : 115200
AVR Part : ATmega328P
Chip Erase delay : 9000 us
PAGEL : PD7
BS2 : PC2
RESET disposition : dedicated
RETRY pulse : SCK
serial program mode : yes
parallel program mode : yes
Timeout : 200
StabDelay : 100
CmdexeDelay : 25
SyncLoops : 32
ByteDelay : 0
PollIndex : 3
PollValue : 0x53
Memory Detail :

Block Poll Page Polled
Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff
flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff
lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00
signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00

Programmer Type : Arduino
Description : Arduino
Hardware Version: 3
Firmware Version: 4.4
Vtarget : 0.3 V
Varef : 0.3 V
Oscillator : 28.800 kHz
SCK period : 3.3 us

avrdude.exe: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude.exe: Device signature = 0x1e950f (probably m328p)
avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: reading flash memory:

Reading | ################################################## | 100% 5.22s

avrdude.exe: writing output file "backup.hex"

avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: safemode: Fuses OK (E:00, H:00, L:00)

avrdude.exe done. Thank you.

上の実行の結果、ファイルが実行パスに出力されていることを確認しました。
面白くなってきました。
このファイルのサイズは78KBで、テキストエディターでも見える形式です。
行頭に”:”があり4byte分アドレス相当の情報、実データ、最後に1byteチェックサムがついているようですね。

つぎにEEPROMの中身を読みだしてみます。

> .\avrdude.exe "-CC:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf" -v -p atmega328p -c arduino "-PCOM4" -b115200 -U eeprom:r:eeprom.hex:i

avrdude.exe: Version 6.3-20190619
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2014 Joerg Wunsch

System wide configuration file is "C:\Users\<ユーザ名>\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf"

Using Port : COM4
Using Programmer : arduino
Overriding Baud Rate : 115200
AVR Part : ATmega328P
Chip Erase delay : 9000 us
PAGEL : PD7
BS2 : PC2
RESET disposition : dedicated
RETRY pulse : SCK
serial program mode : yes
parallel program mode : yes
Timeout : 200
StabDelay : 100
CmdexeDelay : 25
SyncLoops : 32
ByteDelay : 0
PollIndex : 3
PollValue : 0x53
Memory Detail :

Block Poll Page Polled
Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff
flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff
lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00
calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00
signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00

Programmer Type : Arduino
Description : Arduino
Hardware Version: 3
Firmware Version: 4.4
Vtarget : 0.3 V
Varef : 0.3 V
Oscillator : 28.800 kHz
SCK period : 3.3 us

avrdude.exe: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude.exe: Device signature = 0x1e950f (probably m328p)
avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: reading eeprom memory:

Reading | ################################################## | 100% 3.11s

avrdude.exe: writing output file "eeprom.hex"

avrdude.exe: safemode: lfuse reads as 0
avrdude.exe: safemode: hfuse reads as 0
avrdude.exe: safemode: efuse reads as 0
avrdude.exe: safemode: Fuses OK (E:00, H:00, L:00)

avrdude.exe done. Thank you.

eepromのデータを読み出せました。ファイルサイズは3kbです。ですが、中身はFlashメモリの冒頭部分と同じようでした。 確認のため、 eeprom_clearというサンプルをアップロードして確認してみます。→結果、再度読み込んだファイルの中身は、変わりませんでした。 上のEEPROMの読み出しは機能していないようです。失敗しているよう。

サンプルに、eeprom_clearや eeprom_readがあるのでこれを使ってみます。eeprom_readをアップロードして確認してみます。右上のシリアルモニタとシリアルプロッタを使って、確認すると。EEPROMの1kb分が0で帰ってきました。

基本的な動きが確認できたので、ここでの検証課題のUSB挿抜でのリセット発生状況の確認と回避確認です。
と、ここで、想定外の情報、Arduino IDE(PC側)のシリアルモニタを開く操作をすると、Arduinoボード本体に自動的にリセットがかかるらしい、USB挿抜以外にもチェックするべき観点が増えた。
要約すると、おもに電源に関連する不安定要素によるものと、 通信制御によるリセットの2つで、通信制御によるものはUSB挿抜と、DTR/RTS信号によるオートリセットということらしい。電源に関してはACアダプターでの安定供給と、シールド対策をするとして、USB挿抜とDTR/RTS信号によるリセットされることについては「 RESET-GND間に10μFのコンデンサを挟む」対策をする。

 さて、要因は1つ増えたが、対策は増えていないので、方針は変えずに先に進めよう。
 さらに追加情報をみつけた、「リセット理由の切り分けは MCUSR (MCU Status Register) レジスタ」でできるらしい。

MCUSRレジスタのビットマップ解説
このレジスタの 下位4ビット がリセット要因を示します。
ビット番号 ビット名 名称 内容
Bit 3 WDRF ウォッチドッグ・リセット ウォッチドッグタイマーのタイムアウトによるリセット
Bit 2 BORF ブラウンアウト・リセット 電源電圧が規定値を下回ったことによるリセット(電源不安定が原因)
Bit 1 EXTRF 外部リセット RESETピンを「LOW」にしたことによるリセット(DTR信号/物理ボタンが原因)
Bit 0 PORF パワーオン・リセット 電源が完全にOFFの状態からONになった時のリセット

本番向け実装も考慮して、MCUSRレジスタも含めてデータ構造設計をします。方針は、あらかじめデータ保存領域を00クリアしておきます。データ登録はブロック単位でおこない、更新は追記のみで上書きはしないものとします。
データブロック構造を次のように定義する。
00 00 00 00 00
最初の1byteを、種別フラグとする。
意味を次のようにする。
00: 未使用
01: リセットデータ
2byte目:MCUSRレジスタ
02: 同期データ
2byte目:クロックタイマー
6?byte目:時刻データ
03: 未定義
1x: チャンネルデータ
  2byte目:クロックタイマー
6?byte目:AD変換データ

とりあえず、LED点滅回数でリセット回数が分かるようにしましょう

時刻データは、PCから受信を想定しています。
EEPROMの容量と1ブロックのサイズは、クロックタイマー+時刻データと クロックタイマー+AD変換データのうちの大きいほうを採用して決めます

LED点滅による通知仕様
リセット回数が5回なら 0.2秒間隔で5回点滅した後、2秒消えた後、5回点滅という繰り返しにします。

できるだけ長期間のロギングをしたいために、無駄なデータ領域を消費しないようにする。
なお、リセット回数は、 EEPROMにどこまで書き込まれたかとリセット回数をカウントアップして保持して、データ追加と点滅回数に使用します
1ブロック 8バイト固定の決定案

Unix Timeは4バイト(32bit)すべてを格納。

バイト01234567備考
01 (Reset)01MCUSR<—millis(4B)—>0000最もシンプルな構成
02 (Sync)02<—Unix Time(4B)—><—millis(4Bの一部)—>※後述
1x (Data)1x00<—millis(4B)—><—AD値(2B)—>

1. EEPROM走査関数: 起動時に先頭から8バイトずつ読み、01の数を数えつつ、最初の00を見つける。

2. リセット記録: setup()の最初で01レコード(MCUSR付き)を追記。

3. LED点滅関数: loop()内でmillis()を監視し、カウントされた回数分だけ点滅(delayを使わない)。

4. PC同期受信: シリアルで 0x02 を受信したら、続く4バイトをUnix Timeとして処理。

※補足:3ヶ月ロギングで1KBのEEPROM(128回分)が溢れるのが心配な場合は、追記時に「これ以上書かない」というガードも入れる。


1.書き込み位置特定: 8バイトすべてが 0x00(完全な未使用領域)であることを条件に、追記位置を決定します。

2.時刻同期(02): PCから [0x02][UnixTime 4B][CheckSum 1B] の計6バイトを受信する仕様とします。チェックサムはUnixTime 4バイトの加算下位1バイトとします。

3.データ構造の遵守:
 1. 01(Reset): [01][MCUSR][00 00 00 00 00 00] (計8バイト)
2. 02(Sync): [02][UnixTime 4B][millis 3B(上位)]
※02にのみ、PC時刻とArduino内時計を紐付けるための基準点としてmillisを保持します。

ここまで作業が進んだところで、EEPROMの読み込み問題に気が付いたので先に書いた対策を実施しました。詳しくはこちらを参照してください→「」

本体実装 (Arduinoスケッチ ResetDetector.ino)

まず、リセットと同期の実装を入れた実験版です。

/*
* Reset logging PreCode
*/

#include <EEPROM.h>
#include <avr/wdt.h>

// --- 定数定義 ---
const int BLOCK_SIZE = 8;
const int EEPROM_LIMIT = 1024;
const int LED_PIN = 13;

int totalResetCount = 0;
int nextAddr = 0;
byte lastMcusr = 0;

void setup() {
lastMcusr = MCUSR;
MCUSR = 0; // 次回のためにクリア

Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);

// 1. 書き込み位置の特定(全8バイトが0x00であることを確認)
findNextAddrAndCount();

// 2. リセット記録(設計案通り、millisを含まない構成)
recordReset();
}

void loop() {
updateLED(); // LED点滅処理(非ブロッキング)
checkSerial(); // PCからの同期信号(02)待ち
}

// 課題1:書き込み位置特定(種別00かつ全データ00を判定)
void findNextAddrAndCount() {
nextAddr = -1;
totalResetCount = 0;

for (int i = 0; i < EEPROM_LIMIT; i += BLOCK_SIZE) {
bool allZero = true;
byte type = EEPROM.read(i);

if (type == 0x01) totalResetCount++;

// 全8バイトが0x00かチェック
for (int j = 0; j < BLOCK_SIZE; j++) {
if (EEPROM.read(i + j) != 0x00) {
allZero = false;
break;
}
}

if (allZero && nextAddr == -1) {
nextAddr = i;
}
}
if (nextAddr == -1) nextAddr = 0; // 満杯時は先頭(運用に合わせて要調整)
}

// 課題4:当初案通りのリセット記録(余計なmillisは排除)
void recordReset() {
if (nextAddr + BLOCK_SIZE > EEPROM_LIMIT) return;

EEPROM.update(nextAddr, 0x01); // 種別フラグ
EEPROM.update(nextAddr + 1, lastMcusr); // MCUSR(補助情報)
for (int i = 2; i < BLOCK_SIZE; i++) {
EEPROM.update(nextAddr + i, 0x00); // 残りは00埋め
}

nextAddr += BLOCK_SIZE;
// 点滅用にカウントを更新
totalResetCount++;
}

// 課題2:時刻同期(チェックサム付き)
void checkSerial() {
if (Serial.available() >= 6) {
if (Serial.read() == 0x02) {
uint32_t unixTime = 0;
byte checksum = 0;

for (int i = 0; i < 4; i++) {
byte b = Serial.read();
unixTime |= ((uint32_t)b << (i * 8));
checksum += b;
}

byte receivedSum = Serial.read();
if (checksum == receivedSum) {
saveSyncData(unixTime);
Serial.println("OK: Sync Success");
} else {
Serial.println("Error: Checksum Mismatch");
}
}
}
}

void saveSyncData(uint32_t ut) {
if (nextAddr + BLOCK_SIZE > EEPROM_LIMIT) return;

uint32_t currentMs = millis();

EEPROM.update(nextAddr, 0x02); // 種別フラグ

// UnixTime (4byte) を記録
for (int i = 0; i < 4; i++) {
EEPROM.update(nextAddr + 1 + i, (ut >> (i * 8)) & 0xFF);
}

// millis() の上位3バイトを記録 (Byte 5, 6, 7)
// 下位8bit(0-255ms)を切り捨てることで、0.25秒精度の同期を実現
EEPROM.update(nextAddr + 5, (currentMs >> 8) & 0xFF);
EEPROM.update(nextAddr + 6, (currentMs >> 16) & 0xFF);
EEPROM.update(nextAddr + 7, (currentMs >> 24) & 0xFF);

nextAddr += BLOCK_SIZE;
Serial.println("OK: Sync Data Recorded (0.25s precision)");
}

void updateLED() {
static unsigned long lastUpdate = 0;
static int flashStep = 0;
static int currentFlash = 0;
static bool pausing = false;

unsigned long now = millis();

if (pausing) {
if (now - lastUpdate >= 2000) {
pausing = false;
currentFlash = 0;
lastUpdate = now;
}
return;
}

if (now - lastUpdate >= 200) {
lastUpdate = now;
if (currentFlash < totalResetCount) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
flashStep++;
if (flashStep >= 2) {
flashStep = 0;
currentFlash++;
}
} else {
digitalWrite(LED_PIN, LOW);
pausing = true;
}
}
}

同期実装(PowerShell sync.ps1)

同期処理を行うのに使用します。 ファイルを右クリックして、「PowerShellで実行」をくちっくして使用します

$ErrorActionPreference = "Stop"  # 全てのエラーを「中断(catchに飛ばす)」対象にする
# --- 設定項目 ---
$portName = "COM4" # Arduinoが接続されているポートを確認して変更してください
$baudRate = 9600

try {
# シリアルポートの初期化
$port = New-Object System.IO.Ports.SerialPort($portName, $baudRate, "None", 8, "One")
$port.Open()
Start-Sleep -Seconds 2 # Arduinoのリセット待ち(DTRによるリセット対策)

# 1. 現在のUnix Time(1970/1/1からの経過秒)を取得
$unixTime = [DateTimeOffset]::Now.ToUnixTimeSeconds()

# 2. 4バイトのバイナリ配列に変換 (リトルエンディアン)
$utBytes = [BitConverter]::GetBytes([uint32]$unixTime)

# 3. チェックサムの計算 (4バイトの加算下位1バイト)
[byte]$checksum = 0
foreach ($b in $utBytes) {
$checksum = ($checksum + $b) -band 0xFF
}

# 4. 送信データの組み立て [0x02] + [Data 4B] + [Checksum 1B]
[byte[]]$sendBuffer = @(0x02) + $utBytes + @($checksum)

# 5. 送信
$port.Write($sendBuffer, 0, $sendBuffer.Length)

Write-Host ("送信成功: UnixTime = {0} (CheckSum: 0x{1:X2})" -f $unixTime, $checksum)
Write-Host ("送信バイナリ: " + [System.BitConverter]::ToString($sendBuffer))

# Arduinoからの応答("OK: Sync Success"など)を確認
Start-Sleep -Milliseconds 500
if ($port.BytesToRead -gt 0) {
$response = $port.ReadExisting()
Write-Host "Arduino応答: $response"
}

}
catch {
Write-Error "エラーが発生しました: $_"
}
finally {
if ($port -and $port.IsOpen) {
$port.Close()
Write-Host "ポートを閉じました。"
}
}


# --- スクリプトの最後に追加 ---
Write-Host "`nEnterキーを押すと終了します..."
Read-Host

実験結果

同期処理の実行結果は次の通り

送信成功: UnixTime = 1767166738 (CheckSum: 0xA2)
送信バイナリ: 02-12-D3-54-69-A2
Arduino応答: OK: Sync Data Recorded (0.25s precision)
OK: Sync Success

ポートを閉じました。

Enterキーを押すと終了します...

リセットの発生状況の検証を実施しました。 USBの挿抜では、リセットは発生していないようです。
9vACアダプタ電源のコンセント抜き差しでも、リセット発生していません。電源供給の9vACアダプタとUSBの切り替えがうまくいっているようです。つまり、ここまでは、LEDの点滅が1回の状態が続いています。
 つぎに、USBと9vACアダプタ電源のコンセント両方を抜きました。再度刺したところで、LEDは2回点滅に変わりました。これは、想定どおりです。ということで、USBの挿抜でのリセット発生は必ず起こるものではなさそうです。
このあと、一度同期をおこなって、追加で「DTR/RTS信号によるオートリセット」を強制的に行ってみます。

検証用コード (PowerShell: reset.ps1)

# --- 設定 ---
$portName = "COM4"
$baudRate = 115200

try {
# 1. ポートの初期化
$port = New-Object System.IO.Ports.SerialPort($portName, $baudRate, "None", 8, "One")

# 2. ポートを開く (この時点で多くのボードは一度リセットされます)
$port.Open()
Write-Host "Port $portName Opened."

# 3. DTR/RTSを操作して明示的にリセットをかける
# 一旦両方をFalse(High電位)にしてからTrue(Low電位)にする
Write-Host "Resetting Arduino..."
$port.DtrEnable = $false
$port.RtsEnable = $false
Start-Sleep -Milliseconds 200 # 信号を安定させるための待ち時間

$port.DtrEnable = $true
$port.RtsEnable = $true

Write-Host "Reset signal sent."

# 4. ブートローダーが立ち上がるまで少し待つ
Start-Sleep -Seconds 2
Write-Host "Arduino should be restarted now."

}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
finally {
if ($port -and $port.IsOpen) {
$port.Close()
Write-Host "Port Closed."
}
}

Write-Host "Press Enter to exit..."
Read-Host

実行結果は次の通り。 このリセット後は、LEDの点滅は3回になりました。 設計通りの挙動です。  このあと、もう一度同期して一旦実験完了です。 

Port COM4 Opened.
Resetting Arduino...
Reset signal sent.
Arduino should be restarted now.
Port Closed.
Press Enter to exit...

それでは、EEPROMにロギングした結果を吸い出して検証してみましょう。

:100000000100000000000000023AD25469D50F0040
:1000100001000000000000000212D35469CA000071
:10002000010000000000000002CCDD54697B0400E8
: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

ほぼ想定の結果が得られました。比較的うまくいっているようです。これからわかることは、01 (reset)の次はすべて00で、MCUSRレジスタから得られた情報はなさそう。
同期情報もしっかり記録されており、同期も機能していそうです。
 事前検証はこれで、完了とします。つぎはいよいよ、AD変換でのデータ収集の実験に入っていきます。

参考サイト:

avrdude の使い方 (電子工作の実験室)
Arduinoのプログラムを吸い出す方法 (Lang-ship)


関連記事




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で、この素子の共振周波数とほぼ一致していました。

関連記事

Win11リモートデスクトップ周りの設定対処していたらテキストコピーができなくなった→copy問題解決

Windows 11での「リモートデスクトップでモヤっと。」を直そうとしていろいろトライしていたら、テキストコピーができなくなった。 まずモヤっと問題と関連があるか調査が必要だが、作業効率を考えると、こっちを優先して対処する必要がある。以前の設定変更を確認して、 書き戻し作業で原因を特定して復旧しました。やはり、モヤっと問題の対処でいじくったとことが問題でした。

問題の原因

記事を確認すると、以下の設定を実施されています:

# グラフィック最適化の無効化  
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v fDisableClip /t REG_DWORD /d 1 /f

このfDisableClip = 1がクリップボードのリダイレクト(コピペ機能)を無効化しています!

解決方法

【最優先】クリップボードのリダイレクトを有効化

**接続先PC(サーバー側)**で以下を実行してください:

# レジストリで無効化されているクリップボード機能を有効化

# パターン1: ポリシーレジストリ
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v fDisableClip /t REG_DWORD /d 0 /f

# パターン2: RDP-Tcp設定(念のため確認・修正)
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v fDisableClip /t REG_DWORD /d 0 /f

# 再起動
Restart-Computer

これで復旧しました。

関連記事

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ファイルをこの下に追加していく予定です。

関連記事

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監視システム・アラート”)を参考にしてください。

関連記事

温度測定、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/」

・温度測定

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

4p
4p

上のように、4か所に温度を測定するための熱電対を設置し、ロギングを実施しています。この状況及び測定結果を公開するページを作成しました。温度測定結果のデータはこちらから。

・AE測定

準備中です。 部材発注し、入手済みです。12/27時点でアナログ部分の製作と検証を完了しました。引き続きAD変換部の構築作業に着手しました。記事については随時公開していきます。 
AE測定、長期間ロギングシステム構築 (その1)
アナログ部製作編  [公開済み]https://mic.or.jp/info/2025/12/29/ae/
アナログ部検証編 [近日公開予定]
AD変換部 検討・事前検証編 [準備中]

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

まとめ

何からの結論が得られることを期待して進めていきます。それぞれのデータ採取に関するページは別途作成・公開予定です。

その他

みつけた抑制策
[参考資料] 凍害抑制に関する研究(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での通知にして、頻度が少ない異常の検知をした場合にだけ使うようにするのがよさそうです。

関連記事