Датчик протечки воды на Ардуино

15 min read
0
0
22


Перейти в магазин

Приветствую всех читателей блога MYSKU! В сегодняшней статье хочу рассказать об одном из своих датчиков протечки воды. Данная версия датчика работает на модуле(YJ-17029) с микроконтроллером nRF52832, модуль оснащен усилителем RFX2401. Устройство питается от батарейки 1/2 AA (14250) 3.6V, рассчитано на длительный срок эксплуатации.


Как и все остальные мои проекты, этот тоже является Arduino проектом, код программы написан в Arduino IDE, дополнительные библиотеки не используются. Датчик сделан под проект Mysensors. Как обычно в статье немного затрону тему протокола Mysensors.


Mysensors это сообщество разработчиков програмного обеспечения с открытым исходным кодом. Данный протокол разработан сообществом для создания радио и проводных сетей. Первоначально проект разрабатывался для платформы Arduino. Стандартная Mysensors сеть состоит из гейта(шлюза), ретранстяторов и конечных устройств(ноды). В одной сети может быть до 254 устройств, каждое из устройств может быть оснащено до 254 сенсоров, датчиков, исполнительных узлов. Модуль Mysensors в Мажордомо (который выполняет роль контролера сети) поддерживает мультигейтовость, соответственно сетей может быть намного больше одной управляемых одним контролером.

Поддерживаемые аппаратные платформы: Linux / Raspberry Pi / Orange Pi | ATMega 328P | ESP8266 | ESP32 | nRF5x | Atmel SAMD, используемое в Arduino Zero (Cortex M0) | Teensy3(MK66FX1M0VMD18) | STM32F1.

Поддерживаемые радиопередатчики: NRF24L01 | RFM69 | RFM95 (LoRa) | nRF5x

Поддерживаемый проводной тип связи: RS485

Поддерживаемые типы связи между гейтом и контролером: MQTT | Serial USB | WiFi | GSM


Описываемая модификация датчика протечки с радиоусилителем и батарейкой 14250 была сделана из стандатной версии на радиомодуле без усилителя и батарейках cr2450, сr2477. Предпологается, что данная версия может быть применена в удаленых местах, местах с низкой проходимостью радиосигнала, если нет возможности разместить контролер протечки(управление перекрывающими редукторами) или репитер рядом с местом установки датчиков протечки. Идея данной версии была предложена одним из пользователей сообщества в телеграм чате Mysensors.


Основной элемент датчика это детектор жидкости. Детектор построен на основе логической микросхемы SN74LVC1G00. Один электрод датчика подключен к земле, второй электрод датчика подключен к выводам микросхемы A и В через резистор 100Oм, так же к этой линии подключен плюс через резистор 1М. Когда контакта с жидкостью нет на ножках микросхемы A и В логическая единица, соответственно на ножке Y подключенной к ножке МК (настроенной на детектирование прерывания через встроенный компоратор) логический ноль. Как только произойдет контакт с жидкостью и на ножках A и B будет низкий уровень и сигнал на ножке микросхемы SN74LVC1G00 так же инвертируется, что вызовет прерывание, которое в свою очередь выведет МК из сна.


Устройство так же имеет тактовую кнопку для реализации сервисных режимов, таких как привязка устройства, обнуление устройства и т.п. Кнопка заведена на ту же ножку МК что и детектор протечки. Обе линии разделены диодами Шотки.


В датчике установлен RGB LED, для индикации регистрации датчика в сети, для индикации сервисных режимов и для индикации детектирования протечки. Естественно светодиод может не использоватся вообще или использоватся частично.


Питание осуществляется напрямую от батарейки, контроль уровня заряда батарейки производится непосредственно с пина VDD, поэтому резисторный делитель напряжения и подключение линии делителя на ножку аналогового входа МК не требуется.


Плату устройства я разрабатывал в программе Диптрейс. Первый прототип изготавливался под ЛУТ. Из нюансов это увеличенная ширина трасс, увеличенные растояния между трассами, увеличенные площадки под межслойные переходы(для более удобного сверления отверстий), отсутствие заливки пустых областей.


Корпус устройства был разработан в редакторе СолидВоркс. Печать выполнялась на SLA 3D принтере ANICUBIC PHOTON. После печати была выполнена обработка наждачной бумагой 320 и 1000 для подгонки стыков крышки и дна корпуса.

