お天気時計の作成(その11:気象庁API移行とエラー対応・完全版)

お天気時計の作成(その11:気象庁API移行とエラー対応・完全版)

これまで「お天気時計」のデータ取得には、livedoor天気の互換APIである「つくみじま(Tsukumijima)氏の天気予報API」を利用させていただいていました。 しかし、APIへのアクセスが不調となったため、今回は気象庁(JMA)のJSON APIから直接データを取得する方式へライブラリ(jma_weather_get.py)を書き換えて更新を行いました。

以前作成したコードとの互換性を保ちつつ、画像取得エラーの防止や表示テキストの最適化を行いましたので、備忘録としてまとめます。


1. 移行にあたって対応した課題

気象庁APIへの移行時に直面した主な課題と、今回の修正で対応したポイントです。

  • 表示テキストの長文化(203などの天気コード対応)
  • 気象庁APIの生テキスト(raw_weather_text)を取得すると「くもり のはじめ頃 雨 所により...」のような極めて長い文字列が返ってくる場合があります。全天気コード(100〜400番台)を辞書(code_map)で網羅し、「曇時々雨」といった短縮表現へ確実に変換するよう対応しました。

  • 特定の天気用アイコンが存在せず404エラーになる

  • 気象庁の画像サーバーには一部の天気コード(例: 111211 など)に対応するSVG画像が存在しません。基本コード(例: 101201)へマッピングを差し替える安全策を組み込みました。

  • **降水確率取得時の UnboundLocalError**

  • 時刻判定ループで変数 cor が未定義のまま参照されるエラーを防ぐため、関数の冒頭で初期値を設定し例外処理を強化しました。

  • **メインプログラム側の ValueError: invalid literal for int()**

  • アイコンURLの末尾から天気コード(数値)を抽出する際、URLフォーマットの差異で空文字となり数値変換エラーが発生していました。返却する weather_url のフォーマットを統一して解消しました。

2. 修正後のPythonライブラリ (lib/jma_weather_get.py)

上記すべての課題に対応し、エラー落ちを防ぐガード処理を追加した最新のライブラリコードです。

import datetime
import os
import re
import urllib.request
import cairosvg
from PIL import Image
import requests


def weather_get():
    # 変数の初期化(エラー時の未定義を確実に防止)
    cor = "0%"
    weather = "不明"
    weather_url = "https://www.jma.go.jp/bosai/forecast/img/100.svg"

    # 140000 = 神奈川県(横浜)の気象庁エリアコード
    area_code = "140000"
    url = f"https://www.jma.go.jp/bosai/forecast/data/forecast/{area_code}.json"

    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/120.0.0.0 Safari/537.36"
        )
    }

    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        print("Error:{}".format(e))
        return cor, weather, weather_url

    else:
        weather_json = response.json()

        # --- 1. 天気と天気コードの取得と変換 ---
        area_weather = weather_json[0]["timeSeries"][0]["areas"][0]
        raw_weather_text = area_weather["weathers"][0]
        raw_code = str(area_weather["weatherCodes"][0]).strip()

        # コードに基づく綺麗な天気名の変換マッピング(全コード網羅版)
        code_map = {
            # 晴れベース (100番台)
            "100": "晴れ",
            "101": "晴時々曇",
            "102": "晴一時雨",
            "103": "晴時々雨",
            "104": "晴一時雪",
            "105": "晴時々雪",
            "106": "晴一時雨か雪",
            "107": "晴時々雨か雪",
            "108": "晴一時雨か雷雨",
            "110": "晴後曇",
            "111": "晴後時々雨",
            "112": "晴後雨",
            "113": "晴後時々雪",
            "114": "晴後雪",
            "115": "晴後雨か雪",
            "116": "晴後雨か雷雨",
            "117": "晴後雷雨",
            "118": "晴後雨か雪",
            "119": "晴後雨か雷雨",
            "120": "晴後雨",
            "121": "晴後雨",
            "122": "晴後雨",
            "123": "晴後山沿い雷雨",
            "124": "晴後山沿い雪",
            "125": "晴後雷雨",
            "126": "晴後雨か雪",
            "127": "晴後雨か雷雨",
            "128": "晴後雨か雪",
            "130": "朝のうち霧後晴",
            "131": "晴明け方一時雨",
            "132": "晴朝晩一時雨",
            "140": "晴時々雨か雪",
            "160": "晴一時雪か雨",
            "170": "晴時々雪か雨",
            "181": "晴後特記事項なし",
            # 曇りベース (200番台)
            "200": "曇り",
            "201": "曇時々晴",
            "202": "曇一時雨",
            "203": "曇時々雨",
            "204": "曇一時雪",
            "205": "曇時々雪",
            "206": "曇一時雨か雪",
            "207": "曇時々雨か雪",
            "208": "曇一時雨か雷雨",
            "209": "霧",
            "210": "曇後晴",
            "211": "曇後時々雨",
            "212": "曇後雨",
            "213": "曇後時々雪",
            "214": "曇後雪",
            "215": "曇後雨か雪",
            "216": "曇後雨か雷雨",
            "217": "曇後雷雨",
            "218": "曇後雨か雪",
            "219": "曇後雨か雷雨",
            "220": "曇後雨",
            "221": "曇後雨",
            "222": "曇後雨",
            "223": "曇後日中時々晴",
            "224": "曇後昼頃から雨",
            "225": "曇後夕方から雨",
            "226": "曇後夜雨",
            "228": "曇後雪",
            "229": "曇後雪",
            "230": "曇後雪",
            "231": "曇海上海岸霧かモヤ",
            "240": "曇時々雨か雪",
            "250": "曇時々雨か雷雨",
            "260": "曇一時雪か雨",
            "270": "曇時々雪か雨",
            "281": "曇後特記事項なし",
            # 雨ベース (300番台)
            "300": "雨",
            "301": "雨時々晴",
            "302": "雨時々止む",
            "303": "雨時々雪",
            "304": "雨か雪",
            "306": "大雨",
            "308": "雨か雷雨",
            "309": "雨一時雪",
            "311": "雨後晴",
            "313": "雨後曇",
            "314": "雨後時々雪",
            "315": "雨後雪",
            "316": "雨後雨か雪",
            "317": "雨後雷雨",
            "320": "朝のうち雨後晴",
            "321": "朝のうち雨後曇",
            "322": "雨後雪か雨",
            "323": "雨後昼頃から晴",
            "324": "雨後夕方から晴",
            "325": "雨後夜は晴",
            "326": "雨後雪",
            "327": "雨後雪",
            "328": "雨一時雪",
            "329": "雨一時雪",
            "340": "雪か雨",
            "350": "雨か雷雨",
            "361": "一部に雨",
            "371": "一部に雨か雪",
            # 雪ベース (400番台)
            "400": "雪",
            "401": "雪時々晴",
            "402": "雪時々止む",
            "403": "雪時々雨",
            "405": "大雪",
            "406": "風雪強い",
            "407": "暴風雪",
            "409": "雪一時雨",
            "411": "雪後晴",
            "413": "雪後曇",
            "414": "雪後雨",
            "420": "朝のうち雪後晴",
            "421": "朝のうち雪後曇",
            "422": "雪後雨か雪",
            "423": "雪後雨",
            "425": "雪後晴",
            "426": "雪後曇",
            "427": "雪後雨",
            "450": "大雪",
        }

        if raw_code in code_map:
            weather = code_map[raw_code]
        elif raw_code.startswith("1"):
            weather = "晴れ"
        elif raw_code.startswith("2"):
            weather = "曇り"
        elif raw_code.startswith("3"):
            weather = "雨"
        elif raw_code.startswith("4"):
            weather = "雪"
        else:
            weather = "不明"

        # --- 2. 安全なアイコンURLの生成 (404対策) ---
        icon_map = {
            "111": "101",
            "112": "110",
            "211": "201",
            "311": "301",
        }
        safe_code = (
            icon_map.get(raw_code, raw_code[0] + "00")
            if len(raw_code) == 3
            else "100"
        )
        weather_url = (
            f"https://www.jma.go.jp/bosai/forecast/img/{safe_code}.svg"
        )

        os.makedirs("./icon", exist_ok=True)

        # --- 3. アイコン画像のダウンロード ---
        try:
            req = urllib.request.Request(weather_url, headers=headers)
            with urllib.request.urlopen(req) as res_img:
                img_data = res_img.read()
                with open("./icon/temp.svg", "wb") as f:
                    f.write(img_data)
                with open("./icon/" + weather_url[-7:], "wb") as f:
                    f.write(img_data)

            # --- 4. SVG -> PNG 変換 ---
            os.environ["LC_CTYPE"] = "ja_JP.UTF-8"
            svg = "./icon/temp.svg"
            png = "./icon/temp.png"

            cairosvg.svg2png(url=svg, write_to=png)
            img = Image.open(png)
        except Exception as img_err:
            print("Image Processing Error:{}".format(img_err))

        # --- 5. 現在の時間帯の降水確率を取得 ---
        try:
            pop_series = weather_json[0]["timeSeries"][1]
            pop_times = pop_series["timeDefines"]
            pop_values = pop_series["areas"][0]["pops"]

            now_utc = datetime.datetime.now(datetime.timezone.utc)

            if pop_values:
                cor = f"{pop_values[0]}%"

            for i, time_str in enumerate(pop_times):
                target_time = datetime.datetime.fromisoformat(time_str)
                if now_utc >= target_time and i < len(pop_values):
                    cor = f"{pop_values[i]}%"
        except Exception as pop_err:
            print("POP Processing Error:{}".format(pop_err))

        print("現在の降水確率 {}".format(cor))
        print("today is  {}".format(weather))

    return cor, weather, weather_url

