AC09 Cat-M1 TYで温湿度データをGoogle スプレッドシートに保存

このページをAIに渡す

いつものAIに相談

Gemini・その他のAI

このページの情報を添えて、仕組みや使い方、つくりたいものを相談できます。

Geminiを開く

質問文をコピーして、Geminiの入力欄に貼り付けてください。

質問文を確認

目次

概要

4-Sensorsリーフの温湿度センサーで測定した温湿度データを、AC09 Cat-M1 TYリーフを使って10分間隔でGoogle スプレッドシートに送信し、保存するサンプルです。

STM32リーフで温湿度データを読み取り、AC09 Cat-M1 TYリーフからCellular LPWA Cat-M1通信網を経由して送信します。保存した温湿度データは、Google スプレッドシートで確認できます。

AV06に接続したバッテリーで、USBを外して動作させる構成を想定しています。FWの書き込み後は、起動時にモデムから時刻が取得できるまで自動で待ち、取得できたら温湿度の送信を開始します。パソコンからの時刻入力は不要です。

システム構成

温湿度データの流れは次のとおりです。

flowchart TD
    sensor["4-Sensors<br/>温湿度測定"]
    mcu["STM32 MCU<br/>読み取り・送信処理"]
    lte["AC09 Cat-M1 TY"]
    network["Cellular LPWA Cat-M1通信網"]
    receiver["Google Apps Script(GAS)<br/>HTTPエンドポイント"]
    sheet["Google スプレッドシート"]

    sensor --> mcu --> lte --> network
    network -->|HTTPS POST / JSON| receiver
    receiver -->|受信日時・温度・湿度を追記| sheet

温湿度データをJSON形式でGASのウェブアプリへ送信します。GASはトークンと温湿度を確認し、スプレッドシートへ追記します。HTTPエンドポイントへの接続にはHTTPSを使用します。

起動後、ネットワーク接続と時刻の取得ができたら初回の温湿度を読み取って送信し、その後は10分ごとに新しい測定値の読取・送信を開始します。接続や応答に10分以上かかった場合は、その周期を飛ばし、処理完了から10分後に再び読取・送信します。通信障害時の再接続を含め、シートに届く間隔が常に10分になることを保証するものではありません。

用意するもの

使用するリーフ

次のリーフを使用します。表は、組み立てた状態の上から下の順です。

順番リーフこのサンプルでの役割
1(最上段)AC09 Cat-M1 TYCellular LPWA Cat-M1通信で温湿度データを送信
2AX04 Spacer(スペーサー)AC09と4-Sensorsの間に配置
3AI01 4-Sensors搭載する温湿度センサーで温度と湿度を測定
4AP03 STM32 MCU温湿度データの読み取りと送信処理を実行
5AZ01C USB-Cパソコンとの接続、プログラムの書き込みやシリアル通信に使用
6(最下段)AV06 1.8V~5.5V接続したバッテリーから各リーフに給電

事前に準備するもの

項目準備内容
Google アカウント温湿度データの保存先となるGoogle スプレッドシートを利用するために必要です。
開発用パソコンSTM32リーフの開発環境を構築しておきます。手順は開発環境設定ページを参照してください。
USB-CケーブルFWの書き込みとログ確認に使用する、データ通信対応のケーブルを用意します。バッテリーで運用するときは外します。
バッテリーAV06の電源・コネクタ仕様に合うバッテリーと接続ケーブルを用意します。
SIMカード次に示すいずれかのSIMを1枚用意します。

使用できるSIM

次のいずれかを使用します。

  • Cellular LPWA Cat-M1リーフに同梱されている1NCE SIM
  • ご自身で契約した1NCE SIM
  • ご自身で契約したSORACOM SIM(plan-D)

SIMを新しく契約する場合は、以下の見出しを開いて手順を確認してください。AC09 Cat-M1 TYではnanoSIMを使用します。