Код основной программы:

bool button_flag;
bool send_flag;
bool detection;
bool nosleep;
byte timer;
bool AckG;
bool AckB;
bool AckL;
bool PRESENT_ACK;
bool flag_lq;
//unsigned long SLEEP_TIME = 86400000; //24 hours
unsigned long SLEEP_TIME = 3600000; //1 hours
unsigned long oldmillis;
unsigned long newmillis;
unsigned long interrupt_time;
unsigned long SLEEP_TIME_W;
uint16_t currentBatteryPercent;
uint16_t batteryVoltage = 0;
uint16_t battery_vcc_min = 2400;
uint16_t battery_vcc_max = 3600;

int16_t linkQuality;

#define MY_DISABLED_SERIAL
#define MY_RADIO_NRF5_ESB
#define MY_RF24_PA_LEVEL (NRF5_PA_MAX)
//#define MY_PASSIVE_NODE
#define MY_NODE_ID 87
#define MY_PARENT_NODE_ID 0
#define MY_PARENT_NODE_IS_STATIC
#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
#define INTR_PIN 6 //(PORT0,  gpio 30)
#include <MySensors.h>
// see https://www.mysensors.org/download/serial_api_20
#define W_L_SENS_CHILD_ID 0
#define LINK_QUALITY_CHILD_ID 253
MyMessage sensMsg(W_L_SENS_CHILD_ID, V_VAR1);
//MyMessage voltMsg(CHILD_ID_VOLT, V_VOLTAGE);


void preHwInit() {
  pinMode(POWER_PIN, OUTPUT);
  digitalWrite(POWER_PIN, HIGH);
  wait(3000);
  pinMode(RED_LED, OUTPUT);
  digitalWrite(RED_LED, HIGH);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(GREEN_LED, HIGH);
  pinMode(BLUE_LED, OUTPUT);
  digitalWrite(BLUE_LED, HIGH);
  pinMode(PIN_BUTTON, INPUT);
  pinMode(W_L_SENS, INPUT);

  pinMode(TXEN_PIN, OUTPUT);
  pinMode(RXEN_PIN, OUTPUT);
}

void before()
{
  NRF_POWER->DCDCEN = 1;
  NRF_UART0->ENABLE = 0;
  sleep(100);
  digitalWrite(BLUE_LED, LOW);
  sleep(150);
  digitalWrite(BLUE_LED, HIGH);
  sleep(150);

  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
}

void presentation() {
  digitalWrite(TXEN_PIN, LOW);
  digitalWrite(RXEN_PIN, LOW);
  wait(100);
  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
  sendSketchInfo("EFEKTA PRO WL Sensor", "1.1");
  digitalWrite(TXEN_PIN, LOW);
  digitalWrite(RXEN_PIN, LOW);
  wait(300);
  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
  present(W_L_SENS_CHILD_ID, S_CUSTOM, "SWITCH STATUS");
  digitalWrite(TXEN_PIN, LOW);
  digitalWrite(RXEN_PIN, LOW);
  wait(300);
  /*
    wait(3000, 0, S_CUSTOM);
    //CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSORn"));
    if (PRESENT_ACK == 0) {
      sleep(2000);
      wait(100);
      present(W_L_SENS_CHILD_ID, S_CUSTOM, "SWITCH STATUS", 1);
      wait(3000, 0, S_CUSTOM);
      //CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSORn"));
    } else {
      PRESENT_ACK = 0;
    }
  */
  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
  present(LINK_QUALITY_CHILD_ID, S_CUSTOM, "LINK_QUALITY");
  digitalWrite(TXEN_PIN, LOW);
  digitalWrite(RXEN_PIN, LOW);
  wait(300);
  /*
    wait(3000, 0, S_CUSTOM);
    //CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSORn"));
    if (PRESENT_ACK == 0) {
    sleep(2000);
    wait(100);
    present(LINK_QUALITY_CHILD_ID, S_CUSTOM, "LINK_QUALITY", 1);
    wait(3000, 0, S_CUSTOM);
    //CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSORn"));
    } else {
    PRESENT_ACK = 0;
    }
  */
}

