洗濯機の運転終了メロディー音を爆音で鳴らしたい

高齢の親曰く「洗濯機の運転終了メロディー音が小さくて聞き取れない、洗濯が終わったことに気が付かず、いつの間にか洗濯していたこと自体を忘れてしまう」という。それはボケてるんじゃないか?という話もあるが、とりあえずそうではない、という前提で以下の話を進めることにする。一般的に全自動洗濯機の場合は洗濯物を投入して洗剤やら何やらを投入しスタートすると、洗濯、濯ぎ、脱水を適切に行ってピロピロいって終わる、その最後のピロピロ音が聞き取れないということなのだ。 高齢ゆえに耳が遠い(可聴周波数が狭くなって高域が聞こえない) そもそも運転終了メロディー音の音量が小さい 洗濯機を置く場所がリビングから遠く、ドアなどを閉めていると聞こえにくい 理由としては様々なことが考えられるが、高齢者は一般的に高い周波数が聞こえにくくなるのは事実としてある。昔の洗濯機のブザー音は「ビーーーーーーー」となんとも味気ないうるさい音がしていたわけだが、まぁあれだとわかるのかも。今そんな洗濯機は売られていないだろう(二層式は今でもそうなのだろうか)。 ということで洗濯の完了をわからせるために運転終了メロディー音を爆音で鳴らしたいわけなのである。完了通知とかスマート家電化はいろいろなアプローチがあって簡単なのはSwitchBot プラグミニを使用して消費電力をモニターし、洗濯が完了すれば消費電力量が低くなることをトリガーとして洗濯の完了を検知して通知する、みたいなことだ。実際自分の洗濯機で試してみると、複数回通知されてしまった。 この原因は何かというと、洗濯→濯ぎ→脱水という各工程に移るその時に消費電力量がゼロに近いインターバルがあるらしくそれを拾ってしまうのであった。何W以下と何分待機という条件をそれぞれうまいこと調整すればうまくいくかもしれない、洗濯が終了して濯ぎに移る、或いは濯ぎが終了して排水を行い、脱水に移るまでの間隔が3-4分程度あるようで、その間の消費電力がほぼゼロなのだろう。 それをうまいこと調整したとして、高齢者にはスマホで通知など意味がなくとにかく洗濯機の運転終了メロディー音を爆音で鳴らしたいわけなので、例えばそのトリガーで別のSwitchBot プラグミニに接続した何か音が鳴るものをONにする、みたいなことを一旦考えたのだが、SwitchBot プラグミニが2個必要で且つAC電源接続と同時に何か音が鳴るもの、が必要である。突き詰めて考えていくと結構煩わしい。他に何があるか。洗濯機側を一切改造しない(※)という制約条件で考えると、 カメラを使って洗濯機の電源のONとか残り時間とかの表示を監視・判断させる 運転終了メロディー音を検知して判断させる 消費電流を検知して判断させる 一番いいのは3つ目である。カメラも不要だしマイクも不要だ。但しAC電流を測定するセンサーが必要である。我が家の洗濯機日立ビートウォッシュ8kgの消費電力はスペック的には255Wであったので、一般的には200-400W程度と考えられる(乾燥機能がないモデルの場合)。乾燥機付きのドラム式の12kgクラスになると1240Wとドライヤー並みになるが、つまりおよそ200W以上を検知できれば良いということになる。 (※)洗濯機側を一切改造しないというのは、だって実家の洗濯機のメーカーが三菱だったかシャープだったかイマイチ思い出せないし、型番に至っては不明だし、詳細スペックが不明なのである。実家において何を使っているわからないのに自分の洗濯機でデバッグしてみる必要があるので、それがそのままうまくいくとは限らないし、そりに合わせたハードウェア的なカスタマイズをしてしまっても意味がないので出来る限りソフトウェアで吸収するべきなのだ。実際のところどうなのか、消費電流を測ってみることにした。 こんな感じで途中にクランプ式の電流計を入れ、この状態で電流を測りながら洗濯してみると、3A前後で変動することがわかった。まぁ、中華製のテスターなので値そのものはあまり信用していない。 洗濯、濯ぎ、脱水中は3A前後を示す。但しやはり、途中途中はゼロに近くなる。それがこれ。 0.09Aを示しているがこれは洗濯から濯ぎに移る給水中、または濯ぎから脱水に移る排水中などの消費電流値である。単純にこの値を正しいと仮定すると0.09A x 100V=9Wは消費しているのだが、3A(300W)から見るとOFFに見えてしまうのだろう。 さて、この測定を何にやらせるかであるが、Wi-FiやBluetoothに対応はしているものの技適がないので使えないESP32C3 SuperMiniがやはり最適か。センサーはSCT013-005 5A 1Vの非侵襲的スプリットコア変流器センサーを使用。これでAC100Vの片側線にクランプして電流を測定してESP32C3 SuperMiniに判定させる。 何か音が鳴るもの、は何が良いか。余っているPC用のスピーカー、ここではELECOMのマルチメディアスピーカ - MS-87SVの片割れを使うこととした(スピーカーと筐体だけ使えればいいので何でもいい)。PAM8403のアンプ基板モジュールを使い、片chだけ使用。電源は余っていたDC5VのACアダプターを使う。 スピーカーに穴あけ加工をしてセンサーのためのジャックと、プッシュスイッチ、ボリュームを取り付ける。あとは中にESP32C3 SuperMiniとPAM8403のアンプ基板モジュール、分圧抵抗などを仕込んで改造する。中身で使ったのはスピーカーだけ。 回路的にはこんな感じになった。画像は実験中で電解コンデンサがあるが、実際は積層セラミックコンデンサに落ち着いた。 これでソフトを焼く。 #include <pitches.h> #include <Arduino.h> #define SPEAKER 10 #define SWITCH_PIN 2 #define CURRENT_PIN 3 #define LED_BUILTIN 8 #define RESTART_INTERVAL (24UL*60UL*60UL*1000UL) #define CALIBRATION_TIME 10000 // 10秒キャリブレーション #define WAIT_TIME 240000 // 4分 #define MEASURE_INTERVAL_MS 20000 // 20秒 #define CURRENT_THRESHOLD 0.1 const float ADC_COUNTS_PER_V = 4095.0f / 3.3f; const float VRMS_1V_COUNTS = ADC_COUNTS_PER_V * 1.0f; const float COUNTS_PER_A_RMS = VRMS_1V_COUNTS / 5.0f; const float CAL_FACTOR = 0.7; const float DEAD_BAND = 0.05; RTC_DATA_ATTR int bootCount = 0; unsigned long lastRestart = 0; bool buzzerOn = false; bool lastSwitchState = HIGH; bool alarmTriggered = false; bool rawHist[4] = {false,false,false,false};//4回連続判定 bool lastCurrentState = false; static const int tempo = 60; static const float wholeNoteDuration = 60000.0 / tempo * 4; int melody[] = { NOTE_G4,NOTE_C5,NOTE_C5,NOTE_D5, NOTE_E5,NOTE_G5,NOTE_G5, NOTE_A5,NOTE_F5,NOTE_C6,NOTE_A5, NOTE_G5,NOTE_A5,NOTE_G5,NOTE_G5 }; int notedurations[] = { 8,8,8,8, 8,8,4, 8,8,8,8, 8,8,8,8 }; int currentNote = 0; unsigned long lastNoteTime = 0; long midpoint = 0; unsigned long lastMeasureTime = 0; bool forceMeasure = true; bool prevBuzzerOn = false; unsigned long offStartTime = 0; // ----- キャリブレーション安全版 ----- long calibrateMidpoint(int samples = 2000) { uint64_t sum = 0; uint64_t sumsq = 0; int minVal = 4095, maxVal = 0; unsigned long start = millis(); int count = 0; while(millis() - start < CALIBRATION_TIME && count < samples) { int v = analogRead(CURRENT_PIN); sum += (uint64_t)v; sumsq += (uint64_t)v * (uint64_t)v; if(v < minVal) minVal = v; if(v > maxVal) maxVal = v; count++; delayMicroseconds(100); } double avg = (double)sum / count; double variance = ((double)sumsq / count) - (avg*avg); double stddev = sqrt(fabs(variance)); Serial.println("=== Calibration Report ==="); Serial.print("Samples: "); Serial.println(count); Serial.print("Average midpoint: "); Serial.println(avg,3); Serial.print("StdDev: "); Serial.println(stddev,3); Serial.print("Min: "); Serial.print(minVal); Serial.print(" Max: "); Serial.println(maxVal); Serial.println("=========================="); return (long)(avg + 0.5); } // ----- 平均値簡易版 ----- long sampleAverageQuick(int samples=500,int pauseUs=200) { unsigned long sum = 0; for(int i=0;i<samples;i++){ int v = analogRead(CURRENT_PIN); sum += (unsigned long)v; if(pauseUs) delayMicroseconds(pauseUs); } return (long)(sum / samples); } // ----- RMS計算 ----- float readCurrentRMS_counts(int samples=500,int pauseUs=200) { unsigned long long sumsq = 0ULL; for(int i=0;i<samples;i++){ long v = analogRead(CURRENT_PIN); long diff = v - midpoint; sumsq += (unsigned long long)(diff*diff); if(pauseUs) delayMicroseconds(pauseUs); } float meanSq = (float)sumsq / (float)samples; return sqrtf(meanSq); } // ----- 4回連続判定 ----- bool decideCurrentState(bool raw, bool lastStable){ rawHist[3]=rawHist[2]; rawHist[2]=rawHist[1]; rawHist[1]=rawHist[0]; rawHist[0]=raw; if(rawHist[0] && rawHist[1] && rawHist[2] && rawHist[3]) return true; if(!rawHist[0] && !rawHist[1] && !rawHist[2] && rawHist[3]) return false; return lastStable; } // ----- メロディ再生 ----- void playMelody(unsigned long now){ if(currentNote >= (int)(sizeof(melody)/sizeof(melody[0]))) return; unsigned long noteDur = (unsigned long)(wholeNoteDuration / notedurations[currentNote]); if(now - lastNoteTime >= noteDur){ lastNoteTime = now; if(melody[currentNote]>0){ ledcWriteTone(SPEAKER, melody[currentNote]); digitalWrite(LED_BUILTIN, LOW); } else { ledcWriteTone(SPEAKER,0); digitalWrite(LED_BUILTIN,HIGH); } currentNote++; if(currentNote >= (int)(sizeof(melody)/sizeof(melody[0]))){ buzzerOn=false; ledcWriteTone(SPEAKER,0); digitalWrite(LED_BUILTIN,HIGH); Serial.println("Buzzer FINISHED"); forceMeasure=true; } } } void setup(){ pinMode(SWITCH_PIN,INPUT_PULLUP); pinMode(CURRENT_PIN,INPUT); ledcAttach(SPEAKER,5000,10); pinMode(LED_BUILTIN,OUTPUT); digitalWrite(LED_BUILTIN,HIGH); Serial.begin(115200); Serial.println("Calibration start..."); midpoint = calibrateMidpoint(); lastMeasureTime=0; forceMeasure=true; } void loop(){ unsigned long now=millis(); if(now-lastRestart >= RESTART_INTERVAL) ESP.restart(); // スイッチ処理 bool switchState = digitalRead(SWITCH_PIN); if(lastSwitchState==HIGH && switchState==LOW){ if(buzzerOn){ buzzerOn=false; ledcWriteTone(SPEAKER,0); digitalWrite(LED_BUILTIN,HIGH); Serial.println("Buzzer STOPPED (switch)"); forceMeasure=true; } else { buzzerOn=true; currentNote=0; lastNoteTime=now; Serial.println("Buzzer START (switch)"); } delay(50); } lastSwitchState = switchState; if(buzzerOn){ playMelody(now); prevBuzzerOn=true; return; } if(prevBuzzerOn && !buzzerOn) forceMeasure=true; prevBuzzerOn=buzzerOn; // 測定処理 if(forceMeasure || (now-lastMeasureTime>=MEASURE_INTERVAL_MS)){ lastMeasureTime=now; forceMeasure=false; long rawAvg = sampleAverageQuick(200,100); float rms_counts = readCurrentRMS_counts(500,200); float currentA = (rms_counts / COUNTS_PER_A_RMS) * CAL_FACTOR; if(currentA < DEAD_BAND) currentA = 0.0; // DEAD_BAND未満の電流は0扱いとする bool rawCurrentNow = (currentA > CURRENT_THRESHOLD); // しきい値超えたらON bool currentNow = decideCurrentState(rawCurrentNow,lastCurrentState); Serial.print("rawAvg="); Serial.print(rawAvg); Serial.print(" I_rms(A)="); Serial.print(currentA,3); Serial.print(" currentNow="); Serial.println(currentNow?"ON":"OFF"); if(lastCurrentState && !currentNow){ offStartTime=now; Serial.println("Detected OFF, timer started"); } if(!currentNow && offStartTime>0 && (now-offStartTime>=WAIT_TIME) && !alarmTriggered){ buzzerOn=true; currentNote=0; lastNoteTime=now; alarmTriggered=true; Serial.println("Buzzer START (auto after stop)"); } if(currentNow){ offStartTime=0; alarmTriggered=false; } lastCurrentState=currentNow; } ledcWriteTone(SPEAKER,0); digitalWrite(LED_BUILTIN,HIGH); } pitches.hはネットに転がっているやつで。 ...

