同じデータでも──意味や価値が違えば、別物になる |ストレージ再考 第2話

見た目が同じでも、意味は同じではない

スマートフォンの中にある一枚の画像。
それはただの「写真」かもしれないし、二度と取り戻せない記録かもしれない。

画素数も、ファイル形式も、見た目も同じ。
それでも、その扱いはまったく変わる。

例えば──
・たまたま撮れた風景
・仕事の記録
・家族の思い出
・証拠として残す必要のある写真

どれも「画像データ」であることに違いはない。
しかし、同じ扱いでよいとは、誰も思わないはずだ。

ここで起きているのは、技術の違いではない。
意味と価値の違いだ。


use
use

画像は「データ」ではなく「位置づけ」で価値が変わる

画像という形式だけを見れば、それは単なるデータだ。
だが、人がそこに意味を与えた瞬間、別の存在になる。

同じ構図の写真でも、

  • 落書きの記録
  • 美術作品の記録
  • 事故の記録
  • 成長の記録

それぞれの立場によって、役割はまるで違う。

画像の本質は「ピクセルの集合」ではない。
どの位置づけで存在しているかが、その正体を決めている。


use2
useB

媒体が変われば、意味も変わる

同じ「画像」でも、残され方はさまざまだ。

洞窟の壁に描かれたもの。
紙に描かれた絵。
油絵。
版画。
写真。

それぞれの時代、それぞれの方法で、
「残す」という行為は繰り返されてきた。

そして、残されたものの中には、
意味がはっきりしているものもあれば、
何のために描かれたのか分からないものもある。

それでも共通しているのは、
“残った”という事実そのものに価値が生まれることがあるという点だ。


極端な例で考えてみる

同じ「絵」でも、価値は極端に変わる。

子どもの落書き。
名画。

どちらも画像であり、どちらも記録だ。
しかし、扱いはまったく違う。

捨てられることもあれば、守られることもある。
複製されることもあれば、唯一のものとして扱われることもある。

ここで起きているのは、
媒体の違いでも、画質の違いでもない。

“何として存在しているか”の違いだ。


画像は、使われ方で別物になる

同じ画像でも、状況によって意味が変わる。

・個人的な記録
・共有される情報
・作品
・資料

そして、その扱いは自然に分かれていく。

気軽に消されるもの。
いつの間にか残り続けるもの。
意図して守られるもの。

人は無意識のうちに、
画像を「同じもの」として扱っていない。


しかし、私たちは深く考えていない

日常の中で、画像は大量に生まれる。
保存され、共有され、忘れられていく。

その一つひとつが、
どのような意味を持ち、
どのような価値を持ち、
どのように扱われるべきなのか。

そこまで意識していることは、ほとんどない。

同じように保存され、
同じように並び、
同じように消えていく。

けれど本当は、
同じであるはずがない。


同じ扱いのままでいいのか

同じ形式。
同じ見た目。
同じ保存先。

それでも、意味は違う。
価値も違う。
置かれている位置も違う。

本当は、全部同じ扱いでいいはずがない。

でも、どう違うのかは、
まだ整理されていない。

だから一度、
考えないといけない。


関連記事

SSDは本当に速くなったのか?- 3年後に感じる“あの重さ”の正体 –

https://mic.or.jp/info/2026/02/05/3-reasons-systems-feel-slower/

データとは何か──「意味」と「価値」から始める記憶の話|ストレージ再考 第1話

ロゼッタ・ストーン ── 読めた記録

記録は、残るだけではデータにならない。
そこに刻まれたものが、誰かに解釈され、意味を持った瞬間、初めて「使えるもの」になる。

ロゼッタ・ストーンは、まさにその象徴だった。
同じ内容が異なる文字で刻まれていたことで、人は古代の文字を読み解く手がかりを得た。
石そのものはただの記録だったが、「読めた」ことでデータになった。

それは、理解された記録だった。

rosetta
Rosetta Stone

チバニアン ── 残った記録

人が残そうとしなくても、痕跡は残る。
地層に刻まれた磁気の向きは、遥かな時間を超えてそのまま保存されていた。
意図も目的もなく、ただ存在し続けることで記録となったもの。

人の手によらない記憶も、この世界には確かにある。

