Защита от протечек с блекджеком и счетчиками

Приветствую.
Есть такая штука — гидролок\нептун\авквасторож — системы перекрытия подачи воды, если происходит не контролируемая утечка. Принцип простой — датчик воды + автоматика + пара кранов с электроприводами. Но дьявол как обычно в деталях: как устроены краны, как устроены датчики протечки и почему один стоит 50 рублей, а другой 500р. На все это дело навернут килограм макетингового булшита, упаковка вырви глаз и т.д.
В рассказе пройдусь по кирпичикам системы, чем руководствовался в выборе.
Вся система строится на заводских датчиках и самодельном контроллере на базе Particle (ex.Spark) Photon (такая esp8266 у которой облачная IDE на wiring из коробки), база девайса stm контроллер + wifi модуль от броадкома. Все это завязано на openhab сервер на Orange Pi One
7156eeaa28b44479a5d5aff2e63ff89c.png


Почему не готовая система?
— Потому что могу сам и это в кайф
— У готовых систем хромает интеграция с внешними системами.
— У готовых систем нет вспомогательных функций — учет показаний счетчиков, датчики температуры воды, нотификация об отключениях воды и прочие пешие эротические фантазии.

Начем с кранов.
Выбирал тупо в лоб по крутящему моменту. Некоторое время проживал в подмосковье, где качество воды (как наверно везде в замкадье) оставляет желать лучшего. Так шаровые краны на 1\2 дюйма если год не трогать — повернуть очень тяжело. А на полотенцесушителе 1 дюймовые я даже и не пытаюсь шевелить — только если усилить плечо ключем разводным, а тут и сорвать чего-нибудь можно. Проблема в отложениях кальциево-хзчего, «зарастают» одноим словом.
Соответсвенно выбор пал на проф серию от гидролока — 21Н*м крутящего момента по ощущениям не рекламный треп, кран просто огромный — оцените место его установки перед покупкой.

758e0718cc9a46e09306bac214db8cf1.jpg

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

f9daa4a251e140c3bbe4b049a5af7f46.jpg

Перед нами верхняя часть платы и шаговый двигатель.
Питается все это от 12 вольт. Замыкание контрольного кабеля на землю переводит кран в закрытое положение.
На плате видим простенький контроллер PIC 12f629. Дожили, контроллер в приводе крана.
Сзади платы самое интересное.

c14e08f2e4224088a534e1811fa3729a.jpg

L293 драйвер шаговика и фотопара (излучатель + фотоприемник). Она смотрит на основную шестиренку привода, которая раскрашена на части — белая и черная, закрыто\открыто.

a14cec1771ef4d52bc15c83e9183124e.jpg
Кран вращается все время в одну сторону, логика контроллера простая — крутим вал, пока не переключимся на нужный цвет. Вращение крана в одну сторону, это меньше износ, а бесконтактный способ определения положения — меньше шансов закисания\сбоя переменного резистора или концевика.

Для монтажа можно открутить кран от привода — держится на 2 гайках.
Между приводом и краном теплоизоляционная прокладка.
bf737c1f7a144c72baba7a17fdc94615.jpg

Ремонт у меня был полтора года назад. Кран покупал года три назад — разобрать, посмотреть внутри, купить еще и накрутить во время ремонта. Ага, сейчас… максимум чего успел в этом ацком цирке — заложить в сборку водоразводки грязевик с переспективой заменить его на кран.
И вот только спустя полтора года — докупил второй кран и накрутил их.

В итоге мы наблюдаем странное и редкое явление (читать голосом Дроздова) — вся информация с сайта производителя подтвердилась. Причем описание своеобразное, как будто писали технари, а потом маркетинг полирнул для народа, но все равно мало кто поймет все фишки. Не хватает раздела на сайте — для интеграторов с техподробностями внутри. Даже на счет повышенного крутящего момента на старте не соврали — кран на старте бухтит движком в 1,5А и через 2–3 сек начинает уже гундеть в обычном (ток 0,7 А) режиме. На закрытие уходит секунд 25–30.