3. Raspberry Pi Zero 起動時自動実行の設定(復習)

本体側のプログラム変更に伴い、自動起動の設定も改めて確認を行いました。 以前投稿した「お天気時計の作成(その5:自動起動)」と同様に、cron を使って起動時に自動実行されるよう設定しています。

設定手順の復習:

crontab -e

設定内容:

@reboot sleep 30 && python3 /home/tomtomst2/python_p/weather_clock_v10.py > /home/tomtomst2/weather.log 2>&1
  • ネットワークが確立するまでの時間を確保するために sleep 30 を挟んでいます。
  • エラー発生時の追跡用として weather.log への標準出力・エラー出力のリダイレクトを設定しています。

4. まとめ

サードパーティ製APIの停止に伴い気象庁公式APIへと移行しましたが、データ構造やレスポンスの違いから初期移行時にはいくつか予期せぬエラーが発生しました。

今回の改修により、以下の改善が実現できました。

  • 表示の安定化: 天気コードの完全網羅とフォールバック処理により、長い文章で画面表示が崩れる問題を解消。
  • 堅牢性の向上: 画像取得エラー(404)や変数の未定義エラーを安全にバイパスする例外処理を実装。
  • 運用の自動化: cron による起動時自動実行とログ出力環境を整備し、メンテナンスフリーでの運用を復活。

気象庁公式APIは外部サービスの存続リスクに左右されにくいため、長期的な運用においても非常に安心感があります。自作お天気時計の動作でお困りの方の参考になれば幸いです。

【Raspberry Pi】ObsidianのノートをInfiniCLOUDへ自動バックアップ!rclone+cron設定手順

【Raspberry Pi】ObsidianのノートをInfiniCLOUDへ自動バックアップ!rclone+cron設定手順

Raspberry Piのローカルディスク(ELECOM HDD)にあるObsidianVaultを、InfiniCLOUDへ毎日自動で世代バックアップする環境を構築しました。

Raspberry Pi OS Bookwormにおけるcronの仕様変更や、WebDAV・DNS周りのトラブルを乗り越えて安定動作するようになったので、手順とハマりポイントをまとめます。


1. 目的

Raspberry Piに接続された外付けHDD(ELECOM HDD)内のObsidianデータを、クラウドストレージ(InfiniCLOUD)へ毎夜自動で同期・世代バックアップします。

2. 使用するツール

  • rclone: WebDAVに対応した高機能ファイル同期ツール
  • cron(root権限): 定時自動実行用タスクスケジューラ

3. 事前確認(準備)

作業をスムーズに進めるため、事前に以下の接続情報とローカルパスを確認しておきます。

