0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RON-V #20. UPS HATの電圧監視と安全Shutdownを実装する

0
Last updated at Posted at 2026-10-20

[!NOTE]
2026-09-23 現行GitHub同期メモ

UPS監視は初期実装から更新されています。現行 main では ups_guard.py がINA219を直接読み、5秒周期で監視します。通常の外部電源喪失は 電流 <= -0.50 A が20秒継続、非常停止は 7.50 V以下、INA219読取エラーは 30秒継続でshutdown要求を出します。現在の状態は /run/ronv/ups-status.json に保持し、Web UIからも参照します。

RON-V 連載リンク
← 前の記事:#19 — スマホから操作するWeb UI
→ 次の記事:#21 — AtomS3RのCAN・IMU・Shutdown制御

RON-Vは車載環境で使用するため、車両側の給電が途切れたときにRaspberry Pi 5を突然電源断させないことが重要です。

現在はWaveshare UPS HAT (B) のINA219を使って、

外部電源あり
 ↓
通常動作

外部電源喪失
 ↓
UPS放電を検出
 ↓
一定時間確認
 ↓
安全shutdown要求

という構成にしています。


20.1 INA219

UPS HATのINA219はI2Cで読みます。

RON-Vの現行実装では、

I2C bus : 1
address : 0x42

です。

確認例:

ls /dev/i2c-*

sudo i2cdetect -y 1

0x42 が見えればINA219へアクセスできます。


20.2 Python依存関係

INA219アクセスには smbus2 を使います。

現在は requirements.txt に、

smbus2==0.6.1

を固定しています。

新規環境では個別pipではなく、

cd /home/ronv/ronv

/home/ronv/.venvs/ronv/bin/pip install \
  -r requirements.txt

で揃えます。


20.3 現行ups_guard.py

監視本体は、

app/power/ups_guard.py

です。

現在は ups_guard.py 自身がINA219を直接読みます。

監視に使う主な定数は、

BUS = 1
ADDR = 0x42

DISCHARGE_A = -0.5
EMERGENCY_V = 7.5

POWER_LOSS_CONFIRM_SEC = 20
UPS_READ_ERROR_CONFIRM_SEC = 30

です。

初期実装ではUPS値をSQLiteへ連続記録する方式も試しましたが、現在の安全停止判定はSQLite記録を前提にしていません。


20.4 5秒周期のsystemd timer

現行timerは、

[Unit]
Description=RON-V UPS Guard Timer

[Timer]
OnBootSec=5s
OnUnitActiveSec=5s
AccuracySec=500ms
Persistent=false
Unit=ronv-ups-guard.service

[Install]
WantedBy=timers.target

です。

serviceはoneshotです。

[Service]
Type=oneshot
User=ronv
RuntimeDirectory=ronv
RuntimeDirectoryPreserve=yes
ExecStart=/home/ronv/.venvs/ronv/bin/python /home/ronv/ronv/app/power/ups_guard.py

約5秒ごとに短時間起動し、UPS状態を判定して終了します。


20.5 通常状態

外部電源があり、放電条件に入っていないときは、

NORMAL

です。

例:

NORMAL 8.188 V 0.120 A UPS state normal

電流値がプラス側なら、今回の実機ではUPSが放電していない状態として観測されます。


20.6 外部電源喪失の判定

車両側電源が切れると、UPSがRaspberry Piと周辺機器へ給電し始め、INA219電流が放電方向へ変化します。

現行条件は、

current <= -0.50 A

です。

1回だけ負になる瞬間ではshutdownしません。

current <= -0.50 A
 ↓
POWER_LOSS_SUSPECTED
 ↓
20秒継続確認
 ↓
SHUTDOWN_PENDING

とします。

開始時刻は、

/run/ronv/ups-power-loss.since

へ保持します。

電流が正常側へ戻ればmarkerを削除し、判定を解除します。


20.7 20秒確認

POWER_LOSS_CONFIRM_SEC は現在20秒です。

実機試験では、例えば、

POWER_LOSS_SUSPECTED ... -1.030 A; 5.5/20s
POWER_LOSS_SUSPECTED ... -0.973 A; 10.5/20s
POWER_LOSS_SUSPECTED ... -1.079 A; 15.8/20s
SHUTDOWN_PENDING ... -1.077 A; 21.3s (confirm 20s)

という流れを確認しました。

つまり瞬間的な負荷変動ではなく、継続した放電を見て外部電源喪失と判定します。


20.8 非常低電圧

通常の20秒確認とは別に、

voltage <= 7.50 V

なら、

SHUTDOWN_PENDING
reason = emergency_low_voltage

とします。

これは通常の外部電源喪失判定が成立しない場合にも深い放電を続けないための保険です。


20.9 INA219読取エラー

電源喪失やI2C異常によってINA219そのものを読めない場合も考慮します。

現行実装では、

UPS read error
 ↓
/run/ronv/ups-read-error.since
 ↓
30秒継続
 ↓
SHUTDOWN_PENDING

です。

