2026年2月4日水曜日

東京アプリ生活応援事業について

東京都が実施する生活支援キャンペーンで、東京都が公式スマートフォンアプリ「東京アプリ」を通じて、現金ではなく 11,000ポイント(1ポイント = 1円相当)受け取れる給付制度です。2月2日からはじまった。

よくわからなかったのでchatGPTとチャットして色々と教えてもらったら、驚愕の事実が、、、(検証はしてないので間違いがあるかも)

まず、付与されたポイントは以下の民間のポイントサービスに1ポイント=1円で交換できるらしい。

  • dポイント
  • au PAY残高
  • 楽天ペイ(楽天ポイント)
  • Vポイント(V Point)
  • メルカリポイント

しかし個人的にはこれらのポイントサービスは使ってない。昔からポイントカードとかは面倒で使ってないのです。例外はポイントサービスの元祖であるヨドバシのポイント、アマゾンのポイント、クレジットカードのポイントだけ。

chatGPTが言うにはdポイントはアマゾンと連携して使えるらしい。でもアマゾンギフトカードは買えないよ、と教えてくれた。手順は以下のような感じ。

  • iPhoneに東京アプリを入れる、マイナンバーカードの認証アプリも必要らしい
  • アプリ上でマイナンバーカードを使ってポイントの申請をする
  • dポイントのアプリを入れてdポイントのアカウントを作る。(ドコモユーザでなくても無料で作れるらしい)
  • ポイントが支給されたら東京アプリ上でdポイントに交換する
  • アマゾンでdポイント連携を設定する
  • アマゾンで買い物するときに普段のカードではなく、dポイント連携払いにする

以上、面倒な作業が続く。「スマホが使えない独居の高齢者に無理だね」ということをchatGPTに聞いたら、「そうですね、本当に困窮している人には届かない施策」とのこと。

事業費なども聞いたら、給付用に450億円で、これは支給対象者の約1/3分しかない。要するに、元々全員に配る(申請する)とは考えてない。また、給付額を含む総事業費が799億円とのことなので349億円がシステム構築、運用管理、宣伝費など。これらには職員の人件費は含まないようなので、それを含めたらもっとかかっている計算。

  • お金かけてなんでこんな面倒な仕組みなの?
  • 現金給付じゃダメなの?(マイナンバーに給付金口座を紐づけたよね)
  • 配るならそもそも税金を下げてよ

などチャットしていたら、元々給付が大きな目的ではなくDX推進が目的らしい。東京アプリがDX推進なのか微妙だが。だから使える人だけ使ってくれればOK。また、減税より給付をしたほうが成果をアピールしやすく、「映える」からだそうな。

なんだかなぁ、と思った次第。個人的には残された時間を考えると極力手間のかかることに時間を使いたくない、と思う今日この頃。でもサーバの混雑が解消したころに申請すると思う。

2026年2月3日火曜日

2ch温度ロガーの製作(12;完成)

サクッと作ろうと思っていた温度ロガーですが、昨年の9月から始めてようやく完成。製作記事がふっとんで気力回復がイマイチなのでここまで。

特徴は次のような感じ。

  • 2chの温度が測定できる
  • WiFiに繋ぐことでntpサーバから日時を取得して記録
  • ログはmicroSDにcsvファイル形式で記録
  • 測定時間間隔を設定できる
  • リチウム電池で2~3日は動作すると思う(実測はしてない)
  • 充電はUSB-TypeC


csvファイル形式でログが記録できるので、Excelで簡単にグラフ化できる。下記のグラフは温度の校正がイマイチでちょっと高めにでている。



内部の様子。左端ケース外はリチウム電池。ケース内側左端はmicroSDのスロット(秋月製)、中央はESP32、右端は充放電モジュール(USB-TypeC)。刺さっているのはプログラム書き込み用のアダプタ


リチウム電池を入れた様子。


自分でも使い方を忘れそうなので側面にラベルを貼った。


 WiFiのアクセスポイントなどの情報は、microSD内のsetup.txtファイルに記載する。
# 温度ロガー設定ファイル
# 例)
# SSID mySSID
# PW   myPassWord
# Interval 0  (0:1秒, 1:10秒, 2:30秒, 3:1分, 4:5分, 5:10分)
#
SSID mySSID
PW myPassWord
Interval 0

側面のmicroSDスロット。


反対側側面からUSB-TypeCで充電中。小さな穴から充放電モジュールの赤色LEDが点灯しているのが分かる。


充電が完了すると青色に光る。




以下、測定までのシーケンス。

電源を入れるとWiFiに接続する。黄色いタクトスイッチを押しながら電源を入れるとWiFiには接続しない。その際のタイムスタンプは2026.1.1 00:00:00になる。WiFi接続に失敗しても同様。


接続完了。


測定時間間隔の設定。1秒、10秒、30秒、1分、5分、10分で指定でき、黄色いタクトスイッチを押すと切り替わる。デフォルトはmicroSD内のsetup.txtに記載。無操作で5秒経過すると、測定開始。