October 26, 2025 · Hirotomo Minakawa

パナソニックテレビドアホンでナショナルサインペットを鳴らしたい

老両親が住んでいる我が実家にはナショナルこと現パナソニックのサインペット・EB15(チャイム)が建築時より取り付けられており、おそらくは50年は経過している。 これは乾電池/トランス式であり、スイッチを押すとピン、離すとポーンと鳴る。原理としては電磁石を使ったもので、コイルに直列に単二乾電池を4本または8V程度を接続してそれをスイッチにてONすると電磁石となり、中にある鉄芯がハンマーとなって動いて鉄琴の如く鉄板を打撃して音を鳴らし、OFFするとばねの力で戻りまた音の異なる鉄板をハンマーが打撃して音を鳴らす、結果としてピンポーンと鳴るという真に古典的なものである。さすがはパナソニック、商品としては現行商品(ニューサインポン・EB177W、こちらは乾電池専用で単三4本となっている)がある。 元々は呼び鈴として取り付けられていたのではあるが、ドアオープン専用のアラームにして利用していた。というのも、呼び鈴用の配線を流用してテレビドアホン化してしまったのだ。かつて昭和の時代は玄関は特に施錠せず、ピンポンが鳴れば「はーい」と応じ、客人が玄関を開けて入ってくる、それで良かったのだが、何かと物騒になった現在では施錠しておくのは当たり前。ともなると呼び鈴ではダメでモニター越しに誰が来たのか、知っている人なのか、宅配なのか、郵便なのか、或いは知らない人なのかなど、ある程度の検討をつける必要があり、知らない人ならばインターホン越しに会話をして要件を聞き出し「お呼びでない」ならばそのまま帰っていただく、そういう世の中になったのである。 従って、呼び鈴スイッチが取り付けられていた玄関にはカメラ玄関子機VL-V522Lを取り付け、その配線を延長してリビングまで引き込んだ先にテレビドアホンVL-MV39を取り付けた。システム的にテレビドアホンとサインペットは共存が難しいのでドアにマイクロスイッチをつけ、ドアをの開閉をスイッチに連動させてピンポンと鳴るようにしたのである。当初はテレビドアホンのA接点を使えばいいかと思ったが、そのままスイッチに代替してもそれはピンだけ鳴って終わり、モニターオフでポーンと鳴る。その間電磁石のコイルには電流が30秒ほど流れっ放しとなり、電池寿命にも良くない。従ってこれはこれ、それはそれで活かすようにしたのだ。万一施錠を忘れていてもドアが開くとピンポンと鳴るため防犯上は良い。別々のシステムにはなったのだがそれで用は足りていた。しかし最近、後期高齢者ともなると耳が遠いのか、テレビドアホンの呼び出し音では聞こえないことがあるらしいのだ。 確かに廊下に設置したサインペットは良く響き渡る。一方、テレビドアホンはリビングにあるので、リビングにいないといまいちわかりにくい。従来のようにピンポーンと響き渡る金属音は欲しいところである。ということは、A接点を利用してそれを元にリレー駆動して全く別の機構でサインペットを鳴らす方法を考えなくてはならない。そして、せっかく機能しているドアオープンアラームも活かすべきである。その回路を稼働するからには電池駆動ではなく、両方ともAC電源にしてしまうことも考えた方が良い。EB15にはトランス配線ができるように端子があるので、ここに手持ちのACアダプターで9Vを印加し、リレー駆動回路はESP32ボードを使って組むこととし、7805を介して5Vをもらうことにした。これで電池も使わなくてよくなるので一石二鳥である。 ATQ209は5Vリレーなので7805の出力から電気を得て、トランジスタでドライブする。プルアップは3.3Vで。間違えるとESP32が死んでしまう。基板にはまだ余裕があるので何かを追加できる。リレーは万一の場合交換が容易なようにICソケットを使用。 A接点とドアオープンスイッチをそれぞれのポートで監視し、短絡(ON)になった瞬間にリレーを動作させる。それぞれテレビドアホンのA接点がONの時は2回ピンポーンピンポーンと鳴り、玄関ドアオープンの時は1回だけピーンポーンとやや長めに鳴るように鳴らし方を変えよう。Wi-FiやBluetoothに対応はしているものの技適がないので使えないESP32C3 SuperMiniの使い道にやや困っていたが、それらは使わないのでOK、こういう用途には最適だろう。もちろん、普通のESP32 DevKITでも構わないのでその場合のスケッチと共存させてある。ついでにSlackへの通知機構はそのまま生かせば使えるようにしてある。 #include <WiFi.h> //Wi-Fi #include <WiFiClientSecure.h> //Wi-Fi #include <ArduinoJson.h> #define A_SW 4 //C3 ドアホンA接点 #define DOOR_SW 3 //C3 ドアオープン検出マイクロスイッチ #define relayPin 10 //C3 ATQ209 #define LED_BUILTIN 8 //C3 内蔵LED //#define A_SW 25 //ドアホンA接点 //#define DOOR_SW 35 //ドアオープン検出マイクロスイッチ //#define relayPin 26 //ATQ209 //#define LED_BUILTIN 2 //内蔵LED #define JST 3600 * 9 #define PUSH_SHORT 50 // チャタリング防止用時間(ms). 50〜100 を推奨 #define RESTART_INTERVAL (24UL * 60UL * 60UL * 1000UL) // 24時間 // 動作時間定数 #define RELAY_ON_TIME_CALL 500 // 呼出時ON時間 #define RELAY_OFF_TIME_CALL 1000 // 呼出時OFF時間 #define RELAY_REPEAT_CALL 2 // 呼出繰り返し回数 #define RELAY_ON_TIME_DOOR 1000 // ドア開時ON時間 #define BLOCK_TIME 3000 // 動作後ブロック時間 // --- Slack Webhook 設定 --- //const char* slackHost = "hooks.slack.com"; //const char* slackPath = "/services/XXXXXXXXX/XXXXXXXXXXX/xxxxxxxxxxxxxxxxxxxxxxxx"; // WiFi設定 //const char* ssid = "Your SSID"; //const char* password = "Your PASS"; //String hostname = "INTPHONE"; //bool done = true; RTC_DATA_ATTR int bootCount = 0; unsigned long lastRestart = 0; //---------------------------------- // デバウンス用構造体(ピンごとに保持) //---------------------------------- struct Debounce { uint8_t pin; int lastStable; // 最後に確定した安定状態 (HIGH/LOW) unsigned long lastChange; // millis() を記録 bool reported; // その LOW 確定を報告済みか (立下がりを一度だけ返すため) }; Debounce dbA = { A_SW, HIGH, 0UL, false }; Debounce dbDoor = { DOOR_SW, HIGH, 0UL, false }; //---------------------------------- // リレー制御関数 //---------------------------------- // リレーをON/OFFする基本動作 void relayOn(int onTime) { digitalWrite(relayPin, HIGH); digitalWrite(LED_BUILTIN, LOW);//C3 //digitalWrite(LED_BUILTIN, HIGH); Serial.println("リレーON"); delay(onTime); digitalWrite(relayPin, LOW); digitalWrite(LED_BUILTIN, HIGH);//C3 //digitalWrite(LED_BUILTIN, LOW); Serial.println("リレーOFF"); } // ドアホン用(2回繰り返し) void interphoneCall() { Serial.println("ドアホン呼出検知"); for (int i = 0; i < RELAY_REPEAT_CALL; i++) { relayOn(RELAY_ON_TIME_CALL); delay(RELAY_OFF_TIME_CALL); } delay(BLOCK_TIME); } // ドアオープン用(1回だけ) void doorOpen() { Serial.println("ドアオープン検知"); relayOn(RELAY_ON_TIME_DOOR); delay(BLOCK_TIME); } //---------------------------------- // デバウンス検出関数(立下がりを1回だけtrueで返す) // 使い方: if (debounceFalling(&dbA)) { ... } //---------------------------------- bool debounceFalling(Debounce *db) { int reading = digitalRead(db->pin); if (reading != db->lastStable) { // 状態が変化した(暫定) -> タイマーをリセット db->lastChange = millis(); db->lastStable = reading; // update the last observed level for timing db->reported = false; // 未報告に戻す return false; } else { // 状態が変わらない -> 経過時間が閾値を超えたら確定 if ((millis() - db->lastChange) > PUSH_SHORT) { // LOWが確定していて、まだ報告していなければ立下がりを返す if (reading == LOW && db->reported == false) { db->reported = true; // 1回だけ報告する return true; } } } return false; } //---------------------------------- void setup() { Serial.begin(115200); lastRestart = millis(); pinMode(A_SW, INPUT_PULLUP); //ドアホンA接点短絡でON pinMode(DOOR_SW, INPUT_PULLUP); //ドアオープン短絡でON pinMode(relayPin, OUTPUT); //リレー出力 pinMode(LED_BUILTIN, OUTPUT); //LED digitalWrite(relayPin, LOW); //リレー初期状態OFF digitalWrite(LED_BUILTIN, HIGH);//LED初期状態OFF(C3SuperMiniは極性反転) //digitalWrite(LED_BUILTIN, LOW);//LED初期状態OFF // WiFi接続 //WiFi.disconnect(true, true); //WiFi.mode(WIFI_STA); //WiFi.setHostname(hostname.c_str()); //WiFi.begin(ssid, password); //while (done) { //Serial.print("Wi-Fi connecting"); //auto last = millis(); //while (WiFi.status() != WL_CONNECTED && last + 5000 > millis()) { //delay(500); //Serial.print("."); //} //if (WiFi.status() == WL_CONNECTED) { //done = false; //} else { //Serial.println("Retry"); //WiFi.disconnect(true, true); ////WiFi.reconnect(); //ESP.restart(); //} //} //Serial.println("Wi-Fi connected."); //Serial.println("IP address: "); //Serial.println(WiFi.localIP()); //configTime(JST, 0, "pool.ntp.org", "jp.pool.ntp.org"); //delay(5000); //sendSlackMessage("🤖 起動OK!"); //Serial.println("起動OK!"); //} // Slack にメッセージを送信する関数 //void sendSlackMessage(const char* message) { //WiFiClientSecure client; //client.setInsecure(); // 簡易的に証明書検証を無効化 //if (!client.connect(slackHost, 443)) { //Serial.println("Slack connection failed"); //return; //} //String payload = String("{\"text\":\"") + message + "\"}"; //String request = String("POST ") + slackPath + " HTTP/1.1\r\n" + //"Host: " + slackHost + "\r\n" + //"Content-Type: application/json\r\n" + //"Content-Length: " + payload.length() + "\r\n\r\n" + //payload; //client.print(request); //// レスポンスを読み出して確認 //while (client.connected()) { //String line = client.readStringUntil('\n'); //if (line == "\r") break; // ヘッダ終了 //Serial.println(line); // デバッグ出力 //} //String response = client.readString(); //Serial.println("Response: " + response); } // //---------------------------------- void loop() { /////////////////////// //time_t t; //struct tm* tm; //static const char* wd[7] = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; //t = time(NULL); //tm = localtime(&t); //char timeStr[16]; //sprintf(timeStr,"%02d:%02d:%02d",tm->tm_hour, tm->tm_min, tm->tm_sec); /* //Serial.printf("%04d/%02d/%02d(%s) %02d:%02d:%02d\n", // tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, // wd[tm->tm_wday], // tm->tm_hour, tm->tm_min, tm->tm_sec); */ // 毎日06:05にRestart(ハングアップ対策) //if ((tm->tm_hour == 06) && (tm->tm_min == 05) && (tm->tm_sec == 00)) { // ESP.restart(); //} // ハングアップ対策で24時間ごとに再起動 if (millis() - lastRestart >= RESTART_INTERVAL) { ESP.restart(); } // ドアホン呼出 (立下がり検出) if (debounceFalling(&dbA)) { interphoneCall(); //sendSlackMessage(String("🚪 おや?誰か来たようだ! (" + String(timeStr) + ")").c_str()); } // ドアオープン (立下がり検出) if (debounceFalling(&dbDoor)) { doorOpen(); //sendSlackMessage(String("🔑 ドアが開きました! (" + String(timeStr) + ")").c_str()); } } 敬老の日の工作ということで。 ...