Еще из опыта: на счет крутящего момента — он избыточный для Мск, тут вода вполне ОК, за полтора года в 100 мкм фильтре пара окалин и никакого заростания. За большой крутящий момент приходится платить и ценой, и временем открытия, и местом в шкафу. Думаю тут обычных приводов хватит от Гидролока Ультимэйт, Нептуна или Аквасторожа. За два последних не поручусь — не разбирал, лет 5 назад у них были частично пластиковые шестерни, сейчас вроде это исправили.
Еще есть гидролок виннер с прямым подключением датчиков к приводу — это если вам не надо все что я наворотил. Там питание автономное от 4 батареек, а база похоже от ультимэйт привода. Вообще, он потенциально интересен и для контроллера самопального — питание 5 вольт, не надо две шины на 5 и 12 вольт городить и можно выбросить опторазвязку.

Датчики протечки.
Купил датчики той же контроры WSU — универсальные. У них два выхода «открытый коллектор», один тянет на землю только при наличии воды, второй — если вода попала, то тянет на землю все время, пока питание не рубанешь. Только первый выход использую, остальная логика в контроллере, но похоже этот выход может пригодиться для каких более кондовых систем диспечеризации.
Провода в комплекте метра три где то.
Цвет проводов — Адъ_и_Израиль. Зацените цитату:
красный (коричневый) провод (Vcc) питание от +5 до +30 вольт.
черный (белый) провод (OUT2)
зеленый провод (OUT1)
желтый провод (GND)

Вот что мешало сделать белый\черный землей?
На приводе крана тоже кстати провода по цвету с логикой не але.
Первый датчик стоит на кухне, под раковиной рядом с посудомойкой.
2ea407a255b8420e9b1d6c26772e2709.jpg
Второй в ванной в специальной водоотводной канаве. Когда делал стяжку — не довел ее до стены. Получился этакий зумпф для сбора воды с ванной и туалета.
51d71589848d4a77a59f8caf0dbf2041.jpg
Из опыта эксплуатации — ужу было одно ложное срабатывание датчика у посудомойки. Судя по логу на один цикл опроса (500 мс) было замыкание, модифицировал код — смена состояния теперь происходит при 10 подряд одинаковых значениях с датчика.
Контакты датчика покрыты позолотой. У товарища подобные датчики уже несколько лет, окисления не замечено.

Датчики давления
Практически — показометры. Точность ± 0,5 атм меня полностью устроила.
На основе датчиков приходит оповещении по отключению воды.
Покупал на Али тут — www.aliexpress.com/item/Pressure-Sensor-Transmitter-DC-5V-G1–4–0–1–2-MPa-0–174-PSI-For-Water/1669537885.html

Датчики температуры
А почему бы и не добавить?
Из полезного — сможет раз в год оповестить об отключении горячей воды.
Используются банальные ds18b20.

Счетчики
Самые обычные Itelma, раз в 10 литров замыкают контакты. На стороне контроллера выход подтянут к + 3,3v, счетчик тянет его на землю.

Контроллер
3184e6d7ad9c4eab99d9962410c7918c.jpg

Внутри
a8372d1170ca43f8b92925fa76a240f8.jpg
f7be62ee80a54ff2a0ed60ad17467a8b.jpg


На базе Particle Photon, подробнее тут www.particle.io
Есть у них версия с 2G или 3G модулем (Electron).
Первые прошивки был полный шлак, поморгать диодами ОК, но как только начинаешь чето сложное колбасить, играть с i2c и прерываниями может терять wifi. Сейчас жить можно.
В принципе можно выкинуь датчики давления из схемы и замутить все на ESP8266 — дерзайте.
Первым делом photon надо привязать к аккаунту particle (делается через App на мобиле или через консоль Particle CLI — пользую только второй метод) и прописать wifi сеть.
После привязки в build.particle.io/build в разделе устройств появляется контроллер и его статус подключения к облаку.
71dc17687ccb4a289d0a0dd2296fdfca.png
У меня все ноды подключаются к облаку только для обновления прошивки. Не то что бы я параноил — просто работа с облаком жрет не богатые ресурсы контроллера.
IDE поддерживает работу с библиотеками, буквально десяток поддерживается самой контрой, остальные — сообществом. По моему наблюдению все распространенное давно портировали, еще фишка — в IDE сразу вижно сколько проектов используют библитеку.