それは、意図せず残った記録だった。


洞窟壁画 ── 残された記録

洞窟の壁に描かれた絵の多くは、その目的が明確には分かっていない。
祈りだったのか、記録だったのか、ただの表現だったのか。

それでも、数万年の時間を越えて残っている。
意味が分からなくても、残存そのものが価値を帯びる。

それは、理由が分からなくても残った記録だった。
第1話 同じデータでも──意味や価値が違えば、別物になる


粘土板 ── 使われた記録

やがて人は、残すために記録を作るようになる。
交易、約束、数量、出来事。
日々の営みの中で、記録は「使うもの」になった。

そこには、残るだけではない役割があった。
読み、参照し、必要に応じて書き直される。

それは、役割を持って使われた記録だった。


石碑・金属刻印 ── 変わらない記録

一方で、書き換えないこと自体に価値が置かれる記録も生まれる。
石に刻まれた文字、金属に残された印。
そこには「変えてはならない」という意思が込められていた。

変わらないことで、信頼が生まれる。
時間が経っても同じであることが、意味を支える。

それは、変えないことに価値がある記録だった。


paper
paper

巻物・書物 ── 蓄えられた記録

知識は、単発の記録から連なりへと変わっていく。
巻物や書物は、断片ではなく「蓄積」を前提とした形だった。

一つひとつの記録が、積み重なり、つながり、参照される。
知識は保存されるだけでなく、増えていく。

それは、積み重ねられていく記録だった。


図書館 ── 集められた記録

記録は、単体ではなく集合として扱われるようになる。
書物が集められ、分類され、共有される場所が生まれる。

そこでは一つの記録ではなく、全体としての知識が意味を持つ。
記録は「個」から「群」へと変わった。

それは、体系として集められた記録だった。


口承 ── 人が担った記録

記録媒体が整う以前、人そのものが記憶の担い手だった。
語り継ぐことで知識は残り、世代を越えて伝えられた。

しかし、人がいなくなれば、それも消える。
記録は人の存在と切り離せなかった。

それは、人が背負っていた記録だった。


焼失した図書館 ── 失われた記録

集められた記録であっても、永遠に残るとは限らない。
火災や戦乱によって、多くの書物が失われてきた。

残したはずのものが、ある日突然消える。
保存されていることと、安全であることは同じではない。

それは、残したはずの記録だった。


未解読文字 ── 読めない記録

記録は存在していても、読めなければ使えない。
文字が残っていても、解釈の手がかりが失われれば、意味は閉ざされる。

そこに情報はある。
しかし、それを取り出す方法がない。

それは、存在していても使えない記録だった。

nazo
不解読文字

関連記事

https://mic.or.jp/info/2026/02/05/3-reasons-systems-feel-slower/

SSDは本当に速くなったのか?- 3年後に感じる“あの重さ”の正体 –

速くなった、という確信

私たちはいつから、
「ストレージは速くなった」と疑わなくなったのだろう。

HDDからSSDへ。
OSの起動は明らかに短くなり、
アプリケーションは即座に立ち上がる。
ファイル検索やコピーも、
体感できるほど速くなった。

ベンチマークの数値を見れば、
その差はさらに明白だ。
SSD化は成功した。
少なくとも、多くの人がそう感じている。

いまや「遅いならSSDにすればいい」という言葉は、
何の違和感もなく使われている。
速くなった、という認識は、
すでに前提条件になっている。

HDD_SSD
HDD_SSD

使い続けたあとに現れる、あの感覚

ところが、しばらく使い続けていると、
多くの人が似たような違和感を口にする。

「最近、少し重い気がする」
「前はもっとキビキビしていた」

再起動すると、一時的に戻る。
初期化すれば、確かに速くなる。
だが、しばらくすると、
また同じ感覚が戻ってくる。

この現象は、もはや珍しいものではない。
それでも私たちは、
深く考えないまま受け入れてきた。


重くなる理由として語られてきたもの

遅くなった理由はいくつも語られてきた。

データが増えたから。
設定が複雑になったから。
そして、もっとも納得しやすい説明がこうだ。

「アップデートで機能が増えたから」

確かに、アップデートのたびに
新しい機能が追加され、
画面は華やかになり、
内部処理も増えていく。