September 18, 2025 · Hirotomo Minakawa

LEDランタンGENTOSのEX-777XP(とそのOEM品)を修理したい

左から、一世を風靡した(?)GENTOSのEX-777XP。そのOEM版であるRAYOVACのSportsman Extreme、そして後継機であるEX-V777Dをそれぞれ持っている。一番最初に買ったのはRAYOVACのSportsman Extreme、その理由はその当時(2010年ころ)既に上位版のEX-888TFを持っていて、同じ暖色ではなくこれは白色だということと、1USD=80円台という円高な時代だったのでAmazon.com(US)から送料込みで買っても日本で買うEX-777XPより安かったのが理由。 そのRAYOVAC、使用頻度はそれほど高くないにもかかわらず壊れてしまって電池を入れると同時にフル点灯してしまい、スイッチが利かないので消せなくなった。電池を抜いておけばいいわけだが何ともよろしくない。修理してみよう、と思って分解すると、中央にコントロール基板が一枚入っているだけである。 回路図をなんとなく起こしてみる。 概ねこのようになっているっぽい。っぽい、っていうのはIC1がマスクされていて細かいところはよくわからないのでピン番号等は不明。おそらくカスタムICであるがSW1を押せばここで便宜上GP3としたPinがLowになり、Q2(型番は不明だが回路からPch-MOS-FETと推測)がONして点灯するわけだ。まぁ細かいところはどうでもよくて「どこが壊れて点きっ放しになるのか」ということである。Q2が壊れたならばFETを適当なものに交換すればよいのだが・・・試しにQ2を強制ONすると点くし、Highに吊ると消えるのでQ2は生きているようだ。つまりIC1が壊れているのである。これはどうしようもない。 オリジナルのEX-777XPの機能はフル点灯(High/72時間)→スイッチ長押しでEcoモード(Eco/144時間)の切替と、さらに長押しで点滅モード(ただ、パカパカと点滅する)というもので、RAYOVACも同じである。Ecoモードで電池が倍持つということは50%で点灯させているのだからPWM制御でおそらくはPICのようなICだろうと推測。つまり点灯回路は全部生きているので、左半分の回路を何かに置き換えてしまえばこのランタンは蘇るわけである。 というわけで、アイドリングストップ解除でも使ったDigispark(ATTiny85)を使うことにして、せっかくだから機能を追加することにした。普段はスリープ→スイッチ短押しで100%→スイッチ短押しで75%→スイッチ短押しで50%→スイッチ短押しで25%→スイッチ短押しでSOS点滅モードでそれぞれ点灯させ、各箇所にて長押しでOFF、というのを考えた。明るさを明暗の2段階から4段階に増やす。そして25%モード時はおやすみモードであり、2時間タイマーでOFFするようにしたい。 SOS点滅モードというのは、モールス信号で・・・---・・・ ・・・---・・・ ・・・---・・・と繰り返し点滅させるものである。ただ、パカパカと点滅するでは芸がないのでモールスでSOSを発するわけだ。見る人が見れば「緊急事態」と理解してもらえるかもしれない。キャンプでそのようなことがあっては危ないのだが、ただの点滅よりは「使える」機能にしておいた方が良いだろう。 Nch-MOS-FETに置き換えるとローサイドSWとなり制御は楽なのだが、部品はそのまま流用することを考えたので右半分の回路はそのままである。あとはプログラミングでどうにでもなろうというものだ。当初用意したのは基板とタクトスイッチと抵抗2本だけ、あとはDigisparkを接続して終了。消費電力的にはそんなに変わらんのではなかろうか。Digisparkは後々、ATTiny85の1チップのみにしてもいい後にDigiparkはATTiny85の1チップに置き換えることにした。 万能基板にて同じ場所にタクトスイッチが来るようにし、裏面にチップ抵抗とチップFETを搭載。中央のLEDは元から無くてもいい(無駄に電池を消費するだけの本体認識表示灯、数秒間に一度点灯して暗い場所でもランタンの位置をお知らせするだけの機能なのでこれをもぎ取る人さえもいた)ので省略。 Digisparkは電池の隙間に収められそうだったので適当に両面テープで固定しておく。 #include <avr/sleep.h> #include <avr/interrupt.h> const int light_pin = 0; // 外付けLED(PWM対応ピン PB0、Pchハイサイド) const int led_pin = 1; // 内蔵LED(PWM対応ピン PB1、通常ロジック) const int button_pin = 2; // タクトスイッチ PB2, GNDに落とす const int max_value = 255; const double C = 255.0; volatile bool buttonInterrupt = false; int mode = 0; // 初期はOFF const unsigned long longPressTime = 2000; // 長押し判定 2秒 const int fadeDelay = 4; // フェード速度 const unsigned long autoOffTime = 7200000; // case4 自動OFF 2時間 unsigned long case4Start = 0; //====================================================== // 外付けLED用:PWM duty を反転して出力 //====================================================== inline void writePWM(uint8_t duty) { duty = 255 - duty; // Pchハイサイド用に反転 analogWrite(light_pin, duty); } //====================================================== // ボタン割り込みISR(処理は軽く:押下の可能性があればフラグを立てる) //====================================================== ISR(PCINT0_vect) { buttonInterrupt = true; } void setup() { // PchハイサイドMOSFETは「HIGHでOFF」なので、pinMode前にOFFを出しておく digitalWrite(light_pin, HIGH); pinMode(light_pin, OUTPUT); pinMode(led_pin, OUTPUT); pinMode(button_pin, INPUT_PULLUP); writePWM(0); analogWrite(led_pin, 0); // ピン変化割り込みを有効化 (PB2 = PCINT2) GIMSK |= (1 << PCIE); PCMSK |= (1 << PCINT2); sei(); enterSleep(); // 電源投入時はOFF } void loop() { // case4 自動OFF判定 if (mode == 4 && millis() - case4Start >= autoOffTime) { mode = 0; writePWM(0); analogWrite(led_pin, 0); enterSleep(); } // ボタンが押された場合 if (buttonInterrupt) { buttonInterrupt = false; delay(30); // チャタリング防止 if (digitalRead(button_pin) == LOW) { unsigned long pressStart = millis(); while (digitalRead(button_pin) == LOW) { // 長押し判定 if (millis() - pressStart >= longPressTime) { mode = 0; writePWM(0); analogWrite(led_pin, 0); enterSleep(); } } unsigned long pressDuration = millis() - pressStart; if (pressDuration < longPressTime) { // 短押しでモード切替 if (mode != 5) { // SOSモードは loop で処理 mode = (mode + 1) % 6; switch (mode) { case 0: writePWM(0); analogWrite(led_pin, 0); enterSleep(); break; case 1: fade(0, max_value); break; case 2: fade(max_value, (int)(max_value*0.75)); break; case 3: fade((int)(max_value*0.75), (int)(max_value*0.5)); break; case 4: fade((int)(max_value*0.5), (int)(max_value*0.25)); case4Start = millis(); // 自動OFFタイマー開始 break; case 5: break; // SOSモードは loop で処理 } } } } } // SOSモードの処理 if (mode == 5) { sos_mode(); // SOS終了後 mode が0ならOFF処理 if (mode == 0) { writePWM(0); analogWrite(led_pin, 0); enterSleep(); } } } void enterSleep() { set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_cpu(); sleep_disable(); } void fade(int start_value, int end_value) { int current_value = start_value; while (current_value != end_value) { if (current_value < end_value) current_value++; if (current_value > end_value) current_value--; int duty = get_duty(current_value); writePWM(duty); analogWrite(led_pin, duty); delay(fadeDelay); } } // ---------- SOS モード ---------- // SOSモード(dot=0.25s, dash=0.75s, セット間隔3sOFF) void sos_mode() { while (mode == 5) { // S = dot dot dot for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(250, 250); // O = dash dash dash for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(750, 250); // S = dot dot dot for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(250, 250); // セット間隔 3秒OFF writePWM(0); analogWrite(led_pin, 0); if (wait_ms_with_abort(3000)) return; } } // こま切れで待ち、途中でボタンが押されたら true を返す bool wait_ms_with_abort(unsigned long ms) { unsigned long start = millis(); while (millis() - start < ms) { if (checkButtonSOS()) return true; delay(10); // 10ms刻みで待つ } return false; } // SOSの1パターン(ON→OFF) void sos_blink(int on_ms, int off_ms) { writePWM(255); analogWrite(led_pin, 255); if (wait_ms_with_abort(on_ms)) return; writePWM(0); analogWrite(led_pin, 0); if (wait_ms_with_abort(off_ms)) return; } // SOS中のボタン判定(短押し/長押しどちらでもOFF) bool checkButtonSOS() { if (digitalRead(button_pin) == LOW) { unsigned long pressStart = millis(); while(digitalRead(button_pin) == LOW) { if (millis() - pressStart >= longPressTime) { mode = 0; // 長押しでOFF return true; } } // 短押しでもOFF mode = 0; return true; } return false; } // 人間の目に合わせたガンマ補正付きduty計算 int get_duty(double ratio) { return round(exp(log(255.0) - (1 - (ratio/255.0))*log(C))); } 読んでの通りだが、これで機能的にはグレードUP。内蔵LEDの部分はデバッグ時に使用したので実機搭載時はコメントアウト。フェード演出を無駄に追加したので(笑)、100%点灯は0~ポワーっと明るくなり、75%→50%→25%への偏移はそれぞれシューンと暗くなる。あまり意味はない。 ...

