Disklaimer

Дальше будет про 220, я вообще ни за что не отвечаю, особенно если вас шандарахнет

Предисловие

Есть такая замечательная сигнализация украинского производства - AJAX. Сигнализация действительно отличная, в техническом плане вообще можно сказать безупречная, но есть одно НО. В 2016 году ребята почему-то надули щеки и решили что они на столько крутые что могут положить M12, а то и M16 на потребности клиентов, таких я как.

По состоянию на 2017 сигнализация вообще ни с чем не интегрируется, вообще, никак, ни через какую дырочку, ни через какую апочку, вообще. Долгое время они кормили завтраками - мол "вот-вот мы что-то такое придумаем, вы только верьте и ждите", в 2017 опять таки достаточно красноречиво сказали "неначасі, скуштуйте насіння". 

Чего мне в идеале хочется от интеграции сигнализации и дома, в порядке приоритета:

  1. Иметь событие постановки и снятия с сигнализации
  2. Иметь возможность поставить на сигнализацию частично по сигналу дома
  3. Инициировать сирену по сигналу дома
  4. Регистрировать события сигналки в хранилище дома

В рамках этого поста я расскажу как я реализовал пункт 1. Пункты 2 и 3 реализовать чуть сложнее и опаснее, возможно я займусь этим позже. С пунктом 4 совсем беда, без АПИ это скорее всего невозможно.

Итак, сначала - зачем нужно "иметь событие постановки и снятия с сигнализации"? Есть две самые естественные причины:

  • Если вы поставили объект на сигналку это значит что в доме нет никого, можно смело перевести системы дома в экономный режим - никто из людей не будет испытывать дискомфорта, а кот он шерстяной.
  • Если вы поставили на сигналку это значит что вы ушли, и дом внешне "мертвый" и "темный", а хочется сделать так что-бы для нехороших людей он выглядел поживее.

Электрическая часть

У AJAX в номенклатуре прибамбасов для сигналки есть такая штука как WallSwitch - это реле в подрозетник, которым можно управлять, самое важное для меня то что можно ему назначить поведение при постановке/снятии сигналки.

К сожалению, реле реализовано максимально-неудобным для задачи способом, оно не коммутирует нагрузку но подает 220, другими словами просто взять и поцепить его куда-то - нельзя, нужно сначала поиметь с него логический уровень TTL.

Для задачи преобразования 220AC в низкоуровневый DC применил самую распространенную и лобовую схему "детектора нуля" с помощью оптрона. В нете масса ее реализаций, у меня получилась практически 1:1 такая

Далее есть логический уровень, с которым уже можно работать ардуине.

Код

Минус схемы, описанной выше в том что на выходе не логическая "1" при наличии 220 а меандр. Да, можно было-бы впендюрить конденсатор для превращения меандра в не очень ровную единицу, но электролит штука весьма капризная, особенно если китайская, потому я решил проблему бороть софтово, тем более что "я-ж программист"(с).

Наличие меандра на каком-то пине МК целесообразно проверять прерываниями, я на проверяемый пин навешиваю прерывание которое фиксирует время, если прерывание не происходило больше заданного времени (500мс в моем скетче) - считаю что напряжение пропало.

Также в скетче предусмотрен 1с таймер для сервисных функции (мигание светодиодом, передача сигнала наружу). Для связи датчика с умным домом решил использовать MySensors библиотеку, и 2.4Ghz радио-канал. Датчик в сети представляется типом S_LOCK (двухпозиционный замок), что наиболее близко по смыслу к сигнализации. Также на ардуину навешен светодиод, который мигает в разных режимах с разной частотой и сигнализирует момент уведомления сети. Изначально у меня ардуина отправляла сигнал только при смене режима, но на практике оказалось что для распределенной системы это плохая идея - в текущей версии отправляет при смене и также каждые 10 секунд текущее состояние.

Полный скетч для ардуины выглядит так:

#include <SPI.h>
#include "options.h"
#define MY_NODE_ID 1
#include <MySensors.h>


#define CHILD_ID 0

#define AC_PIN 3
#define LED_PIN A5

volatile int counter = 0;
volatile int pingcounter = 0;
volatile int ac_status = 0;
volatile unsigned long ac_timer;

bool ledState;
MyMessage msg(CHILD_ID, V_LOCK_STATUS);

void presentation()
{
 // Send the sketch version information to the gateway and Controller
 sendSketchInfo("AJAX binding", "1.1");

 // Register LOCK sensor to GW
 present(CHILD_ID, S_LOCK);
}

void tick()
{
 if (pingcounter++ > 10) {
   sendCode((ac_status == 0) ? false : true);
   pingcounter = 0;
 }
 if (ac_status == 2) {
   digitalWrite(LED_PIN, ledState);
   ledState = !ledState; 
 }
}



