
В прошлом году купил оборудование для аварийного перекрытия кранов при обнаружении протечки от «Аквасторож». Долго не мог его поставить. Была идея интегрировать его в Z-Wave сеть и получить аналог gidrolock, но работающий на батарейках. Наконец-то руки дошли…
Аквасторож представляет собой базу с подключаемыми кранами и датчиками протечки. Данный комплекс может работать, как от сети 220 В через адаптер, так и от батареек. Разработчики предусмотрели возможность подклюения к системам «умный дом». Путём замыкания одной пары контактов в Ethernet розетке можно открыть краны, а другой закрыть их. Контакты реле замыкаются на 1 секунду при обнаружении протечки. На плате есть не распаянный разъём UART, но в данной статье расскажу о реализации документированных функций.
Задачи разработки
- Дистанционное включение\выключение кранов.
- Информирование о протечке.
- Два счётчика воды.
- Не нарушать работу «Аквасторожа».
Делать решил на базе ZUNo shield. Поставляется в герметичным корпусе с гермовводом, на борту имеет клеммники и свободное место для установки батарейки и дополнительных электронных компонентов. Ардуино подобный.
Ethernet разъём

Один из замыкаемых проводов это «земля» «аквасторожа». Можно объединить земли ZUNo и «Аквасторожа» и управлять кранами непосредственно через GPIO ZUNo. Так я и сделал. Но в случае выхода ZUNo из строя (например села батарейка) на управляющие линии «Аквасторожа» подаётся «ноль» и он начинает циклически перезагружаться. Подобный вариант подключения сильно влияет на надёжность всей системы, поэтому немного усложнив схему перешёл на два герконовых реле, которые обеспечили гальваническую развязку от «Аквасторожа». Потребляют реле около 7 мА во включенном состоянии. Чтобы переключить краны нужно на одну секунду включить одно реле, что вполне приемлемо. Заряда батарейки хватило на несколько тысяч переключений. (Сейчас у меня на руках есть электромагнитные импульсные однокатушечные реле. Для их переключения нужно подать импульс 1 мс, что гораздо энергоэффективнее. Но для управления нужно 4 транзистора и две ножки ввода/вывода на реле).
Сон в Z-wave
Немного расскажу о том как спят Z-Wave устройства и о вытекающей отсюда проблеме.
Z-wave устройства могут быть спящими или часто просыпающимися. Спящее устройство самое энергоэффективное, но ему нельзя послать команду (в моём случае на переключение кранов). Мне подходит второй тип. Устройство FLiRS — Frequently Listening Routing Slaves. Настроенное на такой режим работы устройство просыпается каждую секунду и если за короткий промежуток времени не получает сигнал на полное просыпание от контроллера – засыпает. Например: шлю команду на открытие кранов. Контроллер понимает, что моё устройство, часто слушающее, и посылает в течение секунды особый короткий пакет (wakeup beam), чтобы все FLIRS устройства в сети проснулись. Как только моё устройство примет этот пакет оно отправляет отчёт о том, что проснулось и готово принять команду. Получает команду на закрытие кранов. Снова засыпает. И так каждый раз, когда происходит управление устройством. Недостаток в том, что устройство может принять wakeup beam как в конце рассылки контроллером, так и в начале. Контроллер шлёт его около секунды. В худшем случае устройство проснётся в начале этой рассылки, и будет ждать почти секунду, до поступления команды. Но поскольку открывать и закрывать краны часто не нужно, это не является серьёзным недостатком.
Реализация
ZUNo Shield имеет небольшую макетную плату, на которой можно разместить необходимые компоненты.

Схема содержит два реле и два транзистора для их управления. Простенькая схемка.

Пара слов об энергопотреблении.
ZUNo shield содержит микросхему драйвер для протокола RS-485 и подтягивающий резистор для пина «11» на нижней колодке, для протокола One Wire. После удаления этих компонентов основным потребителем остаётся ZUNo.

Потребление в режиме сна составляет около 5-10 мкА, а в активном режиме до 60 мА (реле активно и ZUNa работает на передачу).
Осциллограммы потребления тока для разных режимов работы
Направление оси тока сверху вниз.
Устройство в ожидании команды:

Примерно каждую секунду видны короткие пики, в течение которых устройство просыпается и проверяет, не пришёл ли wakeUp beam.
Устройство получило команду:

Сначала устройство проснулось, получило wakeUp beam, дождалось получения команды (от 0 до 1 секунды), если команда на управление кранами, то включает соответствующее реле на 1 секунду (на этом этапе нужно контроллер переводить в сон с сохранением ножек в текущем состоянии, но я побоялся и поленился) и остальное время тратится на внутреннюю работу чипа, после чего ZUNо засыпает. Итого почти 3,5 секунды на одну операцию открытия или закрытия кранами. Ужасно долго, но из-за того, что подобные операции будут выполняться крайне редко, оптимизацией можно пренебречь. Да и даст она мало, потому-что скетч в Arduino ide это лишь малая часть того, что «ворочается» в этом маленьком микроконтроллере, и что надёжно спрятано производителем от любопытных.
Схема подключения к «Аквасторожу»

Заключение
Получилось добавить достаточно аккуратно «Аквасторож» в существующую Z-Wave сеть. Главным минусом является отсутствие обратной связи от «Аквасторожа». На данном этапе жду новой версии библиотеки ZUNo, в которой будет исправлен баг, не дающий нормально спать ZUNo, поэтому вместо фотографии с установленным и подключенным «Аквасторожем» картинка с отладочным процессом.

Спасибо за внимание!
Скетч
//#define _DEBUG #define OPEN_PIN 11 #define CLOSE_PIN 12 #define LEAK_PIN 19 #define INT1 18 uint8_t valve_action = 0; #ifdef _DEBUG uint8_t const METER1_PIN = 8; #else uint8_t const METER1_PIN = 7; #endif #define METER2_PIN 8 #include "EEPROM.h" #define MAGIC_VALUE 42 #define ADDR_ACTION 1 #define CH_METER_1 4 #define CH_METER_2 8 #define CNT_ON_OFF_CICL 12 // Global variables byte pin7SwitchBinaryState = 0; DWORD eeprom_buf = 0; #define LEAK_CHANNEL 3 DWORD meter_cnt1; DWORD meter_cnt2; #define ADR_MET1 4 #define ADR_MET2 5 uint8_t alarm_clr = LOW; // Z-Wave channels ZUNO_SETUP_CHANNELS( ZUNO_SWITCH_BINARY(pin7SwitchBinaryGetter, pin7SwitchBinarySetter), ZUNO_SWITCH_BINARY(alarmGetter, alarmSetter), ZUNO_SENSOR_BINARY(ZUNO_SENSOR_BINARY_TYPE_WATER, getterSensorBinary), ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE , ZUNO_METER_WATER_SCALE_PULSECOUNT, 4, 0, getterMETER1, resetterMETER1), ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE , ZUNO_METER_WATER_SCALE_PULSECOUNT, 4, 0, getterMETER2, resetterMETER2) ); ZUNO_SETUP_BATTERY_LEVELS(2700, 3300); ZUNO_SETUP_SLEEPING_MODE(ZUNO_SLEEPING_MODE_FREQUENTLY_AWAKE); void close_water() { #ifdef _DEBUG Serial1.println("close"); #endif digitalWrite(CLOSE_PIN, HIGH); delay(1000); digitalWrite(CLOSE_PIN, LOW); //delay(1000); } void open_water() { #ifdef _DEBUG Serial1.println("open"); #endif digitalWrite(OPEN_PIN, HIGH); delay(1000); digitalWrite(OPEN_PIN, LOW); //delay(1000); } #define LEAK_DETECTED LOW #define LEAK_END HIGH #define ADDR_LEAK_ST_LAST 2 #define ADR_B1_F 3 #define ADR_B2_F 4 #define NZ_ADR_LEAK 5 uint8_t last_leak_st; #define EEPROM_MAGIC 0x11223342 #define EEPROM_ADR_MAGIC 0 void setup() { #ifdef _DEBUG Serial1.begin(9600); Serial1.println("serial init"); #else pinMode(METER1_PIN, INPUT); pinMode(METER2_PIN, INPUT); #endif pinMode(CLOSE_PIN, OUTPUT); pinMode(OPEN_PIN, OUTPUT); pinMode(LEAK_PIN, INPUT_PULLUP); pinMode(INT1, INPUT_PULLUP); digitalWrite(CLOSE_PIN, LOW); digitalWrite(OPEN_PIN, LOW); byte n; NZRAM.get(0x0, &n, 1); if (n == MAGIC_VALUE) { // correct magic value after wake up from sleep mode // trust NZRAM data } else { // incorrect magic, first boot after battery insert ot rebooted due to low battery // initialize NZRAM magic n = MAGIC_VALUE; NZRAM.put(0x0, &n, 1); NZRAM.write(ADDR_ACTION, LOW); NZRAM.write(ADDR_LEAK_ST_LAST, LEAK_END); NZRAM.write(ADR_B1_F, HIGH); NZRAM.write(ADR_B2_F, HIGH); } EEPROM.get(EEPROM_ADR_MAGIC, &eeprom_buf, sizeof(DWORD)); if(eeprom_buf != EEPROM_MAGIC) { eeprom_buf = EEPROM_MAGIC; EEPROM.put(EEPROM_ADR_MAGIC, &eeprom_buf, sizeof(DWORD)); resetterMETER1(); resetterMETER2(); eeprom_buf = 0; EEPROM.put(CNT_ON_OFF_CICL, &eeprom_buf, sizeof(DWORD)); } } uint8_t last_btn_st; void check_btn(uint8_t meter_pin, uint8_t NZ_adr_st) { last_btn_st = NZRAM.read(NZ_adr_st); if(digitalRead(meter_pin) == LOW) { if(last_btn_st != LOW) { for(uint8_t i=0; i<3; ++i) { if(digitalRead(meter_pin) == LOW) delay(5); else return; } last_btn_st = LOW; NZRAM.write(NZ_adr_st, last_btn_st); } } else { if(last_btn_st == LOW) { for(uint8_t i=0; i<3; ++i) { if(digitalRead(meter_pin) == HIGH) delay(5); else return; } last_btn_st = HIGH; NZRAM.write(NZ_adr_st, last_btn_st); if(NZ_adr_st == ADR_B1_F) inc_met(ADR_MET1); else inc_met(ADR_MET2); } } } //=----------------------------------------------------------- void loop() { if(digitalRead(LEAK_PIN) == LEAK_DETECTED) { if(NZRAM.read(ADDR_LEAK_ST_LAST) != LEAK_END) { zunoSendReport(LEAK_CHANNEL); NZRAM.write(ADDR_LEAK_ST_LAST, LEAK_END); } } check_btn(METER1_PIN, ADR_B1_F); check_btn(METER2_PIN, ADR_B2_F); if(zunoGetWakeReason() == ZUNO_WAKEUP_REASON_RADIO) { uint32_t start_time=0; #define ACTION_T_OUT 2000 start_time = millis(); //while(NZRAM.read(ADDR_ACTION)== 0) while(valve_action== 0) if((millis() - start_time) >= ACTION_T_OUT) { #ifdef _DEBUG Serial1.println("T_OUT"); #endif break; } else delay(50); #ifdef _DEBUG Serial1.println(millis() - start_time); #endif if(NZRAM.read(ADDR_ACTION)) { valve_action = 0; NZRAM.write(ADDR_ACTION, LOW); if(pin7SwitchBinaryState == LOW) close_water(); else open_water(); } if(alarm_clr) // Если пришла команда сброса тревоги { alarm_clr == LOW; zunoSendReport(LEAK_CHANNEL); } } if(digitalRead(INT1)==0) { zunoSetWUOptions(ZUNO_WUPFLAGS_INT1_HIGH); } else { zunoSetWUOptions(ZUNO_WUPFLAGS_INT1_LOW); } zunoSendDeviceToSleep(); } //----------------------------------------------------------- // Getters and setters void inc_met(uint8_t num_chennel) { uint8_t eeprom_adr_met; if(num_chennel == ADR_MET1) eeprom_adr_met = CH_METER_1; else eeprom_adr_met = CH_METER_2; EEPROM.get(eeprom_adr_met, &eeprom_buf, sizeof(DWORD)); eeprom_buf++; EEPROM.put(eeprom_adr_met, &eeprom_buf, sizeof(DWORD)); zunoSendReport(num_chennel); } DWORD getterMETER1() { EEPROM.get(CH_METER_1, &eeprom_buf, sizeof(DWORD)); return eeprom_buf; } DWORD getterMETER2() { EEPROM.get(CH_METER_2, &eeprom_buf, sizeof(DWORD)); return eeprom_buf; } void resetterMETER1() { eeprom_buf = 0; EEPROM.put(CH_METER_1, &eeprom_buf, sizeof(DWORD)); } void resetterMETER2() { eeprom_buf = 0; EEPROM.put(CH_METER_2, &eeprom_buf, sizeof(DWORD)); } void pin7SwitchBinarySetter(byte value) { valve_action = 1; NZRAM.write(ADDR_ACTION, HIGH); pin7SwitchBinaryState = value; if(value == 255) // if open valve, then off leak alarm { NZRAM.write(ADDR_LEAK_ST_LAST, LOW); zunoSendReport(LEAK_CHANNEL); } } byte pin7SwitchBinaryGetter() { return pin7SwitchBinaryState ? 0xFF : 0; } byte getterSensorBinary() { return digitalRead(LEAK_PIN) ? 0 : 0xFF; } byte alarmGetter() { uint8_t ret; ret = NZRAM.read(ADDR_LEAK_ST_LAST); return ret ? 0xFF : 0; } void alarmSetter(byte value) { alarm_clr = HIGH; NZRAM.write(ADDR_LEAK_ST_LAST, value); }