それなら、多少重くなるのは仕方がない。
多くの人が、そう考えている。


本当に増えたのは「機能」なのか

だが、ここで一度立ち止まってみたい。

アップデートとは、
実際には何をしている行為なのだろう。

大量のファイルが書き換えられ、
差分や履歴が保存され、
ロールバックのための情報が積み重なっていく。
ログやキャッシュも増え続ける。

アップデートとは、
極めて書き込みの多い作業だ。

機能が増えたことによる負荷は、確かに存在する。
しかし、その裏で起きている
「書き換えられた量」は、
どれほど意識されてきただろうか。


「速さ」は、何を測っているのか

ストレージの速さは、
多くの場合、数値で語られる。

シーケンシャルリード。
ランダムアクセス。
IOPS。

それらは確かに重要だ。
だが、そこで測られているのは
ある瞬間の性能でしかない。

時間の経過は考慮されているだろうか。
使い方の違いは反映されているだろうか。
書き込みが積み重なったあとの状態は、
どこまで想定されているのだろう。

速さとは、
初速のことなのか。
それとも、
使い続けた先まで含めた性質なのか。


過去の常識が、いまも残っている

かつて、性能低下の代名詞だったものがある。
断片化だ。

HDDの時代、
データが物理的に散らばることは、
確かに速度低下の大きな原因だった。

だが、少なくともSSDにおいて、
それが支配的な要因であった時代は
すでに終わっている。

それでもなお、
私たちは説明しやすい物語を
手放せずにいる。


本当に、そうなのか

データが増えたから、遅くなった。
本当にそうなのか。

アップデートで機能が増えたから、重くなった。
本当にそうなのか。

古くなったから、仕方がない。
環境が汚れたから、仕方がない。
本当に、そうなのか。

私たちは、
何が起きているのかを理解した上で
「遅くなった」と言っているのだろうか。

それとも、
もっと見えにくい原因から
目をそらしているだけなのだろうか。


再スタート

さあ、はじまりました。久しぶりのストレージネタです。張り切っていきましょう。ストレージといえば、以前のMIC-NETの主要コンテンツで、読者の多いネタでした。そのネタを新らたな角度から見直してみましょう。過去コンテンツに興味のある方はヒストリ検索とかしてみてください。再度取り上げてほしいものがあればコメントください。

第1話 データとは何か──「意味」と「価値」から始める記憶の話


関連記事

なぜ壁は膨らんだのか?IT駆使で迫るコンクリート劣化の原因調査 ~2から6週間分 中間まとめ ~

昨年12月に本格着手して、さまざまな角度でITを駆使しながら進めてきています。以下は、ここまでに確認できた結果の要点の書き出してまとめます。 当初計画した調査環境の整備が整ってから2週間ほどしか経っていませんが、当初の仮説とは少し異なる新たな仮説を立てています。

ATOMCAM2撮影、画像キャプチャ(コンクリート劣化)


カメラでの定点観測は2025/12/23より運用開始: 上のとおり、キャプチャ開始時点から、後述のAEセンサー、温度センサー(熱電対)を追加しています。また、上右よりの2本のテープ状のものは水濡れ検知シールです。水に濡れると下の写真のように赤色に変わり乾いても赤のままなので、水が垂れてきたことがあったかどうか分かります。今のところ、水は出てきていません。同様に熱電対を入れた塩ビ管内にも。このシールを入れたので、壁の内側で結露が発生したかどうかを判別できるでしょう。

mizu
水検知シール

MCR-4TCでの温度測定

温度測定
温度測定

今のところ気温が一瞬0℃になりましたが、コンクリート裏はそこまでは下がっていません。気温が下がってコンクリート内の温度が連動するところもあれば、連動しないところもあります。単純に外気温だけがコンクリート裏側の温度に効いているわけではないようです。

 また、場所によって外気温の影響が異なるように見えます。

AE測定

