Сигнализация на Arduino: PIR, датчик двери, клавиатура и сирена
Собери сигнализацию на Arduino: PIR, герконовый датчик двери, зоны вибрации и лазера, код на клавиатуре, задержки на вход и выход, реле сирены и уведомления в Telegram на ESP32.
Сигнализация на Arduino — это охранное устройство, которое можно собрать своими руками: оно включает сирену, когда кто-то без разрешения заходит в твой дом или комнату, даёт несколько секунд на ввод кода после открытия двери и, если хочешь, сразу присылает уведомление на телефон. В этом руководстве ты соберёшь сигнализацию на четыре зоны: PIR-датчик движения, геркон на двери, датчик вибрации, который ловит удары по оконному стеклу, и лазерный барьер поперёк коридора. Ставить на охрану и снимать с неё ты будешь кодом на клавиатуре 4x4, сирена на 12 V включается через реле, а всем управляет одна плата Arduino Uno.
Шаг за шагом мы повторим в миниатюре то, что умеют коммерческие охранные панели: задержки на вход и выход, режим «Дома» (ночной режим), блокировку после неверных кодов, ограничение времени работы сирены и память тревог. Вместо вложенных блоков if мы напишем код в виде конечного автомата, чтобы в любой момент было понятно, что делает сигнализация. Потом ты перенесёшь ту же логику на ESP32, чтобы ставить на охрану, снимать с неё и получать тревоги через Telegram, напишешь версию на MicroPython для Raspberry Pi Pico и узнаешь, как справиться с самой надоедливой проблемой — ложными тревогами.

Как работает сигнализация на Arduino?
Любая сигнализация состоит из трёх частей: входов (датчиков), мозга, который принимает решения (Arduino), и выходов (сирена, зуммер, уведомление). Каждый датчик охраняет свою зону. В профессиональных панелях зоны делятся по тому, как они ведут себя при срабатывании, и именно это отличает хорошую сигнализацию от надоедливой поделки, которая воет при каждом открытии двери. В нашей системе будет четыре зоны:
| Зона | Датчик | Тип зоны | Если сработает на охране |
|---|---|---|---|
| Входная дверь | Геркон (MC-38) | С задержкой | Запускается задержка на вход 20 с; если код не введён — тревога |
| Гостиная, внутри | PIR (HC-SR501) | Внутренняя (проходная) | Мгновенная тревога; ждёт, если идёт задержка на вход; выключена в режиме «Дома» |
| Окно | Вибрация (SW-420) | Мгновенная | Мгновенная тревога после 3 ударов за 3 с |
| Коридор, лестница | Лазер + LDR (KY-008) | Внутренняя (проходная) | Мгновенная тревога; ждёт, если идёт задержка на вход |
| Газ, пожар, вскрытие | MQ-2, датчик пламени, датчик вскрытия корпуса | Круглосуточная (24 ч) | Тревога даже при снятой охране (идея для доработки) |
Благодаря зоне с задержкой сирена не включается в ту же секунду, когда ты приходишь домой: дверь открывается, зуммер предупреждающе пищит, и на ввод кода есть 20 секунд. Внутренняя проходная (follower) зона терпелива, пока идёт задержка на вход, поэтому путь от двери к клавиатуре мимо PIR тревогу не вызывает. Но если кто-то проникает внутрь, не открывая дверь, например через окно, и оказывается перед PIR, задержка на вход не запущена, и тревога звучит сразу. Мгновенная зона срабатывает всегда и без задержки: нет смысла давать 20 секунд тому, кто взламывает окно. Уход работает похоже: при постановке на охрану запускается 30-секундная задержка на выход, и выход за дверь в это время ничего не запускает.
Список деталей для сигнализации на Arduino
| Деталь | Кол-во | Назначение |
|---|---|---|
| Arduino Uno (или Nano) | 1 | Мозг системы; пины в этом руководстве одинаковы на Uno и Nano |
| PIR-датчик движения HC-SR501 | 1 | Обнаружение движения в помещении, 3–7 м, около 110° |
| Герконовый датчик двери MC-38 (или геркон + магнит) | 1 | Размыкает цепь, когда открывается дверь или окно |
| Модуль датчика вибрации SW-420 | 1 | Ловит удары по стеклу или раме |
| Лазерный модуль KY-008 + LDR + резистор 10 kΩ | 1 комплект | Световой барьер поперёк коридора |
| Плёночная матричная клавиатура 4x4 | 1 | Постановка на охрану и снятие с неё по коду |
| Активный зуммер (5 V) | 1 | Звук нажатия клавиш, предупреждение на вход и выход, внутренняя сирена |
| 1-канальный модуль реле на 5 V | 1 | Включает сирену на 12 V с развязкой от Arduino |
| Сирена на 12 V (лучше со стробоскопом) | 1 | Основной звук тревоги, обычно 100–120 dB |
| Красный и зелёный светодиоды + 2 резистора по 220 Ω | 1 комплект | Индикаторы «на охране» и «готово» |
| Питание 12 V, модуль LM2596, аккумулятор 12 V (по желанию) | 1 комплект | Питание и резерв при отключении электричества |
| Макетная плата, перемычки, 4-жильный кабель для сигнализации | - | Соединения и удалённые датчики |