測定開始。測定は指定した時刻が時間間隔で正時になったとき。例えば10秒毎なら0秒、10秒、20秒…のときに測定し、1分毎なら毎分0秒のときに測定する。


これまでの経緯。温度センサの値を読み取るAD変換の補正法などChatGPTに聞いて試行錯誤したので最初の頃と最終版は異なっている。



需要があるか分かりませんが、回路図(クリックで拡大)とスケッチ(キレイではない)です。いずれも無保証です。


処理は1秒毎のタイマ割り込みでスリープから目覚めて、そのときの時刻が測定時間間隔の正時と一致したら測定してmicroSDに書き出して、またスリープする。一致しないとすぐにスリープ。

//---------------------------------------------------------------------------------------
//                                                                         '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秒毎の割り込みで目覚めて正時になったか確認(再びスリープ)
//---------------------------------------------------------------------------------------
#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; // 温度センサMCP9700Aの出力ピンを接続したADCピン
const int SENSOR_PIN2 = 33; // 温度センサMCP9700Aの出力ピンを接続したADCピン

esp_adc_cal_characteristics_t adc_chars;

#define SETUP_FILE "/setup.txt"
#include <FaBoLCDmini_AQM0802A.h>
FaBoLCDmini_AQM0802A lcd;

// 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);
  pinMode(SENSOR_PIN1, ANALOG);
  pinMode(SENSOR_PIN2, ANALOG);

  analogReadResolution(12); // 分解能12bit
  analogSetAttenuation(ADC_11db);

  // ADCキャリブレーション
  esp_adc_cal_characterize(
    ADC_UNIT_1,           // GPIO32?39
    ADC_ATTEN_DB_11,      // 0?3.3V
    ADC_WIDTH_BIT_12,
    1100,                 // デフォルトVref(mV)
    &adc_chars
  );

  // モニタ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();

  // ログファイル名を決めるためにタイムスタンプ取得
  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 = temp_sensing(SENSOR_PIN1);
    float temp2 = temp_sensing(SENSOR_PIN2);
    char buff1[6],buff2[6],buff[20];
    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 temp_sensing(const int sensor) {
  float offset;

  uint32_t voltage_mV = 0;
  const int N = 100;
  for ( int i = 0 ; i < N ; i++ ) {
    int adcRaw = analogRead(sensor);
    // ADC値 → 電圧(mV)(補正済み)
    voltage_mV += esp_adc_cal_raw_to_voltage(adcRaw, &adc_chars);
  }

  // 電圧 → 温度(℃)
  float temperatureC = ((float)voltage_mV/N - 500.0) / 10.0;

  // 温度センサ毎の校正
  if (sensor==SENSOR_PIN1) offset = -0.9;
  else offset = -1.2;

  return temperatureC + offset;
}

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
//---------------------------------------------------------------------------------------

2026年2月1日日曜日

2ch温度ロガーの製作(12;完成、と思って書いた記事が消えた)

2時間くらいかけて書いていた記事が消えた(T_T)

今日はもうやめておきます。気力が回復したら書きます。


2026年1月31日土曜日

TOFセンサ(測距センサ)を試してみた

TOF(Time Of Fright;飛行時間)センサというものを試してみました。VL6180というチップを使ったTOF050C(50cmまで)と、VL53L0Xを使ったTOF200C(2mまで)の2つのモジュールを購入。とても薄くて小さいモジュールで、何かに組み込むにはよい感じです。

(追記 26.02.05) アリエクのショップには50cmまでと書かれていたが、よく調べたらVL6180は定格100mmまでで、実用範囲で200~250mmらしい。

TOFセンサについては下記のサイト参照。



裏側はこんな感じです。


arduino nanoに繋いで動かしてみました。VL6180X library by Pololuというライブラリを使い、サンプルスケッチをほぼそのまま。まだ殆ど調べてません。配線は電源アースの他にI2CのSDA,SCLの2本のみです。

50cmまで測定できるTOF050Cですが、最大255mmが返ってきました。設定で変更できるのかも。センサの前で手を動かすときちんと反応します。距離もほぼ合ってます。こんな短い距離で光の飛行時間差が分かるというのが凄い。



#include "Wire.h"
#include "VL6180X.h"

VL6180X sensor;

void setup() {
  Serial.begin(9600);
  Wire.begin();  
  sensor.init();
  sensor.configureDefault();
  sensor.setTimeout(500);
}

void loop() { 
  Serial.print(sensor.readRangeSingleMillimeters()); 
  Serial.println("mm");
  delay(1000);
}

最終的に非接触のスイッチを作ろうと思っています。数十センチ離れていても反応するようにしたいので、フォトリフレクタではちょっと力不足。シャープの測距センサはちょっとでかいのでこれを試してみました。常時動かすのでセンサの寿命が気になるところですが。


2026年1月29日木曜日

iPad Pro(9.7インチ)のバッテリが、、、

かみさんが使っているiPad Proのバッテリがすぐなくなるというので調べてみました。このiPad Pro2016年12月に購入したので今年で10年目。この機種は自分ではバッテリの状態が分からないので、Macに繋いでcoconutBatteryというアプリで確認。下記がその画面。

