Как ни странно, но до сих пор у меня не было ни одного датчика из тех, которые принято относить к "охранным сигнализациям".
Но вот понадобилось поставить сигнализацию на некоторую Железную Дверь, которая должна быть всегда закрыта, если без присмотра. Весь процесс - далее.
Задача простая: сообщать, когда дверь открывается, когда закрывается, и если осталась открытой - напоминать об этом.
Но дверь железная - буквально, в том смысле что она из железных профтруб, в железной раме, а еще - она на улице, т.е. всякие варианты с микровыключателями тут не подходят, всё это будет съедено коррозией.
Поэтому за основу взял индуктивный датчик: такие обычно используются во всяких промышленных системах. Работают просто: подносишь железку - включаются, убираешь - выключаются. Продаются на маркетплейсах, довольно распространенная штука.

Вот именно такой датчик и буду использовать, установив его так, чтобы закрытая дверь приводила к срабатыванию. Сам он герметичный, а блок управления - рядом, в серой пластиковой распаечной коробке - как показала практика такие коробки прекрасно переносят условия улицы, дождь, снег, солнце.
Дрель, метчик на 3, два болтика из комплекта и кусок серой трубы для кабеля.

Питание 12 вольт уже есть, датчик работает от 10 до 30. Остается подключить к этому всему ESP, завязать в PainlessMesh, ловить события "дверь открыта".
Также, для контроля работоспособности, отправлять периодически состояние статус: если очередное сообщение не приходит больше трех минут - значит что-то вышло из строя.
Подключить напрямую датчик нельзя: там будет 12в (может быть и можно, ограничив напряжение стабилитроном, но нет такой необходимости), поэтому использую обычный оптрон.

Также, потребуется понижение напряжения питания с 12 до 3.3 вольт, для этого использую готовый модуль DC-DC.
Ну, и чтобы все это было как-то аккуратнее - соберу на печатной плате.
Размеры печатной платы под коробку 60х35 мм. Разводку платы делаю как обычно во Fritzing. Знаю, что "профессионалы" ее терпеть не могут за "ардуинистость", но она как раз подходит для подобных случаев.
Проблема в том, что для создания печатной платы нужны "отпечатки" деталей, и если с оптронами, резисторами, и даже ESP12 проблемы нет - то найти готовый "отпечаток" под безымянный модуль с Али - задача непростая.
А во Fritzing его можно просто нарисовать!
Для этого нужно сначала сфотографировать модуль. У него несколько контактных отверстий, размещать его я буду на штырьках на лицевой стороне платы, соответственно, фотографирую "сверху", а контактные площадки будут на плате "снизу".

Фотографию модуля нужно развернуть так, чтобы он был по возможности горизонтально и без искажений - с этим прекрасно справится Gimp.
Теперь нужен SVG-редактор. Как ни странно, наиболее известный Inkscape здесь не подходит, создаваемые им SVG не подходят без ручного ковыряния текста SVG, а это как бы неудобно.
Поэтому я использую тут boxy-svg.
Принцип следующий: импортируем картинку модуля, под ее размеры подгоняем viewport, и указываем реальные размеры в миллиметрах.
Теперь всё что будет нарисовано "по фотографии" - уже будет соответствовать реальным размерам.


Самая важная часть - нарисовать контактные площадки.
В данном случае это должны быть кольца, с отверстием посередине - для этого подходит инструмент "эллипс", точнее круг, у которого отключено заполнение, но задана увеличенная толщина линии периметра. Потом, на реальной плате, это будет выглядеть как кольцо.
У этого модуля контактные площадки сдвоенные - но лучше нарисовать одинарные, одна площадка - один контакт.

Контактные площадки группируются в группу, называемую copper1 - это значит они будут отображаться на нижней стороне платы. (copper0 - лицевая, верхняя сторона).Нюанс в том, что у группы есть название, и есть ID - вот тут важно чтобы ID был copper1.


Также рисую примерно расположение деталей на модуле и группирую их в группу layout - это не обязательно, просто потом так легче узнавать, какой именно модуль используется.
Фотографию из файла можно удалить, она больше не нужна.

Дальше во Fritzing находим что-нибудь похожее, чтобы не рисовать еще и схему - тоже 4 контакта, вход-выход, и редактируем, сохраняя как новую деталь.
Отмечаем контакты как штырьки - это заставит программу размещать деталь на лицевой стороне.
В разделе PCB загружаем новый SVG, и отмечаем какой контакт к какой площадке относится.