① InfiniCLOUDの接続情報を確認

  1. InfiniCLOUD のマイページにログインします。
  2. 「外部アプリ接続」 セクションを開き、「外部アプリ接続を許可する」にチェックを入れます。
  3. 表示される以下の情報を手元にメモ(またはコピー)しておきます。
    • WebDAV接続URL(例: https://xxxx.infini-cloud.net/dav/
    • 接続ID
    • アプリパスワード(※管理画面のログイン用パスワードではなく、ここで再発行・表示される英数字)

② ObsidianVaultのローカルパスを確認

WebDAVサーバー(Apache)の設定ファイルを確認し、ObsidianVaultの公開ディレクトリ(実体)がローカルディスクのどこにあるかを特定します。

1. ApacheのWebDAV設定ファイルを確認:

sudo cat /etc/apache2/sites-available/webdav.conf
  • 設定内の AliasDocumentRoot の記述から、WebDAVの公開パスを確認します。

2. マウント状態とディレクトリの存在を確認:

# 外付けHDDなどのマウント位置を確認
df -h | grep /mnt

# 該当ディレクトリが存在するか確認
ls -ld /mnt/elecom/webdav/ObsidianVault
  • 本環境の例: /mnt/elecom/webdav/ObsidianVault (ext4フォーマットの外付けHDD上)

4. rcloneの設定

rcloneのインストール

まだインストールしていない場合は、Raspberry Piで以下を実行します。

sudo apt update
sudo apt install rclone -y

接続設定の追加

以下のコマンドでインタラクティブ設定を開始します。

rclone config

対話形式で以下のように設定を選択・入力します。

  • New remote: infini(任意の名前)
  • Storage type: WebDAV
  • Vendor: Nextcloud(InfiniCLOUDと互換性あり)
  • URL: 先ほど確認した WebDAV接続URL
  • User: 先ほど確認した 接続ID
  • Pass: 先ほど確認した アプリパスワード

5. バックアップ構成

  • バックアップ元(ローカル)
/mnt/elecom/webdav/ObsidianVault
  • バックアップ先(最新版データ)
infini:ObsidianBackup
  • バックアップ先(世代管理データ)
infini:ObsidianBackupHistory/YYYY-MM-DD

6. バックアップコマンド(手動テスト)

まずは手動で同期コマンドを実行し、正常に転送できるか確認します。

rclone sync /mnt/elecom/webdav/ObsidianVault \
  infini:ObsidianBackup \
  --backup-dir infini:ObsidianBackupHistory/$(date +%Y-%m-%d) \
  --verbose

7. cronによる自動化(root権限で登録)

【重要】Raspberry Pi OS Bookwormの注意点 Bookwormでは一般的なユーザーのcron(crontab -e)が実行されない挙動が発生するため、sudo crontab -e(root権限)に登録するのが最も確実で安定します。

cronの設定手順

  1. rootのcrontabを開きます。
sudo crontab -e
  1. 以下の設定(環境変数および実行スケジュール)を追記します。
HOME=/home/tomtomst
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

30 3 * * * /usr/bin/rclone sync /mnt/elecom/webdav/ObsidianVault infini:ObsidianBackup --backup-dir infini:ObsidianBackupHistory/$(date +\%Y-\%m-\%d) --verbose >> /var/log/rclone_backup.log 2>&1

💡 実行時刻を「03:30」に設定した理由

  • 毎週日曜日 03:00 に動作するSDカードバックアップ処理が 03:15 頃に完了するため。
  • ディスクI/Oの重なりや競合を避けるために 03:30 に設定しています。

8. 復元方法(リストア)

万が一ローカルデータが破損・消失した場合は、以下のコマンドで特定日付の世代データから復元可能です。

rclone copy infini:ObsidianBackupHistory/<日付>/ /mnt/elecom/webdav/ObsidianVault

9. 今回の構築でハマった点と改善ポイント

構築の過程で直面した問題と、その対策まとめです。

1. ネットワーク・DNS周りの通信不安定

  • 原因: TailscaleのDNS自動書き換えやNetworkManagerによる resolv.conf の上書き、不要なIPv6の有効化が干渉。
  • 対策: DNS書き換えと上書きを抑制し、IPv6を無効化することでWebDAV通信が劇的に安定しました。

2. Raspberry Pi OS (Bookworm) でユーザーcronが動かない

  • 原因: OSの仕様変更により、通常のユーザー権限でのcron実行が不安定に。
  • 対策: sudo crontab -e による root cron に移行。ログ出力(/var/log/rclone_backup.log)を設定して動作を保証。

3. 同期エンジンとバックアップの完全分離

  • メリット: Obsidianの端末間同期にはコミュニティプラグインの 「Sync Engine」 を使用し、バックアップ処理(rclone)とは完全に分離しました。これにより、同期エラーや誤消去が発生してもクラウド上の世代バックアップから安全に復元できます。

まとめ

DNS・WebDAV・rclone・cronといった複数の設定を最適化することで、「完全自動・世代管理付き・堅牢」 なObsidianバックアップ環境が完成しました。

特に 「Bookwormでのroot cron利用」「事前準備でのInfiniCLOUD接続情報・パスの確定」 は、同じ環境で構築する際に知っておくと大幅な時間短縮になるポイントです!

**Raspberry Pi と Tailscaleで「自宅 LAN 全体」を外部から安全にアクセスできるようにした手順まとめ**

Raspberry Pi と Tailscaleで「自宅 LAN 全体」を外部から安全にアクセスできるようにした手順まとめ

自宅の Raspberry Pi を使って、外出先から 自宅 LAN 内のすべての機器(NAS・PC・プリンターなど) に安全にアクセスできる環境を構築しました。

これまでは Raspberry Pi のサーバーだけ外部アクセスできる状態でしたが、
今回の設定によって 自宅 LAN 全体が “どこからでも使えるプライベートネットワーク” に進化しました。

この記事では、
Tailscale のインストール → サブネットルーター化 → LAN 全体への外部アクセス
までの流れをまとめます。

この記事を参考にすれば、あなたの自宅 LAN も安全に外部アクセスできるようになります。


1. Raspberry Pi に Tailscale をインストールする

まず Raspberry Pi に Tailscale を導入します。

■ インストールコマンド

curl -fsSL https://tailscale.com/install.sh | sh

■ 起動(ログイン)

sudo tailscale up

ブラウザが開くので、Google / Microsoft / GitHub などでログインします。

■ Pi の Tailscale IP を確認

tailscale ip

例:

100.100.xx.xx

この IP は外出先から Raspberry Pi にアクセスするための専用アドレスです。


2. スマホ・PCにも Tailscale をインストール

Tailscale は複数端末を同じネットワークに参加させることで機能します。

■ Windows / Mac

https://tailscale.com/download

■ iPhone / Android

App Store / Google Play で「Tailscale」を検索

ログインすると Raspberry Pi が一覧に表示されます。

一覧が表示されない場合は「Machines」を選択すると表示されます。


3. Raspberry Pi を「サブネットルーター」にする(最重要)

市販の NAS やプリンターなどは Tailscale をインストールできません。
そこで Raspberry Pi を 自宅 LAN のゲートウェイにします。

■ 自宅 LAN のネットワーク範囲を公開する

sudo tailscale up --advertise-routes=192.168.2.0/24

(自宅 LAN が 192.168.2.xxx の場合)


4. Tailscale 管理画面でルートを承認する

ブラウザで Tailscale 管理画面を開きます。

raspberrypiの個所ををクリックすると管理画面が表示されます。

■ Machines → Raspberry Pi を選択

「Subnets」に次のような表示が出ます:

  • 192.168.2.0/24(Awaiting Approval)

「Review」を押し、

「192.168.2.0/24」にチェックを入れて「Save」を押すと承認されます。

これで Tailscale 側の設定は完了です。


5. Raspberry Pi の IP フォワーディングを有効化する(必須)

サブネットルーターとして動作するには、
Raspberry Pi が LAN のパケットを転送できる必要があります。

■ 一時的に有効化

sudo sysctl -w net.ipv4.ip_forward=1

これで即座に NAS へアクセスできるようになります。

■ 永続化(再起動しても有効にする)

sudo nano /etc/sysctl.conf

ファイル内に次の行を追加:

net.ipv4.ip_forward=1

保存後、反映:

sudo sysctl -p

確認:

cat /proc/sys/net/ipv4/ip_forward

1 と表示されれば永続化成功


6. 外出先から自宅 LAN 内の機器にアクセスできるようになった

ここまで設定すると、外出先から次のようにアクセスできます。

■ NAS へアクセス

\\192.168.2.50
http://192.168.2.50

■ 自宅 PC の共有フォルダへアクセス

\\192.168.2.10

■ ルーター管理画面へアクセス

http://192.168.2.1

■ Raspberry Pi の WebDAV / Samba / VNC もそのまま利用可能

自宅 LAN 全体が外出先から安全に使えるようになります。


今回の設定でできるようになったこと

  • Raspberry Pi だけでなく 自宅 LAN 全体に外出先からアクセス可能
  • NAS のファイルを外出先で直接扱える
  • 自宅 PC の共有フォルダにアクセスできる
  • ルーターやプリンターなど LAN 内の機器を遠隔管理できる
  • ポート開放不要で安全(ゼロトラストネットワーク)
  • 有線 LAN を優先しつつ、Wi‑Fi に自動フェイルオーバー

これは家庭内ネットワークの利便性を大きく向上させる構成でした。


まとめ:Raspberry Pi × Tailscale は家庭LANの最強アップグレード

今回の構成によって、
自宅 LAN 全体を外出先から安全に使える環境が完成しました。

特に便利なのは、

  • NAS のファイルを外出先で直接扱える
  • 自宅 PC の共有フォルダにアクセスできる
  • 自宅のネットワーク機器をすべて遠隔管理できる
  • しかもポート開放不要で安全

という点です。

Raspberry Pi を使った Tailscale サブネットルーター構成は、
家庭内ネットワークを大幅に便利にしてくれる強力な方法でした。


Raspberry Pi 4:SDカードを毎週自動バックアップし、障害時にPCから復元する方法(備忘録)

Raspberry Pi 4:SDカードを毎週自動バックアップし、障害時にPCから復元する方法(備忘録)

Raspberry Pi の SDカードは突然壊れることがあります。
実際に、サーバー運用中に ext4 が破損して起動不能になったため、
「毎週自動バックアップ → 障害時は PC から復元」できる仕組みを構築しました。

この記事では、
バックアップの仕組み・自動化・復元方法(初心者向け)
をまとめています。


1. バックアップの全体構成

  • バックアップ先:外付けHDD(ELECOM)
  • 保存場所:/mnt/elecom/Backup
  • バックアップ方式:dd + pigz(高速圧縮)
  • 実行タイミング:毎週日曜 3:00
  • 保存世代:最新12世代のみ保持
  • 復元方法:Windows PC で SDカードに書き戻す

2. バックアップフォルダの作成

ELECOM の HDD にバックアップ用フォルダを作成します。

sudo mkdir -p /mnt/elecom/Backup
sudo chown -R tomtomst:tomtomst /mnt/elecom/Backup
sudo chmod 775 /mnt/elecom/Backup

3. pigz(高速 gzip)をインストール

sudo apt install -y pigz

4. 自動バックアップスクリプト

/usr/local/bin/backup_sd.sh を作成します。

sudo nano /usr/local/bin/backup_sd.sh

内容:

#!/bin/bash

# バックアップ保存先
TARGET_DIR="/mnt/elecom/Backup"

# 日付付きファイル名
DATE=$(date +%Y%m%d)
IMG_FILE="$TARGET_DIR/rpi_backup_$DATE.img.gz"

# ELECOM がマウントされている場合はアンマウント
if mountpoint -q /mnt/elecom; then
    sudo umount /mnt/elecom
fi

# 再マウント(fstab に設定されている前提)
sudo mount /mnt/elecom

# バックアップ開始
dd if=/dev/mmcblk0 bs=4M status=progress | pigz > "$IMG_FILE"

# ログ記録
echo "$(date): Backup completed: $IMG_FILE" >> /var/log/sd_backup.log

# ★ 最新12世代だけ残し、それ以外は自動削除 ★
ls -1t $TARGET_DIR/rpi_backup_*.img.gz | tail -n +13 | while read OLD_FILE; do
    rm -f "$OLD_FILE"
    echo "$(date): Deleted old backup: $OLD_FILE" >> /var/log/sd_backup.log
done

保存して終了(Ctrl+O → Enter → Ctrl+X)。

実行権限を付与:

sudo chmod +x /usr/local/bin/backup_sd.sh

5. 毎週1回自動バックアップ(cron設定)

sudo crontab -e

以下を追加:

0 3 * * 0 /usr/local/bin/backup_sd.sh

これで 毎週日曜 3:00 に自動バックアップが実行されます。


6. バックアップファイルの例

rpi_backup_20260802.img.gz
rpi_backup_20260809.img.gz
...
(最新12個だけ残る)

7. Windows PC から SDカードを復元する方法(初心者向け)

バックアップした .img.gz を使って、
壊れた Raspberry Pi を完全復元する手順です。


7-1. 必要なもの


7-2. バックアップファイルを PC にコピー

Windows のエクスプローラーで Raspberry Pi にアクセス:

\\raspberrypi\backup

または IP アドレス:

\\192.168.x.x\backup

最新のバックアップファイルを PC にコピーします。

例:

rpi_backup_20260802.img.gz

7-3. gzip を解凍して .img にする

Windows では 7-Zip が簡単です。


7-4. Raspberry Pi Imager で SDカードに書き込む

  1. Raspberry Pi Imager を起動
  2. 「OSを選ぶ」→「カスタムイメージを使用」
  3. 解凍した .img を選択
  4. 書き込み先 SDカードを選択
  5. 「書き込む」を押す

これだけで 完全復元できます。


7-5. Raspberry Pi を起動する

書き込みが終わった SDカードを Raspberry Pi に挿して起動すると、
バックアップ時点の状態に完全復元されています。

  • OS設定
  • サーバー設定
  • cron
  • Webサービス
  • ファイル構成

すべてバックアップ時点のままです。


8. まとめ

  • SDカードは壊れやすいので 定期バックアップは必須
  • ELECOM の HDD に 毎週自動バックアップ
  • 最新12世代だけ保持して容量を節約
  • 障害時は Windows PC で SDカードに書き戻すだけで復旧
  • Raspberry Pi Imager の「カスタムイメージ」が便利

追記:amba共有の設定例と fstab の最適化(Raspberry Pi 4 バックアップ運用)

Raspberry Pi の SDカードを毎週自動バックアップし、
外付けHDD(ELECOM)に保存する運用では、

  • PCからバックアップファイルにアクセスできること(Samba共有)
  • バックアップ先のHDDが毎回確実にマウントされること(fstab最適化)

がとても重要です。

ここではその設定をまとめます。


1. Samba共有の設定例(BackupフォルダをPCから見えるようにする)

ELECOM の HDD に作成した /mnt/elecom/Backup
Windows / macOS からアクセスできるようにします。


1-1. Samba設定ファイルを編集

sudo nano /etc/samba/smb.conf

一番下に以下を追加します:

[backup]
   path = /mnt/elecom/Backup
   browseable = yes
   writable = yes
   create mask = 0664
   directory mask = 0775
   valid users = tomtomst

保存して Samba を再起動:

sudo systemctl restart smbd

1-2. Windows からアクセスする方法

エクスプローラーのアドレス欄に入力:

\\raspberrypi\backup

または IP 指定:

\\192.168.x.x\backup

1-3. macOS からアクセスする方法

Finder → 移動 → サーバへ接続:

smb://raspberrypi/backup

1-4. これでできること

  • バックアップファイル(.img.gz)を PC から確認
  • 障害時に PC へコピーして復元作業ができる
  • Raspberry Pi 側のバックアップ運用がより安全になる

2. fstab の最適化(ELECOM HDD を毎回確実にマウントする)

バックアップスクリプトは毎週自動で動きます。
そのため、バックアップ先の ELECOM HDD が 確実に /mnt/elecom にマウントされていることが重要です。

fstab を最適化すると:

  • 再起動後も自動でマウント
  • デバイス名(sda → sdb)が変わっても安全
  • I/O負荷が減り、HDDの寿命が伸びる
  • cron バックアップが確実に成功する

というメリットがあります。


2-1. HDD の UUID を確認

lsblk -f

例:

sda1 ext4  UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

この UUID をコピーします。


2-2. fstab を編集

sudo nano /etc/fstab

一番下に追加:

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/elecom ext4 defaults,noatime 0 2

オプションの意味

  • defaults
    標準の安全な設定
  • noatime
    読み込み時にアクセス時刻を更新しない
    → HDDの負荷が減る
    → バックアップ時のI/Oが安定する
  • 0 2
    ext4 の標準設定(fsck の順番)

2-3. 設定をテスト

sudo mount -a

エラーがなければ成功。


2-4. 再起動して確認

sudo reboot

再起動後:

lsblk -f

/mnt/elecom にマウントされていれば OK。


3. まとめ(Samba + fstab 最適化の効果)

✔ PCからバックアップファイルにアクセスできる

→ 障害時の復元がスムーズ

✔ HDDが毎回確実にマウントされる

→ cron バックアップが失敗しない

✔ I/O負荷が減り、HDDの寿命が伸びる

→ 長期運用に向いている

✔ バックアップ運用が「完全自動化」される

→ 手動操作ゼロで毎週バックアップが取れる


📘 Raspberry Pi で Samba と WebDAV を再構築した備忘録

📘 Raspberry Pi で Samba と WebDAV を再構築した備忘録

― 既存 TOSHIBA(disk_01)+新規 ELECOM(disk_02)構成 ―

はじめに

Raspberry Pi 4 に接続している外付け HDD を使って、
Samba によるファイル共有環境の拡張と、
WebDAV によるスマホ同期環境の追加を行ったときの備忘録。

今回の作業は、USBハブ経由で複数の HDD を接続していた際に
SDカードが破損するトラブルが発端となった。
その原因調査から、Samba の再構築、WebDAV の追加まで一通り整えたので記録しておく。


1. USBポートが不安定に見えた件(実際は USBハブが原因)

● 発端

新規で追加した ELECOM の HDD を ext4 にフォーマットしようとしたところ、
SDカードが破損して Raspberry Pi が起動不能に。

当初は「Raspberry Pi 4 の USBポートが不安定なのでは?」と疑った。

● 実際の原因

使用していた USBハブが 電源供給なし(バスパワー)タイプで、
複数の HDD を同時に接続していたため 電力不足が発生。

フォーマットのような大量書き込み処理で電圧が落ち、
SDカード側に I/O エラーが波及して破損したと考えられる。

● 調査結果

lsusb -t の結果:

  • ELECOM → 5000M(USB3.0)
  • LOGITEC → 5000M(USB3.0)
  • LaCie → 480M(USB2.0)

ELECOM と LOGITEC は USB3.0 で正常動作していたため、
Raspberry Pi 4 の USBポートは正常と判明。

LaCie は内部の USBブリッジが USB2.0 の可能性が高い。

● 対処

  • HDD を USBハブ経由ではなく Raspberry Pi に直接接続
  • ext4 フォーマットが正常に完了
  • 以降は安定動作

● 教訓

  • Raspberry Pi に HDD を複数接続する場合、
    バスパワーの USBハブは絶対に使わない。
  • セルフパワーの USBハブか、直接接続が必須。

2. ストレージ構成と自動マウント設定

● ディスク構成

  • TOSHIBA → disk_01(従来使用していた Samba 用ディスク)
  • ELECOM → disk_02(今回追加した Samba 用ディスク)
    • Samba の容量増加用
    • WebDAV の保存先
    • スマホ同期のメイン領域

● ext4 でフォーマット

ELECOM を ext4 でフォーマットし直した。

● UUID の確認

sudo blkid

● /etc/fstab に登録(再起動しても自動マウント)

sudo nano /etc/fstab
UUID=798d0fc5-67f4-4549-941e-84ba09c8ebf2 /mnt/toshiba ext4 defaults 0 2
UUID=90bdad69-a038-4b79-8129-6d752db35adc /mnt/elecom ext4 defaults 0 2

● マウントのフォルダ作成

sudo mkdir -p /mnt/toshiba
sudo mkdir -p /mnt/elecom

● マウントテスト

sudo mount -a

これで 再起動後も自動マウントされる安定したストレージ環境になった。


3. Samba の再構築(disk_01 / disk_02)

● Sambaのインストール

sudo apt update
sudo apt install samba samba-common-bin -y

● 共有フォルダの作成

sudo mkdir -p /mnt/toshiba/samba
sudo mkdir -p /mnt/elecom/samba
sudo chown -R tomtomst:users /mnt/toshiba/samba /mnt/elecom/samba
sudo chmod -R 775 /mnt/toshiba/samba /mnt/elecom/samba

● Samba ユーザー

sudo smbpasswd -a tomtomst
sudo smbpasswd -a family

● smb.conf の共有設定

sudo nano /etc/samba/smb.conf
[disk_01]
   path = /mnt/toshiba/samba
   browseable = yes
   writable = yes
   valid users = tomtomst family
   force user = tomtomst
   guest ok = no

[disk_02]
   path = /mnt/elecom/samba
   browseable = yes
   writable = yes
   valid users = tomtomst family
   force user = tomtomst
   guest ok = no

● guest アクセスの無効化

[global]
map to guest = never

● 再起動

sudo systemctl restart smbd
sudo systemctl restart nmbd

4. WebDAV の構築(ELECOM=disk_02)

● Apache2 と WebDAV モジュール

sudo apt install apache2 -y
sudo a2enmod dav
sudo a2enmod dav_fs
sudo systemctl restart apache2

● WebDAV 用ディレクトリ

sudo mkdir -p /mnt/elecom/webdav
sudo chown -R www-data:www-data /mnt/elecom/webdav
sudo chmod -R 775 /mnt/elecom/webdav

● WebDAV サイト設定(webdav.conf)

sudo nano /etc/apache2/sites-available/webdav.conf
<VirtualHost *:80>
    ServerName raspberrypi

    Alias /webdav /mnt/elecom/webdav

    <Directory /mnt/elecom/webdav>
        DAV On
        Options Indexes FollowSymLinks
        AllowOverride None

        AuthType Basic
        AuthName "WebDAV"
        AuthUserFile /etc/apache2/webdav.passwd
        Require valid-user
    </Directory>
</VirtualHost>

● WebDAV 認証ユーザー

sudo apt install apache2-utils -y
sudo htpasswd -c /etc/apache2/webdav.passwd tomtomst
sudo htpasswd /etc/apache2/webdav.passwd family

● サイト有効化

sudo a2ensite webdav
sudo systemctl reload apache2

5. 今回の構成のポイントまとめ

  • USBハブの電力不足が SDカード破損の原因だった
  • Raspberry Pi 4 の USBポートは正常
  • TOSHIBA(disk_01)=従来の Samba ディスク
  • ELECOM(disk_02)=新規追加ディスク(Samba+WebDAV)
  • ext4 フォーマット+UUID+fstab で自動マウントを実現
  • Samba と WebDAV が同じ物理ディスク上で共存
  • スマホ(Obsidian など)との同期が可能になった

おわりに

今回の作業では、USBハブの電力不足という予想外のトラブルから始まり、
ストレージの再構築、Samba の拡張、WebDAV の追加まで一通り整えることができた。

特に、USBハブ問題と自動マウント設定は忘れがちなポイントなので、
備忘録として残しておく価値がある。

今後は WebDAV を使ったスマホ同期や、
rsync によるバックアップ運用などを進めていく予定。


Raspberry Pi Zero 2W × WiFi_IOT_Sensor × はてなブログ 毎日自動投稿システムを構築した話(cron暴走の解決まで)

■ 背景

以前、Raspberry Pi Pico 2W に接続した各種センサの値を Google スプレッドシートへ定期送信するツール 「WiFi_IOT_Sensor」 を作成した。 (参考:2025/03/29 記事) https://tomtomst.hatenablog.com/entry/2025/03/29/231107

また、先日サブブログの方で、Python からはてなブログへ自動投稿する仕組みを紹介した。 (参考:2026/07/05 記事) https://tomtomst.hateblo.jp/entry/2026/07/05/124247

今回はこの 2つのツールを組み合わせて、毎日定時にベランダのセンサデータを自動投稿する仕組みを Raspberry Pi Zero 2W 上に構築した。


■ 今回の目的

  • ベランダに設置した WiFi_IOT_Sensor のデータを

  • 毎日 0:00 に

  • Raspberry Pi Zero 2W が自動で取得し
  • グラフ生成・統計処理を行い
  • サブブログへ自動投稿する

完全自動の「ベランダ環境日報ブログ」を作ることが今回のゴール。


■ 自動投稿プログラムの構成

今回作成した自動投稿プログラム(blog_daily.py)は、以下の流れで動作する。

① Google スプレッドシートから前日のデータを取得 WiFi_IOT_Sensor が 30 分ごとに送信している

  • 温度
  • 湿度
  • 気圧
  • 照度

の 4 種類を、指定日の 0:00〜23:59 の範囲で読み出す。

② データ解析 - 最高温度 / 最低温度 - 最高照度 / 最低照度 - 気圧の上昇・下降判定 - 日の出 / 日の入り時刻の計算 などを自動で算出。

③ グラフ生成 Pillow を使って、 温度・湿度・気圧・照度の 4 種類の折れ線グラフを PNG で生成。

④ はてなブログへ投稿 タイトルは自動生成:

2026年07月19日 ベランダのセンサデータ

本文には - 統計情報 - グラフ画像 - 日の出・日の入り などを Markdown 形式で挿入し、 Hatena Blog API 経由で投稿する。


■ Raspberry Pi Zero 2W で定時実行する方法(cron)

Zero で毎晩 0:00 に自動実行するため、cron を使用。

crontab の設定

LANG=ja_JP.UTF-8
HOME=/home/tomtomst
PATH=/home/tomtomst/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games

@reboot cd /home/tomtomst/Documents/python ; python3 ./weather_clock_main.py > /home/tomtomst/Desktop/exec-error.log 2>&1

0 0 * * * /usr/bin/python3 /home/tomtomst/Documents/python/blog_daily.py > /home/tomtomst/Desktop/blog_daily.log 2>&1

ポイント

  • HOME=/home/tomtomst を入れないと Google 認証が失敗する

  • PATH= の「= の前にスペース」を入れると cron が壊れる

  • Zero は非力なので「毎分実行」は絶対に避ける

  • ログ出力を指定しておくとトラブル時に原因が分かりやすい


■ 苦労した点(今回のハイライト)

今回の構築で最も苦労したのは cron の暴走。

● 壊れた crontab 行が残り続ける

編集したつもりでも、内部的に壊れた行が残っており、 blog_daily.py が 毎分複数回起動する状態に。

● Zero が高負荷でフリーズ

  • CPU 1コア
  • RAM 512MB の Zero では、毎分の Google 認証+画像生成は完全にオーバーキル。

● syslog が大量の cron ログで埋まる

blog_daily.py が 1 分間に 2〜3 回起動し続け、syslog が数千行の cron ログで埋まる。

● 解決方法

最終的に

crontab -r

で crontab を完全削除し、 正しい設定を入れ直すことで解決した。

■ 実行結果

実際に自動投稿された記事のレスポンスは以下の通り:

Status: 200
<title>2026年07月19日 ベランダのセンサデータ</title>

毎晩 0:00 に、 ベランダの環境データが自動でブログに投稿されるようになった。

tomtomst.hateblo.jp


■ まとめ

  • WiFi_IOT_Sensor(Pico 2W)
  • Raspberry Pi Zero 2W
  • Google スプレッドシート
  • Hatena Blog API
  • cron

これらを組み合わせることで、 完全自動の「ベランダ環境日報ブログ」が完成した。

今回のトラブルシューティング(cron 暴走)は大変だったが、 結果として Zero の挙動や cron の癖を深く理解できた。

今後は - グラフの見やすさ改善 - 天気 API の追加 - 画像のコラージュ化 なども検討していきたい。


■ プログラムソース

  • ファイル名:blog_daily.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import gspread
from google.oauth2.service_account import Credentials
import datetime

import os
import sys

# blog_tool_v04.py を読み込む
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import blog_tool_v04 as bt


# ================================
# Google スプレッドシート読み込み
# ================================
def load_sensor_data():
    # 新しい認証スコープ(google-auth 用)
    scope = [
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/drive"
    ]

    # 認証ファイルのパス
    cred_path = "/home/tomtomst/Documents/python/credentials.json"

    # 新しい認証方式(Zero で確実に動く)
    credentials = Credentials.from_service_account_file(
        cred_path,
        scopes=scope
    )
    gc = gspread.authorize(credentials)

    # スプレッドシート名とシート名
    sh = gc.open("ベランダのセンサ")
    ws = sh.worksheet("Pico2W_01")

    # 全データ取得
    records = ws.get_all_records(
        expected_headers=["日時", "室温", "湿度", "気圧", "照度"]
    )

    # pandas を使わずに DataFrame を作る(Zero 最適化)
    # → pandas は Zero では重すぎて使えないため
    data = []
    for r in records:
        data.append({
#            "timestamp": datetime.datetime.strptime(r["日時"], "%Y-%m-%d %H:%M:%S"),
            "timestamp": datetime.datetime.strptime(r["日時"], "%Y/%m/%d %H:%M:%S"),
            "temp": float(r["室温"]),
            "humid": float(r["湿度"]),
            "pressure": float(r["気圧"]),
            "lux": float(r["照度"])
        })

    # 前日のデータ抽出
    today = datetime.datetime.now()
    target_date = today - datetime.timedelta(days=1)

    start = target_date.replace(hour=0, minute=0, second=0)
    end   = target_date.replace(hour=23, minute=59, second=59)

    df_day = [d for d in data if start <= d["timestamp"] <= end]

    return df_day, target_date


# ================================
# 日の出 / 日の入り(照度から判定)
# ================================
def detect_sun_times(df):
    threshold = 100

    # 日の出:閾値を初めて超えた時刻
    sunrise = None
    for row in df:
        if row["lux"] > threshold:
            sunrise = row["timestamp"]
            break

    # 日の入り:午後のデータだけを見る
    sunset = None
    for row in df:
        # 午後(12:00以降)だけを対象にする
        if row["timestamp"].hour >= 12 and row["lux"] < threshold:
            sunset = row["timestamp"]
            break

    return sunrise, sunset


# ================================
# 統計値計算
# ================================
def calc_stats(df):
    temps = [d["temp"] for d in df]
    humids = [d["humid"] for d in df]
    pressures = [d["pressure"] for d in df]
    luxes = [d["lux"] for d in df]

    max_temp = max(temps)
    min_temp = min(temps)
    max_lux  = max(luxes)
    min_lux  = min(luxes)

    pressure_trend = "上昇" if pressures[-1] > pressures[0] else "下降"

    return max_temp, min_temp, max_lux, min_lux, pressure_trend

# ================================
# グラフ生成
# ================================

from PIL import Image, ImageDraw
import numpy as np
from PIL import ImageFont

def create_light_graph(df, col, target_date):
    width, height = 800, 300
    img = Image.new("RGB", (width, height), "white")
    draw = ImageDraw.Draw(img)

    # フォント(Zero でも確実に存在する)
    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24)

    values = np.array([d[col] for d in df], dtype=float)
    if len(values) == 0:
        return None

    v_min, v_max = values.min(), values.max()
    norm = (values - v_min) / (v_max - v_min + 1e-6)

    xs = np.linspace(50, width - 50, len(values))
    ys = height - (norm * (height - 50)) - 25

    for i in range(len(xs) - 1):
        draw.line((xs[i], ys[i], xs[i+1], ys[i+1]), fill="blue", width=2)

    # ★フォント付きで大きく描画
    draw.text((10, 10), f"{target_date.strftime('%Y-%m-%d')} {col}", fill="black", font=font)

    base_dir = os.path.dirname(os.path.abspath(__file__))
    img_dir = os.path.join(base_dir, "blog_images")
    os.makedirs(img_dir, exist_ok=True)

    filename = os.path.join(img_dir, f"{col}.png")
    img.save(filename)

    return filename