Датчики: что ловит каждая зона?
- PIR (HC-SR501): Реагирует на изменение инфракрасного излучения человеческого тела и видит движение на расстоянии 3–7 метров в угле около 110 градусов. Выход у него 3,3 V, но питание нужно 5 V. Первые 30–60 секунд после включения датчик «изучает» комнату и может срабатывать сам по себе, поэтому код игнорирует первые 60 секунд. Подробности — в статье PIR-датчик движения.
- Герконовый датчик двери (MC-38): Два пластиковых корпуса: один крепится к коробке, другой к дверному полотну. Когда дверь закрыта, магнит рядом и контакт замкнут; стоит двери открыться больше чем на 15–25 мм, контакт размыкается. Читается одним пином со встроенной подтяжкой; что внутри, мы разбирали в статье геркон.
- Датчик вибрации (SW-420): Нормально замкнутый контакт с металлическим стержнем внутри пружины. Когда по стеклу или раме ударяют, контакт на мгновение размыкается, и компаратор LM393 на плате превращает это в цифровой импульс. На большинстве модулей в покое на выходе LOW, а при ударе ненадолго появляется HIGH; проверь свой модуль тестом датчиков. Чувствительность задаётся подстроечным резистором на плате; подробности — в статье датчик вибрации SW-420.
- Лазерный барьер (KY-008 + LDR): Лазер 650 nm мощностью около 5 mW постоянно светит на фоторезистор на другой стороне коридора. Когда луч перекрывают, сопротивление LDR подскакивает с нескольких сотен ом до десятков килоом, и аналоговое показание резко падает. Советы по установке и юстировке — в статье лазерный модуль.
- Клавиатура 4x4: Матрица из 16 клавиш на 4 линиях строк и 4 линиях столбцов; занимает 8 пинов и читается одной строкой с библиотекой Keypad. Как работает сканирование строк и столбцов, рассказано в руководстве по матричной клавиатуре.
- Сирена и зуммер: У активного зуммера есть свой генератор, поэтому он пищит, как только на пин подан HIGH; он идеален для звука клавиш и предупреждений на вход и выход. Настоящий отпугивающий звук даёт сирена на 12 V, которую ты включаешь через модуль реле. Чем активный зуммер отличается от пассивного, смотри в статье что такое зуммер.
Схема подключения и таблица пинов
В таблице ниже подключение для всех трёх плат. Версия для Arduino Uno использует все четыре зоны и клавиатуру. Версия для ESP32 ставится на охрану и снимается с неё командами в Telegram вместо клавиатуры, а версия для Pico сделана короткой, поэтому лазерный барьер есть только на Uno. Номера пинов своей платы можно посмотреть в утилите распиновки, а резисторы для светодиодов посчитать в калькуляторе резистора для светодиода.
| Подключение | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Клавиатура R1–R4 (строки) | D9, D8, D7, D6 | – (Telegram) | GP2, GP3, GP4, GP5 |
| Клавиатура C1–C4 (столбцы) | D5, D4, D3, D2 | – (Telegram) | GP6, GP7, GP8, GP9 |
| PIR VCC / OUT / GND | 5V / D10 / GND | VIN (5V) / GPIO 27 / GND | VBUS / GP16 / GND |
| Геркон (2 провода) | D11 и GND | GPIO 33 и GND | GP15 и GND |
| SW-420 VCC / DO / GND | 5V / D12 / GND | 3V3 / GPIO 32 / GND | 3V3(OUT) / GP17 / GND |
| KY-008 S / − (лазер всегда включён) | 5V / GND | – | – |
| LDR между 5V и A0, 10 kΩ между A0 и GND | A0 | – | – |
| Активный зуммер + (− на GND) | A1 | GPIO 25 | GP14 |
| Модуль реле, IN | A2 | GPIO 26 | GP13 |
| Зелёный светодиод (через 220 Ω): готово | A3 | – | – |
| Красный светодиод (через 220 Ω): на охране | D13 | GPIO 2 (встроенный светодиод) | Встроенный светодиод |
Сигнализация нужнее всего, когда отключают электричество, поэтому о питании стоит подумать с самого начала. Практичная схема: как в профессиональных панелях, блок питания для сигнализации на 13,8 V с подключением аккумулятора питает сирену, а после понижения до 5 V понижающим преобразователем LM2596 — ещё и пин 5V на Arduino. Когда сеть пропадает, работу подхватывает герметичный свинцово-кислотный аккумулятор 12 V 7 Ah. Расчёт простой: Uno, PIR, лазер, светодиоды и SW-420 в режиме ожидания потребляют около 100 mA при 5 V; это 0,5 W, что при КПД преобразователя 85% даёт примерно 50 mA со стороны аккумулятора. Даже если использовать только 80% ёмкости, аккумулятор на 7 Ah держит систему 4–5 дней; 0,2–0,5 A, которые сирена потребляет во время работы, немного сокращают этот срок. Подставь свои значения в калькулятор времени работы от батареи, а варианты питания смотри в статье как питать Arduino.
Сборка шаг за шагом
- В Arduino IDE открой «Менеджер библиотек» (Library Manager), введи Keypad и установи библиотеку от Mark Stanley и Alexander Brevig. Без неё при компиляции появится ошибка Keypad.h: No such file or directory.
- Держи клавиатуру клавишами к себе; первые четыре провода слева (R1–R4) подключи к D9, D8, D7, D6, а следующие четыре (C1–C4) — к D5, D4, D3, D2.
- Подключи PIR к 5V, D10 и GND; оба подстроечных резистора поверни до упора против часовой стрелки, а перемычку режима срабатывания поставь в положение H.
- Два провода геркона подключи к D11 и GND, а SW-420 — к 5V, D12 и GND.
- Поставь LDR между 5V и A0, а резистор 10 kΩ — между A0 и GND; размести LDR на дне чёрной трубки и направь лазер точно в центр трубки.
- Подключи зуммер к A1, вход IN модуля реле — к A2, а его VCC и GND — к 5V и GND на Arduino; светодиоды подключи к A3 и D13 через резисторы 220 Ω.
- Сначала загрузи тест датчиков ниже, открой монитор порта на скорости 9600 бод и по очереди вызови срабатывание каждого датчика.
- Затем загрузи полный скетч сигнализации, смени код и несколько дней проверяй систему только с зуммером, прежде чем подключать сирену.
Первый тест: все датчики в мониторе порта
Прежде чем переходить к коду сигнализации, убедись, что каждый датчик ведёт себя так, как ожидается. Этот небольшой скетч дважды в секунду выводит состояние PIR, открыта ли дверь, число импульсов вибрации за последние полсекунды и аналоговое значение LDR. Пин вибрации читается постоянно, потому что импульсы SW-420 могут длиться всего несколько миллисекунд, и код с delay() между чтениями их пропустит.
1// Alarm sensör testi: tüm girişleri saniyede iki kez yazdırır / Alarm sensor test: prints every input twice a second
2const int PIR_PIN = 10; // HC-SR501 OUT
3const int KAPI_PIN = 11; // reed kapı kontağı, diğer ucu GND / reed door contact, other leg to GND
4const int TITRESIM_PIN = 12; // SW-420 DO
5const int LAZER_PIN = A0; // LDR bölücünün orta noktası / LDR divider midpoint
6
7int titresimSayisi = 0; // yarım saniyedeki darbe sayısı / pulses in the last half second
8int oncekiTitresim = LOW;
9unsigned long sonYazma = 0;
10
11void setup() {
12 Serial.begin(9600);
13 pinMode(PIR_PIN, INPUT);
14 pinMode(KAPI_PIN, INPUT_PULLUP); // kapı kapalı = LOW, açık = HIGH / door closed = LOW, open = HIGH
15 pinMode(TITRESIM_PIN, INPUT);
16}
17
18void loop() {
19 int t = digitalRead(TITRESIM_PIN); // kısa darbeleri kaçırmamak için sürekli oku / read constantly so short pulses are not missed
20 if (t == HIGH && oncekiTitresim == LOW) titresimSayisi++;
21 oncekiTitresim = t;
22
23 if (millis() - sonYazma >= 500) {
24 sonYazma = millis();
25 Serial.print(F("PIR: "));
26 Serial.print(digitalRead(PIR_PIN));
27 Serial.print(F(" Kapi / door: "));
28 Serial.print(digitalRead(KAPI_PIN) == HIGH ? F("ACIK / open") : F("kapali / closed"));
29 Serial.print(F(" Titresim / vibration: "));
30 Serial.print(titresimSayisi);
31 Serial.print(F(" Lazer / laser: "));
32 Serial.println(analogRead(LAZER_PIN));
33 titresimSayisi = 0;
34 }
35}Конечный автомат: мозг сигнализации
Если писать сигнализацию на блоках if и delay(), она быстро превращается в клубок: пока идёт задержка на вход, клавиатура не читается, а пока воет сирена, код не ввести. Решение — конечный автомат, в котором система в каждый момент находится ровно в одном состоянии и переходит между состояниями по определённым событиям. В коде состояния задаются через enum, loop() на каждом проходе применяет только правила текущего состояния, а ожидание отмеряется через millis() вместо delay(). Так цикл выполняется тысячи раз в секунду, и ни одно нажатие клавиши или событие датчика не теряется.
| Состояние | Когда наступает | Что делает | Когда заканчивается |
|---|---|---|---|
| DISARMED (снято с охраны) | При включении и после кода + # | Следит за датчиками, но тревогу не поднимает; когда всё готово, горит зелёный светодиод | Код + A или код + B |
| EXIT DELAY (задержка на выход) | Код + A (полная охрана) или B (режим «Дома») | Отсчитывает 30 с; зуммер пищит раз в секунду и ускоряется в последние 10 с | ARMED по истечении времени; DISARMED по коду + # |
| ARMED (на охране) | Когда заканчивается задержка на выход | Следит за всеми зонами; красный светодиод горит постоянно | ENTRY, если открыта дверь; ALARM при срабатывании мгновенной или внутренней зоны |
| ENTRY DELAY (задержка на вход) | Дверь открыта во время охраны | Зуммер пищит 20 с; PIR и лазер ждут | DISARMED по коду + #; ALARM по истечении времени |
| ALARM (тревога) | Сработала одна из зон | Звучат сирена и зуммер; сирена замолкает через 3 мин, красный светодиод часто мигает | DISARMED только по коду + # |
Постановка на охрану, снятие и код на клавиатуре
| Клавиши | Функция |
|---|---|
| 2580 + A | Полная охрана: активны все четыре зоны, задержка на выход 30 с |
| 2580 + B | Режим «Дома» (ночной): PIR выключен; дверь, окно и лазер активны |
| 2580 + # | Снятие с охраны; если была тревога, в монитор порта выводится сработавшая зона |
| * или C | Стереть неправильно набранный код |
| D | Тест зон: в монитор порта выводятся значения двери, PIR и лазера |
| 3 неверных кода | Клавиатура блокируется на 60 с; в это время не принимается даже правильный код |
Код при постановке на охрану тоже нужен: так дети или гости не поставят систему на охрану случайно. Ещё код проверяет готовность: если попытаться поставить на охрану при открытой двери или перекрытом лазерном луче, раздастся длинный сигнал, и система на охрану не встанет — точно как у коммерческих панелей. Не оставляй код по умолчанию 2580 и избегай легко угадываемых кодов вроде 1234, 0000 или года рождения. Чтобы менять код без повторной загрузки скетча, храни его в EEPROM и добавь меню для ввода нового кода с клавиатуры. Добавь рядом с клавиатурой дисплей LCD с I2C, и обратный отсчёт и сообщения о состоянии будут видны ещё и на экране; именно поэтому пины A4 и A5 оставлены свободными.
Код сигнализации на Arduino
Код ниже — сердце этого руководства. В разделе настроек вверху можно поменять код, задержки, время работы сирены, порог лазера и тип реле; остальное трогать не нужно. На Arduino Uno он занимает около 6 КБ памяти программ и 620 байт ОЗУ, так что места для LCD или RFID-считывателя остаётся с запасом.
1// Arduino ev alarm sistemi: 4 bölge, keypad, giriş/çıkış gecikmesi, siren / Arduino home alarm: 4 zones, keypad, entry/exit delay, siren
2#include <Keypad.h>
3
4// ---- Ayarlar / Settings ----
5const char SIFRE[] = "2580"; // kendi şifreni yaz / set your own code
6const unsigned long CIKIS_SURE = 30000; // çıkış gecikmesi 30 sn / exit delay 30 s
7const unsigned long GIRIS_SURE = 20000; // giriş gecikmesi 20 sn / entry delay 20 s
8const unsigned long SIREN_SURE = 180000; // siren en fazla 3 dk çalar / siren sounds for at most 3 min
9const unsigned long PIR_ISINMA = 60000; // PIR ilk 60 sn yok sayılır / PIR is ignored for the first 60 s
10const unsigned long KILIT_SURE = 60000; // 3 yanlış şifrede 60 sn kilit / 60 s lockout after 3 wrong codes
11const int LAZER_ESIK = 600; // ışınlı ve ışınsız değerin ortası / midpoint of beam and no-beam readings
12const bool LAZER_VAR = true; // lazer bariyerin yoksa false yap / set to false without a laser barrier
13const bool ROLE_AKTIF_LOW = true; // çoğu röle modülü LOW'da çeker / most relay modules switch on with LOW
14
15// ---- Pinler / Pins ----
16const int PIR_PIN = 10; // HC-SR501 OUT
17const int KAPI_PIN = 11; // reed, diğer ucu GND / reed, other leg to GND
18const int TITRESIM_PIN = 12; // SW-420 DO
19const int LAZER_PIN = A0; // LDR bölücü / LDR divider
20const int BUZZER_PIN = A1; // aktif buzzer / active buzzer
21const int ROLE_PIN = A2; // siren rölesi / siren relay
22const int YESIL_LED = A3; // hazır / ready
23const int KIRMIZI_LED = 13; // kurulu / armed
24
25const byte SATIR = 4, SUTUN = 4;
26char tuslar[SATIR][SUTUN] = {
27 {'1', '2', '3', 'A'},
28 {'4', '5', '6', 'B'},
29 {'7', '8', '9', 'C'},
30 {'*', '0', '#', 'D'}
31};
32byte satirPinleri[SATIR] = {9, 8, 7, 6}; // R1–R4
33byte sutunPinleri[SUTUN] = {5, 4, 3, 2}; // C1–C4
34Keypad keypad = Keypad(makeKeymap(tuslar), satirPinleri, sutunPinleri, SATIR, SUTUN);
35
36enum Durum { COZULU, CIKIS_GECIKMESI, KURULU, GIRIS_GECIKMESI, ALARM };
37const char* const DURUM_ADI[] = { "COZULU / DISARMED", "CIKIS / EXIT DELAY", "KURULU / ARMED", "GIRIS / ENTRY DELAY", "ALARM" };
38
39struct Giris { // gürültü süzgeçli giriş / debounced input
40 bool ham;
41 bool kararli;
42 unsigned long degisim;
43};
44Giris gKapi = {false, false, 0};
45Giris gPir = {false, false, 0};
46Giris gLazer = {false, false, 0};
47
48Durum durum = COZULU;
49unsigned long durumZamani = 0; // duruma giriş anı / when the state began
50bool evdeModu = false; // true: PIR devre dışı / true: PIR bypassed
51bool sirenAcik = false;
52bool pirHazir = false;
53const char* tetikleyen = ""; // alarmı başlatan bölge / zone that caused the alarm
54char girilen[9] = ""; // girilen şifre, en fazla 8 hane / typed code, max 8 digits
55byte haneSayisi = 0;
56byte yanlisSayisi = 0;
57bool kilitli = false;
58unsigned long kilitZamani = 0;
59unsigned long bipZamani = 0, bipSuresi = 0;
60unsigned long darbe[3] = {0, 0, 0}; // son 3 titreşim olayı / last 3 vibration events
61int oncekiTitresim = LOW;
62
63// Okuma "sure" ms boyunca aynı kalırsa kabul edilir / a reading is accepted once it stays the same for "sure" ms
64bool suz(Giris &g, bool okuma, unsigned long sure) {
65 unsigned long simdi = millis();
66 if (okuma != g.ham) {
67 g.ham = okuma;
68 g.degisim = simdi;
69 }
70 if (simdi - g.degisim >= sure) g.kararli = g.ham;
71 return g.kararli;
72}
73
74// 3 sn içinde 3 ayrı darbe = zorlama; tek vuruş alarm vermez / 3 separate hits within 3 s = forcing; a single knock is ignored
75bool titresimVar(unsigned long simdi) {
76 int okuma = digitalRead(TITRESIM_PIN);
77 bool sonuc = false;
78 if (okuma == HIGH && oncekiTitresim == LOW && simdi - darbe[2] > 150) { // 150 ms içindeki sekmeler tek olay / bounces within 150 ms count once
79 darbe[0] = darbe[1];
80 darbe[1] = darbe[2];
81 darbe[2] = simdi;
82 if (darbe[0] != 0 && simdi - darbe[0] <= 3000) sonuc = true;
83 }
84 oncekiTitresim = okuma;
85 return sonuc;
86}
87
88void bip(unsigned long ms) { // kısa = tuş sesi, uzun = hata / short = key click, long = error
89 bipZamani = millis();
90 bipSuresi = ms;
91}
92
93void sirenYaz(bool acik) {
94 sirenAcik = acik;
95 if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
96 else digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
97}
98
99void durumDegis(Durum yeni) {
100 durum = yeni;
101 durumZamani = millis();
102 Serial.print(F("Durum / state: "));
103 Serial.println(DURUM_ADI[yeni]);
104}
105
106void alarmVer(const char* bolge) {
107 tetikleyen = bolge;
108 sirenYaz(true);
109 durumDegis(ALARM);
110 Serial.print(F("ALARM! Bolge / zone: "));
111 Serial.println(bolge);
112}
113
114bool kurulabilirMi() { // açık kapı ya da kesik ışınla kurulmaz / never arm with an open door or a broken beam
115 if (gKapi.kararli) {
116 Serial.println(F("Kapi acik, kurulamaz / door open, cannot arm"));
117 return false;
118 }
119 if (LAZER_VAR && gLazer.kararli) {
120 Serial.println(F("Lazer kesik, kurulamaz / beam broken, cannot arm"));
121 return false;
122 }
123 return true;
124}
125
126void kur(bool evde) {
127 if (!kurulabilirMi()) {
128 bip(800);
129 return;
130 }
131 evdeModu = evde;
132 for (int i = 0; i < 3; i++) darbe[i] = 0;
133 tetikleyen = "";
134 durumDegis(CIKIS_GECIKMESI);
135 Serial.println(evde ? F("Evde modu: PIR devre disi / stay mode: PIR bypassed") : F("Tam kurulum / away mode"));
136}
137
138void coz() {
139 sirenYaz(false);
140 if (tetikleyen[0] != '\0') { // alarm hafızası / alarm memory
141 Serial.print(F("Alarm hafizasi / alarm memory: "));
142 Serial.println(tetikleyen);
143 }
144 durumDegis(COZULU);
145 bip(150);
146}
147
148void sensorRaporu() {
149 Serial.print(F("Kapi / door: "));
150 Serial.print(gKapi.kararli ? F("ACIK / open") : F("kapali / closed"));
151 Serial.print(F(" PIR: "));
152 Serial.print(digitalRead(PIR_PIN));
153 Serial.print(F(" Lazer / laser: "));
154 Serial.println(analogRead(LAZER_PIN));
155}
156
157void tusIsle(char t) {
158 if (t >= '0' && t <= '9') {
159 if (haneSayisi < 8) {
160 girilen[haneSayisi++] = t;
161 girilen[haneSayisi] = '\0';
162 }
163 bip(30);
164 return;
165 }
166 if (t == '*' || t == 'C') { // girilen kodu sil / clear the typed code
167 haneSayisi = 0;
168 girilen[0] = '\0';
169 bip(30);
170 return;
171 }
172 if (t == 'D') { // bölge testi / zone test
173 sensorRaporu();
174 return;
175 }
176
177 // A, B ve # önce şifre ister / A, B and # need the code first
178 bool dogru = strcmp(girilen, SIFRE) == 0;
179 haneSayisi = 0;
180 girilen[0] = '\0';
181 if (kilitli) {
182 Serial.println(F("Tus takimi kilitli / keypad locked"));
183 bip(800);
184 return;
185 }
186 if (!dogru) {
187 Serial.println(F("Yanlis sifre / wrong code"));
188 if (++yanlisSayisi >= 3) {
189 kilitli = true;
190 kilitZamani = millis();
191 yanlisSayisi = 0;
192 }
193 bip(800);
194 return;
195 }
196 yanlisSayisi = 0;
197 if (durum == COZULU && t == 'A') kur(false); // tam kurulum / away
198 else if (durum == COZULU && t == 'B') kur(true); // evde modu / stay
199 else if (durum != COZULU && t == '#') coz(); // çöz / disarm
200 else bip(800);
201}
202
203void cikislariGuncelle() {
204 unsigned long simdi = millis();
205 unsigned long gecen = simdi - durumZamani;
206 bool buzzer = simdi - bipZamani < bipSuresi; // tuş sesi / key click
207 bool kirmizi = false;
208
209 switch (durum) {
210 case CIKIS_GECIKMESI: {
211 unsigned long periyot = (gecen > CIKIS_SURE - 10000) ? 250 : 1000; // son 10 sn hızlanır / faster in the last 10 s
212 buzzer = buzzer || (gecen % periyot) < 60;
213 kirmizi = (gecen % 1000) < 500;
214 break;
215 }
216 case KURULU:
217 kirmizi = true;
218 break;
219 case GIRIS_GECIKMESI:
220 buzzer = buzzer || (gecen % 400) < 200; // "şifreni gir" uyarısı / "enter your code" warning
221 kirmizi = (gecen % 400) < 200;
222 break;
223 case ALARM:
224 buzzer = buzzer || sirenAcik;
225 kirmizi = (gecen % 200) < 100; // hızlı yanıp sönme = alarm hafızası / fast blink = alarm memory
226 break;
227 default:
228 break;
229 }
230 bool hazir = !gKapi.kararli && !(LAZER_VAR && gLazer.kararli);
231 digitalWrite(BUZZER_PIN, buzzer ? HIGH : LOW);
232 digitalWrite(KIRMIZI_LED, kirmizi ? HIGH : LOW);
233 digitalWrite(YESIL_LED, (durum == COZULU && hazir) ? HIGH : LOW);
234}
235
236void setup() {
237 Serial.begin(9600);
238 sirenYaz(false); // pin çıkış olmadan önce "kapalı" yaz, açılışta röle çekmez / write "off" first so the relay doesn't click at boot
239 pinMode(ROLE_PIN, OUTPUT);
240 pinMode(BUZZER_PIN, OUTPUT);
241 pinMode(KIRMIZI_LED, OUTPUT);
242 pinMode(YESIL_LED, OUTPUT);
243 pinMode(PIR_PIN, INPUT);
244 pinMode(KAPI_PIN, INPUT_PULLUP); // kapı kapalı = LOW; kablo kesilirse HIGH / door closed = LOW; a cut wire reads HIGH
245 pinMode(TITRESIM_PIN, INPUT);
246 keypad.setDebounceTime(20);
247 durumDegis(COZULU);
248 Serial.println(F("PIR 60 sn isiniyor / PIR warming up for 60 s"));
249}
250
251void loop() {
252 char t = keypad.getKey();
253 if (t) tusIsle(t);
254
255 unsigned long simdi = millis();
256 if (kilitli && simdi - kilitZamani >= KILIT_SURE) kilitli = false;
257 if (!pirHazir && simdi >= PIR_ISINMA) pirHazir = true;
258
259 bool kapiAcik = suz(gKapi, digitalRead(KAPI_PIN) == HIGH, 50);
260 bool hareket = suz(gPir, digitalRead(PIR_PIN) == HIGH, 200) && pirHazir;
261 bool isinKesik = LAZER_VAR && suz(gLazer, analogRead(LAZER_PIN) < LAZER_ESIK, 30);
262 bool titresim = titresimVar(simdi);
263
264 switch (durum) {
265 case CIKIS_GECIKMESI:
266 if (simdi - durumZamani >= CIKIS_SURE) durumDegis(KURULU);
267 break;
268 case KURULU: // anlık bölgeler önce / instant zones first
269 if (titresim) alarmVer("Pencere (titresim) / window (vibration)");
270 else if (isinKesik) alarmVer("Lazer bariyeri / laser beam");
271 else if (hareket && !evdeModu) alarmVer("Ic mekan (PIR) / interior (PIR)");
272 else if (kapiAcik) durumDegis(GIRIS_GECIKMESI);
273 break;
274 case GIRIS_GECIKMESI: // PIR ve lazer giriş süresince bekler / PIR and laser wait during the entry delay
275 if (titresim) alarmVer("Pencere (titresim) / window (vibration)");
276 else if (simdi - durumZamani >= GIRIS_SURE) alarmVer("Giris kapisi / entry door");
277 break;
278 case ALARM:
279 if (sirenAcik && simdi - durumZamani >= SIREN_SURE) {
280 sirenYaz(false); // siren susar, hafıza kalır / siren stops, memory stays
281 Serial.println(F("Siren sustu, sifre bekleniyor / siren off, waiting for the code"));
282 }
283 break;
284 default:
285 break;
286 }
287 cikislariGuncelle();
288}Основа кода — функция suz() (фильтр). Каждый раз, когда «сырое» значение входа меняется, запоминается отметка времени, и устойчивое состояние не меняется, пока значение не продержится заданное время (50 мс для двери, 200 мс для PIR, 30 мс для лазера). Этот простой фильтр не даёт коротким помехам на длинных кабелях и дребезгу контактов включить тревогу. Чтение пина двери с INPUT_PULLUP — тоже сознательный выбор: когда дверь закрыта, геркон соединяет пин с GND, и читается LOW; когда дверь открывают или кто-то перерезает кабель, на пине появляется HIGH. Эта логика замкнутого шлейфа, как в настоящих сигнализациях, считает перерезанный провод тем же, что и открытую дверь.
С вибрацией всё иначе. Даже от одного удара SW-420 несколько раз «подпрыгивает» за считаные миллисекунды; titresimVar() (есть ли вибрация) сначала считает все отскоки в пределах 150 мс одним событием, а затем хранит время трёх последних событий. Если три события укладываются в 3 секунды, это считается настоящей попыткой взломать окно; ветер, стукнувший дверью, или мяч, один раз попавший в стекло, тревогу не вызовут. Сирена сама замолкает через 3 минуты, но система остаётся в состоянии ALARM, а красный светодиод часто мигает, сообщая тому, кто вернётся домой, что была тревога. После ввода правильного кода название зоны из переменной tetikleyen (сработавшая зона) выводится в монитор порта; это маленькая версия памяти тревог коммерческих панелей.
Как настроить задержки на вход и выход
Если задержки слишком короткие, сигнализация сработает на тебя же; если слишком длинные — у взломщика будет время. Задержку на выход сделай вдвое больше времени, которое нужно, чтобы дойти от клавиатуры до двери и запереть её, а задержку на вход — примерно вдвое больше времени, чтобы открыть дверь, дойти до клавиатуры и ввести код. В коммерческих панелях обычно ставят 30–60 секунд на выход и 15–30 секунд на вход. Если повесить клавиатуру прямо у двери, но так, чтобы её не было видно снаружи, задержку на вход можно оставить короткой. Важно и поведение зуммера: при выходе короткий сигнал раз в секунду ускоряется в последние 10 секунд, чтобы чувствовалось, что время уходит; при входе прерывистый сигнал по 200 мс напоминает о вводе кода.
Подключение сирены и реле
В цепи сирены Arduino управляет только катушкой реле, а ток сирены 12 V идёт через контакты реле. Большинство модулей реле — с активным LOW, то есть включаются, когда на пине IN уровень LOW; поэтому в коде есть константа ROLE_AKTIF_LOW. Код записывает уровень «выключено» до того, как сделать пин выходом, так что при загрузке Arduino реле не щёлкает на мгновение и сирена не взвизгивает. Выбирая сирену, смотри на её ток: небольшие сирены на 12 V и 110 dB обычно потребляют 0,1–0,3 A — пустяковая нагрузка для контакта реле на 10 A. Уличная сирена со стробоскопом отпугивает и звуком, и светом и показывает соседям, откуда идёт тревога. Профессиональные панели обычно выключают сирену через 3 минуты, а в некоторых странах на это есть даже законное ограничение, поэтому держи SIREN_SURE (время сирены) в разумных пределах. Если вместо реле нужен полупроводниковый ключ, подойдёт MOSFET с логическим уровнем управления, а для сирен с катушкой (электромагнитных) добавь диод параллельно выводам в обратном направлении.
Уведомления в Telegram с ESP32
Сирена, которую никто не слышит, бесполезна; проще всего узнать о сработавшей сигнализации вдали от дома с помощью ESP32 и Telegram. ESP32 подключается к твоему Wi-Fi и отправляет сообщения на сервер Telegram по зашифрованному соединению, а ещё ей можно отправлять команды. Как создать бота через BotFather, получить токен и свой chat ID (ID чата) и установить библиотеки UniversalTelegramBot и ArduinoJson, мы пошагово объяснили в статье Telegram-бот на ESP32. В этой версии клавиатуры нет; системой управляешь с телефона командами /kur (полная охрана), /evde (режим «Дома»), /coz (снять с охраны) и /durum (состояние). Бот отвечает только на твой chat ID и молча игнорирует команды всех остальных.
1// ESP32 alarm sistemi + Telegram: kurma/çözme komutları ve anlık bildirim / ESP32 alarm + Telegram: arm/disarm commands and instant alerts
2#include <WiFi.h>
3#include <WiFiClientSecure.h>
4#include <UniversalTelegramBot.h> // ArduinoJson kütüphanesi de gerekir / also needs ArduinoJson
5#include <Preferences.h> // kurulu durumu elektrik kesintisinde unutmamak için / remembers the armed state across power cuts
6
7const char* WIFI_SSID = "AgAdi"; // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
8const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // BotFather'dan / from BotFather
10#define CHAT_ID "123456789" // yalnız bu ID komut verebilir / only this ID may send commands
11
12const int PIR_PIN = 27; // HC-SR501 OUT
13const int KAPI_PIN = 33; // reed, diğer ucu GND / reed, other leg to GND
14const int TITRESIM_PIN = 32; // SW-420 DO
15const int BUZZER_PIN = 25; // aktif buzzer / active buzzer
16const int ROLE_PIN = 26; // siren rölesi / siren relay
17const int LED_PIN = 2; // dahili LED: kurulu / onboard LED: armed
18const bool ROLE_AKTIF_LOW = true; // çoğu röle modülü LOW'da çeker / most relay modules switch on with LOW
19
20const unsigned long CIKIS_SURE = 30000; // çıkış gecikmesi / exit delay
21const unsigned long GIRIS_SURE = 20000; // giriş gecikmesi / entry delay
22const unsigned long SIREN_SURE = 180000; // siren en fazla 3 dk / siren for at most 3 min
23const unsigned long PIR_ISINMA = 60000; // PIR ısınma süresi / PIR warm-up time
24const unsigned long KONTROL_ARALIGI = 2000; // Telegram'a 2 sn'de bir sor / poll Telegram every 2 s
25
26WiFiClientSecure client;
27UniversalTelegramBot bot(BOT_TOKEN, client);
28Preferences ayar;
29
30enum Durum { COZULU, CIKIS_GECIKMESI, KURULU, GIRIS_GECIKMESI, ALARM };
31const char* const DURUM_ADI[] = { "ÇÖZÜLÜ / DISARMED", "ÇIKIŞ GECİKMESİ / EXIT DELAY", "KURULU / ARMED", "GİRİŞ GECİKMESİ / ENTRY DELAY", "ALARM" };
32
33struct Giris { // gürültü süzgeçli giriş / debounced input
34 bool ham;
35 bool kararli;
36 unsigned long degisim;
37};
38Giris gKapi = {false, false, 0};
39Giris gPir = {false, false, 0};
40
41Durum durum = COZULU;
42unsigned long durumZamani = 0;
43bool evdeModu = false;
44bool sirenAcik = false;
45bool pirHazir = false;
46String tetikleyen = "";
47String bekleyen = ""; // gönderilemeyen bildirimler / notifications not sent yet
48unsigned long sonDeneme = 0;
49unsigned long sonKontrol = 0;
50
51volatile unsigned long darbe[3] = {0, 0, 0}; // son 3 titreşim olayı / last 3 vibration events
52volatile bool zorlama = false; // 3 sn'de 3 darbe oldu / 3 hits within 3 s
53
54// Kesme: Telegram beklerken de kısa darbeler kaçmaz / interrupt: short pulses are caught even while Telegram blocks
55void IRAM_ATTR titresimKesmesi() {
56 unsigned long simdi = millis();
57 if (simdi - darbe[2] > 150) { // 150 ms içindeki sekmeler tek olay / bounces within 150 ms count once
58 darbe[0] = darbe[1];
59 darbe[1] = darbe[2];
60 darbe[2] = simdi;
61 if (darbe[0] != 0 && simdi - darbe[0] <= 3000) zorlama = true;
62 }
63}
64
65bool suz(Giris &g, bool okuma, unsigned long sure) {
66 unsigned long simdi = millis();
67 if (okuma != g.ham) {
68 g.ham = okuma;
69 g.degisim = simdi;
70 }
71 if (simdi - g.degisim >= sure) g.kararli = g.ham;
72 return g.kararli;
73}
74
75void sirenYaz(bool acik) {
76 sirenAcik = acik;
77 if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
78 else digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
79}
80
81void bekleyeniGonder() {
82 sonDeneme = millis();
83 if (WiFi.status() != WL_CONNECTED) {
84 WiFi.reconnect(); // bağlantı koptuysa yeniden dene / reconnect if the link dropped
85 return;
86 }
87 if (bot.sendMessage(CHAT_ID, bekleyen, "")) bekleyen = "";
88}
89
90void bildir(const String& metin) { // gönderemezse 15 sn sonra yeniden dener / retries after 15 s if sending fails
91 Serial.println(metin);
92 bekleyen += metin + "\n";
93 if (bekleyen.length() > 1200) bekleyen = bekleyen.substring(bekleyen.length() - 1200);
94 bekleyeniGonder();
95}
96
97void durumDegis(Durum yeni) {
98 durum = yeni;
99 durumZamani = millis();
100 if (yeni == KURULU || yeni == COZULU) { // yalnız kurma ve çözmede yazılır / written only on arm and disarm
101 ayar.putBool("kurulu", yeni == KURULU);
102 ayar.putBool("evde", evdeModu);
103 }
104}
105
106void alarmVer(const String& bolge) {
107 tetikleyen = bolge;
108 sirenYaz(true); // önce siren, sonra mesaj / siren first, message second
109 durumDegis(ALARM);
110 bildir("ALARM! " + bolge);
111}
112
113void kur(bool evde) {
114 if (gKapi.kararli) {
115 bildir("Kapı açık, kurulamadı / door open, not armed");
116 return;
117 }
118 evdeModu = evde;
119 for (int i = 0; i < 3; i++) darbe[i] = 0;
120 zorlama = false;
121 tetikleyen = "";
122 durumDegis(CIKIS_GECIKMESI);
123 bildir(evde ? "Evde modu, 30 sn sonra kurulu (PIR kapalı) / stay mode, armed in 30 s (PIR off)"
124 : "Tam kurulum, 30 sn içinde çık / away mode, leave within 30 s");
125}
126
127void coz() {
128 sirenYaz(false);
129 String m = "Alarm çözüldü / disarmed";
130 if (tetikleyen.length()) m += "\nAlarm hafızası / memory: " + tetikleyen;
131 tetikleyen = "";
132 durumDegis(COZULU);
133 bildir(m);
134}
135
136String durumMetni() {
137 String s = String("Durum / state: ") + DURUM_ADI[durum];
138 if (evdeModu && durum != COZULU) s += " (evde / stay)";
139 s += "\nKapı / door: ";
140 s += gKapi.kararli ? "AÇIK / open" : "kapalı / closed";
141 s += "\nWi-Fi: " + String(WiFi.RSSI()) + " dBm";
142 return s;
143}
144
145void mesajlariIsle(int adet) {
146 for (int i = 0; i < adet; i++) {
147 String chatId = bot.messages[i].chat_id;
148 String metin = bot.messages[i].text;
149 if (chatId != CHAT_ID) continue; // yabancıya yanıt yok / strangers get no answer
150
151 int at = metin.indexOf('@'); // "/kur@bot_adi" -> "/kur"
152 if (at > 0) metin = metin.substring(0, at);
153
154 if (metin == "/kur" && durum == COZULU) kur(false);
155 else if (metin == "/evde" && durum == COZULU) kur(true);
156 else if (metin == "/coz" && durum != COZULU) coz();
157 else if (metin == "/durum") bildir(durumMetni());
158 else bot.sendMessage(chatId, "/kur /evde /coz /durum\n" + durumMetni(), "");
159 }
160}
161
162void setup() {
163 Serial.begin(115200);
164 pinMode(ROLE_PIN, OUTPUT);
165 sirenYaz(false); // açılışta siren kapalı / siren off at boot
166 pinMode(BUZZER_PIN, OUTPUT);
167 pinMode(LED_PIN, OUTPUT);
168 pinMode(PIR_PIN, INPUT);
169 pinMode(KAPI_PIN, INPUT_PULLUP); // kapı kapalı = LOW, açık ya da kablo kesik = HIGH / closed = LOW, open or cut = HIGH
170 pinMode(TITRESIM_PIN, INPUT);
171 attachInterrupt(digitalPinToInterrupt(TITRESIM_PIN), titresimKesmesi, RISING);
172
173 ayar.begin("alarm", false);
174 if (ayar.getBool("kurulu", false)) { // kesintiden önce kuruluysa kurulu başla / start armed if it was armed before the outage
175 evdeModu = ayar.getBool("evde", false);
176 durumDegis(KURULU);
177 }
178
179 WiFi.mode(WIFI_STA);
180 WiFi.begin(WIFI_SSID, WIFI_PASS);
181 unsigned long bas = millis();
182 while (WiFi.status() != WL_CONNECTED && millis() - bas < 20000) delay(500); // Wi-Fi yoksa alarm yine çalışır / the alarm works without Wi-Fi too
183 client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org kök sertifikası / root certificate of api.telegram.org
184 if (WiFi.status() == WL_CONNECTED) {
185 while (bot.getUpdates(bot.last_message_received + 1)) { } // kapalıyken gelen eski komutları atla / skip commands sent while off
186 }
187 bildir(String("Sistem açıldı (elektrik geldi mi?) / system started (power restored?)\n") + durumMetni());
188}
189
190void loop() {
191 unsigned long simdi = millis();
192 if (!pirHazir && simdi >= PIR_ISINMA) pirHazir = true;
193 bool kapiAcik = suz(gKapi, digitalRead(KAPI_PIN) == HIGH, 50);
194 bool hareket = suz(gPir, digitalRead(PIR_PIN) == HIGH, 200) && pirHazir;
195 bool titresim = zorlama;
196 zorlama = false;
197
198 switch (durum) {
199 case CIKIS_GECIKMESI:
200 if (simdi - durumZamani >= CIKIS_SURE) {
201 durumDegis(KURULU);
202 bildir("Sistem kuruldu / system armed");
203 }
204 break;
205 case KURULU:
206 if (titresim) alarmVer("Pencere (titreşim) / window (vibration)");
207 else if (hareket && !evdeModu) alarmVer("İç mekân (PIR) / interior (PIR)");
208 else if (kapiAcik) {
209 durumDegis(GIRIS_GECIKMESI);
210 bildir("Kapı açıldı, 20 sn giriş süresi / door opened, 20 s entry delay");
211 }
212 break;
213 case GIRIS_GECIKMESI:
214 if (titresim) alarmVer("Pencere (titreşim) / window (vibration)");
215 else if (simdi - durumZamani >= GIRIS_SURE) alarmVer("Giriş kapısı / entry door");
216 break;
217 case ALARM:
218 if (sirenAcik && simdi - durumZamani >= SIREN_SURE) {
219 sirenYaz(false);
220 bildir("Siren sustu, alarm hafızada / siren stopped, alarm kept in memory");
221 }
222 break;
223 default:
224 break;
225 }
226
227 unsigned long gecen = millis() - durumZamani;
228 bool buzzer = false;
229 if (durum == CIKIS_GECIKMESI) buzzer = (gecen % 1000) < 60;
230 else if (durum == GIRIS_GECIKMESI) buzzer = (gecen % 400) < 200;
231 else if (durum == ALARM) buzzer = sirenAcik;
232 digitalWrite(BUZZER_PIN, buzzer ? HIGH : LOW);
233 digitalWrite(LED_PIN, (durum == KURULU || durum == ALARM) ? HIGH : LOW);
234
235 if (millis() - sonKontrol > KONTROL_ARALIGI && WiFi.status() == WL_CONNECTED) {
236 int adet = bot.getUpdates(bot.last_message_received + 1);
237 while (adet) {
238 mesajlariIsle(adet);
239 adet = bot.getUpdates(bot.last_message_received + 1);
240 }
241 sonKontrol = millis();
242 }
243 if (bekleyen.length() && millis() - sonDeneme > 15000) bekleyeniGonder();
244}Код для ESP32 отличается в трёх важных местах. Во-первых, отправка сообщения в Telegram или запрос новых команд может занять 1–2 секунды, и всё это время loop() ждёт. Открытой двери и PIR, который держит HIGH хотя бы несколько секунд, это не мешает, но миллисекундные импульсы SW-420 можно пропустить, поэтому вибрация ловится прерыванием (attachInterrupt). Во-вторых, alarmVer() (поднять тревогу) сначала включает сирену и только потом отправляет сообщение, так что, каким бы медленным ни был интернет, сирена не запаздывает. Неотправленные уведомления ставятся в очередь и повторяются каждые 15 секунд. В-третьих, библиотека Preferences сохраняет во флеш-памяти, стоит ли система на охране: если электричество пропадёт и вернётся во время охраны, ESP32 загрузится уже на охране и сообщит, что питание восстановлено. Если реле и сирена делят с ESP32 один слабый источник питания, плата может перезагружаться с ошибкой Brownout detector was triggered (сработал детектор просадки питания); решение — в статье ESP32 постоянно перезагружается.
Сигнализация на Raspberry Pi Pico и MicroPython
Тот же конечный автомат можно собрать на Raspberry Pi Pico в короткой программе на MicroPython. Клавиатура читается функцией сканирования строк и столбцов из руководства по матричной клавиатуре; строки на GP2–GP5, а столбцы на GP6–GP9 со встроенной подтяжкой. PIR питай от VBUS (5 V); его выход и так 3,3 V, поэтому он подключается прямо к GP16. SW-420 всегда питай от пина 3V3(OUT): выход модуля с питанием 5 V может повредить вход Pico. Импульсы вибрации ловятся через Pin.irq, их время хранится в списке, а состояния отслеживаются простыми строками. Время считается через time.ticks_diff(), потому что эта функция даёт правильную разницу даже при переполнении таймера.
1# Raspberry Pi Pico alarm: keypad + PIR + kapı + titreşim, durum makinesi / Pico alarm: keypad + PIR + door + vibration, state machine
2from machine import Pin
3import time
4
5SIFRE = "2580" # kendi şifreni yaz / set your own code
6CIKIS_MS, GIRIS_MS, SIREN_MS = 30000, 20000, 180000
7
8KEYS = ["123A", "456B", "789C", "*0#D"]
9rows = [Pin(n, Pin.OUT, value=1) for n in (2, 3, 4, 5)] # R1–R4
10cols = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (6, 7, 8, 9)] # C1–C4
11pir = Pin(16, Pin.IN) # HC-SR501 OUT (3,3 V)
12kapi = Pin(15, Pin.IN, Pin.PULL_UP) # reed, diğer ucu GND / reed, other leg to GND
13titresim = Pin(17, Pin.IN) # SW-420 DO, modül 3V3'ten / module powered from 3V3
14buzzer = Pin(14, Pin.OUT, value=0) # aktif buzzer / active buzzer
15role = Pin(13, Pin.OUT, value=1) # aktif LOW röle: 1 = kapalı / active-LOW relay: 1 = off
16led = Pin("LED", Pin.OUT) # dahili LED: kurulu / onboard LED: armed
17
18darbeler = [] # son titreşim zamanları / recent vibration times
19son_darbe = 0
20
21
22def titresim_irq(p):
23 global son_darbe
24 simdi = time.ticks_ms()
25 if time.ticks_diff(simdi, son_darbe) > 150: # sekmeler tek olay / bounces count once
26 son_darbe = simdi
27 darbeler.append(simdi)
28
29
30titresim.irq(trigger=Pin.IRQ_RISING, handler=titresim_irq)
31
32
33def tus_oku():
34 for r, row in enumerate(rows):
35 row.value(0) # bu satırı etkinleştir / activate this row
36 for c, col in enumerate(cols):
37 if col.value() == 0:
38 row.value(1)
39 return KEYS[r][c]
40 row.value(1)
41 return None
42
43
44durum = "COZULU"
45zaman = time.ticks_ms()
46acilis = time.ticks_ms()
47pir_hazir = False
48evde = False
49girilen = ""
50son_tus = None
51
52
53def degis(yeni):
54 global durum, zaman
55 durum, zaman = yeni, time.ticks_ms()
56 print("Durum / state:", yeni)
57
58
59def alarm(bolge):
60 role.value(0) # siren açık / siren on
61 degis("ALARM")
62 print("ALARM! Bolge / zone:", bolge)
63
64
65while True:
66 tus = tus_oku()
67 if tus != son_tus: # değişimi 20 ms sonra doğrula / confirm the change after 20 ms
68 time.sleep_ms(20)
69 if tus_oku() != tus:
70 tus = son_tus # sekme, yok say / contact bounce, ignore it
71 if tus and tus != son_tus: # her basış bir kez / each press once
72 if tus in "0123456789":
73 girilen = (girilen + tus)[-8:]
74 elif tus in "*C":
75 girilen = ""
76 else:
77 dogru = girilen == SIFRE
78 girilen = ""
79 if not dogru:
80 print("Yanlis sifre / wrong code")
81 elif durum == "COZULU" and tus in "AB":
82 if kapi.value():
83 print("Kapi acik, kurulamaz / door open, cannot arm")
84 else:
85 evde = tus == "B" # B = evde modu, PIR kapalı / B = stay mode, PIR off
86 darbeler.clear()
87 degis("CIKIS")
88 elif durum != "COZULU" and tus == "#":
89 role.value(1)
90 degis("COZULU")
91 son_tus = tus
92
93 simdi = time.ticks_ms()
94 gecen = time.ticks_diff(simdi, zaman)
95 if not pir_hazir and time.ticks_diff(simdi, acilis) > 60000:
96 pir_hazir = True # PIR ısındı / PIR warmed up
97 kapi_acik = kapi.value() == 1
98 hareket = pir_hazir and pir.value() == 1 and not evde
99 darbeler[:] = [t for t in darbeler if time.ticks_diff(simdi, t) < 3000]
100 zorlama = len(darbeler) >= 3 # 3 sn'de 3 darbe / 3 hits in 3 s
101
102 if durum == "CIKIS" and gecen >= CIKIS_MS:
103 degis("KURULU")
104 elif durum == "KURULU":
105 if zorlama:
106 alarm("Pencere / window")
107 elif hareket:
108 alarm("PIR")
109 elif kapi_acik:
110 degis("GIRIS")
111 elif durum == "GIRIS":
112 if zorlama:
113 alarm("Pencere / window")
114 elif gecen >= GIRIS_MS:
115 alarm("Kapi / door")
116 elif durum == "ALARM" and role.value() == 0 and gecen >= SIREN_MS:
117 role.value(1) # siren susar, hafıza kalır / siren stops, memory stays
118
119 gecen = time.ticks_diff(time.ticks_ms(), zaman)
120 if durum == "CIKIS":
121 buzzer.value(1 if gecen % 1000 < 60 else 0)
122 elif durum == "GIRIS":
123 buzzer.value(1 if gecen % 400 < 200 else 0)
124 else:
125 buzzer.value(1 if durum == "ALARM" and role.value() == 0 else 0)
126 led.value(1 if durum in ("KURULU", "ALARM") else 0)
127 time.sleep_ms(10)Как уменьшить число ложных тревог
Главный враг сигнализации — не взломщик, а ложная тревога: после трёх срабатываний впустую сирену всерьёз не будут воспринимать ни соседи, ни ты. Часть мер ниже уже есть в коде, остальные касаются монтажа и проводки:
- Дождись прогрева: Первые 30–60 секунд после включения PIR может срабатывать сам по себе; код игнорирует первые 60 секунд.
- Используй фильтр: Ожидание, пока каждый вход продержится в устойчивом состоянии заданное время (антидребезг), отсеивает короткие помехи на кабелях.
- Считай удары: Если ждать 3 удара за 3 секунды, а не один стук, отсеиваются ветер, проезжающие грузовики и хлопающие двери. При необходимости увеличь число до 4–5 или уменьши чувствительность подстроечным резистором на плате.
- Аккуратно размести PIR: Не направляй его на батареи отопления, кондиционеры, окна на солнечной стороне и шторы; лучше всего он замечает движение, которое пересекает поле зрения сбоку. Если дома есть питомцы, повесь датчик повыше, слегка наклони вниз и уменьши чувствительность.
- Требуй два датчика: В критичных местах условие, что PIR и лазер или PIR и дверь сработают с разницей в несколько секунд (перекрёстные зоны, cross-zoning), не даёт одному неисправному датчику вызвать тревогу.
- Очисти питание: Просадка питания при включении реле может вызвать срабатывание PIR. Поставь конденсаторы 100 µF и 100 nF между VCC и GND каждого датчика и прокладывай провода реле и сирены отдельно от проводов датчиков.
- На длинных кабелях ставь внешние подтягивающие резисторы: Встроенная подтяжка Arduino — это 20–50 kΩ, и на 10-метровом кабеле она ловит наводки; внешний резистор 4,7 kΩ плюс конденсатор 100 nF на пине делают линию гораздо устойчивее.
- Защити лазер от внешнего света: Помести LDR в чёрную трубку и проверь порог отдельно днём и ночью.
Частые ошибки и их решения
- Тревога сразу после постановки на охрану: PIR ещё прогревается или датчик двери подключён неправильно. В тесте датчиков закрытая дверь должна давать LOW; если читается HIGH, магнит слишком далеко или оборван провод.
- Клавиатура не реагирует или читает не ту клавишу: Перепутаны провода строк и столбцов. Вместо перепайки поменяй порядок в массивах satirPinleri и sutunPinleri в коде.
- При постановке на охрану звучит длинный сигнал: Это не неисправность, а предупреждение о неготовности: открыта дверь или лазерный луч не доходит до LDR. Нажми D, чтобы проверить зоны.
- Реле на мгновение щёлкает при включении: Уровень «выключено» не записан до того, как пин сделан выходом, или для реле с активным HIGH оставлено ROLE_AKTIF_LOW = true.
- Arduino перезагружается, когда звучит сирена: Сирена и Arduino делят один слабый адаптер, и просадка напряжения перезапускает плату. Возьми адаптер помощнее, отдельный преобразователь и провод потолще; подробности — в статье Arduino постоянно перезагружается.
- Датчик вибрации не срабатывает никогда или срабатывает всё время: Медленно вращая подстроечный резистор на плате, настрой порог и плотно приклей датчик к стеклу или раме; плохо закреплённый датчик не передаёт удар.
- ESP32 не отправляет сообщения: Неверный токен или chat ID, сеть на 5 GHz или бот в Telegram так и не запущен. Пока боту хотя бы раз не отправлена команда /start, он не может писать тебе.
- Лазер днём работает нормально, а ночью даёт ложные тревоги (или наоборот): Порог слишком близок к уровню внешнего освещения. Измерь значения с лучом и без луча в обоих условиях и выбери новый LAZER_ESIK посередине.
Идеи для доработки сигнализации
- Постановка и снятие картой: Добавь рядом с клавиатурой считыватель RFID RC522 и снимай систему с охраны брелоком.
- Круглосуточные (24-часовые) зоны: Добавь датчик газа MQ-2 или датчик пламени и сделай зону газа и пожара, которая поднимает тревогу даже при снятой охране.
- Беспроводные датчики двери: Для дальних окон собери датчики на батарейках с радиомодулями 433 MHz.
- Панель с дисплеем: Выводи обратный отсчёт, состояние и память тревог на LCD с I2C.
- Интеграция с умным домом: Подключи ESP32 к MQTT и Home Assistant и управляй сигнализацией вместе со светом и камерами.
- Тампер (датчик вскрытия): Поставь микропереключатель на крышку корпуса, чтобы при вскрытии коробки звучал зуммер даже при снятой охране.
- Проект с макетом дома: Собери все эти зоны в макете дома как охранный уровень проекта умного дома.
Сигнализация на Arduino объединяет в одном проекте чтение датчиков, работу со временем, конечный автомат и проектирование питания, поэтому это ещё и отличный способ учиться. Сначала запусти тест датчиков, потом несколько дней проверяй сигнализацию только с зуммером, а сирену подключай в последнюю очередь. В приложении BlueGrays есть такие проекты, как «Датчик двери на герконе», «Лазерная охранная система», «Кодовый замок с клавиатурой 4x4 и LCD» и «Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)» с уведомлениями в Telegram, и у каждого есть пошаговые схемы и код.
Частые вопросы
Как сделать сигнализацию на Arduino?
К Arduino Uno подключаются PIR-датчик движения, геркон для двери, клавиатура, активный зуммер и модуль реле, который управляет сиреной. Код пишется в виде конечного автомата, который переводит систему между состояниями «снято с охраны», «задержка на выход», «на охране», «задержка на вход» и «тревога». Ставить на охрану и снимать с неё можно кодом на клавиатуре, а если на охране срабатывает датчик, звучит сирена.
Какие датчики используются в сигнализации на Arduino?
Чаще всего это PIR-датчик движения (HC-SR501) для помещений, герконовые датчики (MC-38) для дверей и окон, датчик вибрации SW-420 для ударов по стеклу и лазерный барьер для коридоров. Можно добавить MQ-2 для газа и датчик пламени для пожара. Каждый датчик задаётся как отдельная зона со своим поведением.
Работает ли сигнализация на Arduino при отключении электричества?
Нет, если она питается только от адаптера. С блоком питания для сигнализации на 13,8 V, который заряжает аккумулятор 12 V 7 Ah, система на Uno с потреблением около 100 mA работает 4–5 дней. В версии для ESP32 состояние охраны сохраняется во флеш-памяти, поэтому после возвращения электричества система снова встаёт на охрану; чтобы уведомления уходили, роутеру тоже нужен ИБП.
Может ли сигнализация на Arduino присылать уведомления на телефон?
Arduino Uno сама этого не умеет, но ESP32 может через Wi-Fi мгновенно отправлять сообщения в Telegram и принимать с телефона команды вроде /kur и /coz. Там, где нет интернета, GSM-модуль SIM800L может отправить SMS или позвонить. Самый надёжный вариант — использовать оба способа вместе.
Почему PIR-датчик даёт ложные тревоги?
Обычные причины — прогрев 30–60 секунд после включения, источники тепла вроде батарей отопления и кондиционеров, шторы, которые колышутся под тёплым солнцем, и помехи по питанию при срабатывании реле. Игнорирование прогрева в коде, размещение датчика подальше от источников тепла и конденсатор между VCC и GND решают большинство таких проблем.
Можно ли использовать сигнализацию на Arduino в настоящем доме?
Она хорошо подходит для учёбы и для охраны комнаты, гаража или кладовки, но не заменяет сертифицированную систему сигнализации. Коммерческие системы проходят испытания по стандартам вроде EN 50131 на защиту от вскрытия, резервное питание, шифрованную беспроводную связь и подключение к пульту централизованного наблюдения. Если ставить систему на Arduino дома, советуем добавить резервный аккумулятор, тампер и второй канал уведомлений.
Что собрать после прочтения
Все проекты
Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)
Умная охранная сигнализация, которая при обнаружении движения PIR-датчиком включает зуммер и светодиод и одновременно сразу отправляет владельцу уведомление в Telegram. В ней есть 30-секундная пауза, ограничивающая частоту повторных тревог; этот механизм не проверяет срабатывания PIR и не устраняет ложные тревоги. Подходит для дома, гаража, склада или мастерской. С ESP32-CAM можно добавить уведомления с фото, а с обычной ESP32 DevKit V1 приходят текстовые.