1回のI2Cエラーで即shutdownせず、

UPS_READ_ERROR_CONFIRM_SEC = 30

で確認します。


20.10 runtime status

現行実装では最新UPS状態を、

/run/ronv/ups-status.json

へatomic writeします。

内容は概ね、

{
  "timestamp_jst": "...",
  "status": "NORMAL",
  "voltage_v": 8.188,
  "current_a": 0.120,
  "power_w": 0.983,
  "percent": 91.2,
  "detail": "UPS state normal"
}

です。

このファイルはWeb UIからも読み込まれます。

RON-V Web
 ↓
UPS Battery
 ↓
Voltage / Current / Power / SOC / Guard

として確認できます。

SOCは電圧からの簡易推定であり、クーロンカウンタによる厳密な残容量ではありません。


20.11 shutdown request

ups_guard.py 自身にroot権限は与えません。

shutdown条件を満たすと、

/run/ronv/ups-shutdown.request

を作成します。

systemd path unit:

[Path]
PathChanged=/run/ronv/ups-shutdown.request
Unit=ronv-ups-shutdown.service

が検出し、

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl poweroff

を実行します。

つまり、

ronv user
 ups_guard
    ↓ request file

root/systemd
    ↓
 poweroff

と権限を分離しています。


20.12 テストモード

実際にshutdownさせず、判定ロジックだけ試すために環境変数を用意しています。

RONV_UPS_TEST_VOLTAGE
RONV_UPS_TEST_CURRENT
RONV_UPS_TEST_ELAPSED_SEC

例えば、

RONV_UPS_TEST_VOLTAGE=8.0 \
RONV_UPS_TEST_CURRENT=-0.8 \
RONV_UPS_TEST_ELAPSED_SEC=21 \
/home/ronv/.venvs/ronv/bin/python \
  /home/ronv/ronv/app/power/ups_guard.py

で外部電源喪失条件を模擬できます。

テストモードでは実shutdown requestを生成しません。


20.13 現行の停止条件

現在の条件をまとめると、

A. 通常停止
current <= -0.50 A
        ↓
20秒継続
        ↓
SHUTDOWN_PENDING

B. 非常停止
voltage <= 7.50 V
        ↓
SHUTDOWN_PENDING

C. センサー異常
INA219 read error
        ↓
30秒継続
        ↓
SHUTDOWN_PENDING

です。

バッテリーを限界まで使うのではなく、

外部電源を失ったことを早めに検出し、ファイルシステムが正常なうちに停止する

ことを優先しています。


20.14 状態確認

現在状態:

cat /run/ronv/ups-status.json

timer:

systemctl status \
  ronv-ups-guard.timer \
  --no-pager

直近ログ:

journalctl \
  -u ronv-ups-guard.service \
  -n 100 \
  --no-pager

shutdown path:

systemctl status \
  ronv-ups-shutdown.path \
  --no-pager

20.15 旧UPS loggerについて

repoには開発履歴として、

app/power/ups_logger.py
app/power/ups_report.sh

も残っています。

これらはSQLiteへUPS値を記録した初期試験のコードです。

ただし、現行の安全shutdown制御は ups_guard.py とruntime status fileを正とします。

現在のGitHubには ronv-ups-log.timer のようなlogger timerは存在せず、UPS安全監視は ronv-ups-guard.timer が担当します。


20.16 shutdownログ

安全停止後の解析にはsystemd journalとshutdownログを利用します。

確認例:

journalctl \
  -u ronv-ups-guard.service \
  -u ronv-ups-shutdown.service \
  --no-pager

journalctl --list-boots --no-pager

実機試験では、放電継続判定のあと ronv-ups-shutdown.service が起動し、正常にpoweroffへ移行することを確認しました。


20.17 POWER_OFF_ON_HALT

Raspberry Pi 5 EEPROMの POWER_OFF_ON_HALT は、UPS HATや車載電源の再投入方式と組み合わせて考える必要があります。

「shutdown後に消費を減らす」ことと、

車両電源OFF
 ↓
shutdown
 ↓
次回車両電源ON
 ↓
自動起動

を両立する必要があるためです。

RON-Vではソフトウェア上のsafe shutdownと、実際の電源再投入挙動を分けて評価します。


まとめ

現在のRON-V UPS監視は、

INA219
  ↓
ups_guard.py
  ↓ 5秒周期
NORMAL
 / POWER_LOSS_SUSPECTED
 / SHUTDOWN_PENDING
  ↓
/run/ronv/ups-status.json
  ↓
Web UI

shutdown条件成立
  ↓
/run/ronv/ups-shutdown.request
  ↓
systemd.path
  ↓
systemctl poweroff

です。

初期の「1分ごとにUPS値をSQLiteへ保存して低電圧を待つ」方式から、

外部電源喪失を放電電流で早期検出し、20秒確認後に安全停止する

方式へ発展しました。

車載ノードでは、UPS容量を最後まで使うことより、SQLite・画像・ファイルシステムを壊さず正常停止することを優先します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?