Исходный код прошивки
// This #include statement was automatically added by the Particle IDE.
#include "Adafruit_SSD1306/Adafruit_SSD1306.h"

// This #include statement was automatically added by the Particle IDE.
#include "MQTT/MQTT.h"

// This #include statement was automatically added by the Particle IDE.
#include "OneWire/OneWire.h"


SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

struct counter_struct {
  float value;
  byte state;
  int pin;
};
struct valve_struct {
  byte state;
  int pin;
};
struct sensor_struct {
  int timeout;
  byte state;
  int pin;
};

unsigned long currentMillis = 0;
unsigned long previous_conected = 100000; //финт ушами 
unsigned long previous_wifi_uptime = 100000; //финт ушами 
unsigned long previous_counter_read = 0; //финт ушами 
unsigned long wifi_uptime;
unsigned long start_temp_timer = 0;
unsigned long read_temp_timer = 0;
byte display_timeout = 0;

//temp onewire 
OneWire ds0 = OneWire(D2);
OneWire ds1 = OneWire(D3);
byte addr0[8];
byte addr1[8];
bool presense0 = false;
bool presense1 = false;
byte data[12];

#define OLED_RESET A7
Adafruit_SSD1306 display(OLED_RESET);


//valve control
retained valve_struct valve[2] = { {0, D4}, {0, D5} };

//counter control
retained counter_struct counter[2] = { {0, 1, A0}, {0, 1, A1} };
volatile int pressure[2] = {A2, A3};
#define SENSOR_TIMEOUT 10
volatile sensor_struct sensor[2] = { {0, 1, D6}, {0, 1, D7} };

void callback(char* topic, byte* payload, unsigned int length);

byte server[] = { 192,168,2,101};
MQTT client(server, 1883, callback);

bool publish_message(const char* t, const char* p, bool retain) 
{
    return client.publish(t, (uint8_t*)p, sizeof(p), retain);
}

bool publish_message(const char* t, int p, bool retain) 
{   
    char buf_d[12];
    int n = sprintf(buf_d,"%d",p);
    return client.publish(t, (uint8_t*)buf_d, n, retain);
}

bool publish_message(const char* t, float p, bool retain) 
{   
    //char buf_f[18];
    String s(p, 4);
//    dtostrf(p, 9, 4, buf_f);
    //int n = sprintf(buf_f,"%f",p);
    return client.publish(t, (uint8_t*)s.c_str(), s.length(), retain);
}

// recieve message
void callback(char* topic, byte* payload, unsigned int length) {
    char p[length + 1];
    memcpy(p, payload, length);
    p[length] = NULL;
    String message(p);
    String t(topic);
    if (t.equals("home/water_count/spark/set"))
    {
        if (message.equalsIgnoreCase("1"))
        {
            Particle.connect();
            if (waitFor(Particle.connected, 10000)) 
                {publish_message("home/water_count/spark", 1, false);}
            else
                {Particle.disconnect(); publish_message("home/water_count/spark", 0, false);}
        }
        else
        {
            Particle.disconnect();
            publish_message("home/water_count/spark", 0, false);
        }
    }
    else if (t.startsWith("home/water_count/valve/"))
    {
        int m = message.toInt();
        int x = t.substring(23,24).toInt();
        if (m > -1 && m < 2 && x > -1 && x <2) 
        {
            set_valve(x, m);
        }
        else
        {
            publish_message("home/water_count/valve/" + t.substring(23,24),  valve[x].state , true);
        }
    }
    else if (t.startsWith("home/water_count/counter/"))
    {
        float m = message.toFloat();
        int x = t.substring(25,26).toInt();
        if (m > -1 && m <= 999999 && x > -1 && x <2) 
        {
            counter[x].value = m;
        }
        publish_message("home/water_count/counter/" + t.substring(25,26),  counter[x].value , true);
    }
}

