先週、シンガポールに出かけたついでにシンガポールの電気街、SIM LIM TOWER と SIM LIM SQUARE、近所のリトルインディアをちょっとだけ訪ねてきました。2つのビルは交差点の斜向かいにあり、他の角は大規模な工事中。
写真はSIM LIM TOWERです。時間の都合もあってここには入らなかったのですが、後で調べてみたらパーツ屋などが入っていてこちらの方が面白そうでした。
https://pic-garage.blogspot.com/
先週、シンガポールに出かけたついでにシンガポールの電気街、SIM LIM TOWER と SIM LIM SQUARE、近所のリトルインディアをちょっとだけ訪ねてきました。2つのビルは交差点の斜向かいにあり、他の角は大規模な工事中。
写真はSIM LIM TOWERです。時間の都合もあってここには入らなかったのですが、後で調べてみたらパーツ屋などが入っていてこちらの方が面白そうでした。
夏休みも終わって9月に入り、雨や曇りの週末が続いています。各地で線状降水帯による内水氾濫も起きていて大変です。我が家周辺は洪水のハザードマップに掛かっていて、50年くらい前に浸水したことがあるようです。当時と比べると治水もよくなっているので大丈夫かと思うのですが、昨今の線状降水帯の激しさは少々心配です。今夜から明日にかけて関東地方に線状降水帯ができるような天気予報もでています。
さて、最近の天候でそうめんや冷や麦の季節も明けた感じですね。今年の関東地方は昨年と比べるとそれほど暑くなく、折角の天気鍋も活躍の場が少なかった。そこで来年に活かすために自由研究のまとめをしておきます。
◆ ソーラークッカー(天気鍋)の製作 2026年6月20日土曜日今年の4月上旬、茎?幹?がひょろひょろ伸びて2メートル近くになっていたパキラを強剪定しました。伸びた先の方にしか葉っぱがついてなくて情けない状態だった。その時の写真は撮り忘れた。
ネットで調べるとパキラは強剪定してもまた芽がでてくるらしかったので、思い切って剪定してみました。写真は剪定してから2週間後位で切り口が乾き始めているとき。越冬していたリビングから外に出す際に撮影。白い粒々は肥料のマグアンプ。
剪定した長い茎は15cm位に切って小さな植木鉢に挿しておいたら葉っぱがでてきました。こちらもすごい生命力。パキラの鉢植えが10個以上に増殖してしまいました。この冬の越冬場所を確保するのが大変。
ということで、パキラの強剪定は無事に終えることができたので記録に残した次第です。
夏休み、少し帰省していました。帰省してもやることは庭木の剪定、伐採、草むしり、除草剤撒きなど自宅と同じです。あとお墓参り。往路では大渋滞にはまり普段の2倍の時間がかかり、帰路では高速で台風一過の土砂降りに見舞われトホホです。
天気鍋は結構利用していますが、雲が多くて最高温度記録は更新できていません。最高温度に達する前にお昼のそうめんや冷や麦に使ってしまうのも一因ですが。今日は冷やし中華に利用。午前中、雲が多くて温度は60℃弱でした。
天気鍋の温度推移の記録に使っている2ch温度ロガーは昨年9月に作り始め、今年の2月には、回路図・スケッチをアップしていました(アップしたことを忘れていた)。ところが、温度センサの防水が破れて異常値を記録 するようになってしまったため、急遽 防水対応のデジタル温度プローブへの交換を行いました。最初からこれを使えばよかった。
以下、これまでの経緯です。
//---------------------------------------------------------------------------------------
// '26.07.29 naka
// '26.01.31 naka
// ver.0 '25.09.14 naka
// ESP32 2ch温度ロガー(小型液晶に時刻も表示)
//
// ・測定の正確な日時はWiFi経由のntpサーバから取得
// ・microSDのsetup.txtファイルに平文でWiFiのSSID,Password 及び測定時間間隔の
// デフォルトの番号を記載(0:1秒, 1:10秒, 2:30秒, 3:1分, 4:5分, 5:10分)
// 例)SSID mySSID
// PW myPassWord
// Interval 0
// ・時間間隔は電源投入後にタクトスイッチでも変更可能(5秒経過すると測定を開始)
// ・WiFiがない環境で使う際は、タクトスイッチを押しながら電源オン
// ・WiWi設定があっても繋がらない場合には、複数回トライして諦める
// ・WiWiがない場合、或いは何度トライして繋がらない場合には日時を26年1月1日00:00:00とする
// ・ログはmicroSD内にタイムスタンプ付のcsvファイルとして格納する
// ・ファイル名が重複する場合にはファイル名に追番が付く
// ・WiFiに繋がない、繋がらない、ntpサーバに繋がらない際の日時は"2026-01-01 00:00:00"となる
// ・測定は指定した時刻が時間間隔で正時になったとき
// 例)10秒毎なら0秒、10秒、20秒…のとき、1分毎なら毎分0秒のとき
// ・スリープから1秒毎の割り込みで目覚めて正時になったか確認(再びスリープ)
//
// 改版履歴
// '26.07.29 センサをディジタルセンサDS18B20に変更
//---------------------------------------------------------------------------------------
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP32Time.h>
#include <SD.h>
#include "FS.h"
#include "SPIFFS.h"
#include "esp_adc_cal.h"
ESP32Time rtc;
#define SD_CS 16 // SD card chip select
#define TACT_SW 25 // タクトスイッチ(WiFi未使用指示、測定時間間隔セット用)
const int SENSOR_PIN1 = 32; // 温度センサの出力ピンを接続
const int SENSOR_PIN2 = 33; // 温度センサの出力ピンを接続
esp_adc_cal_characteristics_t adc_chars;
#define SETUP_FILE "/setup.txt"
#include <FaBoLCDmini_AQM0802A.h>
FaBoLCDmini_AQM0802A lcd;
// デジタルセンサ
// https://github.com/PaulStoffregen/OneWire
#include <OneWire.h>
// https://github.com/milesburton/Arduino-Temperature-Control-Library
#include <DallasTemperature.h>
#define TEMPERATURE_PRECISION 12 // sensor resolution: 9 - 12bit
OneWire oneWireCh1(SENSOR_PIN1);
OneWire oneWireCh2(SENSOR_PIN2);
DallasTemperature sensorsCh1(&oneWireCh1);
DallasTemperature sensorsCh2(&oneWireCh2);
DeviceAddress tempDeviceAddressCh1;
DeviceAddress tempDeviceAddressCh2;
// microSDのsetupファイルに記載されているものが設定される
char ssid[24];
char password[24];
int interval;
int interval_i = 0;
const int interval_tbl[] = {1,10,30,60,300,600}; // 測定間隔(秒)
const int interval_tbl_size = 6;
char LogFile[64]; // 記録用のcsvファイル名(タイムスタンプから自動作成)
void setup()
{
char msg[17];
struct tm timeinfo;
char logfilename[64];
pinMode(TACT_SW, INPUT);
// モニタLCD設定
SetupLCD();
delay(1000);
// setupファイルを読み、WiFiのssid,password、測定間隔のデフォルトを取得
if (!SD.begin(SD_CS, SPI, 24000000)) {
dispLCD_msg("microSD failed "," ");
while(true);
}
read_setup_file(SETUP_FILE);
bool wifi_flag = false;
if (digitalRead(TACT_SW)==LOW) {
dispLCD_msg("WiFi dosen't use"," ");
while(digitalRead(TACT_SW)==LOW);
}
else {
dispLCD_msg("Connecting WiFi "," ");
if (wifi_connect()) {
wifi_flag = true;
}
else {
dispLCD_msg("Connect failed "," ");
delay(1000);
}
}
delay(1000);
if (wifi_flag) {
// WiFi接続に成功した場合、NTPサーバに接続し、時刻設定
configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
int loopcnt = 0;
while (!getLocalTime(&timeinfo)) {
dispLCD_msg("Failed get time ","Retrying ... ");
if (++loopcnt>5) { // 念のため5回繰り返してダメなら抜ける
wifi_flag = false;
break;
}
delay(500);
}
dispLCD_msg(" "," ");
wifi_disconnect(); // 以降はWiFi未接続にする
}
// 起動時にSWが押されていたか、WiFi接続に失敗か、ntpサーバからの時刻取得に失敗した場合は下記の日付と時刻設定
if (!wifi_flag) {
rtc.setTime(1767225600); // 2026-01-01 00:00:00
}
// 測定時間間隔の設定(5秒無操作で戻ってくる)
set_interval();
// デジタルセンサ
sensorsCh1.getAddress(tempDeviceAddressCh1, 0); // get device0 address
sensorsCh1.setResolution(tempDeviceAddressCh1, TEMPERATURE_PRECISION); // set resolution
sensorsCh2.getAddress(tempDeviceAddressCh2, 0); // get device0 address
sensorsCh2.setResolution(tempDeviceAddressCh2, TEMPERATURE_PRECISION); // set resolution
sensorsCh1.begin();
sensorsCh2.begin();
// ダミーで1回空読み(1発目に-127℃が返ってくることがあったので)
sensorsCh1.requestTemperatures(); // request temp.
float temp = sensorsCh1.getTempCByIndex(0);
sensorsCh2.requestTemperatures(); // request temp.
temp = sensorsCh2.getTempCByIndex(0);
// ログファイル名を決めるためにタイムスタンプ取得
getLocalTime(&timeinfo);
sprintf(logfilename, "%02d%02d%02d_%02d%02d%02d.csv",(timeinfo.tm_year + 1900)%100, timeinfo.tm_mon + 1, timeinfo.tm_mday,timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
// WiFiに繋がない場合、時刻情報を取得できないためタイムスタンプのファイル名が同じになることを防ぐ。
get_unique_filename("",logfilename,LogFile,sizeof(LogFile));
// 測定開始メッセージ
dispLCD_msg("Start "," measurement");
// 1,000,000us = 1sのタイマー設定
esp_sleep_enable_timer_wakeup(1000000);
}
void loop()
{
// 温度を測定してログファイルに書き出す
measurement(interval);
// ライトスリープ(タイマー割り込みで1秒毎に目覚める)
esp_light_sleep_start();
}
void set_interval() {
disp_interval(interval_i); // 現在の設定時間を表示
int prevmsec = millis();
while ((millis() - prevmsec)<5000) { // 5秒反応が無ければ時間設定を抜ける
if (digitalRead(TACT_SW)==LOW) {
interval_i++;
prevmsec = millis();
if (interval_i>=interval_tbl_size) interval_i = 0;
disp_interval(interval_i); // 現在の設定時間を表示
while (digitalRead(TACT_SW)==LOW) {} // SWが離されるまで待つ
delay(20); //チャタリング対策
prevmsec = millis();
}
}
interval = interval_tbl[interval_i];
return;
}
void disp_interval(int i) {
int time = interval_tbl[i];
char msg[20];
if (time<60)
sprintf(msg,"%11d(sec)",time);
else
sprintf(msg,"%11d(min)",time/60);
dispLCD_msg("set interval ",msg);
}
void measurement(int period) {
char yymmddhhmmss[17],hhmmss[10],timestamp[17];
struct tm timeinfo;
getLocalTime(&timeinfo);
// 測定する時間なのか確認(割り込みは1秒毎なので)
bool flag = false;
if (period==1) flag = true;
else if (period==10 && timeinfo.tm_sec%10==0) flag = true;
else if (period==30 && timeinfo.tm_sec%30==0) flag = true;
else if (period==60 && timeinfo.tm_sec==0) flag = true;
else if (period==120 && timeinfo.tm_sec==0 && timeinfo.tm_min%2==0) flag = true;
if (flag) {
sprintf(timestamp,"%02d:%02d:%02d",timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
sprintf(yymmddhhmmss, "%02d%02d%02d %s",(timeinfo.tm_year + 1900)%100, timeinfo.tm_mon + 1, timeinfo.tm_mday, timestamp);
float temp1 = sens_temp_digitalCh1();
float temp2 = sens_temp_digitalCh2();
char buff1[8],buff2[8],buff[24];
dtostrf(temp1,4,1,buff1);
dtostrf(temp2,4,1,buff2);
sprintf(buff, "%4s%cC , %4s%cC",buff1,0xDF,buff2,0xDF);
dispLCD_msg(yymmddhhmmss,buff);
write_logfile(timestamp,buff1,buff2); // microSDに書き込み
}
}
float sens_temp_digitalCh1() {
float temp;
sensorsCh1.requestTemperatures(); // request temp.
temp = sensorsCh1.getTempCByIndex(0); // get temp.
return temp;
}
float sens_temp_digitalCh2() {
float temp;
sensorsCh2.requestTemperatures(); // request temp.
temp = sensorsCh2.getTempCByIndex(0); // get temp.
return temp;
}
void dispLCD_msg(char msg1[],char msg2[]) {
lcd.clear();
lcd.setCursor(0, 0); // Col,Raw
lcd.print(msg1);
lcd.setCursor(0, 1); // Col,Raw
lcd.print(msg2);
}
void write_logfile(char* timestamp, char* buff1,char* buff2){
char buff[32];
File outputFile = SD.open(LogFile,FILE_APPEND);
sprintf(buff,"%s,%s,%s",timestamp,buff1,buff2);
outputFile.println(buff);
outputFile.close();
}
void SetupLCD() {
delay(150);
lcd.begin();
lcd.command(0x38);
delay(1);
lcd.command(0x39);
delay(1);
lcd.command(0x14);
delay(1);
lcd.command(0x71);
delay(1);
// lcd.command(0x51); // 5V
lcd.command(0x56); // 3.3V
delay(2);
lcd.command(0x6c);
delay(300);
lcd.command(0x38);
delay(1);
lcd.command(0x01);
delay(2);
lcd.command(0x0c);
delay(2);
}
// Wifi接続
bool wifi_connect() {
char msg[128];
bool connected = false;
for (int i=0;i<3;i++) { // WiFi接続、3回繰り返す
WiFi.begin(ssid, password);
long int StartTime=millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if ((StartTime+5000) < millis()) break; // 5秒待って繋がらないときは諦める
}
if (WiFi.status() == WL_CONNECTED) {
IPAddress ip = WiFi.localIP();
sprintf(msg,"%d.%d.%d.%d",ip[0], ip[1], ip[2], ip[3]);
dispLCD_msg("Connected IP ",msg);
connected = true;
break;
}
delay(100);
}
if (connected)
return true;
else {
wifi_disconnect();
delay(100);
return false;
}
}
void wifi_disconnect() {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
int read_setup_file(char* fileName) {
char buff[128];
char key[128],val[128];
File setupFile = SD.open(fileName,FILE_READ);
if (setupFile) {
while (readLine(setupFile,buff)>=0) {
if (buff[0]!='#') {
sscanf(buff,"%s %s",&key,&val);
if (strcmp(key,"SSID")==0) {
strcpy(ssid,val);
}
else if (strcmp(key,"PW")==0) {
strcpy(password,val);
}
else if (strcmp(key,"Interval")==0) {
int ival;
sscanf(val,"%d",&ival);
if (ival>=0 && ival<interval_tbl_size) { // 異常値チェック
interval_i = ival;
}
}
}
}
setupFile.close();
}
return 0;
}
// microSDから1行読み取る
int readLine(File fp, char *buff){
int i = 0;
if(!fp.available()) {
return -1;
}
while (fp.available()) {
char c = fp.read();
if (c=='\r');
else if (c=='\n') break;
else buff[i++] = c;
}
buff[i] = 0;
return i;
}
/**
* 重複しないファイル名を生成する関数 (ChatGPTに書いてもらった)
* @param fs_prefix マウントポイント (例: "/spiffs")
* @param base_name 元のファイル名 (例: "data.txt")
* @param out_path 結果を格納するバッファ
* @param max_len バッファの最大サイズ
*/
void get_unique_filename(const char* fs_prefix, const char* base_name, char* out_path, size_t max_len) {
char temp_path[128];
char name_part[64];
char ext_part[16];
// 拡張子を探す
const char *dot = strrchr(base_name, '.');
if (dot) {
size_t name_len = dot - base_name;
strncpy(name_part, base_name, name_len);
name_part[name_len] = '\0';
strcpy(ext_part, dot);
} else {
strcpy(name_part, base_name);
ext_part[0] = '\0';
}
// 最初は元の名前でチェック
snprintf(out_path, max_len, "%s/%s", fs_prefix, base_name);
struct stat st;
int counter = 1;
// ファイルが存在し続ける間、ループ
while (SD.exists(out_path)) {
// "パス/ベース名 + 数字 + 拡張子" を組み立て
snprintf(out_path, max_len, "%s/%s_%d%s", fs_prefix, name_part, counter, ext_part);
counter++;
// 無限ループ防止(必要に応じて)
if (counter > 999) break;
}
}
//---------------------------------------------------------------------------------------
// EOF
//---------------------------------------------------------------------------------------
待望の夏休みに入りましたが、初日は庭木の剪定です。午前中は曇が多くて剪定作業にはよかったのですが、天気鍋には厳しい条件でした。それでも熱中症に注意して、スポーツドリンク飲みながら1時間ごとに休憩し、午前中約3時間の作業でした。今日は剪定だけで草むしりまではできなかった。休み中、どこかでやらねば。
天気鍋は以下のような温度推移でした。今朝は曇っていたので太陽追尾のターンテーブルは出さずに実験。11時過ぎあたりから晴れ間も多くなったので、時々手動で太陽の方を向けていました。
11時頃まではあまり水温は上がらず55℃くらい。その後は太陽のおかげで上がり始め、最高温度は72.1℃でした。外気温が急激に上がっているところはセンサに直接太陽光が当たっていたためだと思う。手動太陽追尾のタイミングで影になるところに移した。
グラフで15:00頃に段があるのは、太陽に向け直したのが原因だと思う。太陽追尾はちゃんと効果があることが確認できたのかな、と思っている。
夏休み中、終日晴れる日を期待したいが、週間天気予報をみると来週はずっと曇りや雨が多そう。温度ロガーのプログラムや回路図をアップしていなかったので、需要があるか不明ですが、夏休み中になんとかしたい。
温度ロガーのセンサを防水デジタル温度センサに交換して天気鍋を評価してみました。
朝8:30頃に評価を開始。この時点ではまだ太陽が東の方にあって陽が当たっていないけど、一応太陽の方を向けてセット。まだ太陽追尾のターンテーブルを制御するarduinoはブレッドボードのまま。効果が確認できたらユニバーサル基板に載せ替えるつもり。
また暑くて終日快晴が期待できる日があったら記録を取ってみます。普段は特に記録せずに、かみさんが天気鍋を普段使いする予定。