[ EEPROM Data Analysis Report ]
Type | Date Time (UTC/Local) | Millis (raw) | Details
--------------------------------------------------------------------------------
SYNC | 2026-01-14 13:25:51 | 325120 | Time Synchronized (Unix:1768364751)
DATA | 2026-01-14 14:25:45 | 3919360 | CH0:0, CH1:19, CH2:0, CH3:0
DATA | 2026-01-15 06:45:30 | 62704128 | CH0:0, CH1:8, CH2:0, CH3:0
DATA | 2026-01-16 00:26:07 | 126341632 | CH0:0, CH1:8, CH2:0, CH3:1
DATA | 2026-01-16 06:11:56 | 147090432 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-16 07:11:10 | 150644736 | CH0:0, CH1:0, CH2:3, CH3:7
DATA | 2026-01-16 10:10:08 | 161382912 | CH0:0, CH1:16, CH2:0, CH3:0
DATA | 2026-01-16 10:16:08 | 161742848 | CH0:0, CH1:7, CH2:0, CH3:0
DATA | 2026-01-16 10:57:17 | 164211968 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-16 12:33:41 | 169995520 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-16 12:47:02 | 170796544 | CH0:0, CH1:7, CH2:0, CH3:0
DATA | 2026-01-17 16:21:30 | 270064640 | CH0:0, CH1:11, CH2:1, CH3:1
DATA | 2026-01-18 01:01:31 | 301265664 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-18 05:01:11 | 315645696 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-18 12:22:00 | 342094336 | CH0:0, CH1:1, CH2:6, CH3:6
DATA | 2026-01-19 00:38:54 | 386308608 | CH0:0, CH1:45, CH2:0, CH3:0
DATA | 2026-01-19 06:01:51 | 405686016 | CH0:0, CH1:8, CH2:0, CH3:0
DATA | 2026-01-20 12:54:55 | 516869376 | CH0:0, CH1:6, CH2:0, CH3:0
DATA | 2026-01-20 18:41:19 | 537653504 | CH0:0, CH1:7, CH2:0, CH3:0
DATA | 2026-01-22 01:14:26 | 647640576 | CH0:0, CH1:7, CH2:0, CH3:3
DATA | 2026-01-25 12:50:54 | 948628992 | CH0:0, CH1:9, CH2:0, CH3:2
DATA | 2026-01-29 15:48:20 | 1304874752 | CH0:0, CH1:23, CH2:3, CH3:4
RSET | 2026-01-30 12:55:41 | 0 | System Boot / Reset Detected
SYNC | 2026-01-30 12:58:24 | 162304 | Time Synchronized (Unix:1769745504)
--------------------------------------------------------------------------------
Analysis Finished.

顕著なAE信号検出はなさそうで、たまに何かを検出してはいますが、ほとんど発生していないようです。1/16がちょっと多い感じがしますが、今のところ顕著な問題は起きていなさそうです。

コンクリート壁穿孔工事関連

熱電対を設置した状況からそれぞれの場所ごとにコンクリート壁と、壁内の土の間に隙間があり、場所によってその隙間が、約5mmから約48mmと大きなばらつきがあるように見えます。

中間まとめ 

 今のところ、新たに見えた事象は、コンクリート裏に隙間があるようにみえるということです。その隙間と、コンクリート奥の温度に関連性があるように見えます。つまり、隙間が大きいほど、外気温が下がっても温度が下がりにくそうということです。

 それとは別に隙間に関係なく、タイミングによってどの面の温度が低くなるのかが変わりそうです。

温度測定が終わる4月頃に、熱電対をとおした塩ビパイプを取り出して、内視鏡カメラで壁の裏側の隙間の状態を調査しようと考えています。

今年はほとんど雨降っておらず、乾ききっている感じなので問題事象は発生しにくそうです。

新たな仮説

これまでの仮説はすべての面が均等にアイスレンズが成長するように考えていましたが、片面だけ発生するケースもありそうな感触です。つまり、東面と南面で交互に発生し隙間が広がったり、逆に押されてほとんどなくなったりすることが繰り返されたのかもしれません。

さらにここまでの仮説で将来発生することが予想されていた西面北寄りでの事象発生ですが、下の写真のように新たに発生していることを確認しました。

crack
ひび割れ

ここまでの仮説を補強する材料と言ってよいでしょう。 

関連記事

hsBoxで、ATOM Cam 2の映像をキャプチャで発生した問題と課題と対処策について