void setup() {
//Serial.begin(9600);
    WiFi.on();
    WiFi.connect();
    if (waitFor(WiFi.ready, 5000)) {mqtt_connect();}
    for (int i=0; i < 2; i++) 
    {
        pinMode(valve[i].pin, OUTPUT);
        digitalWrite(valve[i].pin, valve[i].state);
        pinMode(counter[i].pin, INPUT);
        pinMode(sensor[i].pin, INPUT);
        counter[i].state = digitalRead(counter[i].pin);
        pinMode(pressure[i], AN_INPUT);
    }
    pinMode(A4, INPUT_PULLUP);

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x64)
    display.clearDisplay();   // clears the screen and buffer

    //Particle.connect();
}

void loop() 
{
    currentMillis = millis();
    // проверяем наличие сети и подключения к MQTT брокеру
    if (currentMillis - previous_conected >= 30000 || previous_conected > currentMillis)
    {
        previous_conected = currentMillis;
        if (!client.isConnected() & wifi_uptime > 60)
        {
            mqtt_connect();
        }
        publish_message("home/water_count/rssi", WiFi.RSSI(), true);
    }
    if (currentMillis - previous_wifi_uptime >= 1000 || previous_wifi_uptime > currentMillis)
    {
        previous_wifi_uptime = currentMillis;
        WiFi.ready() ? wifi_uptime++ : wifi_uptime = 0;
        //work with button and display
        int fg = digitalRead(A4);
        if (display_timeout > 0)
        {
            display_timeout -= 1;
            if (display_timeout == 0) 
            { 
                display.clearDisplay();
                display.display();
            } 
        }
        if (fg == 0)
        {
            if (display_timeout == 0)
            {
                display.clearDisplay();   // clears the screen and buffer
                display.setTextSize(2);
                display.setTextColor(WHITE);
                display.setCursor(0,0);
                display.print("C=");
                display.println(counter[0].value, 4);
                display.setCursor(0,16);
                display.print("H=");
                display.println(counter[1].value, 4);
                display.setCursor(0,32);
                display.print("Valve=");
                display.print(valve[0].state);
                display.print("|");
                display.println(valve[1].state);
                display.setCursor(0,48);
                display.print("Sensor=");
                display.print(sensor[0].state);
                display.print("|");
                display.println(sensor[1].state);
                display.display();
            }
            display_timeout = 10;
        }
    }
    //counter check
    if (currentMillis - previous_counter_read >= 500 || previous_counter_read > currentMillis)
    {
        previous_counter_read = currentMillis;
        for (int i=0; i < 2; i++) 
        {
            byte count_state = digitalRead(counter[i].pin);
            if (count_state != counter[i].state)
            {
                counter[i].state = count_state;
                if (count_state == 0)
                {
                    counter[i].value += 0.01;
                    char buf18[30];
                    sprintf(buf18,"home/water_count/counter/%d", i);
                    publish_message(buf18 , counter[i].value, true);
                }
            }
            // работаем с датчиком протечки
            byte sensor_state = digitalRead(sensor[i].pin);
            if (sensor_state != sensor[i].state) //
            {
                sensor[i].state = sensor_state;
                sensor[i].timeout = SENSOR_TIMEOUT; 
            }
            if (sensor[i].timeout > 0) 
            {
                sensor[i].timeout -= 1;
                if (sensor[i].timeout == 0)
                {
                    char buf18[30];
                    sprintf(buf18,"home/water_count/sensor/%d", i);
                    publish_message(buf18 , sensor[i].state, true);
                    if (sensor[i].state  == 0)
                    {
                        set_valve(0, 1); //close both valve
                        set_valve(1, 1); //close both valve
                    }
                }
            }
        }
    }

    // temp onewire
    if (currentMillis - start_temp_timer >= 299000 || start_temp_timer > currentMillis)
    { //стартуем расчет
        start_temp_timer = currentMillis;
        presense0 = start_temp0();
        presense1 = start_temp1();
    }
    if (currentMillis - read_temp_timer >= 300000 || read_temp_timer > currentMillis)
    {//считывваем темп
        read_temp_timer = currentMillis;
        start_temp_timer = currentMillis;
        if (presense0) read_temp0();
        if (presense1) read_temp1();
        //preasure calc and send
        char buf18[30]; 
        for (int i=0; i < 2; i++)
        {
            sprintf(buf18,"home/water_count/pressure/%d", i);
            float read_val = analogRead(pressure[i]);
            float value = (read_val - 600.0) / 300.0 ;
            publish_message(buf18 , value, false);
        }
    }
    //Particle.process();
    client.loop();
}

