Система защиты от протечек с беспроводными датчиками

Публикация 29.07.2018

Данная система защиты от протечек работает через 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.

Не знаю на долго или нет, но сейчас он предоставляется бесплатно об этом прямо сказано на сайте. Клево.

* комментарии публикуются после модерации
Нет комментариев