しばらく前にアリエクで見つけて注文してあった0.42インチOLED付きのESP32-C3が今日届きました。中々届かないので行方不明になってしまったのかと半分諦めていましたが無事に到着。送料込みで500円弱です。
小さいですがWiFi, Bluetoothが載っています。技適は不明。試しにOLEDに文字を描いてみた様子です。図形も描けます。
横からみるとこんな感じ。
裏側です。
多くのピンが必要ないアプリならこれでよいかな。OLEDにステータスなどを表示できるので便利そう。
しばらく前にアリエクで見つけて注文してあった0.42インチOLED付きのESP32-C3が今日届きました。中々届かないので行方不明になってしまったのかと半分諦めていましたが無事に到着。送料込みで500円弱です。
小さいですがWiFi, Bluetoothが載っています。技適は不明。試しにOLEDに文字を描いてみた様子です。図形も描けます。
裏側です。
多くのピンが必要ないアプリならこれでよいかな。OLEDにステータスなどを表示できるので便利そう。
三連休ですが天気がイマイチですね。以前10月10日が晴れの特異日で東京オリンピックの開会式の日程となり、それを記念して体育の日が設定されました。今年も10日(金)は晴れ間がありました。
温度ロガーの測定時以外はスリープ(ライトスリープ)するようにしてみました。timerで測定時間間隔毎に目覚めて、測定して液晶表示を書き換え、microSDに書き込む処理を行います。その後、再びスリープ。
下の写真はスリープせずにずっと起きているときの消費電流で約41mAです。意外と少なく、2,000mAhのリチウム電池で約2日間持ちます。
前回、ユニバーサル基板に載せたら液晶表示ができなくなったと書きましたが、さんざん調べた後でとんでもない凡ミスだったことに気付きました。
I2CのSDA(IO21),SCL(IO22)の論理ピン名と、ESP32の物理ピン番号を間違えていて、SDA(IO21)を物理ピン21へ、SCL(IO22)を物理ピン22へ繋いでいました。先週から何度もチェックしたり、半田付け具合を確認していましたが全く気付かずにいました。今朝の朝食後にタブレットで何気なくピンアサイン表を眺めていて突然気付いた次第。トホホです。
昨日、6年前にAmazonの箱とダイソー300円スピーカで作ったFMラジオが突然鳴らなくなりました。普段、かみさんがキッチンの窓際に置いて聴いているので段ボールが日焼けして白くなっている。2代目として本気出してMDFで作ったラジオはリモートワーク部屋に置いてある。最近はほぼ出勤であまり使わなくなった。
当初エネループの電池切れだと思い充電した後、取り付けようとしたときに電池ボックスが壊れていたことに気付いた。外すときには気付かなかった。
1週間遅れでようやく彼岸花(ヒガンバナ)が咲きました。今年は記録的な猛暑の影響で流石にヒガンバナの内蔵タイマもズレたのでしょう。 まだ半分くらいは蕾です。
ヒガンバナについては 一昨年にも書いていましたが、そのときはピッタリでした。
それにしても廻りの雑草がひどいな。
温度ロガーをケースに収めるためにユニバーサル基板に実装しています。主要部品の実装が完了したので、プログラムを書きこんでみました。写真右上に見えるピンソケットが書き込み用の端子。
無事に書き込めましたが、液晶画面に何も表示されません。通常だと最初にWiFi接続中のメッセージが表示されるはずがダメです。そこで適宜デバッグのためにSerial.printlnを入れてどこまで動いているか見てみました。シリアルモニタで見ると処理は正常に動作しているようです。温度ログもmicroSDにも書き込めています。
温度ロガーはユニバーサル基板に載せてケースに収めることにしました。
基板や部品を手元にあったタカチのケースに収める検討を行い、取り合えず液晶画面の表示窓を開けたところ。写真は液晶を裏からマスキングテープで仮止めした状態です。
測定間隔の時間設定はmicroSDに設定ファイルを置いていましたが、時間設定を変えるたびにファイルを書き換えるのが面倒でした。測定間隔は1秒、10秒、30秒、1分、10分位で十分なので、タクトスイッチ(黄色)で切り替えるようにするつもりでスイッチの穴を開けています。プログラムはこれから。
先日、温度がずれているのでAD変換の電圧範囲を変えて無理やり合わせた、と書きましたが、温度センサMCP9700Aのオフセット電圧(0℃の時の出力電圧)を間違えていたのに気付きました。0.5Vが正しいのですが、0.4Vで計算していました。
また、ESP32のAD変換のキャリブレーション値による補正ですが、analogReadMilliVolts()で取得すれば補正後の電圧がミリボルト単位で取得できるということで試してみました。室温は手元にあった温度計と比較してほぼ近い温度が表示されています。28.0℃と28.2℃は温度センサの個体差でしょう。ちなみにAD変換は100回測定した平均値を使っています。
温度センサをケーブルで伸ばしてジャケット(ガラス管)に入れる工作です。温度センサは ニキシー管時計を作ったときにコロンのネオン管を収めた 小さいガラスチューブに丁度入りました。少量で購入できなかったのでまだ大量に残っている。
温度ロガーに秋月のマイクロSDカードスロットDIP化キット(写真左上に写ってます)を繋いで、ログを記録できるようにしました。
温度センサはMCP9700Aという3端子のアナログ出力のものを試しています。温度に比例した電圧がでてくるのでAD変換します。ESP32はAD変換のキャリブレーションがあるようですがまだ調べ切れていません。当初、室内にある温度計より低めの温度がでてきたので、本来の電圧範囲の3.3Vを勝手に3.5Vとして計算してみたらそれっぽい温度を示しました。上の写真はノートパソコンの上に置いたのでちょっと高めになっています。
こんな場当たり的な対策ではダメだと思いますが、取り合えず大まかな温度変化がとれればよいのでひとまずこれで。
購入した丸形LEDは、丸形蛍光管の価格とほぼ同じ価格で、30Wが16Wになって約半分の省エネにもなってよい。寿命も通常なら長いはず。照明器具がグロー式(点灯管式)ならグロー管を外すだけで交換可能というもの。
点灯も瞬時になるというが、これまでも電子点灯管を使っていてすぐに点灯していたので、そこはそれほどメリットはない。
カバーをとって蛍光管と点灯管を取り外してみると、内部が蛍光灯の紫外線で結構日焼けしているのが目立つ。このままだとちょっと残念なので、白いラッカースプレーで塗装することにしました。丁度、以前の工作の残っていたスプレーがあったので。
久しぶりの雨で少し涼しくなりました。と言っても30度だけど、昨日まで暑すぎたのでこれでも涼しく感じる。
見守りセンサ を入れるために購入したタカチのケースが届いた。右上のケースが第一候補だが、送料を薄めるためにいつか使うだろうと色々買ってみた。幾つかは使わないでデッドストックになりそうだけど。
ショップのメニュー画面では、全て難燃性ABSとなっていたけどパッケージは赤色のみに記載があり、黒文字のものは特に記載なし。本当にすべて難燃性なのか?
今週末は工作の時間が取れるか微妙なので、完成はもうしばらく先になると思う。見守り動作がちゃんと活きているかの確認のためにプログラムも一部見直したい。電源が切れていたり、WiFiの不具合などでメッセージが飛ばないと困る。月に1回程度は人感センサが感知したタイミングで見守り中であることをLINEにメッセージすることで少しは安心。
他にも作りたくなったものがでてきており、頭の中で構想中。週末に手持ちの部品の在庫を確認して具体化するつもり。
久しぶりの電子工作は、テレビドラマにもなっている ひとりでしにたい を読んでから孤独死が気になりだして作った、24時間ひとの動きが無かったときにLINEに通知する見守りセンサです。24時間だと心配なら12時間でもよいけど、ぐっすり寝ていたり冬の寒い時期は12時間くらい動き出さないことがあるかも。まぁこの辺は活動時間などをみて適宜調整。
電源を入れると最初に以下のようなメッセージがLINEに届きます。
以下はテストのために、動きが検出できないまま 1H経過で黄色LED点灯、2H経過で赤色LED点灯とLINEメッセージ送信に設定しています。本番では12H、24Hにします。
//----------------------------------------------------------------------------- // 2025.08.24 naka // LINE通知する見守りセンサ // ・人感センサで活動をモニタリング // ・起動時に見守り開始のメッセージをLINEに送る // ・一定時間(24H)活動がないとメッセージをLINEに送る // ・その後も活動がないと一定時間(24H)毎にメッセージを送り続ける // ・旅行などで不在になる場合は「不在」スイッチを押すと見守り停止 // ・不在から復帰(人感センサが反応)したら、自動的に見守り再開 // ・LED表示で見守り状態を示す // - 緑LED点灯:見守り中、緑LED点滅:不在(見守り停止中) // - 黄LED点灯:12H活動反応なし // - 赤LED点灯:24H活動反応なし、LINEメッセージ送信 //----------------------------------------------------------------------------- #define SENSOR 4 #define SW 5 #define LED_GREEN 15 #define LED_YELLOW 16 #define LED_RED 17 #include <WiFi.h> #include <HTTPClient.h> // LINE messaging API const String token_url = "https://api.line.me/oauth2/v3/token"; const String msg_url = "https://api.line.me/v2/bot/message/broadcast"; const char ssid[] = "XXXXXXXX"; const char password[] = "XXXXXXXXXXXX"; const char channel_id[] = "XXXXXXXXXX"; const char channel_pw[] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; const String messageBegin = "見守りを開始します。"; const String messageNoactive = "お母さんの活動が24時間不明です。至急連絡してください。"; // Timer setting volatile uint32_t count = 0; volatile uint32_t waitingTimeCount = 0; volatile int status = 0; // 0:見守り中, 1:12時間経過、2:24時間活動なし(メッセージ送信)、3:不在中 //const uint32_t LimitRed = 60 * 60 * 24; // 24時間 //const uint32_t LimitYellow = 60 * 60 * 12; // 12時間 //const uint32_t WaitingTime = 60 * 30; // 30分 不在スイッチを押してからの退出までの待ち時間(不在スイッチを押した直後に人感センサが反応しないように) const uint32_t LimitRed = 60 * 60 * 2; // 2時間 const uint32_t LimitYellow = 60 * 60 * 1; // 1時間 const uint32_t WaitingTime = 60; // 1分 不在スイッチを押してからの退出までの待ち時間(不在スイッチを押した直後に人感センサが反応しないように) hw_timer_t * timer = NULL; void IRAM_ATTR countUp() { if (status<3) count++; // 見守り中, 3:不在中 else if(status==3 && waitingTimeCount>0) waitingTimeCount--; // 不在スイッチを押してからの退出までの待ち時間カウントダウン } void setup() { pinMode(SENSOR, INPUT); pinMode(SW, INPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_RED, OUTPUT); digitalWrite(LED_GREEN, LOW); digitalWrite(LED_YELLOW, LOW); digitalWrite(LED_RED, LOW); status = 0; // タイマ割り込み設定 timer = timerBegin(1000000); // Attach onTimer function to our timer. timerAttachInterrupt(timer, &countUp); // Set alarm to call onTimer function every second (value in microseconds). // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter). timerAlarm(timer, 1000000, true, 0); // 動作確認のために動作開始メッセージを送る wifi_connect(); // WiFi接続 send_msg2line(messageBegin); // LINE notifyに見守り開始メッセージを送る wifi_disconnect(); // WiFi接続解除 } void loop() { if (waitingTimeCount>0) {} else if (digitalRead(SENSOR)==HIGH) { count = 0; status = 0; } else if (count>LimitYellow && status==0) { status = 1; } else if (count>LimitRed) { count = 0; status = 2; wifi_connect(); // WiFi接続 send_msg2line(messageNoactive); // LINE notifyに活動がない旨のメッセージを送る wifi_disconnect(); // WiFi接続解除 } if (status==0) { digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_YELLOW,LOW); digitalWrite(LED_RED, LOW); } else if (status==1) { digitalWrite(LED_GREEN, LOW); digitalWrite(LED_YELLOW,HIGH); digitalWrite(LED_RED, LOW); } else if (status==2) { digitalWrite(LED_GREEN, LOW); digitalWrite(LED_YELLOW,LOW); digitalWrite(LED_RED, HIGH); } else if (status==3) { blinkLED(LED_GREEN); } // 不在スイッチ確認 if (digitalRead(SW)==LOW) { while(digitalRead(SW)==LOW) delay(10); // チャタリング対策 count = 0; status = 3; waitingTimeCount = WaitingTime; // 待ち時間設定 } } void blinkLED(int LED) { digitalWrite(LED,HIGH); delay(50); digitalWrite(LED,LOW); delay(900); } void wifi_connect() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } // 以下5行は、普通は不要なハズ(我が家のアクセスポイントは無いと繋がらない) IPAddress gateway(192,168,0,1); IPAddress subnet(255,255,255,0); IPAddress dns1(8, 8, 8, 8); // Google DNS primery server IPAddress dns2(8, 8, 4, 4); // Google DNS secondary server WiFi.config(WiFi.localIP(),gateway,subnet,dns1,dns2); } void wifi_disconnect() { WiFi.disconnect(true); } String get_token() { HTTPClient http; http.begin(token_url); http.addHeader("Content-Type","application/x-www-form-urlencoded"); String body = "grant_type=client_credentials&"; body += "client_id=" + String(channel_id) + "&"; body += "client_secret=" + String(channel_pw); int httpCode = http.POST(body); String token=""; if(httpCode == 200){ String S = http.getString(); int i = S.indexOf("\"access_token\""); if((i>0) && (S.substring(i+15, i+16).equals("\""))){ token = S.substring(i+16, i+16+174); } } else{ // エラー:黄色LED 5点滅 blinkLED(LED_YELLOW); blinkLED(LED_YELLOW); blinkLED(LED_YELLOW); blinkLED(LED_YELLOW); blinkLED(LED_YELLOW); } http.end(); return token; } void send_msg2line(String message) { String token = get_token(); if (token.length()!=174) { // エラー:赤色LED 5回点滅 blinkLED(LED_RED); blinkLED(LED_RED); blinkLED(LED_RED); blinkLED(LED_RED); blinkLED(LED_RED); return; } HTTPClient http; http.begin(msg_url); http.addHeader("Content-Type","application/json"); http.addHeader("Authorization","Bearer " + token); String json = "{\"messages\":[{\"type\":\"text\",\"text\":\"" + message + "\"}]}"; http.POST(json); http.end(); } //--------------------------------------------------------------- // EOF //---------------------------------------------------------------