def create_graphs(df, target_date):
    for col in ["temp", "humid", "pressure", "lux"]:
        create_light_graph(df, col, target_date)

# ================================
# Markdown 本文生成
# ================================
def make_markdown(date, sunrise, sunset, stats):
    max_temp, min_temp, max_lux, min_lux, pressure_trend = stats

    sunrise_str = sunrise.strftime("%H:%M") if sunrise else "判定不可"
    sunset_str  = sunset.strftime("%H:%M") if sunset else "判定不可"

    md = f"""# {date.strftime('%Y年%m月%d日')} ベランダのセンサデータ

## 日の出・日の入り(照度センサ判定)
- 日の出:{sunrise_str}
- 日の入り:{sunset_str}

## 統計値
- 最高温度:{max_temp} ℃
- 最低温度:{min_temp} ℃
- 最高照度:{max_lux} lx
- 最低照度:{min_lux} lx
- 気圧:{pressure_trend}

## グラフ
![temp](blog_images/temp.png)
![humid](blog_images/humid.png)
![pressure](blog_images/pressure.png)
![lux](blog_images/lux.png)
"""

    return md


# ================================
# メイン処理
# ================================
def main():
    df, target_date = load_sensor_data()

    sunrise, sunset = detect_sun_times(df)
    stats = calc_stats(df)

    create_graphs(df, target_date)

    md_text = make_markdown(target_date, sunrise, sunset, stats)

    # 一時 Markdown ファイルを作成
    base_dir = os.path.dirname(os.path.abspath(__file__))
    md_path = os.path.join(base_dir, "daily_post.md")

    with open(md_path, "w", encoding="utf-8") as f:
        f.write(md_text)

    # Hatena Blog のサブブログへ投稿
    bt.post_or_update(bt.BLOGS["sub"]["blog_id"], md_path, "sub")