void setup() {
  digitalWrite(TXEN_PIN, LOW);
  digitalWrite(RXEN_PIN, LOW);


  digitalWrite(BLUE_LED, LOW);
  wait(100);
  digitalWrite(BLUE_LED, HIGH);
  wait(200);
  digitalWrite(BLUE_LED, LOW);
  wait(100);
  digitalWrite(BLUE_LED, HIGH);
  lpComp();
  detection = false;
  SLEEP_TIME_W = SLEEP_TIME;
  /*
    while (timer < 10) {
    timer++;
    digitalWrite(GREEN_LED, LOW);
    wait(5);
    digitalWrite(GREEN_LED, HIGH);
    wait(500);
    }
    timer = 0;
  */
  wait(100);
  sendBatteryStatus();
  wait(100);
  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
  send(sensMsg.set(detection), 1);
  digitalWrite(TXEN_PIN, LOW);
  wait(2000, 1, V_VAR1);
  digitalWrite(RXEN_PIN, LOW);
}

void loop() {
  if (nosleep == 0) {
    oldmillis = millis();
    sleep(SLEEP_TIME_W);
  }

  if (detection) {
    if (digitalRead(PIN_BUTTON) == 1 && button_flag == 0 && digitalRead(W_L_SENS) == 0) {
      //back side button detection
      button_flag = 1;
      nosleep = 1;
    }
    if (digitalRead(PIN_BUTTON) == 1 && button_flag == 1 && digitalRead(W_L_SENS) == 0) {
      digitalWrite(GREEN_LED, LOW);
      wait(10);
      digitalWrite(GREEN_LED, HIGH);
      wait(50);
    }
    if (digitalRead(PIN_BUTTON) == 0 && button_flag == 1 && digitalRead(W_L_SENS) == 0) {
      nosleep = 0;
      button_flag = 0;
      digitalWrite(GREEN_LED, HIGH);
      lpComp_reset();
    }

    if (digitalRead(W_L_SENS) == 1 && digitalRead(PIN_BUTTON) == 0) {
      //sens detection
      newmillis = millis();
      interrupt_time = newmillis - oldmillis;
      SLEEP_TIME_W = SLEEP_TIME_W - interrupt_time;
      wait(100);
      digitalWrite(TXEN_PIN, HIGH);
      digitalWrite(RXEN_PIN, HIGH);
      send(sensMsg.set(detection), 1);
      digitalWrite(TXEN_PIN, LOW);
      wait(3000, 1, V_VAR1);
      digitalWrite(RXEN_PIN, LOW);
      if (AckG == 1) {
        while (timer < 10) {
          timer++;
          //digitalWrite(RED_LED, LOW);
          //wait(20);
          //digitalWrite(RED_LED, HIGH);
          //wait(30);
          digitalWrite(BLUE_LED, LOW);
          wait(20);
          digitalWrite(BLUE_LED, HIGH);
          wait(30);
        }
        timer = 0;
        AckG = 0;
        wait(200);
      } else {
        while (timer < 10) {
          timer++;
          digitalWrite(RED_LED, LOW);
          wait(20);
          digitalWrite(RED_LED, HIGH);
          wait(30);
        }
        timer = 0;
        digitalWrite(TXEN_PIN, HIGH);
        digitalWrite(RXEN_PIN, HIGH);
        send(sensMsg.set(detection), 1);
        digitalWrite(TXEN_PIN, LOW);
        wait(3000, 1, V_VAR1);
        digitalWrite(RXEN_PIN, LOW);
        if (AckG == 1) {
          while (timer < 10) {
            timer++;
            digitalWrite(RED_LED, LOW);
            wait(20);
            digitalWrite(RED_LED, HIGH);
            wait(30);
            digitalWrite(BLUE_LED, LOW);
            wait(20);
            digitalWrite(BLUE_LED, HIGH);
            wait(30);
          }
          timer = 0;
          AckG = 0;
        } else {
          while (timer < 20) {
            timer++;
            digitalWrite(RED_LED, LOW);
            wait(20);
            digitalWrite(RED_LED, HIGH);
            wait(30);
          }
          timer = 0;
        }
        lpComp_reset();
      }
    }

    if (SLEEP_TIME_W < 60000) {
      SLEEP_TIME_W = SLEEP_TIME;
      sendBatteryStatus();
    }
  }

  else {
    //if (detection == -1) {
    SLEEP_TIME_W = SLEEP_TIME;
    sendBatteryStatus();
  }
}