Manufacture dateが2017年8月になっていて、あれ?っと思ったが、一度画面表示がおかしくなって修理に出したことがあるのを思い出した。1年以内だったので本体ごと交換されたのだと思う。ということで、この個体は使い始めて8年数か月くらい。


Battery Healthが46.8%となっており正常時の半分以下。充電回数も657回と多く、一般的な仕様の500回を遥かに超えている。すでにAppleによるバッテリ交換サービスは終了していた。正規ショップ以外ならまだ交換できるかもしれないけど。

10年前に発売された機種だけど、普段かみさんがYoutubeを見たり、雑誌を読んだり、ネットを見るにはまだまだ十分使える。昨日だったかはセキュリティアップデートも配信された。まだ見捨てられてない模様。

家から持ち出すことは殆どなく、常時ACアダプタに繋いで使うと言っているのでしばらくはこのまま。この先、不便になることがあったら買い替えるかも知れない。

2026年1月25日日曜日

2ch温度ロガーの製作(11;校正)

校正というのか、温度を無理やりオフセットして手元にあるシンワの温度計と同じになるようにしました。写真はすべて同じ19.4℃を表示していますが偶々です。0.1~0.2℃位は動きます。そもそも単純にオフセットしてよいものなのか? 温度によって変わりそうな気もするけど。そこまで精度を求めるものでもないので、これで完了とします。


センサを繋いだポートのAD変換、キャリブレーション値による補正変換、無理やりの校正は以下のような感じです。補正変換は100回測定の平均値をとってから1回でよいかも。
float temp_sensing(const int sensor) {
  float offset;

  uint32_t voltage_mV = 0;
  const int N = 100;
  for ( int i = 0 ; i < N ; i++ ) {
    int adcRaw = analogRead(sensor);
    // ADC値 → 電圧(mV)(補正済み)
    voltage_mV += esp_adc_cal_raw_to_voltage(adcRaw, &adc_chars);
  }

  // 電圧 → 温度(℃)
  float temperatureC = ((float)voltage_mV/N - 500.0) / 10.0;

  // 温度センサ毎の校正
  if (sensor==SENSOR_PIN1) offset = -0.9;
  else offset = -1.2;

  return temperatureC + offset;
}

需要があるか不明ですが、回路図とスケッチ全体はコードを少しキレイにしてから公開します。

2026年1月24日土曜日

2ch温度ロガーの製作(10;評価)

しばらく放置していた2ch温度ロガーのプログラム書き換えやケースへのラベル貼りが終わったので、ヨーグルト発酵時の温度推移を記録して評価してみました。

発酵器の設定温度は40℃なのですが、温度ロガーの示す値は44.3℃と高い。まぁ発酵器も自作なので温度の精度は不明ですが、こんなに違うのはちょっと変。以前から薄々高くでるなと思ってはいたのですが。


発酵器は以前、納豆発酵器として作った以下です。最近はもっぱらヨーグルト作りに活用しています。もう15年になるのかと少々驚いてます。
センサの一つは扉の取っ手に引っかけてあります。稼働中は扉も若干温まるので室温よりは高めになります。温度コントローラの温度表示が消えているように写っていますが、ダイナミック点灯で、カメラのシャッター速度が速くて写りませんでした。

もう一つのセンサは、発酵器の内部の牛乳ヨーグルトの種を入れたタッパの横。発酵器の温度センサと並べて配置してあります。


1分毎に記録した温度推移はこんな感じ。45℃近くまで上がっていますが、実際はこんなに高くないと思う。室温が12時前後で高いのはファンヒータを動かしていたため。


ChatGPTにESP32のAD変換について聞いてみて、個体ごとにキャリブレーション値を持っているのでそれで補正すればよい、との回答だったので一応キャリブレーション値を表示してみた。

デフォルトVref(mV) 1100に対して、1135だったので少々高い。ここから逆算して温度を補正すると44.6℃が41.7℃になる。それでも高い気がする。補正値を考慮して電位を返してくれる下記の関数があるのでそれを組み込んでみた。

uint32_t voltage_mV = esp_adc_cal_raw_to_voltage(adcRaw, &adc_chars);

それでも、手元にあるシンワの温度計2台が示す室温19.7℃に対して、ch1:21.1℃, ch2:21.4℃ と約1.5~1.8℃程高い温度になる。chの温度差はセンサの個体差だと思う。

chatGPTに聞いたら以下の答えが返ってきた。

原因①:MCP9700Aの仕様上の誤差(一番大きい)

データシートより

  • 精度:±2℃(典型)

  • 室温付近でも普通に ±1℃ 以上ズレる

つまり👇

1.5℃ズレてても「正常範囲」

これ、地味にショックですが事実です😅

今すぐできる対策(効果順)

対策①:1点校正(超おすすめ)

室温を正解として補正

const float tempOffset = -1.5; // 実測差分 temperatureC += tempOffset;

👉 温度センサは 校正してナンボ


ということでオフセット補正することにします。