はじめに
遠方に住む祖母の見守りとして、「テレビをつけたらLINE通知が届く」IoTシステムをM5AtomUで作ってみました。
ただし、通知が毎回届くと迷惑なので、「1日1回だけ通知する」ように制御しています。
実現したいこと
- M5AtomUが起動したらLINE通知を送る
- ただし、同じ日に複数回通知しない
- 通知履歴はEEPROMに保存
- Wi-Fi接続が失敗したら再接続を試みる
実装のポイント
1. EEPROMに年月日を保存(3バイト)
int today = (timeinfo.tm_year * 10000) + (timeinfo.tm_mon * 100) + timeinfo.tm_mday;
EEPROM.write(0, today & 0xFF);
EEPROM.write(1, (today >> 8) & 0xFF);
EEPROM.write(2, (today >> 16) & 0xFF);
EEPROM.commit();
2. 保存された日付と比較して通知制御
int lastSent = EEPROM.read(0) + (EEPROM.read(1) << 8) + (EEPROM.read(2) << 16);
if (today == lastSent) {
Serial.println("今日はすでに通知済みです。スキップします。");
return;
}
3. Wi-Fi接続失敗時に再接続処理(最大3回)
int wifiAttempts = 0;
bool wifiConnected = false;
while (wifiAttempts < 3) {
WiFi.begin(ssid, password);
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) {
delay(500);
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
break;
}
wifiAttempts++;
}
if (!wifiConnected) {
Serial.println("WiFi接続に3回失敗しました。通知は送信されません。");
return;
}
学びと工夫
- tm_yearは1900年からの年数なので、2025年なら125となる
- EEPROMは1バイトずつしか保存できないので、3バイトに分割して保存
- M5AtomUは2.5GHz接続のみ対応
- LINE Messaging APIは月200回までメッセージ送信が無料(回数=メッセージ数×送信先数)
おわりに
家庭用としての作成なため、できるだけコストをかけない「無料枠に収まる範囲」での実装にこだわりました。
以前まではIFTTTを使ってメールを送信させようとしていましたが、近年有償化していました。(確か、webhookの部分だった気がする。)
また、筆者はプログラミング超初心者なため、Windows標準搭載のCopilotさんに教えてもらいながら作成しました。この投稿自体も、大体Copilotさんに作ってもらっています。便利になりましたね。
実生活でも役立つものが作れたので満足です。また何か思いつけば作りたいと思います。
作ってみたい人に向けて、次の項目でコードを全文載せているので、良かったら使ってください。
*LINE Messaging APIでのAPIトークン作成は別途必要です。
*手練れの方は良きように修正してみてください。
コード全文
#include <EEPROM.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>
void setup() {
Serial.begin(115200);
EEPROM.begin(10);
//Wi-Fi接続情報の設定
const char* ssid = "SSIDを入力";
const char* password = "パスワードを入力";
//LINE Messaging APIのトークン設定
const char* token = "APIトークンを入力";
//通知を送信
int wifiAttempts = 0;
bool wifiConnected = false;
while (wifiAttempts < 3) {
Serial.println("WiFi接続開始" + String(wifiAttempts + 1) + " 回目");
WiFi.begin(ssid, password);
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20)
{
delay(500);
Serial.print(".");
retry++;
}
if (WiFi.status() == WL_CONNECTED)
{
wifiConnected = true;
break;
}
Serial.println("\nWiFi接続失敗。再試行します。");
wifiAttempts++;
}
if (!wifiConnected) {
Serial.println("WiFi接続に3回失敗しました。通知は送信されません。");
return;
}
Serial.println("\nWiFi接続成功。");
// NTPで現在の日付を取得
configTime(9 * 3600, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("時刻取得失敗。通知は送信されません。");
return;
}
int today = (timeinfo.tm_year * 10000) + (timeinfo.tm_mon * 100) + timeinfo.tm_mday;
int lastSent = EEPROM.read(0) + (EEPROM.read(1) << 8) + (EEPROM.read(2) << 16);
Serial.println("今日の日付コード: " + String(today));
Serial.println("前回通知コード: " + String(lastSent));
if (today == lastSent) {
Serial.println("今日はすでに通知済みです。スキップします。");
return;
}
HTTPClient http;
http.begin("https://api.line.me/v2/bot/message/broadcast");
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", String("Bearer ") + token);
int year = timeinfo.tm_year + 1900;
int month = timeinfo.tm_mon + 1;
int day = timeinfo.tm_mday;
String message = String(year) + "年" + String(month) + "月" + String(day) + "日にテレビをつけました!";
String payload = "{\"messages\":[{\"type\":\"text\",\"text\":\"" + message + "\"}]}";
int attempts = 0;
int httpCode = 0;
while (attempts < 3){
httpCode = http.POST(payload);
Serial.println("通知試行 " + String(attempts + 1) + " 回目 → HTTPコード: " + String(httpCode));
if (httpCode == 200) {
Serial.println("通知成功!");
EEPROM.write(0, today & 0xFF); // 下位バイト
EEPROM.write(1, (today >> 8) & 0xFF); // 中位バイト
EEPROM.write(2, (today >> 16) & 0xFF); // 上位バイト
EEPROM.commit(); //保存確定
break;
} else {
Serial.println("通知失敗。再送します。");
delay(1000);
}
attempts++;
}
if (httpCode != 200) {
Serial.println("通知は3回試行しましたが失敗しました。");
}
http.end();
}
void loop() {
}