void receive(const MyMessage & message) {
  if (message.type == V_VAR1) {
    if (message.sensor == W_L_SENS_CHILD_ID) {
      if (mGetCommand(message) == 1) {
        if (message.isAck()) {
          AckG = 1;
        } else {

        }
      }
    }
  }
  if (message.type == I_BATTERY_LEVEL) {
    if (message.sensor == 255) {
      if (mGetCommand(message) == 3) {
        if (message.isAck()) {
          AckB = 1;
        } else {

        }
      }
    }
  }
  if (message.type == V_VAR1) {
    if (message.sensor == 255) {
      if (mGetCommand(message) == 1) {
        if (message.isAck()) {
          AckL = 1;
        } else {

        }
      }
    }
  }
  /*
    if (mGetCommand(message) == 0) {
    PRESENT_ACK = 1;
    CORE_DEBUG(PSTR("MyS: !!!ACK OF THE PRESENTATION IN THE FUNCTION RECEIVE RECEIVED!!!n"));
    }
  */
}


void sendBatteryStatus() {
  wait(100);
  batteryVoltage = hwCPUVoltage();
  wait(20);

  if (batteryVoltage > battery_vcc_max) {
    currentBatteryPercent = 100;
  }
  else if (batteryVoltage < battery_vcc_min) {
    currentBatteryPercent = 0;
  } else {
    currentBatteryPercent = (100 * (batteryVoltage - battery_vcc_min)) / (battery_vcc_max - battery_vcc_min);
  }
  digitalWrite(TXEN_PIN, HIGH);
  digitalWrite(RXEN_PIN, HIGH);
  sendBatteryLevel(currentBatteryPercent, 1);
  digitalWrite(TXEN_PIN, LOW);
  wait(3000, C_INTERNAL, I_BATTERY_LEVEL);
  digitalWrite(RXEN_PIN, LOW);
  if (AckB == 1) {
    AckB = 0;
    flag_lq = 1;
  } else {
    digitalWrite(TXEN_PIN, HIGH);
    digitalWrite(RXEN_PIN, HIGH);
    sendBatteryLevel(currentBatteryPercent, 1);
    digitalWrite(TXEN_PIN, LOW);
    wait(3000, C_INTERNAL, I_BATTERY_LEVEL);
    digitalWrite(RXEN_PIN, LOW);
    if (AckB == 1) {
      AckB = 0;
      flag_lq = 1;
    }
  }
  //send(powerMsg.set(batteryVoltage), 1);
  //wait(2000, 1, V_VAR1);

  //sleep(10000); //
  if (flag_lq == 1) {
    linkQuality = calculationRxQuality();
    wait(50);
    digitalWrite(TXEN_PIN, HIGH);
    digitalWrite(RXEN_PIN, HIGH);
    sendSignalStrength(linkQuality, 1);
    digitalWrite(TXEN_PIN, LOW);
    wait(2000, 1, V_VAR1);
    digitalWrite(RXEN_PIN, LOW);
    if (AckL == 1) {
      AckL = 0;
    } else {
      sendSignalStrength(linkQuality, 1);
      wait(2000, 1, V_VAR1);
      if (AckL == 1) {
        AckG = 0;
      }
    }
    flag_lq = 0;
  }
}


void lpComp() {
  NRF_LPCOMP->PSEL = INTR_PIN;
  NRF_LPCOMP->ANADETECT = 1;
  NRF_LPCOMP->INTENSET = B0100;
  NRF_LPCOMP->ENABLE = 1;
  NRF_LPCOMP->TASKS_START = 1;
  NVIC_SetPriority(LPCOMP_IRQn, 15);
  NVIC_ClearPendingIRQ(LPCOMP_IRQn);
  NVIC_EnableIRQ(LPCOMP_IRQn);
}

void s_lpComp() {
  if ((NRF_LPCOMP->ENABLE) && (NRF_LPCOMP->EVENTS_READY)) {
    NRF_LPCOMP->INTENCLR = B0100;
  }
}

void r_lpComp() {
  NRF_LPCOMP->INTENSET = B0100;
}

#if __CORTEX_M == 0x04
#define NRF5_RESET_EVENT(event)                                                 
  event = 0;                                                                   
  (void)event
#else
#define NRF5_RESET_EVENT(event) event = 0
#endif


void lpComp_reset () {
  s_lpComp();
  detection = false;
  NRF_LPCOMP->EVENTS_UP = 0;
  r_lpComp();
}