August 26, 2025 · Hirotomo Minakawa

ネット非対応のテレビドアホンでネット通知したい

インターホンやテレビドアホン、かつてはいろいろなメーカーが出していた。今はおそらくパナソニックとアイホン(iPhoneの商標はアイホン株式会社のライセンスにもとづき使用されています、のアイホン)の2社がシェアのほとんどではなかろうか。 我が家のそれはパナソニック製だが当然ネットには非対応。本体に録画機能があって留守中に誰かが来たことはわかるが、それは帰宅して本体を見るまではわからない。そう、固定電話の留守番電話みたいなもので「新着あり」表示が点灯しているのを見て初めて「ああ、誰か来たんだな」ということがわかる程度。 今ならネットに対応し、リアルタイムで外部からスマホで応答するようなものもあるかもしれないが、あってもきっと高い。まぁ壊れるまで買い替えるようなものでもないし、そういうのは決まって専用アプリが必要とか、子機だけ異様に高いとか、どこかがクソ仕様なのは昔から決まっている。 ので・・・ひとまず本体VL-MV39の裏を見ると、この機種には「A接点出力」があるではないか。 取説によれば、A接点に接続可能な機器として挙げられているのは「光るチャイム、メロディサイン、警報ランプ付ブザー、回転灯」など。つまり「玄関の呼出ボタンを押すと連動してこれらがONとなる端子」と理解できる。つまりこの端子を拝借して、ここがONしたらネットに通知するようにすれば「ネット対応」にできるわけだ(一方通行だけどないよりマシ)。 自宅のWi-Fiを経由してネットに飛ばし、自分のスマホ宛に通知する仕掛けが出来れば「何かカッコイイ」し、何よりリアルタイムにどこにいても来客があったことは確認できる、というよりは(もちろん、防犯的にはそれでも良いと思う)、誰かが来たことがあらかじめわかっていれば帰宅してから本体の録画をチェックすればいいのであって、そのチェック忘れの防止にもなるわけだ。今回の目的はどちらかと言えばそっち。 そう、いつの間にか「新着あり」表示が点灯していて録画を見ると何件も溜まっていたなんてことは常である。とはいえ、郵便屋さんと宅配の人がほとんどで、再配達の伝票があればそこで突き合わせてOKなんだけど。それ以外ってセールスとか宗教とか得体の知れないやつだけど、たまに近所の人とかが来てることがあって「あれ、何だろう?」と気になったりはするしね。 というわけで、ここにESP32を使用する。通知はLINE Notifyが簡単で良い(LINE Notifyはサービス終了になったのでSlackに変更した)。ほんで、こんな具合にA接点出力とGPIOをつないでプログラムをごにょごにょと書く。 #include <WiFi.h> //Wi-Fi #include <WiFiClientSecure.h> //Wi-Fi #include <ArduinoJson.h> #define A_SW 25 //ドアホンA接点 #define LED_BUILTIN 2 //内蔵LED #define JST 3600 * 9 #define PUSH_SHORT 50 // チャタリング防止用時間(ms). 50-100で調整 // --- Slack Webhook 設定 --- const char* slackHost = "hooks.slack.com"; const char* slackPath = "/services/XXXXXXXXX/XXXXXXXXXXX/xxxxxxxxxxxxxxxxxxxxxxxx"; // WiFi設定 const char* ssid = "your SSID"; const char* password = "your PASSWORD"; String hostname = "INTPHONE"; bool done = true; RTC_DATA_ATTR int bootCount = 0; unsigned long lastRestart = 0; //---------------------------------- // デバウンス用構造体 //---------------------------------- struct Debounce { uint8_t pin; int lastStable; // 最後に確定した安定状態 (HIGH/LOW) unsigned long lastChange; // millis() を記録 bool reported; // その LOW 確定を報告済みか (立下がりを一度だけ返すため) }; Debounce dbA = { A_SW, HIGH, 0UL, false }; //---------------------------------- // デバウンス検出関数(立下がりを1回だけtrueで返す) // 使い方: if (debounceFalling(&dbA)) { ... } //---------------------------------- bool debounceFalling(Debounce *db) { int reading = digitalRead(db->pin); if (reading != db->lastStable) { // 状態が変化した(暫定) -> タイマーをリセット db->lastChange = millis(); db->lastStable = reading; // update the last observed level for timing db->reported = false; // 未報告に戻す return false; } else { // 状態が変わらない -> 経過時間が閾値を超えたら確定 if ((millis() - db->lastChange) > PUSH_SHORT) { // LOWが確定していて、まだ報告していなければ立下がりを返す if (reading == LOW && db->reported == false) { db->reported = true; // 1回だけ報告する return true; } } } return false; } // ドアホン用(3回繰り返し) void interphoneCall() { Serial.println("ドアホン呼出検知"); } //---------------------------------- void setup() { Serial.begin(115200); lastRestart = millis(); pinMode(A_SW, INPUT_PULLUP); //ドアホンA接点短絡でON pinMode(LED_BUILTIN, OUTPUT); //LED digitalWrite(LED_BUILTIN, LOW);//LED初期状態OFF // WiFi接続 WiFi.disconnect(true, true); WiFi.mode(WIFI_STA); WiFi.setHostname(hostname.c_str()); WiFi.begin(ssid, password); while (done) { Serial.print("Wi-Fi connecting"); auto last = millis(); while (WiFi.status() != WL_CONNECTED && last + 5000 > millis()) { delay(500); Serial.print("."); } if (WiFi.status() == WL_CONNECTED) { done = false; } else { Serial.println("Retry"); WiFi.disconnect(true, true); //WiFi.reconnect(); ESP.restart(); } } Serial.println("Wi-Fi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); configTime(JST, 0, "pool.ntp.org", "jp.pool.ntp.org"); delay(5000); sendSlackMessage("🤖 起動OK!"); Serial.println("起動OK!"); } // Slack にメッセージを送信する関数 void sendSlackMessage(const char* message) { WiFiClientSecure client; client.setInsecure(); // 簡易的に証明書検証を無効化 if (!client.connect(slackHost, 443)) { Serial.println("Slack connection failed"); return; } String payload = String("{\"text\":\"") + message + "\"}"; String request = String("POST ") + slackPath + " HTTP/1.1\r\n" + "Host: " + slackHost + "\r\n" + "Content-Type: application/json\r\n" + "Content-Length: " + payload.length() + "\r\n\r\n" + payload; client.print(request); // レスポンスを読み出して確認 while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") break; // ヘッダ終了 Serial.println(line); // デバッグ出力 } String response = client.readString(); Serial.println("Response: " + response); } //---------------------------------- void loop() { /////////////////////// time_t t; struct tm* tm; static const char* wd[7] = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; t = time(NULL); tm = localtime(&t); char timeStr[16]; sprintf(timeStr,"%02d:%02d:%02d",tm->tm_hour, tm->tm_min, tm->tm_sec); // 毎日06:05にRestart(ハングアップ対策) if ((tm->tm_hour == 06) && (tm->tm_min == 05) && (tm->tm_sec == 00)) { ESP.restart(); } // ドアホン呼出 (立下がり検出) if (debounceFalling(&dbA)) { interphoneCall(); sendSlackMessage(String("🚪 おや?誰か来たようだ! (" + String(timeStr) + ")").c_str()); } } USBから書き込んで、動作確認したら完了。なお、インターホン本体にAC100Vが直接接続されているモノは電気工事士でないと電源は外すことが出来ない(私は電気工事士免状を持っているので無問題)。玄関子機への配線や、コンセントタイプの電源であればもちろん無資格で外すことは可能。一旦外してボックスを追加してESP32を収納し、内部から電源を取って動作するように加工した。信頼できる超小型の5V電源ってiPhone純正の充電器くらいしか思い浮かばないんだけど、インターホン本体内部から5V取れたらそうしようかな。壁付けから宙に浮いて見えるようになって、むしろカッコイイ(とは言わない配線や基板が剥き出しのものがごちゃごちゃするのは美しくないのでそれよりはマシ)。 ...

