🥬
ラズパイ × 土壌湿度センサ × LINE通知で植木鉢の水やりアラートを作る
はじめに
植物の水やりを忘れて枯らしてしまう――そんな経験はありませんか?
本記事では Capacitive Soil Moisture Sensor V1.2(静電容量式土壌湿度センサ)と Raspberry Pi 3B+ を使って、土壌水分を計測し、閾値判定の結果を LINE に通知する仕組みを紹介します。
ポイントは以下です。
- Raspberry Piでセンサ値を取得(MCP3008を介したADC)
- 判定結果を Google Apps Script(GAS) の Web アプリへ POST
- GAS が LINE Messaging API を呼び出して通知
- ログは週ごとに CSV に保存(Excelでの可視化を想定)
使用デバイス
- Raspberry Pi 3B+
- Capacitive Soil Moisture Sensor V1.2(アナログ出力)
- MCP3008(SPI接続の10bit ADC)
- ジャンパ線・ブレッドボード
(電源は 3.3V 系で統一することをおすすめします)
システム構成(簡易図)
配線のポイント
MCP3008 ↔ Raspberry Pi (SPI)
MCP3008 | Raspberry Pi(GPIO) |
---|---|
VDD, VREF | 3.3V |
AGND, DGND | GND |
CLK | GPIO11 (SCLK) |
DOUT | GPIO9 (MISO) |
DIN | GPIO10 (MOSI) |
CS | GPIO8 (CE0) |
センサ ↔ MCP3008
- Sensor VCC -> 3.3V(推奨)
- Sensor GND -> GND
- Sensor AOUT -> MCP3008 CH0
注意: Raspberry Pi の GPIO は 3.3V 領域です。5V を直接流すと破損する恐れがあります。センサやADCの電源は3.3V系で揃えてください。
センサ値とキャリブレーション
- 静電容量型センサは安定性が高く腐食しにくいですが、土質や鉢・設置深さで出力が変わります。
- まず「完全に乾いた土」と「十分に湿った土」で実測して、DRY/WET の基準値を決めてください。
例(あくまで実測例)
- DRY ≒ 700
- WET ≒ 300
閾値の例(運用例)
-
THRESH_HIGH = 600
→ これ以上で「求水」(乾燥) -
THRESH_LOW = 400
→ これ以下で「充水」(湿潤) - その間は「様子見」
Google Apps Script (GAS) 側の実装
GASを Web アプリとしてデプロイし、RPi からの POST を受け取り LINE に通知します。
実運用では スクリプトプロパティ に LINE のチャネルアクセストークンや通知先を入れておくと安全です。
手順(概要)
- Google ドライブ → 新規 → スクリプトを作成
- 下記のコードを貼り付け
- スクリプトのプロパティに
LINE_TOKEN
とLINE_TO
を設定 - デプロイ → 新しいデプロイ → 種類「ウェブアプリ」→ 公開設定を適切に(開発中は自分のみ、必要なら匿名可)
// Code.gs
// ==============================
// LINE Messaging API 用設定
// ==============================
// スクリプトプロパティからアクセストークンを取得
const CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('CHANNEL_ACCESS_TOKEN');
// LINE ユーザーID
const USER_ID = 'XXX'; //LINEアカウントで確認して記載
// ==============================
// doPost 関数 (本番用)
// ==============================
/* GAS Webhook受信用関数
HTTP POSTリクエスト受信時に実行される
引数"e"は「イベントオブジェクト」で、送られてきたデータやパラメータが入っている
*/
function doPost(e) {
/* msgにPOSTリクエストで受信したmessageの内容を格納
messageが空の場合は、固定値を代入する
*/
var msg = e.parameter.message || "メッセージが空です!";
var messages = [];
/* msgが"image"形式の場合 */
if (msg === "image") {
/* 画像を送信(例:公開URLの画像を使用)*/
var imageUrl = e.parameter.imageUrl || "https://i.imgur.com/q4yV02g.jpeg";
/* messages配列に属性を追加 */
messages.push({
type: "image",
originalContentUrl: imageUrl,
previewImageUrl: imageUrl
});
/* msgが"image"形式でない場合 */
} else {
// テキストを送信
messages.push({
type: 'text',
text: msg,
});
}
/* USER_IDとmessagesを紐づけ、LINE APIで送信する配列に格納 */
var payload = {
to: USER_ID,
messages: messages
};
/* エラーが発生する可能性がある処理を実行 */
try {
/* [UrlFetchApp.fetch]第一引数が送信先URL、第二引数でリクエストのオプション(メソッド、ヘッダー、データなど)を指定する,GASメソッド */
var response = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", { //LINE Messaging API の「プッシュメッセージ」エンドポイント
method: "post", //POSTはデータ送信
contentType: "application/json", // JSON形式のデータ送付
headers: {
"Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN //Authorizationに「Bearer + アクセストークン」を設定し、LINE API に正しい権限を持っているアプリと伝える
},
payload: JSON.stringify(payload), //実際に送るデータ(本文)
});
/* LINE APIなどに送信した結果の本文をログに残す*/
Logger.log("Response: " + response.getContentText());
/* try部分エラー時に実行される,ログにエラーを残す */
} catch (error) {
Logger.log("Error: " + error.message);
return ContentService.createTextOutput("Error occurred: " + error.message);
}
return ContentService.createTextOutput("OK");
}
// ==============================
// デバッグ用関数
// ==============================
function testDoPost() {
// デバッグ用モックイベント
var e1 = { parameter: { message: "テスト1" } };
var e2 = { parameter: { message: "テスト2" } };
Logger.log(doPost(e1).getContent());
Logger.log(doPost(e2).getContent());
}
function doGet(e) {}
セキュリティ補足
- Web アプリを「全員(匿名含む)」にした場合は、**POST 送信元の検証(秘密トークン)**を実装してください。
- GAS側で
data.secret
を受け取り、事前に共有した値と比較する方法が簡単です。
Raspberry Pi 側の実装(Python)
依存パッケージ
sudo apt update
sudo apt install -y python3-pip
pip3 install spidev requests
SPIを有効化(sudo raspi-config
→ Interface Options → SPI → Enable)してください。
send_line_sensorvalue.py(例)
import spidev
import time
import requests
import csv
import os
from datetime import datetime
# --- Google Apps Script経由でLINEに送信するためのURL(公開範囲は「全員」)
GAS_URL = "XXXX" # GASでデプロイした際のURLを記載
def init_spi():
"""
MCP3008用SPIデバイスの初期化
"""
spi = spidev.SpiDev()
spi.open(0, 0) # バス0, CE0
spi.max_speed_hz = 1350000 # MCP3008対応クロック
return spi
def read_adc_average(spi, channel, count=10):
"""
指定チャネルからADC値を複数回取得し、その平均値を返す
"""
values = []
for _ in range(count):
adc = spi.xfer2([1, (8 + channel) << 4, 0]) # MCP3008通信
value = ((adc[1] & 3) << 8) + adc[2] # 10bit値に変換
values.append(value)
time.sleep(0.05) # 50ms間隔で取得(ノイズ対策)
avg_value = int(sum(values) / len(values))
return avg_value
def send_line_message(message):
"""
LINE(GAS経由)にメッセージを送信
"""
try:
response = requests.post(GAS_URL, params={"message": message})
if response.status_code == 200:
print("LINEにメッセージ送信成功!")
else:
print(f"エラー発生!HTTPステータスコード: {response.status_code}")
except Exception as e:
print(f"通信エラー: {e}")
def write_log(value, log_file="log.csv"):
"""
センサ値をCSVファイルに追記(タイムスタンプ付き)
"""
file_exists = os.path.isfile(log_file)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(log_file, "a", newline="") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(["timestamp", "sensor_value"])
writer.writerow([now, value])
def check_flags(value, prev_flag):
"""
センサ値に応じて求水/充水フラグを判定し、必要ならLINEにメッセージ送信
ヒステリシスあり(同じメッセージの連続送信防止)
"""
# 求水フラグ: 550超でON、450未満でOFF
# 充水フラグ: 350未満でON、450超でOFF
if prev_flag != "求水" and value > 550:
send_line_message("のどがかわいた…")
return "求水"
elif prev_flag == "求水" and value < 450:
return None # 求水フラグ解除
if prev_flag != "充水" and value < 350:
send_line_message("おなかいっぱい!")
return "充水"
elif prev_flag == "充水" and value > 450:
return None # 充水フラグ解除
return prev_flag # フラグ変更なし
def main():
"""
センサ値を監視し、状態に応じてLINE送信&10分ごとにログ保存
10秒ごとにセンサ値(平均)を取得
"""
print("センサ値を監視し、状態に応じてLINE送信&10分ごとにログ保存します。Ctrl+Cで終了。")
spi = init_spi()
prev_flag = None # ← ファイルからの復元をやめて、毎回Noneから開始
last_log_time = time.time() # 最後にログ保存した時刻
try:
while True:
# センサ値を10回取得し平均値を算出
avg_value = read_adc_average(spi, 0, count=10)
print(f"センサ値(平均): {avg_value}")
# 状態判定&LINE送信
prev_flag = check_flags(avg_value, prev_flag)
# 10分ごとにCSVログ保存
now = time.time()
if now - last_log_time >= 600:
write_log(avg_value)
print("10分ごとにセンサ値をlog.csvに保存しました。")
last_log_time = now
time.sleep(10) # 10秒毎に計測
except KeyboardInterrupt:
print("プログラムを終了します。")
finally:
spi.close()
if __name__ == "__main__":
main()
Excelでの可視化(週ごとファイル)
- 1ファイル = 1週間分の時系列データ(最大168点 = 24×7)
- ExcelでCSVを開き、
time
を X 軸、moisture
を Y 軸にして折れ線グラフを作成します
セキュリティと運用上の注意
-
GASの公開設定: Webアプリを匿名公開すると誰でもPOST可能になるため、
secret
トークンを使った認証をGAS側で実装することを推奨します。 - 電源断対策: モバイルバッテリ駆動で突然電源が落ちると SD カード破損のリスクがあるため、UPS HAT や電源管理ボード(PiJuice、Waveshare UPS HAT、PiSugarなど)を検討してください。
- 電圧レベル: ADCやセンサは3.3V運用を基本に。5V出力のまま接続しない。
トラブルシューティング(よくあるハマりどころ)
- 値が不安定 → センサ接触不良、ノイズ、土壌の接触面を確認。複数サンプルの平均化が有効。
- GASの403/401 → LINEトークン/デプロイ権限を確認。
今後の拡張アイデア
- ハードウェア信頼性の確保(防水性、防塵性)
→ PCB基板及びコネクタ作成
→ センサ保護ケースの作成 - 大容量バッテリ、ソーラー充電実装
→パススルーで充電可能なバッテリ必要 - パラメータの外部管理化(YAML使う)
- データをスプレッドシートやクラウド(BigQuery,GoogleDrive等)にためて長期解析
- 温湿度センサを追加して環境監視を強化
- LINEのリッチメニューやボタンで「水やり済み」を手動記録
- 電磁弁+ポンプで自動給水(ハードと安全回路の検討が必須)
まとめ
本記事では、Raspberry Pi 3B+ と Capacitive Soil Moisture Sensor V1.2 を用いて、土壌湿度を週単位でログ取りしつつ、閾値判定で LINE に通知するシステムを紹介しました。シンプルなフローなので、まずは動作確認→キャリブレーション→運用に移すと良いでしょう。
※記事の一部に生成AIを使用しています。
Discussion