void setup() {
 pinMode(AC_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(AC_PIN), acCheck, CHANGE);

 pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH);

 //set timer1 interrupt at 1Hz
 TCCR1A = 0;// set entire TCCR1A register to 0
 TCCR1B = 0;// same for TCCR1B
 TCNT1 = 0;//initialize counter value to 0
 // set compare match register for 1hz increments
 OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
 // turn on CTC mode
 TCCR1B |= (1 << WGM12);
 // Set CS12 and CS10 bits for 1024 prescaler
 TCCR1B |= (1 << CS12) | (1 << CS10);
 // enable timer compare interrupt
 TIMSK1 |= (1 << OCIE1A);
}

void acCheck()
{
 ac_timer = millis();
 if (ac_status == 0) ac_status = 1;
}

ISR(TIMER1_COMPA_vect) { //timer1 interrupt 1Hz toggles status

 unsigned long timer = millis();
 if (ac_status > 0) {
   if (timer > ac_timer + 500) {
     // AC OFF
     ac_status = 0;
     ac_timer = 0;
     sendCode(false);
   } else if (ac_status == 1) {
     sendCode(true);
     ac_status = 2;
   } else if (ac_status == 2) {
     // ON
   }
 }

 tick();
} 

void loop()
{

}

void sendCode(bool state) {
 Serial.println(state ? "ON" : "OFF");
 // The LED will be turned on to create a visual signal transmission indicator.
 digitalWrite(LED_PIN, HIGH);

 int retries = 5;
 while(!send(msg.set(state ? 1 : 0)) && retries-- > 0) {
   delay(100);
   Serial.print("REPEAT LEFT: "); Serial.println(retries);
 }
 //Turn the LED off after the code has been transmitted.
 digitalWrite(LED_PIN, LOW);
}

В сети MySensors у меня работает MQTT шлюз, который данные сети датчиков отправляет в MQTT брокер. Теперь осталось это все счастье взлететь скормить в openhab.

Как я упоминал выше - используется датчик S_LOCK (V_LOCK_STATUS = 36), а скетче также видно что нода имеет #1 (MY_NODE_ID 1), сенсор #0 (#define CHILD_ID 0) - "логично" предположить что топик MQTT будет иметь примерный вид "1/0/1/0/36. Почему именно так - все есть в доке на MySensors. Мой шлюз еще от себя помечает топик префиксом "/test/out".

Можно настраивать openhab:

secure.items

Group gSecure       "Secure"   <alarm>

Number secureState "Сигналізація [%s]" (gSecure, Mysensors) {mqtt="<[local:/test/out/1/0/1/0/36:state:default]"}

secure.rules

Тут немного пояснений - система получилась многоступенчатой, а значит с приличной вероятностью отказа. Я решил что разумно датчик сигнализации иметь в трех состояниях - "на сигнализации", "снято", "хз че, вообще непонятно". Третье состояние на случай если где-то что-то отвалилось, поломалось и openhab перестал получать от датчика сигнал. Если openhab 1 минуту не получает от датчика состояние он считает что тот умер и ставит третье состояние, до того момента пока сигнал не появится.

import org.joda.time.*
import org.openhab.model.script.actions.Timer

var Timer secureStateTimer = null

rule "secure alive"
when
    Item secureState received update
then
    if (secureStateTimer != null) {
        secureStateTimer.cancel();
        secureStateTimer = null
    }

    secureStateTimer = createTimer(now.plusMinutes(1), [|
        secureState.state = UNDEF;
        secureStateTimer.cancel();
        secureStateTimer = null
    ])
end

rule "secure notify"
when
    Item secureState changed
then
    if (secureState.state == UNDEF) {
        // lost sensor (
        sendBroadcastNotification("Потерян сенсор AJAX");
    } else {
        switch (secureState.state) {
            case 0: {
                // off
                //Color_Color.sendCommand(OFF);
                sendBroadcastNotification("Выключен AJAX");
            }
            case 1: {
                // on
                sendBroadcastNotification("Включен AJAX");
            }
            default: { 
                logInfo("myScenes","incorrect state: {}",secureState.state)
            }
        }
    }
end

scenes.sitemap

sitemap scenes label="My home automation" {
    Frame label="Сигналізація" {
        Text label="На охороні" visibility=[secureState==1] icon="shield"
        Text label="Знято з охорони" visibility=[secureState==0] icon="parents_1_1"        
        Text label="Відсутній зв'язок" visibility=[secureState==UNDEF] icon="error"       
    }
}

Вот собственно и все, теперь в openhab есть secureState Item, на смену состояния которого можно ставить любую логику. Обратите внимание - тестировать это все ночью когда ваша семья спит, плохая идея, почему-то.