1NCE SIMの契約手順

ここでは、ご自身で1NCEからSIMを購入する手順を説明します。日本向けの1NCEサービスの直接契約は法人専用です。契約条件は公式のサービス案内で確認してください。

  1. オンラインショップを開く
    1NCE日本語サイトからオンラインショップへ進み、請求先の国が日本になっていることを確認します。
  2. SIMの種類と枚数を選ぶ
    カード型の「IoT SIM Card Business(3-in-1)」を選びます。このサンプルで必要なSIMは1枚です。3-in-1 SIMは、届いてからnanoサイズに切り離して使用します。
  3. アカウントを作成する
    画面の案内に従ってアカウントを作成し、会社情報や連絡先、配送先などの注文に必要な情報を入力します。すでにアカウントがある場合はログインします。
  4. 注文と支払いを完了する
    SIMの種類・枚数・配送先・料金を確認し、画面で案内される方法で支払います。
  5. SIMと管理画面を確認する
    SIMが届いたら、1NCEカスタマーポータルにログインし、購入したSIMとその状態を確認します。

購入の案内は1NCE公式サイトのお問い合わせ・購入案内、SIMの仕様はIoT SIM Card Businessの公式資料(英語)を参照してください。

SORACOM SIM(plan-D)の契約手順

ここでは、SORACOMのユーザーコンソールからplan-DのnanoSIMを申し込む手順を説明します。新規購入するSIMは、plan-D(D-500MB)のデータ通信のみ・ナノサイズを選びます。

  1. SORACOMアカウントを作成する
    公式のアカウント作成手順から登録画面を開き、個人または法人の利用区分、国、カバレッジタイプ「日本」を選びます。メールアドレスとパスワードを設定し、メール認証と契約者情報の入力を完了します。すでにアカウントがある場合はログインします。
  2. 支払い方法を登録する
    SORACOMユーザーコンソールにログインし、右上のユーザー名から「支払い設定」を開きます。クレジットカード払いの場合は「+新しいクレジットカードを登録」から登録します。
  3. 新規注文を開始する
    「メニュー」→「発注」→「+新規注文」の順に開き、カバレッジタイプ「日本」の「SIMカード」タブを選びます。
  4. plan-DのnanoSIMを選ぶ
    plan-D(D-500MB)の「データ通信のみ」「ナノサイズ」を選び、数量を1枚にします。発送方法を選び、合計枚数と料金を確認して次へ進みます。
  5. 配送先を登録して注文する
    お届け先を登録・選択し、注文内容と利用条件を確認して「注文を確定する」を押します。詳しい画面操作は公式のSIM申込手順を参照してください。
  6. SIM到着後に受け取り確認をする
    ユーザーコンソールの「メニュー」→「発注」から該当する注文を開き、「受け取り確認」を行います。
  7. SIM管理画面で登録を確認する
    「メニュー」→「SORACOM Air for セルラー」→「SIM管理」を開き、カバレッジタイプ「日本」でSIMが表示されることを確認します。受け取り確認と登録の詳細は公式のSIM登録手順を参照してください。

開発環境の準備

サンプルコードを書き込む前に、パソコンにSTM32リーフの開発環境を構築してください。開発環境設定ページのSTM32 MCU向けの案内を参照してください。

このサンプルはPlatformIOとSTM32 Arduinoフレームワークを使用します。AP03用のボード定義とピン設定はプロジェクト内に含まれています。TLSと温湿度センサーのライブラリは初回ビルド時にPlatformIOが取得します。

Leafonyの組み立て

リーフは、上から次の順に重ねます。スペーサーリーフは、AC09 Cat-M1 TYリーフと4-Sensorsリーフの間に配置します。

上
  AC09 Cat-M1 TY
  AX04 Spacer
  AI01 4-Sensors
  AP03 STM32 MCU
  AZ01C USB-C
  AV06
下

Google スプレッドシートの準備

