老両親が住んでいる我が実家にはナショナルこと現パナソニックのサインペット・EB15(チャイム)が建築時より取り付けられており、おそらくは50年は経過している。

これは乾電池/トランス式であり、スイッチを押すとピン、離すとポーンと鳴る。原理としては電磁石を使ったもので、コイルに直列に単二乾電池を4本または8V程度を接続してそれをスイッチにてONすると電磁石となり、中にある鉄芯がハンマーとなって動いて鉄琴の如く鉄板を打撃して音を鳴らし、OFFするとばねの力で戻りまた音の異なる鉄板をハンマーが打撃して音を鳴らす、結果としてピンポーンと鳴るという真に古典的なものである。さすがはパナソニック、商品としては現行商品(ニューサインポン・EB177W、こちらは乾電池専用で単三4本となっている)がある。
元々は呼び鈴として取り付けられていたのではあるが、ドアオープン専用のアラームにして利用していた。というのも、呼び鈴用の配線を流用してテレビドアホン化してしまったのだ。かつて昭和の時代は玄関は特に施錠せず、ピンポンが鳴れば「はーい」と応じ、客人が玄関を開けて入ってくる、それで良かったのだが、何かと物騒になった現在では施錠しておくのは当たり前。ともなると呼び鈴ではダメでモニター越しに誰が来たのか、知っている人なのか、宅配なのか、郵便なのか、或いは知らない人なのかなど、ある程度の検討をつける必要があり、知らない人ならばインターホン越しに会話をして要件を聞き出し「お呼びでない」ならばそのまま帰っていただく、そういう世の中になったのである。
従って、呼び鈴スイッチが取り付けられていた玄関にはカメラ玄関子機VL-V522Lを取り付け、その配線を延長してリビングまで引き込んだ先にテレビドアホンVL-MV39を取り付けた。システム的にテレビドアホンとサインペットは共存が難しいのでドアにマイクロスイッチをつけ、ドアをの開閉をスイッチに連動させてピンポンと鳴るようにしたのである。当初はテレビドアホンのA接点を使えばいいかと思ったが、そのままスイッチに代替してもそれはピンだけ鳴って終わり、モニターオフでポーンと鳴る。その間電磁石のコイルには電流が30秒ほど流れっ放しとなり、電池寿命にも良くない。従ってこれはこれ、それはそれで活かすようにしたのだ。万一施錠を忘れていてもドアが開くとピンポンと鳴るため防犯上は良い。別々のシステムにはなったのだがそれで用は足りていた。しかし最近、後期高齢者ともなると耳が遠いのか、テレビドアホンの呼び出し音では聞こえないことがあるらしいのだ。
確かに廊下に設置したサインペットは良く響き渡る。一方、テレビドアホンはリビングにあるので、リビングにいないといまいちわかりにくい。従来のようにピンポーンと響き渡る金属音は欲しいところである。ということは、A接点を利用してそれを元にリレー駆動して全く別の機構でサインペットを鳴らす方法を考えなくてはならない。そして、せっかく機能しているドアオープンアラームも活かすべきである。その回路を稼働するからには電池駆動ではなく、両方ともAC電源にしてしまうことも考えた方が良い。EB15にはトランス配線ができるように端子があるので、ここに手持ちのACアダプターで9Vを印加し、リレー駆動回路はESP32ボードを使って組むこととし、7805を介して5Vをもらうことにした。これで電池も使わなくてよくなるので一石二鳥である。
SignPetDriverATQ209は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());
}
}敬老の日の工作ということで。
で、よく考えてみるとネット通知不要なら3.3VロジックのESP32系を使うよりDigispark(ATTINY85)を使う方が部品点数が少なくて済む。7805も不要だし、トランジスタも不要。意外にATTINY85の出番があるように思う。
// ATtiny85 用スケッチ(ドアホン A 接点 & ドアオープン検出 & リレー制御)
// GPIO LOWでリレーON(シンク駆動版)
#include <Arduino.h>
#include <avr/wdt.h>
// Digispark/ATtiny85 基準:D0=PB0, D1=PB1(LED), D2=PB2, D3=PB3, D4=PB4
#define relayPin 0 // リレー出力(ATQ209コイルをシンク駆動)
#define LED_PIN 1 // 内蔵LEDピン(一般に Digispark は 1)
#define A_SW 2 // ドアホンA接点(短絡でON:GNDへ落ちる)
#define DOOR_SW 3 // ドアオープン検出マイクロスイッチ(短絡でON:GNDへ落ちる)
// 極性(実機に合わせて切り替え)
#define LED_ACTIVE_LOW false // true: LOWで点灯, false: HIGHで点灯
#define RELAY_ACTIVE_HIGH false // ★GPIO LOWでリレーON(シンク駆動)
// ==== 動作定数 ====
#define PUSH_SHORT 50 // チャタリング防止用時間(ms):50〜100推奨
#define RESTART_INTERVAL (24UL * 60UL * 60UL * 1000UL) // 24時間ごとに再起動
// 動作時間定数
#define RELAY_ON_TIME_CALL 500 // 呼出時ON時間(0.5秒)
#define RELAY_OFF_TIME_CALL 1000 // 呼出時OFF時間(1秒)
#define RELAY_REPEAT_CALL 2 // 呼出繰り返し回数(2回)
#define RELAY_ON_TIME_DOOR 1000 // ドア開時ON時間(1秒)
#define BLOCK_TIME 3000 // 動作後ブロック時間(3秒)
// ==== 再起動管理 ====
unsigned long lastRestart = 0;
// ==== デバウンス用構造体(ピンごとに保持) ====
struct Debounce {
uint8_t pin;
int lastStable; // 最後に確定した安定状態 (HIGH/LOW)
unsigned long lastChange; // millis() を記録
bool reported; // その LOW 確定を報告済みか (立下がりを1回だけ返すため)
};
// 初期状態はプルアップ入力なので HIGH を安定状態として開始
Debounce dbA = { A_SW, HIGH, 0UL, false };
Debounce dbDoor = { DOOR_SW, HIGH, 0UL, false };
// ==== ユーティリティ ====
inline void setLed(bool on) {
if (LED_ACTIVE_LOW) {
digitalWrite(LED_PIN, on ? LOW : HIGH);
} else {
digitalWrite(LED_PIN, on ? HIGH : LOW);
}
}
inline void setRelay(bool on) {
if (RELAY_ACTIVE_HIGH) {
digitalWrite(relayPin, on ? HIGH : LOW); // HIGHでONの場合
} else {
digitalWrite(relayPin, on ? LOW : HIGH); // ★LOWでON(シンク駆動)
}
}
void forceRestart() {
// ウォッチドッグで強制リセット
wdt_enable(WDTO_15MS);
while (true) { /* WDT リセット待ち */ }
}
// ==== リレー制御関数 ====
// リレーをON/OFFする基本動作
void relayOn(int onTimeMs) {
setRelay(true); // リレーON(GPIO LOWでONに設定済み)
setLed(true);
delay(onTimeMs);
setRelay(false); // リレーOFF
setLed(false);
}
// ドアホン呼出(2回繰り返し)
void interphoneCall() {
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() {
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; // 直近の観測レベルを更新
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() {
lastRestart = millis();
// デバウンスの初期タイムスタンプ(起動直後の誤検知防止)
dbA.lastChange = lastRestart;
dbDoor.lastChange = lastRestart;
pinMode(A_SW, INPUT_PULLUP); // A接点短絡でLOWになる
pinMode(DOOR_SW, INPUT_PULLUP); // ドアスイッチ短絡でLOWになる
pinMode(relayPin, OUTPUT); // リレー出力
pinMode(LED_PIN, OUTPUT); // LED
setRelay(false); // リレー初期状態OFF
setLed(false); // LED初期状態OFF
}
// ==== ループ ====
void loop() {
// ハングアップ対策で24時間ごとに再起動
if (millis() - lastRestart >= RESTART_INTERVAL) {
forceRestart();
}
// ドアホン呼出 (立下がり検出)
if (debounceFalling(&dbA)) {
interphoneCall();
}
// ドアオープン (立下がり検出)
if (debounceFalling(&dbDoor)) {
doorOpen();
}
}