if __name__ == "__main__":
    main()

🌞 ミニソーラーパネルで駆動時間を延ばせるか試した(再登校)

🌞 ミニソーラーパネルで駆動時間を延ばせるか試した(再登校)

前回の記事では、Raspberry Pi Pico2W の電源を on/off する回路を追加し、
バッテリー駆動時間を約 1 ヵ月に延ばせたことを紹介しました。

👉 前回の記事
https://tomtomst.hatenablog.com/entry/2026/01/10/220415

今回はその続きとして、
ミニソーラーパネルを取り付けて、さらに駆動時間を延ばせるかを検証した結果をまとめます。


1. 前回の結果(ソーラーパネルなし)

2000mAh のバッテリーで 約 1 ヵ月動作することを確認。

● バッテリー駆動のみの結果


2. 今回の目的:ソーラーパネルで充電しながら動作させる

「もっと長く動かしたい」ということで、
ミニソーラーパネルを追加して充電しながら動作させることを試しました。

● 購入したミニソーラーパネル

Amazon: https://www.amazon.co.jp/dp/B0BPWTVH13?ref=ppx_yo2ov_dt_b_fed_asin_title

  • 定格電圧:5.5V
  • 電流:60mA
  • サイズ:60mm × 60mm

日光が当たれば 5V / 60mA 程度の発電が期待できる仕様です。


