
なぜ「手動の温度管理」はあなたのビジネス(人生)を密かに破壊するのか?
「今日も現場の温度、大丈夫だろうか…」
この記事を開いたあなたは、おそらくそんな微かな不安を毎日、無意識に抱えているはずです。 「今のところ問題は起きていない。でも、もし異常が起きたとき、自分は真っ先に気づけるだろうか?」
その「見えないリスクへの予期不安」が、あなたの貴重な集中力を少しずつ削っています。
実は、多くの一流経営者や現場責任者が、あなたと同じ悩みを抱えていました。そして、彼らが最後に行き着いた答えが、この記事で公開する「デバイス代だけで構築できる、プロに頼めば効果なデバイスとシステムの構築で数十万クラスの管理システム」です!
アプリ確認の「1日3分」が奪い去る、年間18時間の集中力

「アプリを開いて確認するだけ」という行為は、実は脳に大きな負荷をかけています。確認を忘れた時のリスク、そして「異常に気づけない」という不安。これらはすべて、あなたのパフォーマンスを低下させるノイズです。
SwitchBotの標準アプリも優秀ですが、複数の拠点を一気に、かつグラフや詳細な指標で確認するには不向きです。
「わざわざ見に行く」から「勝手にお知らせが届く」へ。 このスイッチひとつで、あなたの自由時間は劇的に増えます。
SwitchBot温度・湿度計プラス×APIが「最強の投資」と言い切れる理由

他社製品が数万円する中、数千円でこの「精度」と「API開放」は異常です。本記事では、この家庭用ガジェットを「工場の心臓部」に変える魔法を教えます。
LINE Notifyの限界を突破!「新バージョン」の圧倒的付加価値
他社製の業務用センサーが数万円〜十数万円する中、数千円の「SwitchBot温湿度計プラス」の精度と、API(システムの窓口)が開放されている点は、もはや異常とも言えるコスパです。
LINE Notifyの終焉、そして「Messaging API」への進化
これまで多くのブログで紹介されていた「LINE Notify」は、2025年3月31日をもってサービスが終了します。今から作るなら、LINE Messaging API(公式アカウント)一択です。
本ガイドでは、最新規格に対応した「一生モノ」のシステムを構築します。
スマホで見惚れる「ダッシュボード型レポート」

文字だけの機械的な通知はもう終わりです。 スマホを開いた瞬間、Looker Studioを彷彿とさせる「針が動くメーター」と「24時間の時系列推移表」が目に飛び込んできます。
現場指標「絶対湿度」と「飽差(VPD)」も自動算出
さらに、プロの現場で必須の指標である「絶対湿度(g/m³)」と「飽差(VPD)」も自動計算して表示。
カビの発生リスクやウイルスの生存率、植物の成長環境まで、0.1秒で状況を判断できる視認性。これこそが、真のDXです。
【新機能】「横向きレイアウト」のPDFレポートをメール送信
LINEだけでなく、正式な記録用として「画像付きPDF」をメールで自動送付します。Googleの弱点だった「PDFで画像が消える」問題を独自開発の技術で克服。そのまま会議資料やエビデンスとして活用可能です。
Looker Studioも廃止した!
【知識不要!コピペで実装】Switchbot APIで実現する温湿度管理の自動化 | Google連携からLINE通知までAIを活用してIoT化でシステム構築
【完全図解】LINE Messaging APIと連携するための3ステップ
ここからは、実際の操作画面をイメージしながら進めてください。
LINE公式アカウントの作成とAPIの有効化
LINE Developers にアクセスし、個人のLINEアカウントでログインします。
「プロバイダー」を新規作成(名前は「MyDX」など何でもOK)。
「Messaging API」を新規作成。チャネル名(Botの名前)を決めます。
作成後、「Messaging API設定」タブの一番下にある「チャネルアクセストークン(長期)」を発行してメモ(コピー)してください。これがコードの LINE_TOKEN になります。