約1か月連続キャプチャできていましたが、キャプチャできなくなる事象が発生したのでその原因調査と対策をしていきます。そして恒久対策に向けた対策案も検討して、以前に公開したツールの今後の強化項目にしていきます。

cam
cam

調査 その1

まず、状況整理ですが、キャプチャは、1回のcron起動で、3つのATOM CAM2を順にキャプチャしてファイル保存するように実装していました。一番最初にキャプチャするカメラ画像は10:00にキャプチャしたものが最後で、そのほかの2つは、14:40が最後でした。つまり、1つ目のキャプチャに失敗しても2つめ以降のキャプチャはできていましたが、その後3つともキャプチャできなくなっています。運用点検のため、hsBoxを再起動したタイミングあたりでキャプチャできなくなっていたようです。
 まずは、切り分けしていくためウォークスルー方式で問題となりそうなところを順にチェックしていきます。まずは、NASのマウント状況を確認しました。マウントはできており問題ありませんでした。
次に、ATOM CAM2のIP探索の確認です。 スマホでATOM CAM2のIPを確認して、そのIPが取れているかを、arpコマンドで確認します。 すると、3台のカメラともにMACが取れていないことが判明した。

暫定対処 その1

それぞれの、IPにpingを打ってみます。 それぞれ、反応がありMACがとれました。これにより、14:40が最後だった2台のカメラのキャプチャが復旧しました。
 残りの1台は、NAS上へのフォルダ作成には成功したものの、画像ファイルがまだ置かれていない状況になっています。

根本対策 その1

 今後の強化策です。 既存実装でもブロードキャストでpingを打つ実装を入れていますが、ブロードキャストではarpに反映されないのが問題のようです。そこで、MACが取れなかったとき、DHCPで割り振られる範囲に対して明示的に個別にpingを打つ実装を追加することにします。

調査 その2

まだ復旧できていない1台分のカメラについて調査を行います。

実装を、最後にキャプチャできたタイミングで何かあったかを確認してみましたが、特に問題はないようです。手動で、FFMPEGコマンドを実行すると、次の結果が返ってきました。

> python3 .\cap_once.py
FFmpeg failed: Command '['ffmpeg', '-rtsp_transport', 'tcp', '-i', 'rtsp://**:***@192.168.*.*/live', '-ss', '00:00:01', '-vframes', '1', '-q:v', '2', '-y', '\\\\192.168.*.*\\***\\2026-01-25\\20260125_124510.jpg']' timed out after 20 seconds

タイムアウトしていますが、pingは通っている。 前のセッションが残っていて接続待ちの状態になっている?のかという説はあるが、 スマホからの確認では動作している。 スマホでの画像表示と、rtspは別動作なので、rtsp側だけの問題(セッション残留?)の可能性がある。

暫定対処 その2

セッション残留の問題だと仮定して、rtspの再起動での復旧を試みる。スマホでの操作で一度「PCで再生」をオフにして再起動してみる。
 この操作を行うとrtsp用のユーザとパスワードが更新された。新たなユーザとパスワードを反映すると、キャプチャに成功し、 自動キャプチャは復旧した。

問題の原因は、残留セッションとみてよいようだ。 これは、ATOM CAM2側で何とかしてほしいが、 録画の都合(解像度の高い録画をしたい)から古いバージョンのファームウェアを使用している。このため最新パッチを適用することができず、ATOM CAM2側での”残留セッション”の改善は望めない。そこで、hsBox側での別の改善策を取ることにする。

その2の問題の 改善策案

”残留セッション”問題の対処方針は、上の”暫定対処”の問題を早期検知し、できるだけ手間をかけずに復旧できるようにするというものである。

 復旧手順を明記した内容での問題検知通知を出し、スマホ操作で「PCで再生」を再起動し、あらたなユーザ名・パスワードをGUI設定画面で更新するというものである。
とりあえず今時点での簡便な復旧策はこの辺でしょう。

他に良い案があれば、コメント投稿をお願いします。


関連記事

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

hsBox1.3で、太陽光発電機器故障を検知して通知してみよう