August 20, 2025 · Hirotomo Minakawa

引っ越しで自転車を安価に輸送したい

2025年春、子が無事に!?大学進学を決めて引っ越すことになった。大学のそばにアパートを借りたので距離的には歩いて通えなくもないし、徒歩圏内にコンビニ、スーパー、ドラッグストアもあるが学生に自転車は必須と言える。 子には高校時代使用していた自転車が二台ある。 CANNONDALEのMTB (自宅⇔自宅最寄り駅) シティサイクル、いわゆるママチャリ (高校最寄り駅⇔高校) JRを介して2台を乗り継いでいたわけだが、シティサイクルなら現地で2-3万円で購入できるので送料をかけて送るよりは現地で買ってしまった方が安い。 何よりいわゆるママチャリは発送しないと断っている業者がほとんどである。その理由は構造上タイヤが簡単に外れない、フレームが重いなどいろいろあるのだろうが、引っ越し便などを使うと送れないこともない。しかし3辺の合計が200cmを超える場合はかなり制限される。 ただMTBやクロスバイク、ロードなど「ちゃんとした自転車」は発送を受け付けている業者があるのと、現地で2-3万円で購入できるシティサイクルより安価に発送できるのならばCANNONDALEを送って向こうで乗りたい、ということになった。 色々調べると、 カンガルー自転車イベント便/カンガルー自転車輸送便 というのを発見。ものすごく安い。イベント便とあるのでおそらくは自転車レース等の大会向けに設定されているようなのであるが、問い合わせてみれば規定内なら引越しに使ってもらっても全然大丈夫ですとのこと。 梱包サイズは3辺280cm未満、30kg以内 自転車の梱包は、お客さまの責任において十分に行ってください 輪行箱および輪行ケースは、ダンボール製、プラスチック製、ハードケース製、または、内側に十分なクッション素材を含むものなど外部からの衝撃に耐えうる素材の自転車専用のケースをご利用ください 自立しない、部分梱包、自転車の一部が出ている梱包のものはお取扱いが致しかねます。また段ボールなどをつなぎあわせた簡易梱包も取扱い致しかねます 輪行箱および輪行ケースの、持ち手・キャスター等が輸送中に破損した場合は補償致しかねます 注意点としてはこんなところだ。つまり適当な段ボールではダメ、輪行箱なら良い、ということである。箱を持っていない場合は注文も出来るようだが、安いものでも4400円。 自転車を新車で納入されるときに使われる梱包用の段ボール箱なら自転車屋さんに行けばあるのではないかと、まずはシティサイクルを買った実績のあるダイシャリンさんに電話をしてみた。残念ながら「捨ててしまったばかりだ、もう少しすればまた入荷があるのでそのタイミング次第、段ボールはうちとしては次々捨てるだけなのでもらってくれる分には無料で差し上げます」とのことだった。なるほど。 次に、CANNONDALEのパーツを注文したことがあるサイクルベースあさひさんに電話をしてみた。すると「BianchiのMTBを今、開梱したばかりで箱あります、今日中に取りに来られるならば差し上げますが明日以降だと潰してしまいます」とのことだった。 「あ!それ、ください」ということで、取りに行くことにした。とはいえものすごくデカい箱なので軽トラックまたは軽バンが必要だ。2025年3月11日、妻の知人に軽トラを借りた。お代は缶ビール6本。ありがたいね。 スバルサンバー、5MT、エアコンパワステなし、スパルタンだね。そんなわけで、会社の帰りに借りに行ってそのままサイクルベースあさひに向かい、箱を積んで帰ってきて子と一緒に自転車を梱包した。前輪とサドルとペダルを外してそれをエアキャップ(いわゆるプチプチ)で包んで適当に段ボールやエアキャップで保護しながら入れるとうまいこと収まった。ストレッチフィルム(いわゆるラップ)でぐるぐる巻きにしてからPPバンドをかけて完成。西濃からネットで集荷の手配をした。送り先は宇都宮市。秋田市からの送料は7280円である(安い!)。2025年3月28日、引っ越しの前日に集荷手配となった。 西濃さんが自宅までやってきてくれて、ひょいと入れてくれました。梱包完璧です、大丈夫ですとお墨付きを得た。 3月29日、引っ越し開始。30日には注文していた冷蔵庫など家電の搬入が完了し、31日が自転車引き取りの日であったが、無事搬送されてきた。受け取りの際は時間指定ができないことと、いわゆる置き配は不可なのでそこだけは要注意であるが、事前に電話をもらうことにしてあったのでスムーズに受け取りすることができた。 BianchiのMTBの箱に入った子のCANNONDALE、無事に降臨。早速開梱して組み立てる。組み立てといってもクイックで外れている前輪を取り付け、サドルを取り付け、レンチでペダルを取り付けるだけだ。そしてタイヤの空気を調整してすぐ乗れるようになった。 段ボールはそのまま処分。ここから引っ越すならまた使うかもしれないけれど、その時また自転車屋さんからもらえばいいよね。って、このアパートから歩いて5分の所にサイクルベースあさひさんがあるではありませんか! 何か困ったら駆け込めるし、いいね。というわけで、カンガルー自転車イベント便/カンガルー自転車輸送便は実におすすめなのでありました。個人的にもう使うことはないかもしれないが、この記事が誰かの役に立つかもしれないのでここに記載しておく。