SwitchBot APIの解放
アプリの「プロフィール」>「設定」>「アプリバージョン」を10回連打して開発者モードを出現。
「開発者オプション」から「トークン」と「シークレット」をコピー。
ついでに、管理したいデバイスのデバイスIDもここで確認しておきます。
【秘策】「グループID」を一瞬で特定する
LINE Notifyと違い、送り先のIDを特定するのが少し大変ですが、本記事のコードには「IDを自動返信してくれるデバッグ機能」を盛り込んでいます。Botをグループに招待して一言しゃべれば、IDが届きます。これが
LINE_DESTです。
SwitchBot API v1.1の認証設定
最新のv1.1では「シークレットトークン」が必要です。
隠しメニュー「開発者モード」を解放する
SwitchBotアプリの「プロフィール」>「設定」>「アプリバージョン」を10回連打します。
出現した「開発者オプション」を開き、「トークン」と「シークレット」をメモします。
【コピペで完成】GAS(Google Apps Script)の実装
用意したGoogleスプレッドシートを開きます。
上部メニューの 「拡張機能」>「Apps Script」 をクリック。
最初から入力されている文字をすべて消して、記事末尾にある 「黄金コード」 をすべて上書きで貼り付け、保存(Ctrl+S)してください。
/* ==========================================
1. 設定エリア
========================================== */
const LINE_TOKEN = 'あなたのLINEチャネルアクセストークンを入力してください';
const LINE_DEST = '送信先のLINEユーザーIDまたはグループIDを入力してください';
const SPREADSHEET_ID = 'あなたのスプレッドシートIDを入力してください';
const SHEET_NAME = 'データ保存用シート名(例:Sheet1)';
const SB_TOKEN = 'あなたのSwitchBotオープンAPIトークンを入力してください';
const SB_SECRET = 'あなたのSwitchBotクライアントシークレットを入力してください';
// ★メール送信設定
const RECIPIENT_EMAIL = 'レポートを受け取るメールアドレスを入力してください';
const EMAIL_SUBJECT = '温度・湿度管理レポート';
/**
* 取得対象のデバイスリスト
* name: 拠点名(レポートに表示されます)
* id: SwitchBotのデバイスID
*/
const DEVICE_LIST = [
{ name: "拠点名A", id: "デバイスID_A" },
{ name: "拠点名B", id: "デバイスID_B" },
{ name: "拠点名C", id: "デバイスID_C" },
// 必要に応じて追加してください
];
/* ==========================================
2. 自動トリガー設定
========================================== */
/**
* 全自動実行のためのトリガーを登録する関数
* スクリプトエディタから手動で一度だけ実行してください
*/
function setupAllTriggers() {
const triggers = ScriptApp.getProjectTriggers();
// 既存のトリガーをリセット
triggers.forEach(t => ScriptApp.deleteTrigger(t));
// ① データの取得(1分おきに最新情報を保存)
ScriptApp.newTrigger('getSwitchBotData')
.timeBased()
.everyMinutes(1)
.create();
// ② LINEプレミアムダッシュボードの送信(毎日16時〜17時の間)
ScriptApp.newTrigger('sendPremiumDashboard')
.timeBased()
.atHour(16)
.everyDays(1)
.create();
// ③ PDFレポートのメール送信(毎日16時〜17時の間)
ScriptApp.newTrigger('sendPdfReportEmail')
.timeBased()
.atHour(16)
.everyDays(1)
.create();
console.log("トリガーの登録が完了しました。");
}
/* ==========================================
3. データ取得・保存(SwitchBot API連携)
========================================== */
function getSwitchBotData() {
const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = ss.getSheetByName(SHEET_NAME);
const now = new Date();
const dateStr = Utilities.formatDate(now, "Asia/Tokyo", "yyyy/MM/dd");
const timeStr = Utilities.formatDate(now, "Asia/Tokyo", "HH:mm");
const rows = [];
DEVICE_LIST.forEach(device => {
try {
const status = getDeviceStatus(device.id);
if (!status) return;
const temp = status.temperature;
const humi = status.humidity;
const batt = status.battery || 100;
const vpd = calculateVPD(temp, humi);
const absHumi = calculateAbsoluteHumidity(temp, humi);
// スプレッドシートに書き出す形式
rows.push([device.name, device.id, dateStr, timeStr, temp, humi, absHumi, vpd, batt]);
} catch (e) {
console.log(`${device.name} のデータ取得に失敗: ${e.message}`);
}
});
if (rows.length > 0) {
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
console.log(`${timeStr} のデータを保存しました。`);
}
}
/** 絶対湿度の計算 (217 * e / (temp + 273.15) * (h/100)) */
function calculateAbsoluteHumidity(temp, humi) {
const e = 6.11 * Math.pow(10, (7.5 * temp) / (temp + 237.3));
const absHumi = 217 * e / (temp + 273.15) * (humi / 100);
return absHumi.toFixed(2);
}
/** 飽差(VPD)の計算 */
function calculateVPD(temp, humi) {
const svp = 0.61078 * Math.exp((17.27 * temp) / (temp + 237.3));
const avp = svp * (humi / 100);
return (svp - avp).toFixed(2);
}
/** SwitchBot API v1.1 認証署名生成とステータス取得 */
function getDeviceStatus(deviceId) {
const t = Date.now().toString();
const nonce = Utilities.getUuid();
const data = SB_TOKEN + t + nonce;
const signature = Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, data, SB_SECRET)).toUpperCase();
const url = `https://api.switch-bot.com/v1.1/devices/${deviceId}/status`;
const params = {
method: "get",
headers: { "Authorization": SB_TOKEN, "sign": signature, "nonce": nonce, "t": t, "Content-Type": "application/json" },
muteHttpExceptions: true
};
const res = UrlFetchApp.fetch(url, params);
const json = JSON.parse(res.getContentText());
return (json.statusCode === 100) ? json.body : null;
}
/* ==========================================
4. LINE送信・メール通知(ビジュアルレポート)
========================================== */
/** LINEへのリッチなFlex Message送信 */
function sendPremiumDashboard() {
console.log("LINEレポート送信処理を開始します...");
const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = ss.getSheetByName(SHEET_NAME);
const allData = sheet.getDataRange().getValues();
if (allData.length <= 1) return;
DEVICE_LIST.forEach(device => {
try {
const deviceName = device.name;
const deviceRows = allData.filter(row => String(row[0]).trim() === deviceName.trim());
if (deviceRows.length === 0) return;
// 直近24時間のデータを1時間ごとに抽出
const hourlyMap = {};
deviceRows.forEach(row => {
const rDate = (row[2] instanceof Date) ? Utilities.formatDate(row[2], "Asia/Tokyo", "yyyy/MM/dd") : String(row[2]);
const displayTime = getHHmm(row[3]);
const dateTimeKey = rDate + " " + displayTime.split(":")[0] + ":00";
hourlyMap[dateTimeKey] = row;
});
const sortedKeys = Object.keys(hourlyMap).sort();
const hourlyData = sortedKeys.slice(-24).map(key => hourlyMap[key]);
const lastEntry = deviceRows[deviceRows.length - 1];
const latestDateStr = (lastEntry[2] instanceof Date) ? Utilities.formatDate(lastEntry[2], "Asia/Tokyo", "MM/dd") : String(lastEntry[2]);
// QuickChart APIを利用したグラフ画像URLの生成
const chartOpts = (val, color, title) => encodeURIComponent(JSON.stringify({
type: 'radialGauge', data: { datasets: [{ data: [val], backgroundColor: color }] },
options: { centerPercentage: 75, title: { display: true, text: title, fontSize: 18 }, trackColor: '#eee' }
}));
const tempUrl = "https://quickchart.io/chart?c=" + chartOpts(lastEntry[4], '#ff7675', `現温 ${lastEntry[4]}℃`);
const humiUrl = "https://quickchart.io/chart?c=" + chartOpts(lastEntry[5], '#3498db', `現湿 ${lastEntry[5]}%`);
const battUrl = "https://quickchart.io/chart?c=" + chartOpts(lastEntry[8], '#2ecc71', `電池 ${lastEntry[8]}%`);
const tableRows = hourlyData.map(row => ({
"type": "box", "layout": "horizontal", "contents": [
{ "type": "text", "text": getHHmm(row[3]), "size": "xxs", "color": "#2d3436", "flex": 3, "weight": "bold" },
{ "type": "text", "text": row[4] + "℃", "size": "xxs", "align": "center", "weight": "bold", "color": "#d63031", "flex": 3 },
{ "type": "text", "text": row[5] + "%", "size": "xxs", "align": "end", "color": "#0984e3", "flex": 3 }
]
}));
// LINE Flex Message の構造体
const flexContents = {
"type": "bubble", "size": "giga",
"header": { "type": "box", "layout": "vertical", "contents": [
{ "type": "box", "layout": "horizontal", "contents": [
{ "type": "text", "text": deviceName + " 環境管理", "weight": "bold", "color": "#e67e22", "size": "sm", "flex": 7 },
{ "type": "text", "text": latestDateStr, "size": "xxs", "color": "#aaaaaa", "align": "end", "flex": 3, "gravity": "bottom" }
]},
{ "type": "text", "text": "直近24時間 推移レポート", "weight": "bold", "size": "lg", "margin": "md", "color": "#2d3436" }
]},
"body": { "type": "box", "layout": "horizontal", "spacing": "md", "contents": [
{ "type": "box", "layout": "vertical", "flex": 4, "contents": [
{ "type": "image", "url": tempUrl, "size": "full", "aspectRatio": "1:1" },
{ "type": "image", "url": humiUrl, "size": "full", "aspectRatio": "1:1" },
{ "type": "image", "url": battUrl, "size": "full", "aspectRatio": "1:1" },
{ "type": "box", "layout": "vertical", "margin": "md", "contents": [
{ "type": "text", "text": "VPD / 絶対湿度", "size": "xxs", "color": "#aaaaaa", "align": "center" },
{ "type": "text", "text": `${lastEntry[7]}kPa / ${lastEntry[6]}g`, "weight": "bold", "size": "sm", "align": "center" }
]}
]},
{ "type": "box", "layout": "vertical", "flex": 6, "backgroundColor": "#f8f9fa", "paddingAll": "8px", "cornerRadius": "md", "contents": [
{ "type": "box", "layout": "horizontal", "contents": [
{ "type": "text", "text": "時刻", "size": "xxs", "color": "#999999", "flex": 3 },
{ "type": "text", "text": "温度", "size": "xxs", "color": "#999999", "align": "center", "flex": 3 },
{ "type": "text", "text": "湿度", "size": "xxs", "color": "#999999", "align": "end", "flex": 3 }
]},
{ "type": "separator", "margin": "xs" },
{ "type": "box", "layout": "vertical", "spacing": "xs", "margin": "sm", "contents": tableRows }
]}
]}
};
sendLineFlex(flexContents, deviceName);
Utilities.sleep(1000); // 連続送信による負荷回避
} catch (e) {
console.error(`[${device.name}] のLINE送信エラー: ` + e.message);
}
});
}
/** PDFレポートを生成してメール送信(HTML-PDF変換) */
function sendPdfReportEmail() {
console.log("PDFレポート作成を開始します...");
const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
const sheet = ss.getSheetByName(SHEET_NAME);
const allData = sheet.getDataRange().getValues();
if (allData.length <= 1) return;
const now = new Date();
const dateStr = Utilities.formatDate(now, "Asia/Tokyo", "yyyy/MM/dd");
let html = `<html><head><style>
@page { size: A4 landscape; margin: 10mm; }
body { font-family: 'Helvetica', sans-serif; color: #2d3436; margin: 0; padding: 0; }
.page-break { page-break-after: always; }
.header { border-bottom: 3px solid #e67e22; padding-bottom: 5px; margin-bottom: 15px; overflow: hidden; }
.device-title { font-size: 24px; font-weight: bold; color: #e67e22; }
.chart-box { width: 32%; display: inline-block; text-align: center; }
.chart-img { width: 100%; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border-bottom: 1px solid #dfe6e9; padding: 6px; text-align: center; font-size: 11px; }
th { background-color: #f1f2f6; }
</style></head><body>`;
DEVICE_LIST.forEach((device, index) => {
const deviceName = device.name;
const deviceRows = allData.filter(row => String(row[0]).trim() === deviceName.trim());
if (deviceRows.length === 0) return;
const hourlyMap = {};
deviceRows.forEach(row => {
const rDate = (row[2] instanceof Date) ? Utilities.formatDate(row[2], "Asia/Tokyo", "yyyy/MM/dd") : String(row[2]);
const dtKey = rDate + " " + getHHmm(row[3]).split(":")[0] + ":00";
hourlyMap[dtKey] = row;
});
const sortedKeys = Object.keys(hourlyMap).sort();
const hourlyData = sortedKeys.slice(-24).map(key => hourlyMap[key]);
const current = deviceRows[deviceRows.length - 1];
// PDF埋め込み用の画像をBase64形式で取得
const chartBaseOpts = (val, color, title) => JSON.stringify({
type: 'radialGauge', data: { datasets: [{ data: [val], backgroundColor: color }] },
options: { centerPercentage: 70, title: { display: true, text: title, fontSize: 22 }, trackColor: '#eee' }
});
const tBase64 = getChartBase64(chartBaseOpts(current[4], '#ff7675', `温度 ${current[4]}℃`));
const hBase64 = getChartBase64(chartBaseOpts(current[5], '#3498db', `湿度 ${current[5]}%`));
const bBase64 = getChartBase64(chartBaseOpts(current[8], '#2ecc71', `電池 ${current[8]}%`));
html += `
<div class="container ${index < DEVICE_LIST.length - 1 ? 'page-break' : ''}">
<div class="header"><span class="device-title">${deviceName} 環境管理レポート (${dateStr})</span></div>
<div style="text-align:center;">
<div class="chart-box"><img src="${tBase64}" class="chart-img"></div>
<div class="chart-box"><img src="${hBase64}" class="chart-img"></div>
<div class="chart-box"><img src="${bBase64}" class="chart-img"></div>
</div>
<table>
<thead><tr><th>日付</th><th>時刻</th><th>温度</th><th>湿度</th><th>絶対湿度</th><th>VPD</th></tr></thead>
<tbody>`;
hourlyData.forEach(row => {
const rowDate = (row[2] instanceof Date) ? Utilities.formatDate(row[2], "Asia/Tokyo", "MM/dd") : String(row[2]);
html += `<tr><td>${rowDate}</td><td>${getHHmm(row[3])}</td><td>${row[4]}℃</td><td>${row[5]}%</td><td>${row[6]}g</td><td>${row[7]}kPa</td></tr>`;
});
html += `</tbody></table></div>`;
});
html += `</body></html>`;
const blob = HtmlService.createHtmlOutput(html).getAs('application/pdf').setName(`環境管理レポート_${dateStr}.pdf`);
MailApp.sendEmail({
to: RECIPIENT_EMAIL,
subject: EMAIL_SUBJECT,
body: `環境管理レポートを送付いたします。\n送信日時: ${Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd HH:mm")}`,
attachments: [blob]
});
}
/** 外部APIから画像を取得してBase64に変換(PDF埋め込み用) */
function getChartBase64(jsonConfig) {
const url = "https://quickchart.io/chart?w=300&h=300&c=" + encodeURIComponent(jsonConfig);
try {
const res = UrlFetchApp.fetch(url);
return "data:image/png;base64," + Utilities.base64Encode(res.getContent());
} catch (e) {
return "";
}
}
/* ==========================================
5. 共通補助関数
========================================== */
/** 日付や数値から「HH:mm」形式の文字列を取得 */
function getHHmm(value) {
if (!value) return "00:00";
if (value instanceof Date) return Utilities.formatDate(value, "Asia/Tokyo", "HH:mm");
if (typeof value === 'number') {
const totalMinutes = Math.round(value * 24 * 60);
const hours = Math.floor(totalMinutes / 60) % 24;
const mins = totalMinutes % 60;
return ("0" + hours).slice(-2) + ":" + ("0" + mins).slice(-2);
}
const timeMatch = String(value).match(/\d{1,2}:\d{2}/);
return timeMatch ? timeMatch[0] : "00:00";
}
/** LINE Messaging API へのリクエスト送信 */
function sendLineFlex(contents, deviceName) {
const url = 'https://api.line.me/v2/bot/message/push';
const options = {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + LINE_TOKEN },
payload: JSON.stringify({ to: LINE_DEST, messages: [{ type: "flex", altText: "環境レポート", contents: contents }] }),
muteHttpExceptions: true
};
const res = UrlFetchApp.fetch(url, options);
console.log(`[LINE送信] ${deviceName}: ${res.getContentText()}`);
}上記コードがわからなくてAIに書いてもらうのであれば、【上記コードを私の環境に置き換えて全文提供して】と言えば解決!
ここからが本番!「3ステップ」ボタン操作ガイド
貼り付けたあと、画面上部のメニューから選んで実行するだけです。
① 【ボタン1】checkMyDeviceIDsでIDを特定
実行すると、ログにあなたの温湿度計の「名前」と「ID」が一覧表示されます。それをコピーして、コード内の
DEVICE_LISTに貼り付けてください。
② 【ボタン2】setupSystemで自動化開始
これを1回押すだけで、「1分ごとのデータ自動記録」と「毎朝のLINE&PDFレポート送信」がすべて予約されます。
もうあなたが画面を開く必要はありません。
③ 【新機能】sendPdfReportEmailでメールテスト
このボタンを押すと、即座にあなたのメールアドレスへ「画像付き・横向きデザイン」のPDFレポートが届きます。その美しさをぜひ確認してください。
④ 【実行3】setupSystem を選んで「実行」する
最後に、メニューから 初回に一度だけ
setupAllTriggers()を手動で選んで実行する必要があります。
セットアップの流れ
1️⃣ 初回セットアップ(一度だけ実行)
setupAllTriggers() を手動で実行
↓
自動トリガーが設定される
↓
以降は自動で動作
2️⃣ 具体的な手順
Google Apps Scriptエディタを開く
スプレッドシートから「拡張機能」→「Apps Script」
コードを貼り付ける
提供されたコード全体をエディタに貼り付け
setupAllTriggers() を実行
関数選択ドロップダウンから setupAllTriggers を選択
「実行」ボタンをクリック
初回は権限の承認が必要です(画面の指示に従って承認)
トリガーが自動設定される
✅ 毎分データ取得(getSwitchBotData)
✅ 毎日16時台にLINE送信(sendPremiumDashboard)
✅ 毎日16時台にメール送信(sendPdfReportEmail)
3️⃣ トリガー設定の確認方法
実行後、以下の方法で確認できます。
方法1: Apps Scriptエディタで確認
3つのトリガーが表示されていればOK
万が一、自動実行を止めたいときは?
メニューから stopAllTriggers を選んで実行してください。すべての予約が安全に解除されます。
まとめ
「自分には難しそう」と感じましたか? ご安心ください。コードを貼り付け、「実行」ボタンをクリックした瞬間、あなたのスマホは「24時間、不眠不休で現場を見守る有能な部下」に変わります。
数千円の投資で手に入る「安心感」と、自動化によって生まれる新しい「時間」。 さあ、あなたも今すぐSwitchBotを手に入れて、次世代の管理システムをその手にしてください!