Google Apps Script(GAS)で、温湿度データを受け取るHTTPエンドポイントを作成します。LeafonyはこのエンドポイントにHTTPSのPOSTリクエストを送り、GASが受信した温度と湿度をGoogle スプレッドシートに追記します。

保存するデータ

temperatureシートに、次の3列を保存します。1回の送信で1行が追加されます。

列見出し保存する内容
A受信日時(UTC)GASがデータを書き込むときのUTC日時。例:2026-09-18T03:00:00.000Z
B温度(℃)4-Sensorsリーフで測定した温度の数値。例:25.5
C湿度(%RH)4-Sensorsリーフで測定した相対湿度の数値。例:50.0

湿度は相対湿度(%RH)です。送信JSONでは、温度にtemperature_c、湿度にhumidity_pctを使用します。

日時は測定時刻ではなく、Google側での受信時刻です。末尾のZはUTCを表します。日本時間はUTCに9時間を加えた時刻です。Leafonyからの送信間隔は10分ですが、シートに記録される時刻の間隔は通信時間によって変動します。

スプレッドシートとGASプロジェクトを作成する

  1. Google アカウントにログインし、Google スプレッドシートで新しいスプレッドシートを作成します。名前は、たとえば「Leafony 温湿度ログ」にします。

  2. ブラウザーのURLからスプレッドシートIDを控えます。次のURLのSPREADSHEET_IDに当たる部分です。末尾のgidは含めません。

    https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
  3. スプレッドシートの「拡張機能」→「Apps Script」を開きます。GASプロジェクトに「Leafony 温湿度受信」などの名前を付けます。

スプレッドシートからのGASプロジェクト作成方法は、Google公式のコンテナバインドスクリプトの説明を参照してください。このサンプルでは、Webアプリからも保存先を特定できるよう、スプレッドシートIDを指定して開きます。

受信用のGASコードを登録する

エディターにあるコード.gsの内容を、以下のコードに置き換えて保存します。doPost(e)がJSON形式の本文を読み取り、トークンと温湿度を確認してから行を追記します。doGet()は書き込みを行いません。

GASコード

const SHEET_NAME = 'temperature';
const HEADERS = ['受信日時(UTC)', '温度(℃)', '湿度(%RH)'];

// スクリプト プロパティを設定した後、エディターから一度実行します。
function setup() {
  const config = readConfiguration();
  if (!config) {
    throw new Error('API_TOKEN と SPREADSHEET_ID のスクリプト プロパティを確認してください。');
  }

  const lock = LockService.getScriptLock();
  if (!lock.tryLock(5000)) {
    throw new Error('処理中です。少し待って setup を再実行してください。');
  }
  try {
    const spreadsheet = SpreadsheetApp.openById(config.spreadsheetId);
    const sheet = spreadsheet.getSheetByName(SHEET_NAME)
      || spreadsheet.insertSheet(SHEET_NAME);
    if (sheet.getLastRow() === 0) {
      sheet.appendRow(HEADERS);
    } else if (!hasExpectedHeader(sheet)) {
      // 温度のみの旧シートは、C 列以降に値や数式がない場合だけ移行します。
      const header = sheet.getRange(1, 1, 1, 2).getValues()[0];
      if (header[0] === HEADERS[0] && header[1] === HEADERS[1]
          && sheet.getLastColumn() <= 2) {
        sheet.getRange(1, 3).setValue(HEADERS[2]);
      } else {
        throw new Error('temperature シートの先頭行を確認してください。既存のデータは変更していません。');
      }
    }
    SpreadsheetApp.flush();
  } finally {
    lock.releaseLock();
  }
}