//****************************** very experimental *******************************


bool sendSignalStrength(const int16_t level, const bool ack)
{
  return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_SET, V_VAR1,
                          ack).set(level));
}
int16_t calculationRxQuality() {
  int16_t nRFRSSI_temp = transportGetReceivingRSSI();
  int16_t nRFRSSI = map(nRFRSSI_temp, -85, -40, 0, 100);
  if (nRFRSSI < 0) {
    nRFRSSI = 0;
  }
  if (nRFRSSI > 100) {
    nRFRSSI = 100;
  }
  return nRFRSSI;
}

//****************************** very experimental *******************************


extern "C" {
  void LPCOMP_IRQHandler(void) {
    detection = true;
    NRF5_RESET_EVENT(NRF_LPCOMP->EVENTS_UP);
    NRF_LPCOMP->EVENTS_UP = 0;
    MY_HW_RTC->CC[0] = (MY_HW_RTC->COUNTER + 2);
  }
}


Управления ножками TXEN и RXEN усилителя осуществляется из кода основной программы, что не очень правильно, наверное имеет смысл добавить управление ножками в файл драйвера внутри библиотеки.


Список используемых компонентов с ссылками на Алиэкспресс:

Модуль YJ-17029
SN74LVC1G00
1PS76SB10.115(SOD-323) SMD Schottky Barrier Diode(2шт)
100UF 107C D-type SMD Tantalum Capacitor
100nF | 50v | +80-20% | SMD 0805 | SMD ceramic capacitor 100nF | Y5V
2UF | 50v | +80-20% | SMD 0805 | SMD ceramic capacitor 2UF | Y5V
14250 Battery Holder 1/2 AA
Micro Button Tact Switch SMD 4Pin 3X4X2.5MM White(2шт)
2x3P | 6pin | 1.27mm | SMT | J1 Pin Header Female
10K | 5% | SMD 0603 | SMD resistor 10K Ohm(3шт)
100K | 5% | SMD 0603 | SMD resistor 100K Ohm
2K7 | 5% | SMD 0603 | SMD resistor 2K7 Ohm
6K8 | 5% | SMD 0603 | SMD resistor 6K8 Ohm
9K1 | 5% | SMD 0603 | SMD resistor 9K1 Ohm
1M | 5% | SMD 0603 | SMD resistor 1M Ohm
100 | 5% | SMD 0603 | SMD resistor 100 Ohm
RGB LED | SMD 0805 | RGBL | RGB LED
M1.4 4mm Round Head Micro Screws(4шт)
M3 15mm Nickel plated Brass Standoff Spacer Female(2шт)
M3 6mm Hexagon Socket Thin low Short Head Cap Screw(2шт)
M3 4mm Hexagon Socket Button Head Screws(2шт)
ANYCUBIC 405nm UV Resin For Photon 3D Printer

Видео работы датчика протечки
Фото датчика:


Датчик работает в режиме пониженного энергопотребления, что сказывается на дальности радиопередачи. Тестировался на расстояниях 30-40метров(находился на улице) от гейта(находился в деревянном доме), доставка сообщений 100%. Ранее когда только были закуплены данные радиомодули для тестов, в режиме пониженного энергопотребления с питанием от повербанка дальность составила 200 метров в условиях городской застройки(радиомодуль находился на улице, принимающий узел находился в бетонной многоэтажке с противоположной стороны дома, относительно передающего радиомодуля). Энергопотребление в режиме сна составляет 3мкА на всю схему. Микросхема SN74LVC1G00 в режиме мониторинга ничего не потребляет. Управление питанием микросхемы осуществляется с ножки МК. В режиме передачи устройство потребляет ~ 24-27мА.


GitHub проекта — github.com/smartboxchannel/EFEKTA_WATER_LEAK_SENSOR


Место где всегда с радостью помогут всем кто хочется познакомиться с MYSENSORS (установка плат, работа с микроконтроллерами nRF5 в среде Arduino IDE, советы по работе с протоколом mysensors, обсуждение проектов — @mysensors_rus

Источник материала.

Перейти в магазин
Load More Related Articles
Load More By admin
Load More In Uncategorized

Оставить комментарий

Check Also

Фигурка Hot Toys MMS461D21 Iron Man from "Iron Man 2"

И снова категорически приветствую! Очень давно хотел себе в коллекцию Железного Чел…