void mqtt_connect()
{
    if (client.connect("water_count"))
    { //подпись на spark и публикуем послднее состояние
        client.subscribe("home/water_count/spark/set");
        publish_message("home/water_count/spark", Particle.connected() ? 1 : 0, true);
        client.subscribe("home/water_count/valve/+/set");
        client.subscribe("home/water_count/counter/+/set");
        
    }
}

bool start_temp0()
{
    
    if ( !ds0.search(addr0)) { ds0.reset_search(); return false;}
    ds0.reset_search();
    if (OneWire::crc8(addr0, 7) != addr0[7]) { return false;}
    
    ds0.reset();
    ds0.select(addr0);
    ds0.write(0x44, 0);
    return true; 
}
bool start_temp1()
{
    
    if ( !ds1.search(addr1)) { ds1.reset_search(); return false;}
    ds1.reset_search();
    if (OneWire::crc8(addr1, 7) != addr1[7]) { return false;}
    
    ds1.reset();
    ds1.select(addr1);
    ds1.write(0x44, 0);
    return true; 
}

bool read_temp0()
{
    //delay(1000);
    ds0.reset();
    ds0.select(addr0);
    ds0.write(0xBE, 0);

    for (int i = 0; i < 9; i++) 
    {
        data[i] = ds0.read();
    }
    int16_t raw = (data[1] << 8) | data[0];
    float celsius = (float)raw * 0.0625;
    if (celsius < 0 || celsius > 100) return false;
    publish_message("home/water_count/temp/0", celsius, false);
    //Serial.println(celsius);
    ds0.reset_search();
    return true;
}
bool read_temp1()
{
    //delay(1000);
    ds1.reset();
    ds1.select(addr1);
    ds1.write(0xBE, 0);

    for (int i = 0; i < 9; i++) 
    {
        data[i] = ds1.read();
    }
    int16_t raw = (data[1] << 8) | data[0];
    float celsius = (float)raw * 0.0625;
    if (celsius < 0 || celsius > 100) return false;
    publish_message("home/water_count/temp/1", celsius, false);
    //Serial.println(celsius);
    ds1.reset_search();
    return true;
}

void set_valve(int vlv, byte state)
{
    valve[vlv].state = state;
    digitalWrite(valve[vlv].pin, state);
    char buf26[26];
    sprintf(buf26,"home/water_count/valve/%d", vlv);
    publish_message(buf26 , state , true);
}



Через MQTT подключаемся к брокеру. Мониторим датчики и шлем в соответствующие ветки mqtt события и значения.
Например
home/water_count/valve/0 — привод хол воды.
home/water_count/counter/0 — показания счетчика хол воды
Подписываемся на команды изменения состояния привода и установки текущего значения счетчика (холодной и горячей воды):
client.subscribe («home/water_count/valve/+/set»);
client.subscribe («home/water_count/counter/+/set»);
На устройстве одна кнопка — по нажатию включаем экран, рисуем текущие показания счетчиков, сенсоров и кранов. Экран OLED, быстро выгорает если делать включенным все время.
STARTUP (System.enableFeature (FEATURE_RETAINED_MEMORY));
Это интересная пограммно-аппаратная фишка контроллера stm, в reference Particle называют ее BackupSRAM. У Photon есть вывод vbat — это не батарейное питание и не зарядка. Пока есть напряжение на этой ноге, содержимое 4 кбайт SRAM сохраняется при полной обесточенности контроллера. Таким образом отпадает проблема износа EEPROM.
В коде переменные, которые надо загнать в эту память объявляют с указанием: retained.
Аппаратно я реализовал подпитку от суперконденсатора на 1,5F.
По даташиту память сдохнет на 1,6v, по моим стендовым опытам на протоборде это настанет через 2 недели примерно с моим конденсатором.
Логика закрытия кранов при срабатывании датчиков «автономна» и не зависит от подключения к openhab. Есть 3 ходовый переключатель прямого управления приводами — автоматика, OFF (открытые краны), Close (закрываем).