function doPost(e) {
  try {
    const body = e && e.postData && e.postData.contents;
    if (typeof body !== 'string' || body.length === 0) {
      return jsonResponse({ ok: false, error: 'invalid_request' });
    }
    // length は受信本文のバイト数。文字数も確認して過大な本文を解析しません。
    if (e.postData.length > 1024 || e.contentLength > 1024 || body.length > 1024) {
      return jsonResponse({ ok: false, error: 'body_too_large' });
    }

    let data;
    try {
      data = JSON.parse(body);
    } catch (_) {
      return jsonResponse({ ok: false, error: 'invalid_json' });
    }
    if (!data || typeof data !== 'object' || Array.isArray(data)) {
      return jsonResponse({ ok: false, error: 'invalid_request' });
    }

    const config = readConfiguration();
    if (!config) {
      return jsonResponse({ ok: false, error: 'not_configured' });
    }
    if (typeof data.token !== 'string' || data.token !== config.token) {
      return jsonResponse({ ok: false, error: 'invalid_token' });
    }
    if (typeof data.temperature_c !== 'number'
        || !Number.isFinite(data.temperature_c)
        || data.temperature_c < -40 || data.temperature_c > 120) {
      return jsonResponse({ ok: false, error: 'invalid_temperature' });
    }
    if (typeof data.humidity_pct !== 'number'
        || !Number.isFinite(data.humidity_pct)
        || data.humidity_pct < 0 || data.humidity_pct > 100) {
      return jsonResponse({ ok: false, error: 'invalid_humidity' });
    }

    const lock = LockService.getScriptLock();
    if (!lock.tryLock(5000)) {
      return jsonResponse({ ok: false, error: 'busy' });
    }
    try {
      const sheet = SpreadsheetApp.openById(config.spreadsheetId)
        .getSheetByName(SHEET_NAME);
      if (!sheet || !hasExpectedHeader(sheet)) {
        return jsonResponse({ ok: false, error: 'sheet_not_initialized' });
      }
      sheet.appendRow([new Date().toISOString(), data.temperature_c, data.humidity_pct]);
      SpreadsheetApp.flush();
      return jsonResponse({ ok: true });
    } finally {
      lock.releaseLock();
    }
  } catch (_) {
    // 例外の詳細や設定値を HTTP 応答に含めません。
    return jsonResponse({ ok: false, error: 'internal_error' });
  }
}

function doGet() {
  return jsonResponse({ ok: false, error: 'method_not_allowed' });
}

function readConfiguration() {
  const properties = PropertiesService.getScriptProperties();
  const token = properties.getProperty('API_TOKEN');
  const spreadsheetId = properties.getProperty('SPREADSHEET_ID');
  if (!token || token.length < 32 || token.length > 128 || /[^A-Za-z0-9_-]/.test(token)
      || !spreadsheetId || !spreadsheetId.trim()) {
    return null;
  }
  return { token: token, spreadsheetId: spreadsheetId.trim() };
}

function hasExpectedHeader(sheet) {
  if (sheet.getLastRow() === 0) return false;
  const header = sheet.getRange(1, 1, 1, 3).getValues()[0];
  return HEADERS.every((value, index) => header[index] === value);
}

function jsonResponse(value) {
  return ContentService.createTextOutput(JSON.stringify(value))
    .setMimeType(ContentService.MimeType.JSON);
}

POSTリクエストの受け取り方はWebアプリの公式ガイド、行の追記はappendRow()の公式リファレンスを参照してください。

スクリプト プロパティを設定する

GASエディター左側の「プロジェクトの設定」を開き、「スクリプト プロパティ」に次の2項目を追加して保存します。

プロパティ設定する値
SPREADSHEET_ID先ほど控えたスプレッドシートID
API_TOKENこのサンプル用に生成する、32〜128文字の英数字・ハイフン・アンダースコアだけで構成したトークン

トークンは、パソコンのターミナルで次のコマンドを実行すると、64文字のランダムな16進数として生成できます。表示された文字列をAPI_TOKENに設定し、後でLeafonyの設定にも同じ値を使用します。

openssl rand -hex 32