April 1, 2025 · Hirotomo Minakawa

Digispark互換機でアイドリングストップキャンセラーを作りたい

以前作った**555がワンショット動作をしてリレーを一定時間だけONするってだけの基板**であるが、取付後、毎日快調に動作している。そんな中で、DigiSpark互換機、ATTiny85というその名の如く小さなマイコンがあることを知った。Arduinoの開発環境が使えてGPIOは6本あり、三端子レギュレータ78L05も搭載されているので12Vをぶち込んでもいいだろう。データシートを見ると各GPIOは40mA流せることになっているから、普段よく使用する5VリレーのATQ209の定格励磁電流は28.1mAなので、追加部品不要で直接ドライブできる。実に好都合。 マイコンだと「何秒後にリレーを何秒ON」みたいなことは得意なわけで、そんなプログラムを組んでみる、11秒後にリレーを0.5秒ONしてやるだけのプログラムを作ってみた。 /* アイドリングストップキャンセラー ・For ATTiny85 Internal Clock 8MHz ・ATQ209 DC5V_Relay */ unsigned long previousMillis = 0; // 最後に処理を行った時間 unsigned long interval = 11000; // 11秒 unsigned long waitTime = 30000; // 30秒 bool actionDone = false; // 一度だけ実行するためのフラグ void setup() { pinMode(4, OUTPUT); // リレー接続端子 pinMode(1, OUTPUT); // ボード上のLED // 実行までLEDを明滅させる for (int i = 0; i < 5; i++) { for (int brightness = 0; brightness <= 255; brightness++) { analogWrite(1, brightness); // PWM による明るさ調整 delay(10); // 少し待つ } delay(40); // 40ms for (int brightness = 255; brightness >= 0; brightness--) { analogWrite(1, brightness); // PWM による明るさ調整 delay(10); // 少し待つ } delay(40); // 40ms } } void loop() { unsigned long currentMillis = millis(); // 現在の時間を取得 // 11秒を超えたら一度だけリレーとLEDを500ms ONにする if (currentMillis - previousMillis >= interval && !actionDone) { digitalWrite(4, HIGH); // リレーON digitalWrite(1, HIGH); // LED_ON delay(500); // 500ms ON digitalWrite(4, LOW); // リレーOFF digitalWrite(1, LOW); // LED_OFF actionDone = true; // 一度実行したのでフラグを立てる previousMillis = currentMillis; // 最後の実行時刻を更新 } // タイムカウントが30秒を超えたら待機状態へ if (currentMillis - previousMillis >= waitTime) { while (true) { // ここで待機状態にする(動作しない) } } } で、これをArduinoIDEで焼こうと思ったらUSBでつないだだけではどうもうまくいかないっぽくて。先人の教えを調べるとArduino UNOを母艦にして焼くArduino as ISPというのが正攻法らしいのでそれに従ってみたらすんなり焼けた。 ...

