Беспроводной датчик протечки воды на nRF52832, DIY проект

    Приветствую всех читателей раздела «DIY или Сделай сам» на Habr! Сегодня хочу рассказать об очередном своем проекте, эта статья будет о датчике протечки воды на батарейном питании. Как и в предыдущих проектах, это устройство работает на микроконтроллере nRF52832. Есть три версии этого датчика, во всех трех версиях используются готовые модули с nRF52832, в этой статье речь пойдет о средней версии в котором используется модуль YJ-17103 от HOLYIOT.



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

    Как и все другием мои проекты, этот тоже является Arduino проектом и как и все проекты за последний год(примерно) этот так же сделан под проект Mysensors. Как и в других своих статьях, немного затрону тему Mysensors и в этой статье.

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

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

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

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

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

    Вернемся к датчику протечки. Устройство работает от батареек CR2430, CR2450 или CR2477. Потребление во сне составляет менее 3мкА. Скорость передачи — 250Kbps, 10-15ms. Энергопотребление в момент передачи составляет не более 8мА. Теоретически срок работы на одной батарейке примерно равен сроку саморазряда батарейки. На практике все конечно менее радужно, так как есть процедура регистрации, презентации, периодическая отправка уровня заряда, так что срок работы от одной батарейки скорее ближе к значению — срок саморазряда/2 :). Питание осуществляется напрямую от батарейки, контроль уровня заряда батарейки производится непосредственно с пина VDD. В датчике установлен RGB LED для индикации регистрации датчика в сети, для индикации сервисных режимов и для индикации детектирования протечки. Естественно светодиод может не использоваться вообще или использоваться частично.

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



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





    Фотографии датчика















    Код тестовой программы
    wl_standart_test.ino

    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 = 172800000; //48 hours
    //unsigned long SLEEP_TIME = 3600000; //1 hour
    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 = 2300;
    uint16_t battery_vcc_max = 3000;
    
    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 86
    #define MY_PARENT_NODE_ID 0
    #define MY_PARENT_NODE_IS_STATIC
    #define MY_TRANSPORT_UPLINK_CHECK_DISABLED
    #define INTR_PIN 3 //(PORT0,  gpio 5)
    #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(24, OUTPUT);
      //pinMode(20, OUTPUT);
    }
    
    void before()
    {
      NRF_POWER->DCDCEN = 1;
      NRF_UART0->ENABLE = 0;
      digitalWrite(BLUE_LED, LOW);
      sleep(50);
      digitalWrite(BLUE_LED, HIGH);
    }
    
    void presentation() {
      sendSketchInfo("EFEKTA ST WL Sensor", "1.1");
      present(W_L_SENS_CHILD_ID, S_CUSTOM, "SWITCH STATUS");
      present(LINK_QUALITY_CHILD_ID, S_CUSTOM, "LINK_QUALITY");
    }
    
    void setup() {
      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;
      wait(100);
      sendBatteryStatus();
      wait(100);
      send(sensMsg.set(detection), 1);
      wait(2000, 1, V_VAR1);
    }
    
    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;
          send(sensMsg.set(detection), 1);
          wait(3000, 1, V_VAR1);
          if (AckG == 1) {
            while (timer < 10) {
              timer++;
              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;
            send(sensMsg.set(detection), 1);
            wait(3000, 1, V_VAR1);
            if (AckG == 1) {
              while (timer < 10) {
                timer++;
                digitalWrite(BLUE_LED, LOW);
                wait(20);
                digitalWrite(BLUE_LED, HIGH);
                wait(30);
              }
              timer = 0;
              AckG = 0;
            } else {
              while (timer < 10) {
                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 {
    
            }
          }
        }
      }
    }
    
    
    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);
      }
      sendBatteryLevel(currentBatteryPercent, 1);
      wait(3000, C_INTERNAL, I_BATTERY_LEVEL);
      if (AckB == 1) {
        AckB = 0;
        flag_lq = 1;
      } else {  
        sendBatteryLevel(currentBatteryPercent, 1); 
        wait(3000, C_INTERNAL, I_BATTERY_LEVEL); 
        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);
        sendSignalStrength(linkQuality, 1);
        wait(2000, 1, V_VAR1);
        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);
      }
    }
    

    MyBoardNRF5.h

    #ifndef _MYBOARDNRF5_H_
    #define _MYBOARDNRF5_H_
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif // __cplusplus
    
    
    #define PINS_COUNT (32u)
    #define NUM_DIGITAL_PINS (32u)
    #define NUM_ANALOG_INPUTS (8u)
    #define NUM_ANALOG_OUTPUTS (8u)
    
    #define PIN_LED1 (27)
    #define PIN_LED2 (25)
    #define PIN_LED3 (26)
    #define RED_LED (PIN_LED1)
    #define GREEN_LED (PIN_LED2)
    #define BLUE_LED (PIN_LED3)
    
    #define PIN_BUTTON (14)
    #define W_L_SENS (8)
    
    #define POWER_PIN (7)
    
    #define PIN_SERIAL_RX (12)
    #define PIN_SERIAL_TX (11)
    
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif
    


    nRF52832 программно настроен на работу в режиме пониженного энергопотребления (DC-DC Mode), Вывод МК из сна по сигналу от микросхемы SN74LVC1G00 настроен через внутренний компаратор LPCOMP. Устройство так же имеет тактовую кнопку для реализации сервисных режимов, таких как привязка устройства, обнуление устройства и т.п. Кнопка заведена на ту же ножку МК что и детектор протечки. Обе линии разделены диодами Шоттки. Микросхема SN74LVC1G00 в режиме мониторинга ничего не потребляет. Управление питанием микросхемы осуществляется с ножки МК.

    На данный момент почти закончена разработка контроллера протечки воды, с которым данные датчики должны работать.

    Видео с демонстрацией работы датчика протечки


    GitHub проекта
    (гербер файлы, софт, модели корпуса, список компонентов)

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

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

      +11
      Ой. Напишу-ка я немного критики.

      Датчик протечки — последний рубеж обороны, критически важный по надёжности датчик. Вот по ней, родимой, и буду проходится.

      Для начала — а собственно датчика протечки я и не увидел, то что показано это обработчик\ передатчик срабатываюший на замыкание контактов. Ну не считать же датчиком два винта? Хотя многие люди считали (до протечки)\считают что датчик протечки можно слелать из говна и палок любых токопроводящих поверхностей, типа вода токопроводящая -она замкнёт. Но вода- она разная бывает…

      Давайте смотреть. На датчик у вас идёт постоянный ток, что во влажных условиях, означает наличие электролиза. Что ведёт к покрытию проводников окислами, что ведёт к потере проводимости. Мне скажут — там мизерный ток, какой электролиз? Ток мизерный, зато время гигантское — годы. Или вы собираетесь постоянно чистить контакты?

      Далее. Предатчик у вас слабенький, так что вполне вероятно что прийдётся подбирать его местоположение среди труб с точки зрения радиовидимости, а датчик подключать по кабелю. Но ваш передатчик понятия не имеет поключено ли к нему что-либо. Ему все равно, для него обрыв — нормальное состояние. А мыши кабели любят.

      Что видится? Датчик на переменном токе с терминальным резистором. И максимальное сопротивление датчика мерять время от времени. Либо датчик влажности: тут и контроль на обрыв и постоянный ток не помеха и как влажность до 100% дошла — тревога.
        +1
        На датчик у вас идёт постоянный ток, что во влажных условиях, означает наличие электролиза.

        При разности потенциалов между электродами менее 1,23 В, электролиз не идет.
        Если разность потенциалов таки больше, можно использовать емкостный датчик.
          0
          Там вроде 3,3В, правда через мегаомы. Я очень не доверяю датчикам протечек работающим на замыкание водой. Электролиз -только одна из причин. Ещё они тупо могут покрыватся всякой токонепроводящей грязью, которой обычно полно там, где может скапливатся вода в случае протечки.
            0
            Толстая плёнка окислов тут может быть проблемой, грязь — не особо, при заливании датчика она намокнет и будет проводить.

            Емкостной датчик красивее, но там есть свои проблемы.
              +1
              Вот прибывает у нас водичка, а на ней масляная\соляровая плёночка. Плёночка доходит до контактов РАНЬШЕ водички и начинает их покрывать плохо проводящей несмачиваемой гадостью.

              Вот еще что в голову пришло. Как, при контактном датчике, отнесётся передатчик к факту протекания по воде переменного тока от залитого двигателя\клапана? По идее — никак. Но я не знаю точно.

              А идеальных датчиков нет. Приходится выкручиватся с тем, что есть. Но бинарные датчики (замыкатели\размыкатели) при своей простоте очень сложно поддаются дистанционному контрою целостности. А как говорил мой преподователь по промавтоматике «прежде чем брать значечние с датчика — убедись что датчик не сдали в металолом»

              Просто для устройства защиты, надёжность — главное, цена вторична (она на порядки меньше цены защищаемого). И если надёжности нет, то и ставить устройсво нет смысла.
              Для подобных датчиков «последнего рубежа» надёжность определяется количеством 9 после запятой в числе 99.999% В данном устройстве, боюсь, что надёжность и до 90 недотянет.

              Я бы не начинал всю эту писанину, если бы это не обзывалось как «датчик протечки воды».
                0
                В промышленности вообще требования к датчику будут немножко другие, и задачи там тоже немножко другие. Толку там с точечного датчика в красивом корпусе, если надо двести метров трубы, в которой кипяток на 140 °С, контролировать.

                А дома на кухне у вас вряд ли много солярки разлито.
          0
          Не знаю как у Вас, но в моей квартире протечки бывают крайне редко. Если только под раковиной при неправильной замене картриджа системы обратного осмоса (бывает, недокручу крышку). Для защиты можно использовать вот такое решение. А в санузлах если что-то периодически подтекает, надо вызывать сантехника. В 99.9% случаев там долно быть сухо!
          Датчик влажности (каким, например, принято измерять влажность почвы), датчик влажности воздуха и датчик протечки — совершенно разные приборы. И автор статьи сделал всё правильно. Но, я бы запитал от сети, потому что запорное устройство всё равно потребует более мощного питания, если речь идёт о квартире.
            0
            На его основе можно сделать и датчик. разметить на пути движения мембраны кнопку и будет ее нажимать при заливе.
          0
          Использую датчик проводимости, нормально стоят с графитовыми стержнями и переменным напряжением 3.3В. Перманентно под водой.
            0
            … и переменным напряжением 3.3В. Перманентно под водой.


            Так у вас датчик осушения, да еще на переменном токе! А это совсем другой разговор. У вас и контроль обрыва организовывыется и с загрязнением\окислением всё по другому. Ничего не имею против.
            0
            На одну Ардуино-ноу можно повесить 254 таких датчика? А потом нода может передавать данные через гейт? Не дорогое решение? А надолго батарейки хватит, а то несколько сотен батареек регулярно менять — та ещё тягомотина.
              0
              Вот к стати, да. Насколько реально хватает батарейки 2032 так и не понял. У меня все батарейки хранятся в холодильнике по многу лет практически без саморазряда.
              • UFO just landed and posted this here
                  0
                  … пару десятков месяцев,… так тоже не точно, но точнее чем у вас
                    0
                    предыдущий коммент удален, поэтому мой выше получается «вырванным из контекста».
                  0
                  Если использовать RFM95 (LoRa) — 10 — 15 лет. автономной работы.
                  0
                  по тексту было просто описание возможностей устройств в майсенсорс. в данном случае, конкретно с этим датчиком можно сказать что имеется нода с 3 сенсорами, 1 — детектирование воды, 2- контроль заряда батареи, 3 — уровень сигнала. Теоретически к этому устройству можно еще навесить 254-3=251 сенсор, будь то физические управляемые ножками МК какие то устройтва, подключенные по шинам датчики и передающие в МК какие то данные и тп и тд. А 252-е уже нельзя… Вот об этом речь выше, а не об Ардуино к которой можно зачем то подключить 254 таких датчика с собственными МК :)
                  0
                  Датчик имеет право на жизнь. Только прокладку не забудьте при сборке корпуса. Все таки долгое время работы в потенциально влажном помещении.

                  Only users with full accounts can post comments. Log in, please.