スクリプト プロパティはプロジェクトの編集者も参照できます。設定画面の操作は、Google公式のプロパティ管理手順を参照してください。

保存先シートを初期化する

  1. GASエディターに戻り、上部の関数選択でsetupを選びます。
  2. 「実行」を押します。初回はGoogle アカウントを選び、このスクリプトがスプレッドシートにアクセスすることを許可します。
  3. スプレッドシートに戻り、temperatureシートと、1行目の「受信日時(UTC)」「温度(℃)」「湿度(%RH)」が作成されたことを確認します。

setup()は一度実行すれば十分です。再実行しても、すでに記録したデータは削除されません。同名のシートに別の見出しやデータがある場合は、変更せずエラーで停止します。その場合は既存のシートを別名にしてから再実行してください。

温度だけを保存していた旧版から更新する場合は、GASコードを置き換えてsetup()を再実行します。先頭2列が旧版の見出しと一致し、C列以降に値や数式がない場合に限り、C1へ「湿度(%RH)」を追加します。既存の日時・温度は残り、過去の行の湿度は空欄になります。C列以降にデータがある場合は自動変更しません。

その後、下記の方法でGASのデプロイを更新し、湿度に対応したFWを書き込んでください。新しいGASではhumidity_pctが必須のため、旧版の温度だけの送信は受け付けません。

HTTPエンドポイントとしてデプロイする

  1. GASエディター右上の「デプロイ」→「新しいデプロイ」を開きます。
  2. 「種類の選択」から「ウェブアプリ」を選びます。
  3. 「次のユーザーとして実行」を「自分」にします。
  4. 「アクセスできるユーザー」を「全員」にします。LeafonyはGoogle アカウントにログインしないため、匿名アクセスを許可する設定が必要です。Google Workspaceの設定により「全員」を選べない場合は、組織の管理者に確認してください。
  5. 「デプロイ」を押し、権限の確認が表示された場合は、このスクリプトに必要なアクセスを許可します。
  6. 表示される「ウェブアプリ」のURLをコピーします。末尾が/execになっていることを確認してください。このURLがLeafonyの送信先です。
https://script.google.com/macros/s/DEPLOYMENT_ID/exec

アクセス権の設定とURLの発行はGoogle公式のWebアプリのデプロイ手順に従います。/devで終わるテスト用URLは編集権限のあるユーザー専用なので、Leafonyには/execのURLを設定します。

GASコードを変更した場合は、「デプロイ」→「デプロイを管理」から既存のデプロイを編集し、新しいバージョンを選んで再デプロイします。同じデプロイを更新すれば、送信先URLを維持できます。デプロイの更新方法

パソコンから書き込みを確認する

まず、パソコンからテスト用の温度と湿度を1件送信します。以下のYOUR_DEPLOYMENT_IDを含むURL全体を、コピーしたウェブアプリのURLに置き換えます。YOUR_API_TOKENも、自分で設定したトークンに置き換えてください。

Windows(PowerShell)

  1. スタートメニューで「PowerShell」を検索し、Windows PowerShellまたはPowerShellを開きます。
  2. curl.exe --versionを実行し、バージョン情報が表示されることを確認します。コマンドが見つからない場合は、Windows向けcurlの公式配布ページを参照してください。
  3. 次のURLとトークンを自分の値に置き換え、コード全体をPowerShellへ貼り付けて実行します。
$url = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec'
$payload = '{"token":"YOUR_API_TOKEN","temperature_c":25.5,"humidity_pct":50.0}'
$requestFile = [System.IO.Path]::GetTempFileName()

try {
    Set-Content -LiteralPath $requestFile -Value $payload -Encoding Ascii -ErrorAction Stop
    curl.exe -L $url -H 'Content-Type: application/json' --data-binary "@$requestFile"
} finally {
    Remove-Item -LiteralPath $requestFile
}