March 5, 2025 · Hirotomo Minakawa

燃料キャップホルダを取り付けたい

ガソリンはセルフ給油派である。燃料キャップに最近はストラップが付いていてボディーからは離れない。締め忘れ防止にはいいのだろうが、ぶら下げておくしかなくやり場に困る。ボディーから離れるクルマは給油機に付属しているキャップ置き場に置けばよかったが、フューエルリッド裏に燃料キャップのホルダーが付いている車種もあって前乗っていたTD54Wエスクードもそうだった。じゃあそういうのを付けてみようかということで。 日産純正部品、17255-JK000、70円(税抜)。なぜ日産部品かというと、思い付きだったのと、日曜日もやっているから。秋田スズキの部品課は日曜定休なので。 両面テープで適当に貼り付け。 とりあえず良しとする。

December 1, 2024 · Hirotomo Minakawa

鬱陶しいアイドリングストップをキャンセルしたい

過去に乗ったクルマは当然として、もっとも最近に買ったスズキソリオMA26Sも純ガソリンエンジン車であり、アイドリングストップ機構は付いていなかったが、こともあろうにそれより年式が古い日産モコMG33Sにはアイドリングストップ機構が付いていた。押しつけがましいおせっかい機能は積極的にキャンセルしたいのである。なぜこれがデフォルトで有効なのか理解できない。アイドリングストップ機構をONにもできますよ、ならわかるのだが。 ブレーキで停止する場合、完全停止する前に少し「抜く」クセがある。頭がカックンしないように少しだけ「抜く」ことによって実にスムーズに停止できるのだが、ここに意図しないアイドリングストップが加わると抜く前に15km/h以下でエンジンが停止し、ブレーキを抜くとブレーキを解除したと察知して再始動してしまい、よく言われる「ぎくしゃく感」がさらに増幅してしまう。 バッテリーやスターターモーターに必要以上に負荷がかかっているのは間違いないし、これから冬を迎えるにあたり、やたらエンジンが停まるのは全くありがたくない。子を駅まで送るのにたった2kmで6回もエンジンが停止するのだ。ガソリンの消費は確かに少ないのかもしれないが、クルマが全く温まらない。そもそもエンジンスターターまで付けたというのに、これでは意味がない。ここ2か月弱で2回給油したが普通に運転していて20km/L前後の燃費であり、前に乗っていたスズキMRワゴンMF22Sより4-5km/Lは良いので、アイドリングストップしなくてもK6AよりR06Aはガソリンを消費しないことはわかっている。 ということで、アイドリングストップを永続的にキャンセルしたいと考えるに至ったわけだが、CVTFの交換でもわかったことだけれど、複雑怪奇にいろいろなところにシステムが絡んでいて、その機構を全面的にカットするとか、キャンセルするとか、物理的に除去するとかは簡単にはできない様子なのである。従って、機能として備えられている「アイドリングストップを解除するスイッチ」をエンジン始動後に一回押す、ということをしてやれば良いと理解する。 ACCで12V印加されたら555がワンショット動作をしてリレーを一定時間だけONするってだけ。手持ちの部品の関係で定数は適当。T=1.1R2C2より、1.1 x 220(kΩ) x 33(μF) =7986(mSec)だから約8秒くらいリレーがONする。つまりエンジン始動後、アイドリングストップ解除のスイッチを8秒くらい押したまま、と同じことを自動的にやらせるというもの。8秒も必要無くて実際には1秒もあればいいと思うのだけれど動作したので良しとする。念のためもう一つ作って、そちらは120kΩと33uFにしてみたので約4秒バージョンとなったがそれでもOKだったが、きっと4秒も必要ない。 部品配置を最適化すればもう少しコンパクトになるんじゃないかな。 間にコネクタを入れて、簡単に外せるようにはしておいた。配線は3本。行先はACCとGNDとアイドリングストップ信号線(このクルマの場合スイッチの灰色線だった)。 基板は自己融着テープでぐるぐる巻きにして防水と絶縁を図る。これを適当に隠して配置しておく。デフォルトでエンジン始動時にアイドリングストップ解除となり、且つ、スイッチは効くので押せばアイドリングストップが有効となる、つまりエンジン始動時デフォルト状態が逆になるだけ。最初からそうしてくれよ。

November 4, 2024 · Hirotomo Minakawa

加速にジャダーがあるのでCVTフルードを交換したい

