Данная система защиты от протечек работает через mqtt протокол. Есть возможность управлять кранами удаленно через телефон, отключать/включать сигнализацию протечки, узнавать о ее статусе.
Для отслеживания воды будет предусмотрено 2 типа датчиков - проводные и беспроводные. Сделано это в угоду надежности т.к. любые беспроводные устройства имеют батарейки/аккумуляторы, про которые свойственно забывать.

Вся система представлена на иллюстрации. Как видно, она состоит из двух частей - левая Master и правая Slave.
Конструкция обусловлена наличием двух пар стояков водоснабжения. Если в квартире будет протечка, то отключаться будут все стояки.
К Mater контроллеру подключены аналоговые (проводные) датчики и сигналы радиоприемника 433Mhz. Если будет принят сигнал известного датчика, то сработает тревога. Треворга на Master контроллере от датчиков отправляет в mqtt топик valve/alarm=1 с периодичностью 3 секунлы. Этот топик слушает Slave контроллер, отключает подачу воды у себя. По тому нет необходимости иметь два набора беспроводных датчиков для кажлого контроеллера.
Проводной датчик установлен непосредсвенно под Master контроллером, а беспроводные раскиданы по всей квартире.
Аналоговый датчик представляет собой два провода - минус и сигнальный (подтянутый на плюс). При закорачивании между собой контроллер ловит логическую единицу, включает тревогу.
Беспроводные датчики в данном проекте самые бюджетные - дверные (SC2262, EV1527). Геркон - выпаян, магнит - выброшен. Питающий провод выведен контакторами на низ корпуса. Водичка закорачивает контактор и устройство дает несколько импульсов с своим кодом.