Windows PowerShellではcurlが別のコマンドの別名になっているため、必ずcurl.exeと指定します。JSONの引用符をそのまま送るため、一時ファイルに保存してから送信し、送信後にファイルを削除しています。この例はWindows PowerShell 5.1とPowerShell 7で使えます。Windowsでのcurlの使い方

macOS・Linux

ターミナルで、次のコマンドを実行します。

curl -L \
  'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec' \
  -H 'Content-Type: application/json' \
  --data '{"token":"YOUR_API_TOKEN","temperature_c":25.5,"humidity_pct":50.0}'

送信結果の確認(共通)

成功すると、次のJSONが返ります。temperatureシートに受信日時、温度25.5、湿度50.0の行が追加されたことも確認します。

{"ok":true}

--dataまたは--data-binaryの指定により最初のリクエストはPOSTになります。GASのContentServiceは応答を別のURLにリダイレクトするため、-Lで追従し、リダイレクト先から最終結果をGETで取得します。このコマンドに-X POSTを追加する必要はありません。ContentServiceのリダイレクトについて

HTTPステータスやリダイレクトだけで成功と判断せず、最終的なJSONのokがtrueになっていることを確認します。たとえば{"ok":false,"error":"invalid_token"}の場合は、送信したトークンとスクリプト プロパティが一致しているか確認してください。sheet_not_initializedの場合は、setup()の実行結果とシートの見出しを確認します。

このサンプルでは、応答を受け取れなかった場合に同じ測定値を自動で再POSTしません。応答だけが失われて、シートにはすでに記録されている可能性があるためです。手動で同じテストを実行すると、そのたびに新しい行が追加されます。

サンプルコード

ソースコードの入手

STM32用プロジェクトは、AC09開発リポジトリのplatformio/google_sheetsです。VS Codeでは、このフォルダーを開きます。配布用URLは準備中です。

platformio/google_sheets/
├── platformio.ini
├── boards/                 # AP03のボード定義
├── variants/               # AP03のピン設定
├── include/
│   └── config.example.h    # 自分の設定ファイルのひな形
├── lib/
│   └── LeafonyAC09/        # AC09の初期化・AT通信・Cellular LPWA Cat-M1・TCP
├── src/                    # 温湿度取得・時刻管理・HTTPS・GAS送信
├── docs/
│   └── library-evaluation.md # 公開ライブラリの検討記録
└── gas/
    └── Code.gs             # 上記と同じGASコード

AC09固有の処理は自作ライブラリlib/LeafonyAC09にまとめています。ライブラリの使用例は同ディレクトリのREADME.mdを参照してください。APNなどのSIM設定はサンプル側からライブラリに渡します。TLSにはArduinoBearSSLを使用し、HTTP応答の解析にはサイズ制限のあるパーサーを使用しています。

温湿度取得は、温湿度センサ読み取りサンプルと同じSmartEverything HTS221 1.1.2を使用します。HTS221.hを読み込み、smeHumidity.begin()で初期化し、readTemperature()とreadHumidity()で値を取得します。platformio.iniでは使用するコミットを固定しているため、手動でのインストールは不要です。

センサーは1 Hzで連続測定し、FWは10分ごとに新しい温湿度データを取得・送信します。古い出力を消費してから両方の新しいデータが揃うまで最大1500 ms待ち、取得した値の範囲を確認します。ライブラリ内部のI²C読取には無期限の待機処理があるため、読取途中の通信障害によっては処理が停止する場合があります。1500 msはライブラリ呼出し全体のタイムアウトではありません。

設定項目

include/config.example.hをinclude/config.local.hへコピーし、次を設定します。