2024年10月5日(土)に中古購入した日産モコX-FOUR(4WD)MG33S(2013年12月式、127000km)であるが加速にジャダーがある。アクセル開度一定で0-60km/hと加速していくと加減速を繰り返しながら加速していくような挙動、いわゆるハンチングが感じられる。CVTはそんなもんだという論調もあるが、我が家にあるスズキソリオGX4(4WD)MA26S(2019年11月式、62000km)はそんなことはなく実に滑らかである。走行距離的には倍近く走っているので単純な比較はできないが、何らかの不具合を抱えていると言っていいだろう。怪しい中古車屋でナンバー無し、車検無しの試乗できない状態で買った中古車だから保証もないし、車検は10月2日に通ったばかりで納車されているので文句も言えない。そして厄介なことにこの症状が顕著に出る日とそうでもない日がある。 スズキ・軽・CVT・ジャダーとかそれっぽいワードで検索してみると、ネットには先人の情報がたくさんある。CVTフルード(以降CVTF)の交換である程度は改善するらしいことがわかった。CVTは金属ベルトで駆動しているという構造上、金属粉が多量に出ること、その回収をするためにフィルターとマグネットが入っていること、マグネットから金属粉を除去するにはオイルパンを外す必要があること、オイルパンを外すからにはついでにストレーナーも交換した方が良いことなどを理解。つまり過走行車に出てくる症状のようなので、ソリオも近々CVTF交換をすべきなのだろう。金属粉そのものが悪さしているのか、金属粉がフィルターを詰まらせて油圧の低下を招いているのか、その辺まではわからなかった。 日産モコMG33S≒スズキMRワゴンMF33Sなのであるが、製造元であるスズキにも、OEM先である日産にもフィルターとストレーナーは交換部品としては用意されていないようである。但し、オイルパンのガスケットと、ドレンプラグのガスケットは部品番号が展開図には出てくる。 ガスケット・トランスミッションオイルパン 31397-4A00B、これはスズキの部品番号では24762-82KA0となり、そっちの方が安い(1600円税別)。ガスケット・ドレンプラグ 11026-4A00C、これもスズキの部品番号では24824-54LS0となり、そっちの方が安い(320円税別)。これらを秋田スズキの部品課から購入。部品課だけは日曜定休なので土曜か平日に購入する必要がある。そして、この展開図には出てこないがアイドリングトップ車特有の装備でCVTフルードをエンジン停止中に循環させるための電動ポンプがあって、その、Oリング・Eオイルポンプ 22982-70KN0、これは2個必要(180円税別/個)でスズキには1個しか在庫が無かったので今回はパス。 残りの部品は日産部品秋田で調達。まずはスズキに在庫が一個しかなかったOリング・Eオイルポンプ は日産の部品番号では31356-6A00A(但し320円税別/個なのでスズキで買うべき)。そしてフィルターアッシー・オイル 31726-3XX0A(430円税別)と、そのシール・Oリング 31526-3JX3A(170円税別)。最後にフィルターアッシー・オイルストレーナー 31726-6A00C(1940円税別)。日産部品は日曜もやっているのだが、フィルターとストレーナーは注文になってしまった。ということは、日常的に日産ディーラーではこういった作業は行われていないのであろう、事実スズキも日産もCVTFは無交換指定。 面白いことに、ストレーナー 31726-6A00Cのラベルの下には三菱のラベルが貼ってあった。 三菱の部品番号2824A027がどうやらオリジナルのようだ。NMKV車の製造元である三菱自動車工業が日産に供給している?何はともあれ部品は揃った。 必要な部品をまとめる。 品名 部番 個数 単価/税抜 (2024年時点) 入手先 ガスケット・トランスミッションオイルパン 24762-82KA0 1 1600円 スズキ ガスケット・ドレンプラグ 24824-54LS0 1 320円 スズキ Oリング・Eオイルポンプ 22982-70KN0 2 180円 スズキ フィルターアッシー・オイル 31726-3XX0A 1 430円 日産部品 シール・Oリング 31526-3JX3A 1 170円 日産部品 フィルターアッシー・オイルストレーナー 31726-6A00C 1 1940円 日産部品 そして迷ったのがCVTF、結論から先に言えばオートルブサプライのCVTF、20Lペール缶を買った(15000円税込)。缶はいかにもノーブランドで怪しいんだけど、コスモ石油ルブリカンツ(株)様を始め潤滑油専業メーカー各社様よりご協力頂き、各メーカー商品並びに弊社OEM商品を販売致しております、なんて会社案内に書いてあるし、信用しようと。モコの取説のサービスデータによると日産純正CVTF NS-3を使えとある。で、決まり文句。**必ず日産純正CVTフルードNS-3を使用してください。日産純正CVTフルードNS-3以外のフルードを使用するとオートマチックトランスミッションが破損するおそれがあります。**でも、レベルゲージの入り口にはSUZUKI GREEN2と書いてある(笑)のできっと初期充填はスズキ純正CVTF GREEN2であろうし、MRワゴンもそうなのだろう。 どうでもいいウォッシャー液などをはじめ日産純正が並ぶかと思いきや、不思議なことにトランスファーオイルとデフオイルとLLCはスズキが純正指定されている。正直よくわからない(どちらも日産純正でもラインナップはあると思うのだが)。日産のお客さま相談室に聞いてみることにした。 メンテナンスノート(説明書)にはトランスミッションオイルの指定について、日産純正CVTフルードNS-3となっているが、現実にはCVTFのレベルゲージ挿入口を見るとSUZUKI GREEN2のラベルが貼られており、スズキOEM車なのでSUZUKI GREEN2が正しいようにも思えるが説明書にはNS-3とあるのでどちらも適合すると考えてよろしいですか? 回答はこうだった。**スズキCVT フルード グリーン2、日産純正CVTフルードNS-3どちらも適合しております。なお日産純正CVTフルードNS-2のご使用は不可でございます。 ** で、NS-3とGREEN2の性状を調べてみると、NS-3の密度は0.85、色は青、40℃での粘度は25.81、100℃では6.23。GREEN2は密度0.84、色は緑、それ以外不明。ちなみに日産のNS-2は密度0.84、40℃粘度は32.6。で、このオートルブサプライのCVTFは密度は0.85、40℃粘度は33.26、100℃では7.036と、NS-2よりも少し硬いようだ。純正以外壊れるとか、NS-3指定にNS-2入れるのはダメだとかいろいろゴタクはあるんだけど適合表を見るとどちらもOKであり、まぁ問題ないんじゃないの、と。このCVTFでも十分シャバシャバだから感じとしてはよくわかんないんだけど、シャパシャバなのは燃費を重視した結果で、油膜を考えると硬い方が振動とかも低減するかもしれないし。 ...

October 28, 2024 · Hirotomo Minakawa

ETCを純正位置に取り付けたい

子が栃木県宇都宮市に進学したので何かと高速に乗ることになった。以前乗っていたMF22SスズキMRワゴンにさえ付けていたので、あっても良かろうと。ただ、その付けていたDENSO DIU-3900を移植しようと思ったのであるが、いわゆる旧スプリアス規格に基づいて製造されたETC車載器であって、一応今後「当分の間」は使用しても良いとはなっているがそれも憚られるのでこの際買い替えることにした。そもそも再セットアップをすると数千円かかるし買い替えた方が良い。機種選定にあたり、まず、安いこと。ナビ連動だの、ETC2.0だの、そういうのは一切不要。新セキュリティ規格対応ならば何でもよいということで、日立Astemo HF-EV715がセットアップ込み6280円と安価に売られていたのでそれにした。 機能は一通り問題なし。音声機能とブザータイプに切り替えができるのも好印象。ETCカードが云々といちいち喋るのうるさくて嫌なのよね。これ、古野電気のOEMであることを知る。確かに形がほぼ一緒。でも機能が細部で異なり、中身としてはFNK-M17+ウェルカムメッセージ機能、外観はFNK-M100といったところか。細かいことはどうでもよくて、安けりゃいい、それに尽きる。 純正位置に取り付けた形跡がないので、過去に付けたことが無いのかもしれない。躊躇せずカッターを入れる。除去するのは上の大きい部分だけでよかった。配線は適当にACCとGNDを引っ張ってくればよいが、問題はブラケットである。 廃材のt=1のアルミ板があったので、それを適当に加工してブラケットを作ることにした。 加工したものがこちらになります(料理番組形式で途中省略)。勢い余って2個作ったが1個は知人に譲渡することとして。現物合わせの加工ではあったが、こんなに奥行きは要らなかったね。もっと短くてよい(ETC本体寸法に合わせればよい)。 取付孔を開けてねじ留めして完成。市販品でもあるらしいんだけど廃物利用なので。どうせ見えなくなるので適当でいいし。ここに本体を載せて、両面テープ等で固定すればよい。 あとは電源とアンテナを適当に。ちゃんと高速道路を通過できました。

October 13, 2024 · Hirotomo Minakawa