2025年12月22日で太陽光発電 通知メールがなくなる件。 代替として自前で収集した情報をLINEに通知してみた」をさらに高度化を検討してみた。
 過去の実装(太陽光発電システムの故障判断アルゴリズム(その1))で、故障検知を高精度(6年間で5回検知・誤検知なし。1件はパワコンブレーカーダウン、4件はデータアップロードネットワークのダウン)で実装できていましたが、さきのソーラーフロンティアのサービス終了でこの検知システムが使えなくなりました。そこで、新たな検知システムの実装を、先に公開してシステムを強化して、検討してみます。

 ちなみに、公開済みの発電量収集代替実装では、従来のソーラーフロンティアの発電量との誤差は±2%程度なので、3か所ともに発電量収集代替実装を導入して、既存の検知システムにデータを流し込めば高精度での判定を継続できるようになります。しかし、通常、太陽光発電システムを複数保有しているほぼないので、この方式はほぼ使えません。そこで、ここでは1システムだけであっても判定することができる仕組みを検討してみます。

SolarAlert_
太陽光発電システム 故障通知

故障検知実現の設計方針

最終的には、人が判断するが、できるだけ判定精度を上げたい。過去の高精度判定システム構築での知見を反映して設計する。ポイントは次の通り。
・快晴時の発電量は安定しており、きれいなカーブを描く。
   →判定はこの部分のデータを使う。
・雲がある晴れの日のデータは変化が激しい。
・発電量の小さい日の発電量のばらつきが大きい。
   →判定には使用せず、参考値として通知する。
・四季の変化での発電量の補正が必要
→過去実装では、実績のピークデータをもとに関数化して組み込み
想定発電量と比較し、判定するかどうかを切り分けした。
・外部情報として最も近い気象庁観測点のデータを使用する。
・太陽光パネルの様々な設置場所ごとの違いによる想定発電量を単純な計算式ではなく学習方式を採用する。

使用するデータは、以前に公開したParquet形式で保存したものとする。

実装例 


import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os
import sys
import argparse
import requests
from bs4 import BeautifulSoup

# ==================== 設定エリア ====================
NAS_PATH = r"/mnt/nas/PowerData" #★
MODEL_PATH = os.path.join(NAS_PATH, "learning_model.parquet") # 学習データ保存先
IFTTT_WEBHOOK_URL0 = "https://maker.ifttt.com/trigger/{event}/with/key/{your_key}"
IFTTT_EVENT_NAME = "message_to_myline" #★
your_key = "************" #★
IFTTT_WEBHOOK_URL = IFTTT_WEBHOOK_URL0.replace("{your_key}", your_key)

# 判定しきい値 (期待値の50%以下が2時間続いたら故障疑いなど)
THRESHOLD_RATIO = 0.5 #★
# ====================================================

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, json=payload, timeout=10)
return response.status_code == 200
except Exception as e:
print(f"LINE通知失敗: {e}")
return False

def get_jma_sunshine_hourly(target_date: datetime):
"""気象庁HPから10分ごとの日照時間を取得し1時間単位(0.0-1.0)で返す"""
url = (f"https://www.data.jma.go.jp/stats/etrn/view/10min_s1.php?"
f"prec_no=**&block_no=****&year={target_date.year}&month={target_date.month}&day={target_date.day}&view=p2") #★
try:
res = requests.get(url, timeout=10)
res.encoding = res.apparent_encoding
soup = BeautifulSoup(res.text, 'html.parser')
rows = soup.find_all('tr', class_='mtx')
sunshine_10min = []
for row in rows:
cols = row.find_all('td')
if len(cols) > 10:
val = cols[9].text.strip() # 日照時間の列
sunshine_10min.append(float(val) if val else 0.0)
# 1時間(6個)ごとに合計(最大1.0)
return [sum(sunshine_10min[i:i+6]) for i in range(0, len(sunshine_10min), 6)]
except Exception as e:
print(f"気象庁データ取得失敗: {e}")
return [None] * 24

def update_learning_model(target_date: datetime, hourly_actual: list, hourly_sun: list):
"""日照が良い時のデータを『快晴時の正解』として学習モデルを更新"""
month = target_date.month
try:
if os.path.exists(MODEL_PATH):
model_df = pd.read_parquet(MODEL_PATH)
else:
# 初期モデル: 全月0で作成
model_df = pd.DataFrame(0.0, index=range(1, 13), columns=[f"h_{i}" for i in range(24)])