設定名設定する値
GAS_URLGASのデプロイで取得した、https://script.google.com/macros/s/.../exec形式のURL
GAS_API_TOKENGASのスクリプト プロパティAPI_TOKENに登録した文字列と同じ値
SIM_PROVIDER1NCEは1、SORACOM plan-Dは2
ONE_NCE_APN1NCEポータルに表示されるAPN。ひな形はsensor.net。iot.1nce.netが指定されているSIMでは変更する
SEND_INTERVAL_MS600000UL(10分)。読取・送信の開始間隔。変更する場合は30秒以上に設定する

SORACOMを選ぶと、APNはsoracom.io、認証方式はPAP、ユーザー名とパスワードはどちらもsoraになります。1NCEではPAPと空のユーザー名・パスワードを使用します。APNは各社の案内を確認してください。1NCEのAPN案内、SORACOMのAPN設定

config.local.hとビルドしたファームウェアにはトークンが含まれます。公開リポジトリへ追加せず、自分の環境で使用してください。config.local.hはプロジェクトの.gitignoreで除外しています。

プログラムの流れ

  1. AC09を初期化してCellular LPWA Cat-M1ネットワークへ接続します。
  2. 4-SensorsのHTS221を初期化します。TLS証明書の有効期間を確認するため、モデムから現在時刻が取得できるまで30秒ごとに再確認します。
  3. ライブラリで新しい温度と湿度を読み取り、トークン、temperature_c、humidity_pctをJSONにしてGASへHTTPS POSTします。
  4. GASが返すリダイレクト先へGETし、HTTP 200と{"ok":true}の両方を確認します。このGETにはトークンや測定値を付けません。
  5. 10分周期で繰り返します。温度または湿度の取得失敗や範囲外の値を検出した周期は送信しません。送信結果が不明な測定値は再送せず、次の周期に新しく測定します。

書き込みと実行

  1. platformio.iniのupload_portとupload_commandを、パソコンの接続ポートとSTM32CubeProgrammerのインストール先に合わせます。付属のコマンドはmacOSの例です。OS別の設定はPlatformIOの開発環境設定を参照してください。

  2. プロジェクトのフォルダーでビルドします。

    pio run -e leafony_ap03
  3. STM32リーフをProgram側に切り替えてリセットし、書き込みます。

    pio run -e leafony_ap03 -t upload
  4. Run側へ戻してリセットします。USB接続中にログを確認する場合は、シリアルモニターを開きます。

    pio device monitor -e leafony_ap03 -b 115200
  5. バッテリーで運用するときは電源を切り、AV06へバッテリーを接続します。USBケーブルを外し、STM32がRun側になっていることを確認してAV06の電源を入れます。シリアルモニターを開かなくても起動し、Cellular LPWA Cat-M1接続と時刻取得を進めます。

TLS用の時刻の自動取得

起動後、Cellular LPWA Cat-M1ネットワークへの接続が完了すると、モデムの時刻をAT+CCLK?で読み取ります。時刻が未設定・不正、または取得に失敗した場合は、応答処理の完了から30秒待って再確認します。時刻待ちには制限時間を設けていません。時刻が取得できるまではHTTPS接続や温湿度の送信を行わず、取得できた周期から自動で送信を開始します。

USB接続中にログを確認すると、待機中は次のように表示されます。操作は不要です。

Waiting for network UTC; retrying in 30 seconds

有効な日時が取得できると、次を表示して温湿度の送信へ進みます。

UTC clock set from modem

時刻取得だけが失敗している間はネットワーク接続を維持します。ATコマンドの応答がタイムアウトして通信状態が不明になった場合は、30秒待ってモデムの初期化とCellular LPWA Cat-M1接続から再試行します。Cellular LPWA Cat-M1の登録自体が拒否された場合は、通常の再接続処理に従います。

取得した時刻はSTM32の稼働中に進みます。電源を入れ直した場合は、自動で時刻取得からやり直します。USB接続やシリアルからのTIME入力は必要ありません。

