لعبة ورق وحماية ضد التسرب

تحية طيبة. هناك شيء من هذا القبيل - hydrolock \ neptune \ avkvastorozh - أنظمة إغلاق المياه إذا حدث تسرب غير محكوم. المبدأ بسيط - مستشعر مياه + أتمتة + زوج من الصنابير الكهربائية. لكن الشيطان ، كالعادة ، يكمن في التفاصيل: كيف يتم ترتيب الصنابير ، وكيفية ترتيب أجهزة استشعار التسرب ، ولماذا يكلف أحدهما 50 روبل والآخر يكلف 500 روبل. سيتم لف كيلوغرام من نشرة التخطيط حول هذا الأمر بالكامل ، وسوف تزيل العبوة عينيك ، وما إلى ذلك.

في القصة سوف أسير في طوب النظام ، الذي قادني في الاختيار. تم بناء النظام بالكامل على مستشعرات المصنع ووحدة تحكم محلية الصنع تعتمد على الجسيمات (مثل Spark) الفوتون (مثل esp8266 الذي يحتوي على سحابة IDE على الأسلاك خارج الصندوق) ، وقاعدة الجهاز هي وحدة تحكم stm + wifi من Broadcom. كل هذا مرتبط بخادم مفتوح على Orange Pi One.



لماذا لا ينتهي نظام؟

- لأنني أستطيع القيام بذلك بنفسي وهو مرتفع
- التكامل مع الأنظمة الخارجية عرجاء في الأنظمة الجاهزة.
- لا تحتوي الأنظمة الجاهزة على وظائف مساعدة - مع مراعاة قراءات العدادات ، وأجهزة استشعار درجة حرارة المياه ، والإبلاغ عن انقطاع المياه وغيرها من الأوهام المثيرة للمشي.

لنبدأ بالرافعات


اختار بغباء في الجبهة من حيث عزم الدوران. لبعض الوقت عاش في الضواحي ، حيث تركت نوعية المياه (كما هو الحال في كل مكان في زامكادي) الكثير مما هو مرغوب فيه. لذا فإن الصمامات الكروية 1/2 بوصة إذا لم تلمس السنة - فمن الصعب جدًا تدويرها. وعلى سكة المناشف المسخنة بقياس 1 بوصة ، لا أحاول حتى تحريكها - إلا إذا قمت بتقوية الكتف بمفتاح ، ولكن هنا يمكنك تمزيق شيء ما. تكمن المشكلة في رواسب خشب الكالسيوم الصلب "المتضخم" بكلمة واحدة.

وفقًا لذلك ، وقع الاختيار على السلسلة الاحترافية من hydrolock - 21N * m من عزم الدوران لا يبدو مثل الثرثرة الإعلانية ، فإن الرافعة ضخمة ببساطة - قم بتقييم مكان تركيبها قبل الشراء.



الصنبور مختوم ، وختم مطاطي حول المحيط ، والمدخل تحت الغدة اللولبية.
انزع الغطاء.



أمامنا هو الجزء العلوي من اللوحة ومحرك السائر. كل هذا يعمل بقوة 12 فولت. يؤدي تقصير كبل التحكم إلى الأرض إلى وضع الصمام في وضع الإغلاق. على اللوحة نرى وحدة تحكم PIC 12f629 بسيطة. عاش ، تحكم في محرك الرافعة.

الجزء الخلفي من اللوحة هو الأكثر إثارة للاهتمام.



L293 سائق ومضخم ضوئي (باعث + كاشف ضوئي). تنظر إلى الترس الرئيسي لمحرك الأقراص ، والذي يتم رسمه في أجزاء - أبيض وأسود ، مغلق / مفتوح.



تدور الرافعة طوال الوقت في اتجاه واحد ، منطق التحكم بسيط - نقوم بلف العمود حتى ننتقل إلى اللون المطلوب. يكون دوران الرافعة في اتجاه واحد أقل تآكلًا ، ومن غير المحتمل أن تؤدي طريقة عدم الاتصال لتحديد الوضع إلى تعكر / عطل المقاوم المقاوم أو مفتاح الحد.

للتثبيت ، يمكنك فك الرافعة من محرك الأقراص - يتم تعليقها على صواميل. يوجد حشية عازلة للحرارة بين المحرك والرافعة.



لقد حصلت على الإصلاح قبل عام ونصف. تم شراء الرافعة قبل ثلاث سنوات - لتفكيكها ، والنظر في الداخل ، وشراء المزيد ولفها أثناء الإصلاح. نعم ، الآن ... الحد الأقصى الذي تمكنت منه في هذا السيرك الجهنمي كان وضع جامع ترابي في تجميع إمدادات المياه مع احتمال استبداله برافعة.
وفقط بعد عام ونصف - اشتريت رافعة ثانية وأثقلتها.