updated = False
for h in range(7, 18): # 発電時間帯のみ
# 日照率が0.9以上かつ、これまでの学習値より高い(または初データ)場合に更新
if hourly_sun[h] and hourly_sun[h] >= 0.9:
current_val = model_df.loc[month, f"h_{h}"]
if hourly_actual[h] > current_val:
model_df.loc[month, f"h_{h}"] = hourly_actual[h]
updated = True

if updated:
model_df.to_parquet(MODEL_PATH)
print("学習モデルを更新しました。")
except Exception as e:
print(f"学習モデル更新失敗: {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 main():
parser = argparse.ArgumentParser()
parser.add_argument("--da", type=int, default=0)
args = parser.parse_args()

base_date = datetime.now() + timedelta(days=args.da)
date_label = base_date.strftime("%Y/%m/%d")

try:
df = load_day_data(base_date)
# 1時間ごとの発電量(value1)リスト作成
df['hour'] = pd.to_datetime(df['timestamp']).dt.hour # timestamp列があると想定
hourly_actual = df.groupby('hour')['value1'].mean().reindex(range(24), fill_value=0.0).tolist()
except Exception as e:
print(f"データ読み込みエラー: {e}")
return

# 気象庁データ取得
hourly_sun = get_jma_sunshine_hourly(base_date)

# 学習モデルの更新
update_learning_model(base_date, hourly_actual, hourly_sun)

# 故障検知
is_anomaly = False
anomaly_details = []
if os.path.exists(MODEL_PATH):
model_df = pd.read_parquet(MODEL_PATH)
month = base_date.month

for h in range(8, 17): # 影の影響が少ない時間帯を重点チェック
sun = hourly_sun[h]
if sun and sun > 0.3: # ある程度の日照がある場合
expected = model_df.loc[month, f"h_{h}"] * sun
actual = hourly_actual[h]
if actual < expected * THRESHOLD_RATIO:
is_anomaly = True
anomaly_details.append(f"{h}時(実測{actual:.1f}kW/期待{expected:.1f}kW)")

# メッセージ作成
total_gen = sum(hourly_actual) * (10/60) # 10分データの場合の概算
status_msg = "✅ 正常" if not is_anomaly else "⚠️ 故障の疑い"

message = f"⚡️ {date_label} 発電実績 ({status_msg})\n" \
f"総発電量:{total_gen:.2f} kWh\n"

if is_anomaly:
message += "\n【異常検知】\n" + "\n".join(anomaly_details)
send_line_message("太陽光発電アラート", message, "ALERT")
else:
send_line_message("太陽光発電データ", message, "OK")

print(message)

if __name__ == "__main__":
main()

上の実装は、あくまでもサンプルです。 特に”★”印のある行は環境に合わせて修正してください。

このコードで検証していますが、かなり過敏で誤検知が多いです。
閾値調整で、精度を上げることになると思いますが、過去実装での故障判定ロジックを超える精度にはならなさそうです。 誤検知をゼロにするには、十分に学習させたうえで、1日中快晴だった日のデータでのみで判定する必要がありそうです。

他に改善案などあればコメントをお願いします。

関連記事

 

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/

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での通知にして、頻度が少ない異常の検知をした場合にだけ使うようにするのがよさそうです。

関連記事

本番、仕切り直し。(proxy設定 httpからhttpsに変換、 ポストデータ取得を検証)この方法は断念…   

「フロンティアモニターホームサーバー」のプロキシ設定を変更して、プロキシ経由でのデータ送信を検証してみます。hsBoxのIPとプロキシのポート番号8080を設定しました。すると、フロンティアモニター – ホームエネルギーモニタリングサービス – https://www.frontier-monitor.com/persite/top へのデータ反映が止まりました。当然過去分は見えますが、プロキシ設定変更後のデータが反映されません。 先のポストデータの取得のスクリプトでは、データが取れないどころか、「フロンティアモニターホームサーバー」から何か届いているのかさえも確認できません。スプリプトを改造してスタブ実装で200応答するように改造しましたが、コネクションまでは確認でき接続先サーバーを記録できることまではできましたが、TLS接続してくるのを疑似CAで応答できなさそうであることを確認しました。

solar
solar

PROXY方式についての結論

ユーザー名、パスワードを設定してもhttpsで接続し、httpで接続してくることはない。疑似的接続させることもほぼ不可能である。
 ということで、PROXY方式での情報採取はあきらめました。


再び、内部APIの調査、CGIでデータを採取

次回は、内部CGIでデータをとれるかを検証してみます。 どうもこっちが本命になりそう。


関連記事

さて本番だ、切り替えてみよう。(proxy設定 httpからhttpsに変換、 ポストデータ取得を検証)あと1歩に見えたが…

「フロンティアモニターホームサーバー」のプロキシ設定を変更して、プロキシ経由でのデータ送信を検証してみます。hsBoxのIPとプロキシのポート番号8080を設定しました。すると、フロンティアモニター – ホームエネルギーモニタリングサービス – https://www.frontier-monitor.com/persite/top へのデータ反映が止まりました。当然過去分は見えますが、プロキシ設定変更後のデータが反映されません。 先のポストデータの取得のスクリプトでは、データが取れないどころか、「フロンティアモニターホームサーバー」から何か届いているのかさえも確認できません。横から、テスト用のポストをするとデータは記録されるので、構築した環境は動いているようです。

今回の結果を先に書くと、www.frontier-monitor.comの仕様の古さのために、当初の第一階目のゴールにはたどり着けないということが判明した。そして、いきなり最終ゴールにむけた実装が必要ということがわかった。調査結果を以下に書く。ゴールだけを見たいという人はこの記事は読み飛ばしてもらって構わない。

再びデバック開始

よく見たら、ジャーナルにたくさん「フロンティアモニターホームサーバー」接続記録が出ていました。

journalctl -u fm-mitmproxy.service -f


11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:04.599][192.168.x.xx:57372] server connect www.frontier-monitor.com:443 (150.31.252.104:443)
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:05.235][192.168.x.xx:57372] Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to use. You may need to adjust mitmproxy's tls_version_client_min option.
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:05.240][192.168.x.xx:57372] client disconnect
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:05.245][192.168.x.xx:57372] server disconnect www.frontier-monitor.com:443 (150.31.252.104:443)
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:11.362][192.168.x.xx:43760] client connect
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:11.469][192.168.x.xx:43760] server connect www.frontier-monitor.com:443 (150.31.252.104:443)
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:12.019][192.168.x.xx:43760] Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to use. You may need to adjust mitmproxy's tls_version_client_min option.
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:12.023][192.168.x.xx:43760] client disconnect
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:12.028][192.168.x.xx:43760] server disconnect www.frontier-monitor.com:443 (150.31.252.104:443)
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:18.092][192.168.x.xx:40724] client connect
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:18.143][192.168.x.xx:40724] server connect www.frontier-monitor.com:443 (150.31.252.104:443)
11月 29 17:11:53 hsbox mitmdump[47809]: [17:10:18.670][192.168.x.xx:40724] Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to use. You may need to adjust mitmproxy's tls_version_client_min option.
11月 29

「Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to use. You may need to adjust」このログが大量に出ているが、これが問題だったようだ。 TLS1.0に下げるように要求されている。 hsBoxでも設定で下げれないことはないが、外部公開している入り口が怪しくなるので無理にTLS1.0にさげないことにした。

太陽光機器(192.168.*.**)  
      ↓ CONNECT www.frontier-monitor.com:443 HTTP/1.1    プロキシ宛
mitmproxy(192.168.*.*:8080) ←ここで TLS 開始(クライアント側 TLS)  
      ↓ TLS ハンドシェイク開始  
      × 失敗 → Client TLS handshake failed  
      (mitmproxy → 150.31.252.104:443 にはまだ接続すらしていない)


ということで、データのキャプチャにも失敗し、プロキシ経由でのサーバへのアップロードもできていない。 プロキシ設定してから、 www.frontier-monitor.comへのデータアップロードも止まったままである。
  この記事での成果は、「フロンティアモニターホームサーバー」の送信先がwww.frontier-monitor.comであると確認できたことだ。

一旦、切り戻しして、仕切り直しましょう。そして、最終型にむけて再検討します。


関連記事