この方法には、利用する回線とモデムの設定で正しい日時がAT+CCLK?から返ることが必要です。日時が更新されない環境では、待つだけでは送信を開始できません。AC09と使用するSIMでの時刻自動取得は実機確認前です。バッテリー運用前に電源投入から自動取得できることを確認してください。証明書検証を省略したり、固定日時で送信したりする処理はありません。

このサンプルでは待機中もMCUとモデムが動作しています。スリープによる省電力化とバッテリー持続時間の評価は未実施です。

動作確認

シリアルモニターの表示

保存が確認できた周期にはSaved to Google Sheetsを表示します。以下は表示形式の例で、実機の取得ログではありません。

Temperature (C): 25.50
Humidity (%RH): 50.00
Saved to Google Sheets

測定値だけが表示され、保存完了が出ない場合は、その後のエラーメッセージを確認します。

スプレッドシートへの保存結果

temperatureシートを開き、3列のデータが追記されることを確認します。A列はGASが受信した日時(UTC)、B列は温度(℃)、C列は湿度(%RH)です。パソコンからのテストで追加した行と、Leafonyから追加した行を確認してください。連続した受信日時の差を見て、通常時におおむね10分間隔で記録されることを確かめます。

うまく動かないとき

表示・状況確認すること
Configure include/config.local.hGAS_URLとGAS_API_TOKENにひな形の値が残っていないか確認する
Network unavailableSIMの状態、APN、アンテナと電源を確認する。登録拒否時は5分以上間隔を空けて再接続する
[AT] timeout; hardware restart required直前のtimeout detailsと[diagnostic]を確認する。ネットワーク接続中のATタイムアウトでは30秒後にモデムを再初期化する
HTS221 initialization failed / HTS221 read failed4-Sensorsの接続とI2C通信を確認する
Waiting for network UTC30秒ごとに自動で再確認する。待機が続く場合は、回線・モデムの時刻同期設定とAT+CCLK?の応答を確認する
Unexpected redirectGASのアクセス対象がGoogleログイン不要の「全員」になっているか確認する
GAS did not confirm savingパソコンから同じURL・トークンでテストし、JSONのerrorを確認する。コード変更後はデプロイも更新する
HTTPS TLS connection failed; HTTP request was not sent直前の[TLS]にある失敗理由・エラー番号・経過時間を確認する。この要求のHTTP本文はまだ送信していない
Google Sheets request failed / Result unavailable直前の[TLS]と[HTTPS]、パソコンから同じGAS設定で書き込めるかを確認する。応答を確認できなくてもシートに記録済みの場合があるので、同じ測定値は再送しない

AT+CEREG?の応答待ちで止まる場合は、Cellular LPWA Cat-M1ネットワークへの登録中で、まだGoogleへの送信には進んでいません。AT応答は最大120秒待ちます。Network unavailable; retry delay (seconds)に表示する数値は、応答の待ち時間ではなく、次の接続試行までの待ち時間です。

タイムアウト時の[AT] timeout detailsには受信バイト数と未完了の行の長さなどを、[diagnostic]にはUARTとAC09の信号状態を出力します。また、起動時にはSTM32のリセットフラグを出力します。BOR=1は通常の電源投入でも表示されるため、それだけで動作中の電圧低下とは断定できません。起動からタイムアウトまでのログと、手動リセットの有無、USB-CとAV06バッテリーの接続状態をあわせて確認してください。

Cellular LPWA Cat-M1接続と時刻取得の後は、[TLS]でTLS接続の成否を確認します。reason=timeoutは処理期限、reason=tls_errorはBearSSLのエラーです。[HTTPS] Request sent; waiting for HTTP responseが出たらHTTP要求の書込みは完了していますが、保存確認はまだです。続くresponseログにはHTTPステータスと受信バイト数、応答が完了したかを表示します。URLのパスやトークン、HTTP本文はログへ表示しません。保存成功はSaved to Google Sheetsで確認してください。

関連資料

リーフ写真
技術資料・サンプル・Q&Aから探せます