3. 接続構成

ソーラーパネルは、充電モジュールの USB 端子側に接続するだけで OK。

● 接続図


4. 作成した WiFi-IoT デバイス

● 外観

● 内部


5. 実験結果①:直射日光が当たる場合(約 60 日動作)

3 月頃、ベランダの 直射日光が 1 時間ほど当たる場所に設置して測定。

● 測定結果(直射日光あり)

  • 青線:バッテリー残量
  • 赤線:照度センサの値
  • 約 60 日動作 → ソーラーパネルの効果がしっかり出た

ただし、4月下旬には日照時間が 20 分程度に減少し、発電量も低下。


6. 実験結果②:直射日光がほぼ当たらない場合(約 1 ヵ月)

5 月に再度測定したところ、設置場所にはほとんど直射日光が当たらず、
バッテリー駆動時間は約 1 ヵ月と、ソーラーパネルなしとほぼ同じ結果に。

● 測定結果(直射日光なし)


7. 分かったこと

① 直射日光が当たらないと発電効果はほぼゼロ

→ ソーラーパネルは 直射日光が当たる場所に設置する必要がある

② 直射日光が当たると温湿度センサの値が大きく乱れる

→ 温湿度センサは 直射日光を避けて配置する必要がある


8. 温湿度センサの挙動

● 直射日光が当たる場合

  • 温度:急上昇
  • 湿度:急低下
    → センサが日光の熱を受けてしまい、外気とは異なる値になる

● 直射日光が当たらない場合

自然な外気の変化を測定できている。


9. 結論:私のベランダではソーラーパネルは実用的ではなかった

  • 年間を通して 直射日光が当たる場所がほぼない
  • 一方で、直射日光が当たらない場所は安定して存在する
  • よって、ミニソーラーパネルによる延命は 現実的ではない

● 今後の方針

ミニソーラーパネルは諦めて、
測定間隔を 10 分 → 30 分に延ばすことで
バッテリーを 2〜3 ヵ月持たせる方向に切り替えます。


以上、ミニソーラーパネルを使ったバッテリー延命の検証でした。