Схема платы ниже
1a8f4c63dba34f4e9905b703fabad0fd.png
Проект Eagle вместе с кастомными либами можно скачать тут.
Плата делалась ЛУТ, в дорожках не мельчил.

Водяная баня лучшый друг ЛУТ
351e371c3caf487d9f14f839f89402be.jpg


Блок питания. Нам надо и 12 и 5 вольт. Донор ищется на ebay по строке: «hard drive power adapter 5v 12v», типа такого www.ebay.com/itm/36cm-Cable-12V-5V-AC-Power-Supply-HDD-HARD-DISK-DRIVE-IDE-Adapter-Converter-/161563404216

Корпус.
Распечатывался пластиком PLA на 3d принтере (Tarantula Tevo).
Сопло 0.4 мм, слой 0,25 мм.
Крышка является заодно и базой для крепления платы контроллера.
База с блоком питания крепится к стене.
База с крышкой не скрепляются винтами, хватает натяжения крышки (как у бабушки крышки на банках с вареньем) и работает слоистая структура стенок.
3D модель в архиве.
94511037d3734ad1a573e84e5fc426ef.png

5aa546c0b6dd41d58fd6b33bde08df74.png

Вот как это все выглядить смонтированное на водоразводке.
cfb293f66c3f4bb9ad2f46e812418918.jpg

Openhab
Развернут на Orange Pi One под Armbian.

Конфиг Item