نتيجة لذلك ، نلاحظ ظاهرة غريبة ونادرة (تمت قراءتها بصوت Drozdov) - تم تأكيد جميع المعلومات من موقع الشركة المصنعة. علاوة على ذلك ، فإن الوصف غريب ، كما لو أن التقنيين كتبوا ، ثم تم تسويق التسويق للناس ، لكن لا يزال عدد قليل من الناس يفهمون جميع الرقائق. لا يوجد قسم كافٍ في الموقع - للمتكاملين مع التفاصيل التقنية بالداخل. حتى على حساب زيادة عزم الدوران ، لم يستلقوا في البداية - فالرافعة في البداية تنفجر بمحرك 1.5 أمبير وبعد 2-3 ثوانٍ تبدأ في الصعود في الوضع العادي (الحالي 0.7 أمبير). يستغرق حوالي 25-30 ثانية للإغلاق.

تجربة أخرى: على حساب عزم الدوران - إنها مفرطة لوقت موسكو ، هنا الماء جيد تمامًا ، لمدة عام ونصف في فلتر 100 ميكرومتر يوجد زوج من الخبث ولا يوجد نمو زائد. عليك دفع عزم الدوران الكبير مع السعر ووقت الافتتاح والمكان في الخزانة. أعتقد أنه سيكون هناك ما يكفي من محركات الأقراص التقليدية من Hydrolock Ultimate أو Neptune أو Aquastorozh. لا يمكنني أن أشهد على الأخيرين - لم أقم بذلك ، منذ حوالي 5 سنوات كان لديهم تروس بلاستيكية جزئيًا ، الآن يبدو أنهم قد أصلحوا ذلك.

هناك أيضًا هيدرولوك وينر مع اتصال مباشر بأجهزة الاستشعار بمحرك الأقراص - هذا إذا كنت لا تحتاج إلى كل ما قمت به. هناك ، الطاقة مستقلة من 4 بطاريات ، والقاعدة تشبه محرك الأقراص النهائي. بشكل عام ، من المحتمل أيضًا أن تكون مثيرة للاهتمام لوحدة تحكم ذاتية الصنع - إنها 5 فولت ، لا تحتاج إلى وضع حافلتين على 5 و 12 فولت ويمكنك التخلص من العزل البصري.

أجهزة استشعار التسرب


اشتريت أجهزة استشعار من نفس عداد WSU - عالمي. لديهم نواتج "مجمّع مفتوح" ، واحد يسحب إلى الأرض فقط إذا كان هناك ماء ، والثاني - إذا دخل الماء ، فإنه يسحب إلى الأرض طوال الوقت حتى يتم قطع الطاقة. أستخدم المخرج الأول فقط ، وبقية المنطق موجود في وحدة التحكم ، ولكن يبدو أن هذا الإخراج يمكن أن يكون مفيدًا لبعض أنظمة إرسال المزيد من الوحدات السكنية.

الأسلاك في العدة ثلاثة أمتار في مكان ما. لون الأسلاك Ad_i_Asrael إسرائيل. تحقق من الاقتباس:

سلك أحمر (بني) (Vcc) مدعوم من +5 إلى +30 فولت.
سلك أسود (أبيض)
(OUT2) سلك أخضر (OUT1)
سلك أصفر (GND)

إليك ما منع صنع الأرض البيضاء / السوداء؟ على محرك الرافعة ، بالمناسبة ، الأسلاك الملونة مع المنطق ليست مزعجة. يوجد المستشعر الأول في المطبخ ، أسفل الحوض بجوار غسالة الأطباق.



الثاني في الحمام في خندق تصريف خاص. عندما فعل ذراع التسوية ، لم يأت بها إلى الحائط. وكانت النتيجة نوعًا من حوض لجمع المياه من الحمام والمرحاض.



من تجربة التشغيل - كان هناك بالفعل إنذار كاذب واحد لجهاز الاستشعار في غسالة الصحون. إذا حكمنا من خلال السجل لدورة اقتراع واحدة (500 مللي ثانية) ، كانت هناك دائرة ، عدّلت الشفرة - يحدث تغيير الحالة الآن عند 10 قيم متطابقة متتالية من المستشعر.

ملامسات المستشعر مطلية بالذهب. لدى صديق أجهزة استشعار مماثلة لعدة سنوات ، لم يلاحظ أي أكسدة.

أجهزة استشعار الضغط