Лазерная охранная система
Совсем как сцены ограблений с лазерами в фильмах: лазерный луч светит на датчик освещённости, и как только кто-то входит и прерывает луч, загорается красный светодиод и начинает звучать зуммер. Сигнал

Лазерная сигнализация
Ты ставишь невидимую лучевую ограду, совсем как в кино. Лазер светит на датчик освещённости с другой стороны; как только кто-то проходит и прерывает луч, загорается светодиод и звучит зуммер, а когда

Датчик двери на герконе
Узнай, открыта дверь или закрыта, просто взглянув на огонёк. Маленький магнит крепится на дверь, а чувствительный к магниту контакт — на раму; когда дверь закрыта, они рядом, и горит зелёный светодиод

Кодовый замок с клавиатурой 4x4 и LCD
Маленький электронный дверной замок, который не пускает тех, кто не знает кода. Ты нажимаешь клавишу решётки и вводишь код на клавиатуре; если он верный, экран сообщает, что доступ разрешён, загораетс

Детектор движения на датчике движения
Простой охранник, который сообщает, что кто-то вошёл в комнату. Датчик движения улавливает тепло человеческого тела, а Arduino держит светодиод включённым, пока есть движение, и гасит его, когда движе

Умный электронный сейф
Маленький сейф для ценных вещей, который открывается кодом. Ты вводишь на клавиатуре свой четырёхзначный код; если он верный, сервопривод открывает замок, загорается зелёный свет, а экран показывает с
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