Схема работы с протоколом mqtt была подробно описана в предыдущей статье. В двух словах можно сказать, что нужно иметь mqtt сервер (брокер) для пересылки топиков. Есть ряд сервисов которые предоставляют бесплатные мощности. Вся настройка сводится к регистрации учетной записи cloudmqtt.com (бесплатный лимит 10 устройств). Вы получаете данные для доступа к mqtt брокеру, прописываете их в код микроконтроллеров, а на телефон ставите mqtt клиент.
Для android устройств можно использовать клиент - MQTT Dashboard. В нем так же нужно указать данные для доступа к серверу, сконфигурировать элементы управления.
Subscribe:
Имя "Беспроводной сенсор", топик "valve/id" (Нужен только для отладки. В него записываются все распознанные id беспроводных датчиков);
Имя "Тревога", топик "valve/alarm" (Принимает значения 1 или 0).
Publish:
Имя "Тревога", топик "valve/alarm" (Принимает значения 1 или 0);
Имя "Ванная Х", топик "valve/1" (Принимает значения 1 или 0);
Имя "Ванная Г", топик "valve/2" (Принимает значения 1 или 0);
Имя "Кухня Х", топик "valve/3" (Принимает значения 1 или 0);
Имя "Кухня Г", топик "valve/4" (Принимает значения 1 или 0).
При срабатывании датчика воды на Master контроллере краны valve/1 и valve/2 автоматически перелючаются в значение 1. Slave контроллер получает топик valve/alarm=1 от мастера и закрывает краны valve/1 и valve/2.
Если с мобильного устройства отправить топик разблокировки valve/alarm=0, то краны должны автоматически открыться, но если датчик воды продолжит посылать сигнал потопа, товсе снова заблокируется.
Ручное управление открытием каждым отдельным краном добалено для фетиша и какого-либо серьезного смысла под собой не имеет. По большому счету можно было сделать одну кнопку "valve/alarm".
Код для Master:
// Master #include <ESP8266WiFi.h> #include <PubSubClient.h> #include <RCSwitch.h> const char *mqttServer = "m13.cloudmqtt.com"; // Имя сервера MQTT const int mqttPort = 14483; // Порт для подключения к серверу MQTT const char *mqttUser = "xxxxxx"; // Логи к серверу MQTT const char *mqttPass = "xxxxxx"; // Пароль к серверу MQTT unsigned long transmitterIds[] = {1111111, 2222222}; // Уникальные коды датчиков для которых включается alarm const int sensor = 5; // Проводной датчик воды (D1). Срабатывает при появлении 0 const int drive1 = 12; // Кран 1 (D6) const int drive2 = 14; // Кран 2 (D5) const int led = 2; // Сигнальный светодиод на плате const int receiver = 4; // Прием сигнала беспроводного датчика (D2) const char *topicDrive1 = "valve/1"; // Топик крана 1 const char *topicDrive2 = "valve/2"; // Топик крана 2 const char *topicAlarm = "valve/alarm"; // Топик alarm const char *topicWirlessId = "sensor/id"; // Отладочный топик для беспроводных датчиков int tm = 300; bool alarm = false; bool drive1Close = false; bool drive2Close = false; RCSwitch rcs = RCSwitch(); WiFiClient wclient; PubSubClient client(wclient); void setup() { Serial.begin(74880); // Конфигурируем устройство в сети для работы с SmartConfig WiFi.mode(WIFI_AP_STA); delay(500); // Конфигурируем порты pinMode(led, OUTPUT); pinMode(sensor, INPUT_PULLUP); pinMode(drive1, OUTPUT); pinMode(drive2, OUTPUT); wifiConnect(); // Конфигурируем беспроводной датчик rcs.enableReceive(receiver); client.setServer(mqttServer, mqttPort); client.setCallback(mqttCallback); } void loop() { mqttLoop(); sensorRead(); wirlessSensorCheck(); alarmCheck(); driveValueSet(); delay(1); } // Функция получения данных от сервера void mqttCallback(char* topic, byte* bpayload, unsigned int length) { // Конвертируем byte в sting String payload = ""; for (int i = 0; i < length; i++) { payload = payload + (char)bpayload[i]; } Serial.print("MQTT topic \""); Serial.print(topic); Serial.print("\"="); Serial.println(payload); // Проверяем из нужного ли нам топика пришли данные int value = payload.toInt(); if (alarm == false) { if (String(topic) == String(topicDrive1)) drive1Close = (value == 0); if (String(topic) == String(topicDrive2)) drive2Close = (value == 0); } if (String(topic) == String(topicAlarm)) { if (value == 0) { alarm = false; client.publish(topicDrive1, "1"); client.publish(topicDrive2, "1"); } else alarm = true; } } void mqttLoop() { // Подключаемся к MQTT серверу if (WiFi.status() == WL_CONNECTED) { if (!client.connected()) { Serial.print("Connecting to MQTT server "); Serial.print(mqttServer); Serial.println("..."); if (client.connect("arduinoClient", mqttUser, mqttPass)) { Serial.println("Connected to MQTT server"); // Подписываемся по топики client.subscribe(topicDrive1); client.subscribe(topicDrive2); client.subscribe(topicAlarm); digitalWrite(led, HIGH); } else { Serial.println("Could not connect to MQTT server"); digitalWrite(led, LOW); } } else { client.loop(); } } else digitalWrite(led, LOW); } void wifiConnect() { Serial.println("SmartConfig init"); WiFi.beginSmartConfig(); Serial.println("SmartConfig success"); // Пауза меду попытками подключения while (WiFi.status() != WL_CONNECTED) { Serial.print("."); Serial.println(WiFi.smartConfigDone()); delay(500); } Serial.println(""); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void wirlessSensorCheck() { if (rcs.available()) { int value = rcs.getReceivedValue(); if (value <= 0) { Serial.print("Unknown encoding"); } else { Serial.print("Received "); Serial.print( rcs.getReceivedValue() ); Serial.print(" / "); Serial.print( rcs.getReceivedBitlength() ); Serial.print("bit "); Serial.print("Protocol: "); Serial.println( rcs.getReceivedProtocol() ); // Проверяем значение сигнала, если оно соответствует нашему датчику for (int i = 0; i < sizeof(transmitterIds); i++) { if (transmitterIds[i] == value) { // Сработал беспроводной датчик потопа alarm = true; Serial.println("Wirless sensor alarm"); } } // Для отладки отправляем id датчика char buf[50]; ltoa(value, buf, 10); client.publish(topicWirlessId, buf); } rcs.resetAvailable(); } } void alarmCheck() { if (alarm == true) { drive1Close = true; drive2Close = true; // Отправляем в MQTT клиент alarm раз в 3 секунды if (tm == 0) { client.publish(topicAlarm, "1"); client.publish(topicDrive1, "0"); client.publish(topicDrive2, "0"); tm = 3000; // Пауза меду отправками 3 секунды } tm--; } } // Проверка аналогового датчика воды void sensorRead() { int sensorValue = analogRead(sensor); if (sensorValue < 1023 && alarm == false) { // Сработал датчик потопа alarm = true; Serial.println("Analog sensor alarm"); } } // Управление кранами. В зависимости от типа крана значение управляющего сигнала может быть инвертировано. void driveValueSet() { if (drive1Close == true) digitalWrite(drive1, LOW); else digitalWrite(drive1, HIGH); if (drive2Close == true) digitalWrite(drive2, LOW); else digitalWrite(drive2, HIGH); }
Код для Slave:
// Slave #include <ESP8266WiFi.h> #include <PubSubClient.h> const char *mqttServer = "m13.cloudmqtt.com"; // Имя сервера MQTT const int mqttPort = 14483; // Порт для подключения к серверу MQTT const char *mqttUser = "xxxxxx"; // Логи к серверу MQTT const char *mqttPass = "xxxxxx"; // Пароль к серверу MQTT const int drive1 = 12; // Кран 3 (D6) const int drive2 = 14; // Кран 4 (D5) const int led = 2; // Сигнальный светодиод на плате const char *topicDrive1 = "valve/3"; // Топик крана 3 const char *topicDrive2 = "valve/4"; // Топик крана 4 const char *topicAlarm = "valve/alarm"; // Топик alarm int tm = 300; bool alarm = false; bool drive1Close = false; bool drive2Close = false; WiFiClient wclient; PubSubClient client(wclient); void setup() { Serial.begin(74880); // Конфигурируем устройство в сети для работы с SmartConfig WiFi.mode(WIFI_AP_STA); delay(500); // Конфигурируем порты pinMode(led, OUTPUT); pinMode(sensor, INPUT_PULLUP); pinMode(drive1, OUTPUT); pinMode(drive2, OUTPUT); wifiConnect(); client.setServer(mqttServer, mqttPort); client.setCallback(mqttCallback); } void loop() { mqttLoop(); alarmCheck(); driveValueSet(); delay(1); } // Функция получения данных от сервера void mqttCallback(char* topic, byte* bpayload, unsigned int length) { // Конвертируем byte в sting String payload = ""; for (int i = 0; i < length; i++) { payload = payload + (char)bpayload[i]; } Serial.print("MQTT topic \""); Serial.print(topic); Serial.print("\"="); Serial.println(payload); // Проверяем из нужного ли нам топика пришли данные int value = payload.toInt(); if (alarm == false) { if (String(topic) == String(topicDrive1)) drive1Close = (value == 0); if (String(topic) == String(topicDrive2)) drive2Close = (value == 0); } if (String(topic) == String(topicAlarm)) { if (value == 0) { alarm = false; client.publish(topicDrive1, "1"); client.publish(topicDrive2, "1"); } else alarm = true; } } void mqttLoop() { // Подключаемся к MQTT серверу if (WiFi.status() == WL_CONNECTED) { if (!client.connected()) { Serial.print("Connecting to MQTT server "); Serial.print(mqttServer); Serial.println("..."); if (client.connect("arduinoClient", mqttUser, mqttPass)) { Serial.println("Connected to MQTT server"); // Подписываемся по топики client.subscribe(topicDrive1); client.subscribe(topicDrive2); client.subscribe(topicAlarm); digitalWrite(led, HIGH); } else { Serial.println("Could not connect to MQTT server"); digitalWrite(led, LOW); } } else { client.loop(); } } else digitalWrite(led, LOW); } void wifiConnect() { Serial.println("SmartConfig init"); WiFi.beginSmartConfig(); Serial.println("SmartConfig success"); // Пауза меду попытками подключения while (WiFi.status() != WL_CONNECTED) { Serial.print("."); Serial.println(WiFi.smartConfigDone()); delay(500); } Serial.println(""); Serial.println("WiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void alarmCheck() { if (alarm == true) { drive1Close = true; drive2Close = true; // Отправляем в MQTT клиент alarm раз в 3 секунды if (tm == 0) { client.publish(topicDrive1, "0"); client.publish(topicDrive2, "0"); tm = 3000; // Пауза меду отправками 3 секунды } tm--; } } // Управление кранами. В зависимости от типа крана значение управляющего сигнала может быть инвертировано. void driveValueSet() { if (drive1Close == true) digitalWrite(drive1, LOW); else digitalWrite(drive1, HIGH); if (drive2Close == true) digitalWrite(drive2, LOW); else digitalWrite(drive2, HIGH); }
Если вас смущает, что в коде нет данных для подключения к сети по wi-fi, так это по тому, что используется SmartConfig. При первом включении устройства нужно передать ему ключи доступа. Делается это через приложение ESP8266 SmartConfig. Устройство принимает ключи, сохраняет их в eprom. Больше конфигурировать их не нужно.

Если вы еще не знаете как загружать свой скетч в esp через arduino ide, то велком в статью "ESP как основа умного дома".
К стати, если вы владелец магазина или просто работаете маркетологом, то вас наверняка заинтересует сервис Marionet - конструктор сценариев и маркетинговых воздействий. Проект появился достаточно недавно и быстро набирает популярность. Интересная штука даже если использовать на обычном сайте. Можно видеть всю активность посетителей и воздействовать на них путем всплытия виджетов/писем.. и даже вебхуки. Хоть цель сервиса - магазины, но с такими возможностями можно использовать в проектах далеких от e-commerce.
Не знаю на долго или нет, но сейчас он предоставляется бесплатно об этом прямо сказано на сайте. Клево.