Без картинок — если оставить теги картинок, хабр ломается.
Number watercount_temp1 «T cool [%.1f °C]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/temp1:state:default]" }
Number watercount_temp2 «T hot [%.1f °C]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/temp2:state:default]" }
Number watercount_count0 «Count cool [%.2f М³]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/counter/0:state:default]" }
Number watercount_count1 «Count hot [%.2f М³]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/counter/1:state:default]" }
Number watercount_pressure0 «P cool [%.2f Атм.]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/pressure/0:state:default]" }
Number watercount_pressure1 «P hot [%.2f Атм.]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/pressure/1:state:default]" }
Number watercount_sensor0 «Sensor0 is [MAP (water_sensor.map):%s]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/sensor/0:state:default]" }
Number watercount_sensor1 «Sensor1 is [MAP (water_sensor.map):%s]» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/sensor/1:state:default]" }
Number watercount_valve0 «Valve cool» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/valve/0:state:default], >[mqtt_bro: home/water_count/valve/0/set: command:*: default]» }
Number watercount_valve1 «Valve hot» (gWaterCount) { mqtt=»<[mqtt_bro:home/water_count/valve/1:state:default], >[mqtt_bro: home/water_count/valve/1/set: command:*: default]» }
String watercount_sendStr «LastVol:[%s]» (gWaterCount)
Number watercount_sendCool «Send cool [%.2f М³]» (gWaterCount)
Number watercount_sendHot «Send hot [%.2f М³]» (gWaterCount)
Number watercount_sendSwitch «Autosend» (gWaterCount)
Number watercount_rssi «WaterCount [%d dB]» (gSpark_RSSI) { mqtt=»<[mqtt_bro:home/water_count/rssi:state:default]" }
Number watercount_spark_state «WaterCount Spark» (gSpark) { mqtt=»<[mqtt_bro:home/water_count/spark:state:default], >[mqtt_bro: home/water_count/spark/set: command:*: default]» }


Нужно небольшое правило трансформации для показаний датчика протечки.

Конфиги transform

transform\water_sensor.map
1=dry
0=wet
undefined=undefined


Формируем страницу для управления

Конфиги Sitemap

Sitemap
Text label=«Водоподготовка» icon=«water»
{
Frame
{
Text item=watercount_temp1
Text item=watercount_count0
Text item=watercount_pressure0
Switch item=watercount_valve0 mappings=[1=«Close», 0=«Open»]
}
Frame
{
Text item=watercount_temp2
Text item=watercount_count1
Text item=watercount_pressure1
Switch item=watercount_valve1 mappings=[1=«Close», 0=«Open»]
}
Frame
{
Text item=watercount_sensor0
Text item=watercount_sensor1
}
Frame
{
Switch item=watercount_sendSwitch mappings=[0=«OFF», 1=«ON»]
Text item=watercount_sendStr
Text item=watercount_sendCool
Text item=watercount_sendHot
}
}


И правила обработки

Конфиги Rules
rule «Check watercount_sensor0»
when
Item watercount_sensor0 received update
then
if ((watercount_sensor0.state as DecimalType) == 1)
{
if ((watercount_sensor0.historicState (now.minusSeconds (3)).state as DecimalType) == 1)
{
sendTelegram (»****_bot», «Sensor0 was wet less than 5 seconds»)
}
else
{
sendTelegram (»****_bot», «Sensor0 become dry»)
}
}
else
{
if ((watercount_sensor0.historicState (now.minusSeconds (3)).state as DecimalType) == 0)
{
sendTelegram (»****_bot», «Sensor0 was dry less than 5 seconds»);
}
else
{
sendTelegram (»****_bot», «Sensor0 become wet! Valves will be closed!»)
}
}
end
rule «Check watercount_sensor1»
when
Item watercount_sensor1 received update
then
if ((watercount_sensor1.state as DecimalType) == 1)
{
if ((watercount_sensor1.historicState (now.minusSeconds (3)).state as DecimalType) == 1)
{
sendTelegram (»****_bot», «Sensor1 was wet less than 5 seconds»)
}
else
{
sendTelegram (»****_bot», «Sensor1 become dry»)
}
}
else
{
if ((watercount_sensor1.historicState (now.minusSeconds (3)).state as DecimalType) == 0)
{
sendTelegram (»****_bot», «Sensor1 was dry less than 5 seconds»);
}
else
{
sendTelegram (»****_bot», «Sensor1 become wet! Valves will be closed!»)
}
}
end

rule «Check watercount_temp2»
when
Item watercount_temp2 received update
then
if ((watercount_temp2.state as DecimalType) < 37 )
{
sendTelegram (»****_bot», String: format («Hot water temp drop to %s», watercount_temp2.state.toString));
}
end

rule «Check watercount_pressure0»
when
Item watercount_pressure0 received update
then
if ((watercount_pressure0.state as DecimalType) < 1 && (watercount_pressure0.historicState(now.minusSeconds(3)).state as DecimalType) >= 1)
{
sendTelegram (»****_bot», String: format («Cool pressure drop to %s», watercount_pressure0.state.toString));
}
if ((watercount_pressure0.state as DecimalType) > 1 && (watercount_pressure0.historicState (now.minusSeconds (3)).state as DecimalType) <= 1)
{
sendTelegram (»****_bot», String: format («Cool pressure rise to %s», watercount_pressure0.state.toString));
}
end
rule «Check watercount_pressure1»
when
Item watercount_pressure1 received update
then
if ((watercount_pressure1.state as DecimalType) < 1 && (watercount_pressure1.historicState(now.minusSeconds(3)).state as DecimalType) >= 1)
{
sendTelegram (»****_bot», String: format («Hot pressure drop to %s», watercount_pressure1.state.toString));
}
if ((watercount_pressure1.state as DecimalType) > 1 && (watercount_pressure1.historicState (now.minusSeconds (3)).state as DecimalType) <= 1)
{
sendTelegram (»****_bot», String: format («Hot pressure rise to %s», watercount_pressure1.state.toString));
}
end

rule «Generate send string counters» //every 24 day of mounth in 00.01 minutes
when
Time cron »0 0 1 24 1/1?»
then

var float deltaCool = (watercount_count0.state as DecimalType).floatValue () — (watercount_sendCool.state as DecimalType).floatValue ()
var float deltaHot = (watercount_count1.state as DecimalType).floatValue () — (watercount_sendHot.state as DecimalType).floatValue ()

if (deltaCool >= 0 && deltaHot >= 0)
{
watercount_sendStr.postUpdate (String: format (» %.2f / %.2f м3», deltaCool, deltaHot))
watercount_sendCool.state = watercount_count0.state
watercount_sendHot.state = watercount_count1.state
sendTelegram (»****_bot», String: format («Лизюкова 23, корп 5, кв. 23. Счетчик №2560097 (хол.вода) = %.2f м3. Cчетчик №2538996 (гор.вода) = %.2f м3. %s», (watercount_sendCool.state as DecimalType).floatValue (), (watercount_sendHot.state as DecimalType).floatValue (), watercount_sendStr.state.toString ()))
}
else
{
watercount_sendSwitch.postUpdate (0)
sendTelegram (»****_bot», «Current counters value less than sended last time. Turn off autosend.»)
}
end
rule «Send string counters»
when
Time cron »0 0 23 24 1/1?»
then
if (watercount_sendSwitch.state == 1)
{
sendMail («uk@uk.ru», «Лизюкова 23, корп 5, кв. 23», String: format («Лизюкова 23, корп 5, кв. 23. Счетчик №2560097 (хол.вода) = %.2f м3. Cчетчик №2538996 (гор.вода) = %.2f м3», (watercount_sendCool.state as DecimalType).floatValue (), (watercount_sendHot.state as DecimalType).floatValue ()));
sendTelegram (»****_bot», «Send email with watercount values»);
}
else
{
sendTelegram (»****_bot», «Can’t send email with watercount values — autosend is OFF.»);
}
end

Для отправки сообщений не пользуюсь встроенным функционалом андроид приложения openhab, как и иинтеграцией с их облаком. Мне по душе бот Телеграмм.
Как настроить и подлючить бота можно подсмотреть на wiki
Для отправки писем с почтового ящеика gmail, если у вас двухфактораная аутентификация, надо включить разовый пароль для почтового приложения и прописать именно этот пороль в конфиге openhab.
Пройдусь по правилам.
Check watercount_sensor — контроллер отправляет новые значения сенсора протечки только при смене значения или если было ложное срабатывание (менее 10 циклов). Анализируем пришедшее и историческое значение, формируем информационные сообщения.
Есть нюанс — попытка получить prevoiusItem постоянно отдает текущее значение, решения не нашел — беру значение »-3 сек», если кто поборол — отпишите в коменты или в личку.
Check watercount_temp2 — проверяем, если меньше 37, значить горячая вода стала холодной, надо по приходу включить проточный нагреватель.
Check watercount_pressure — анализируем текущее и предыдущее значение, реагируем сообщением на падение ниже 1 атм и росту выше нее.
Generate send string counters — стартует по cron 24 числа каждого месяца в 1 час ночи. Проверяем, что значения сейчас больше отправленных в прошлый раз. Если меньше — выключаем автоотправку и формируем оповещение. Если ОК — запоминаем значения счетчиков для отправки в УК, отправляем в телеграмм будущее тело письма. Заодно в watercount_sendStr сохраняем, сколько мы потребили за прошлый месяц.
Generate send string counters — стартует по cron 24 числа в 23.00. Проверяет включена ли автоотправка, если вкл — шлем на почту кправляющей компании значения счетчиков.
Получается у меня есть 24 числа весь день, что то исправить или просто вырубить автоотправку, если в телеграм пришла ошибка.

И только тут начинается умный дом…
Объединение систем в единой точке (openhab) позволяет строить логику, не доступную набору автономных систем.
Например: пришло событие увеличения счетчика воды — система безопасности активна, замки входной двери закрыты, потребление электроэнергии посудомойкой и стиралкой менее 5 Вт — значит зафиксирована протечка мимо датчиков. Формируем команду на закрытие кранов, отправляем сообщение боту в Телеграмм.
Но этом как нить потом.

© Geektimes