عمليا - مقاييس العرض. الدقة + - 0.5 صراف آلي يناسبني تمامًا. بناءً على المستشعرات ، يأتي تنبيه إيقاف تشغيل المياه. اشتريت على علي هنا .

أجهزة استشعار درجة الحرارة


ولماذا لا تضيف؟ من المفيد - سيكون قادرًا مرة واحدة في السنة على الإبلاغ عن إغلاق الماء الساخن. تستخدم عاديا ds18b20.

عدادات


Itelma الأكثر شيوعا ، مرة واحدة كل 10 لترات الاتصال. على جانب وحدة التحكم ، يتم سحب الإخراج حتى + 3.3 فولت ، ويسحبه العداد إلى الأرض.

تحكم




بالداخل



استناداً إلى الجسيمات الفوتون ، مزيد من التفاصيل هنا . لديهم نسخة مع وحدة 2G أو 3G (إلكترون). كانت البرامج الثابتة الأولى عبارة عن خبث كامل ، وامض مع الثنائيات OK ، ولكن بمجرد أن تبدأ النقانق المعقدة والمضغ ، يمكن أن يؤدي اللعب مع i2c والمقاطعات إلى فقدان wifi. الآن يمكنك العيش. من حيث المبدأ ، يمكنك طرد مستشعرات الضغط خارج الدائرة وتحريك كل شيء على ESP8266 - اذهب إليه. أولاً ، يحتاج الفوتون إلى ربط حساب الجسيمات (يتم من خلال التطبيق على الهاتف المحمول أو من خلال وحدة التحكم Particle CLI - أستخدم الطريقة الثانية فقط) وتسجيل شبكة wifi. بعد الربط ، يظهر هنا في قسم الجهاز وحدة التحكم وحالة الاتصال بالسحابة.



لدي جميع العقد المتصلة بالسحابة فقط لتحديث البرامج الثابتة. لا يعني أنني سأكون بجنون العظمة - العمل فقط مع السحابة لا يأكل موارد تحكم غنية. يدعم 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 ، في الجسيمات المرجعية يسمونها BackupSRAM. يحتوي الفوتون على إخراج vbat - هذه ليست طاقة البطارية أو الشحن. طالما هناك جهد على هذه الساق ، يتم الاحتفاظ بمحتوى 4 كيلو بايت من SRAM مع إلغاء تنشيط وحدة التحكم تمامًا. هذا يزيل مشكلة التآكل على EEPROM.

في الكود ، يتم الإعلان عن المتغيرات التي يجب دفعها إلى هذه الذاكرة مع الإشارة: الاحتفاظ بها. قمت بتطبيق أجهزة من supercapacitor عند 1.5F. وفقًا لورقة البيانات ، ستموت الذاكرة عند 1.6 فولت ، وفقًا لتجاربي على اللوحة الأولية ، ستأتي في غضون أسبوعين مع مكثفي. منطق إغلاق الرافعات عندما يتم تشغيل أجهزة الاستشعار هو "مستقل" ولا يعتمد على اتصال openhab. يوجد مفتاح ثلاثي الاتجاه للتحكم المباشر في القيادة - أوتوماتيكي ، OFF (نقرات مفتوحة) ، إغلاق (إغلاق).

الرسم البياني للدائرة أدناه:



يمكن تنزيل مشروع النسر ، جنبًا إلى جنب مع libs المخصصة ، من هنا .

تم رسم LUT ، في المسارات لم يتقلص.

أفضل صديق للحمام المائي


وحدة تزويد الطاقة. نحتاج إلى 12 و 5 فولت. يتم البحث عن المتبرع على موقع ئي باي عن السطر: "محول طاقة القرص الصلب 5 فولت 12 فولت" ، مثل هذا .

الإسكان


تم طبعه ببلاستيك PLA على طابعة ثلاثية الأبعاد (تارانتولا تيفو). فوهة 0.4 مم ، طبقة 0.25 مم. الغطاء في نفس الوقت والقاعدة لتركيب لوحة التحكم. يتم إرفاق القاعدة مع مصدر الطاقة بالجدار. لم يتم تثبيت القاعدة ذات الغطاء بمسامير ، وهناك ما يكفي من توتر الغطاء (مثل غطاء الجدة على مرطبانات المربى) والهيكل الطبقي للجدران يعمل.

نموذج ثلاثي الأبعاد في الأرشيف .





إليك كيف يبدو كل شيء مثبتًا على أنبوب مياه.



Openhab


تم نشره على Orange Pi One تحت Armbian.

تكوين العنصر
  -    ,  .
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\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
}
}

وقواعد المعالجة:

قواعد Configs
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
rule "Rotate valves" 
	when
		Time cron "0 0 05 25 1/1 ?"
	then
		if (watercount_valve0.state == 0 && watercount_valve1.state == 0)
		{	
			watercount_valve0.postUpdate(1)
			Thread::sleep(1000)
			watercount_valve1.postUpdate(1)
			Thread::sleep(1000)
			watercount_valve0.postUpdate(0)
			Thread::sleep(1000)
			watercount_valve1.postUpdate(0)
			sendTelegram("****_bot", "Valves was rotated.");
		}
		else
		{
			sendTelegram("****_bot", "Can't rotate valves, it's closed.");
		}
end


لإرسال الرسائل ، لا أستخدم الوظيفة المضمنة لتطبيق openhab android ، ولا أتكامل مع السحابة الخاصة بهم. أنا أحب روبوت Telegram. يمكن رؤية كيفية تكوين وتوصيل البوت على ويكي . لإرسال رسائل من صندوق بريد gmail ، إذا كان لديك مصادقة ثنائية ، فأنت بحاجة إلى تمكين كلمة مرور لمرة واحدة لتطبيق البريد وتعيين كلمة المرور هذه في تكوين openhab.

اتبع القواعد.

تحقق من watercount_sensor- يرسل جهاز التحكم قيم مستشعر التسرب الجديدة فقط عندما يتم تغيير القيمة أو إذا كان هناك إنذار كاذب (أقل من 10 دورات). نقوم بتحليل ما جاء وأهمية تاريخية ، ونشكل رسائل إعلامية. هناك فارق بسيط - محاولة للحصول على prevoiusItem يعطي القيمة الحالية باستمرار ، لم أجد حلاً - أنا آخذ القيمة "-3 ثانية" ، إذا تغلب عليها شخص ما ، اكتبها في التعليقات أو في PM.

تحقق من watercount_temp2 - تحقق مما إذا كان أقل من 37 ، مما يعني أن الماء الساخن أصبح باردًا ، يجب عليك تشغيل سخان التدفق عند الوصول.

تحقق من watercount_pressure - نقوم بتحليل القيمة الحالية والسابقة ، ونرد برسالة على انخفاض أقل من 1 صراف آلي والنمو فوقه.

إنشاء عدادات سلسلة الإرسال- يبدأ في cron يوم 24 من كل شهر في الساعة الواحدة صباحًا. نتحقق من أن القيم الآن أكبر من تلك التي تم إرسالها في المرة الأخيرة. إذا كان أقل ، أوقف الإرسال التلقائي وقم بإنشاء تنبيه. إذا كانت موافق - نتذكر قيم العدادات لإرسالها إلى القانون الجنائي ، نرسل نص الرسالة المستقبلية إلى البرقية. في الوقت نفسه ، نوفر في watercount_sendStr مقدار ما استهلكناه خلال الشهر الماضي.

إنشاء عدادات إرسال سلسلة - يبدأ في cron في اليوم الرابع والعشرين الساعة 23.00. للتحقق مما إذا كان الإرسال التلقائي ممكّنًا ، إذا كان قيد التشغيل - ترسل الخوذة عدادات إلى بريد شركة الشحن. اتضح أن لدي اليوم الرابع والعشرين طوال اليوم ، شيء ما لإصلاحه أو قطع الإرسال التلقائي ، إذا حدث خطأ في البرقيات.

تحديث بالتعليق . تدوير الصمامات- قاعدة إغلاق / فتح الصنبور مرة واحدة في الشهر ضد الغليان. الخامسة والعشرون في الخامسة صباحًا - حتى لا تدخل في عمل غسالة الصحون أو الغسالة ، ولكن حتى لو وصلت إلى هناك - ليس الأمر بالغ الأهمية ، سيتم إغلاق الماء لمدة 3-4 ثوانٍ تقريبًا.

وهنا فقط يبدأ المنزل الذكي ...
يتيح لك الجمع بين الأنظمة في نقطة واحدة (openhab) بناء منطق غير متوفر لمجموعة من الأنظمة المستقلة. على سبيل المثال: وصل حدث زيادة في عداد المياه - نظام الأمان نشط ، وأقفال الباب الأمامي مغلقة ، واستهلاك الطاقة لغسالة الصحون والغسالة أقل من 5 واط ، مما يعني أنه تم الكشف عن تسرب من أجهزة الاستشعار. نقوم بتشكيل أمر لإغلاق الرافعات ، وإرسال رسالة إلى البوت في Telegram. لكن هذا يشبه الخيط لاحقًا.

Source: https://habr.com/ru/post/ar399459/


All Articles