В разделе Schematic уже есть схема "блока питания", и контакты к выводам привязаны - можно не менять.
В разделе Breadboard снова загружаем SVG - должна появится условная картинка с деталями - тут тоже нужно привязать контакты к площадкам.
В разделе Icon можно просто повторно использовать Breadboard, это только для картинки в списке деталей.
Всё это сохраняется как новый элемент - теперь можно добавлять его в схему:

И конечно, развести плату:

Конечно, изготовление сложных плат лучше поручить профессионалам, а для этого надо бы правильно расположить все нужные надписи, добавить какую-нибудь красивую картинку, но потом придется долго ждать чтобы получить свой десяток плат - а мне нужна всего одна и сейчас.
Поэтому просто ограничимся самой платой, без шелкографии, и сделаем ее сами и быстро.
Сохраняем проект как etched pdf, из которого будут нужны два файла: дорожки и маска.

PDF затягиваем в Gimp с разрешением 1200 dpi, инвертируем цвета.
Так же рядом затягиваем маску с тем же разрешением, ничего не инвертируем.

Печатается это все на листе тонкой бумаги, около 80 г/м2, или на кальке

На кусок стектотекстолита накатывается ламинатором пленка фоторезиста (Китай, маркетплейс)

Сверху смачивается обычным подсолнечным маслом и накрывается калькой. Калька тоже смазывается, и она становится прозрачной.

4 секунды под светодиодной УФ-лампой, сдираем верхнюю защитную пленочку - готово к проявке

3 минуты в растворе строительного "жидкого стекла" с водой, примерно 1:2 - незасвеченное сползает лохмотьями

Травление: аптечная 3% перекись + чайная ложка соли + чайная ложка лимонной кислоты - примерно 10 минут

Смывка фоторезиста - "Крот" + вода, соотношение примерно 1:3, ок. 3 минут - остатки фоторезиста обесцвечиваются и сползают.

Ну, дальше нанесение маски, засветка, смывка - это всегда плохо получалось, поэтому углубляться не стоит. Сверление отверстий, обрезка краев шлифмашинкой.
В итоге получается плата, готовая к распайке:

Теперь программная часть.
Уже практически типовой скетч:
/*
* check PIN state, set event
*
*/
#include "painlessMesh.h"
#include <LittleFS.h>
#include <ArduinoJson.h>
#define VERSION "Guard v1.0"
#define OTA_NAME "guard"
#define MESH_PREFIX "ХХХХХ"
#define MESH_PASSWORD "ХХХХХ"
#define MESH_PORT 5678
painlessMesh mesh;
size_t gw_id = 0;
size_t me_id = 0;
int state;
#define PIN_CTL 13
#define IND 2
// =========================================================
// Prototypes
void receivedCallback( uint32_t from, String &msg );
void newConnectionCallback(uint32_t nodeId);
// =========================================================
void MeshSetup(){
gw_id = 0;
mesh.setDebugMsgTypes( ERROR | STARTUP | DEBUG | CONNECTION );
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.initOTAReceive(OTA_NAME);
mesh.setContainsRoot(true);
}
// =========================================================
#define TTL 180000
unsigned long ttl_timer;
int ttl_check;
void TtlSetup(){
ttl_timer = millis();
ttl_check = 0;
}
void TtlLoop(){
if(abs((long)(millis() - ttl_timer)) > TTL){
ttl_timer = millis();
if (ttl_check == 0){
ESP.reset();
}else{
ttl_check = 0;
mesh.sendBroadcast("ping");
}
}
}
// time ===================================================
#include <time.h>
#include <lwip/apps/sntp.h>
void TimeSetup(){
sntp_stop();
}
// =========================================================
unsigned long state_timer;
#define STATE_PERIOD 60000
#define STATE_RND 5000
void StateSetup(){
state_timer = 0;
gw_id = 0;
}
void StateLoop(){
DynamicJsonDocument doc(300); // messages
if(abs((long)(millis() - state_timer)) > STATE_PERIOD){
state_timer = millis();
state_timer += random(STATE_RND);
doc["ident"] = OTA_NAME;
time_t now = time(nullptr);
doc["dtm"] = now;
doc["block"] = "state";
long rssi = WiFi.RSSI();
doc["rssi"] = rssi;
doc["state"] = state;
if(rssi < -60){
digitalWrite(IND,HIGH);
}else{
digitalWrite(IND,LOW);
}
String message;
serializeJson(doc, message);
if(gw_id > 0){
mesh.sendSingle(gw_id, message);
}
else{
mesh.sendBroadcast(message);
}
}
}
// =========================================================
void setup() {
LittleFS.begin();
pinMode(IND,OUTPUT);
digitalWrite(IND,HIGH);
MeshSetup();
TimeSetup();
pinMode(PIN_CTL, INPUT_PULLUP);
state = digitalRead(PIN_CTL);
TtlSetup();
StateSetup();
}
void loop() {
mesh.update();
TtlLoop();
int x = digitalRead(PIN_CTL);
if(state != x){
String str = "{"alarm":1,"state":";
str += x;
str += "}";
state = x;
if(gw_id > 0){
mesh.sendSingle(gw_id, str);
}
else{
mesh.sendBroadcast(str);
}
}
StateLoop();
delay(100);
}
void newConnectionCallback(uint32_t nodeId) {
char buf[80];
time_t now = time(nullptr);
sprintf(buf,"settime %u",now);
mesh.sendSingle(nodeId, buf);
}
void receivedCallback( uint32_t from, String &msg ) {
ttl_check = 1;
// ===============================
if (msg == "state") {
gw_id = from;
state_timer = 0;
} else
// ===============================
if (msg == "version") {
mesh.sendSingle(from, VERSION);
} else
// ===============================
if (msg == "restart") {
mesh.sendSingle(from, "OK");
ESP.restart();
} else
// ===============================
if (msg.startsWith("settime ")) {
String num = msg.substring(8);
time_t tim = num.toInt();
time_t now = time(nullptr);
if(tim != 0 && tim > now){
timeval tv = { tim, 0 };
settimeofday(&tv, nullptr);
}
}
}
Суть программы простая: подключаемся в mesh-сеть, ловим сообщения, отправляем свои.
При подключении к какому-то из модулей он отправляет команду settime - это подерживает единое время во всей сети.
Периодически отправка пингов, периодически отправка сообщений о своем состоянии. Чтобы не накладывались сообщения - рандомизация.
Если нет новых сообщений более чем 180 сек - перезагрузка модуля.
Специфическое для данного модуля - за каждую итерацию проверка пина на подтягивание к "земле", с изменением статуса и отправкой сообщения.
Итого, пока дверь закрыта - от датчика идет ток на оптрон, который опускает пин к "земле". Как только дверь открылась - ток выключается, оптрон закрывается, на входе 1, отправляется сообщение. Потом наоборот.
Периодически передается текущее состояние.
Сообщения, отправляемые датчиком, выглядят примерно так:
mesh/from/2874622439 {"ident":"guard","dtm":1738189126,"block":"state","rssi":-91,"state":0}
Теперь их нужно применить в общую систему. Для этого делается скрипт-драйвер:
#!/usr/bin/perl -w
$|=1;
use Net::MQTT::Simple;
use JSON;
use Data::Dumper;
$SIG{CHLD} = "IGNORE";
########################################
my $mqtt = Net::MQTT::Simple->new("127.0.0.1");
#------------------------------------------
# send MQTT message
sub pub {
my ($topic, $message, $retain) = @_;
my $pid = fork();
if(defined $pid && $pid == 0){
if($retain){
$mqtt->retain($topic => $message);
}else{
$mqtt->publish($topic => $message);
}
sleep(2);
exit(0);
}
}
#------------------------------------------
my $door1 = '2874622439';
# processing
$mqtt->run(
# receive Info requests
"mesh/from/$door1" => sub {
my ($topic, $message) = @_;
print ".";
my $data = undef;
if($message =~ /^{.*}$/){
$data = from_json($message);
}
if(defined $data && defined $data->{ state }){
my $t = $data->{state};
if($t == 1){
pub("alarm/text","Дверь открыта!");
}
elsif($data->{ alarm }){
pub("alarm/text","Дверь закрыта");
}
}
},
);
exit;
Вот теперь всё: дверь открывается - на телефон приходит сообщение, дверь закрывается - снова приходит сообщение.
Избыточно сложно? Может быть, но в пару строк в драйвере можно добавить отправку команды на включение чего-нибудь (красной лампочки, прожектора, сирены), вести лог когда что открывалось.
А еще получился по сути универсальный модуль, который можно подключить к любому "шлейфу", будь это герконные датчики на окнах, PIR-сенсоры, или какие-нибудь лазерные барьеры - просто сообщения будут приходить от разных ID.
Но такой задачи нет. Пока нет.
Автор: JBFW