В прошлом году купил оборудование для аварийного перекрытия кранов при обнаружении протечки от «Аквасторож». Долго не мог его поставить. Была идея интегрировать его в 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);
}

Комментарии (2)


  1. aivs
    10.04.2019 20:39

    Было бы здорово в итоге получить шилд на шилд, чтобы самостоятельно поставить.


    1. sergeyvass Автор
      10.04.2019 22:09

      Сделал плату, которая впаивалась в макетное поле, но сильно аккуратней не стало.