Konstantin Kuyukov -

Возникла идея автоматизировать освещение в квартире, установить систему контроля протечек и связать всё это в единую систему с управлением с любого устаройств.

Схема квартиры:

flat
1. Помещения 1,2,7,8: свет включается при обнаружении движения, гаснет с задержкой.
2. Помещения 5,9,13: свет включается при обнаружении входа в помещение, гаснет после выхода всех людей.
3. Помещения 10,11,12: в дневном режиме свет работает как в помещениях 2 группы. В ночном режиме - только с выключателя.
4. Помещение 3: в дневном режиме свет работает как в помещениях 1 группы. В ночном режиме - только с выключателя.

Необходимо сделать "бюджетное" решение с максимально возможным запасом по расширению.

Для дистанционного управления светом я выбрал продукцию белорусской компании Ноотехника - систему Ноолайт. http://www.noo.com.by/sistema-noolite.html
Закуплены силовые блоки SL111-300 на каждую группу ламп освещения, выключатели PU212-1 и PU312-1, адаптеры для компьютера PC1116, RX1164 (так необходимо как принимать сигналы с выключателей, так и отправлять сигналы на блоки).

Контроль прохода будет осуществляться с помощью решения на базе arduino.
Применю сочетание pir (датчик движения) с ИК-диод/ИК-приемник.
В ходе первых проб выяснил, что использования одних pir недостаточно, так как люди в помещениях всех групп кроме первой могут не двигаться, а использовать микроволновые датчики (датчики присутствия) я не хочу.
Как-то пугает мысль находиться 24 часа в сутки под излучением 10ГГц.
Создание же счетчика людей на основе ИК-диодов/ИК-приемников также оказалось неэффективным. По конструктивным особенностям я могу расположить ИК-пары только на высоте не более 6 см от пола. Счетчик часто ошибается, хороший алгоритм мне так и не удалось придумать.
ИК диод устанавливается на противоположной поверхности проема (люди пересекают луч). Использовать отраженный луч не получилось. Arduino позволяет подключать потребителя максимум 5В 40ма на один выход, что позволило получить радиус обнаружения всего лишь порядка 15см.
В итоге принято решение о применении двух типов датчиков.
Закуплены Arduino Uno, pir'ы, приемник-передатчик 433Мгц (для связи с основным Uno), приемники TSOP2136, ИК-диоды L-53F3C.

Для контроля протечек выбрана система Neptun ProW с проводными датчиками и кранами Bugatti 12В.

Для связи arduino, noolite, neptun и внешнего мира будет использоваться роутер D-link Dir-320 A1. Он не греется, не шумит, работает под Linux.

Update 1.
Перепрошил Dir-320 с dd-wrt
http://www.dd-wrt.com/wiki/index.php/%D0%9F%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B0_DIR-320
Прошивку использовал самого маленького объема с поддержкой usb (так как памяти всего 4Мб).
http://dd-wrt.com/routerdb/de/download/D-Link/DIR-320/A1/A2/dd-wrt.v24_usb_generic.bin/1964

Дальше веселее :)
Чтобы заставить работать роутер с адаптерами noolite необходимо
https://github.com/ermolenkom/noolite
и кросс-компиляция программ от Михаила и библиотеки libusb под mips процессор.
Использовать машину с Kubuntu13 и toolchain от dd-wrt.
Спасибо этой статье, Михаилу Ермоленко и Михаилу Григорьеву.
Можете просто скачать результат libusb (разархивировать в /opt/lib) и noolite (разархивировать в /opt/sbin).

Если кого заитересует, то для компиляции libusb использовалось следующее:

AR=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-ar \
AS=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-as \
LD=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-ld \
NM=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-nm \
CC=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-gcc \
CPP="/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-gcc -E" \
GCC=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-gcc \
CXX=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-g++ \
RANLIB=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-ranlib \
STRIP=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/mipsel-linux-strip \
CPPFLAGS="-O2 -pipe" \
./configure \
--build=i386-pc-linux-gnu \
--host=mipsel-linux \
--target=mipsel-linux \
--disable-udev
make

В noolite был исправлен makefile:

CC = mipsel-linux-gcc
LINK = mipsel-linux-gcc
CCFLAGS = -O -I.
LINKFLAGS = -g -O2 -pthread -lusb-1.0

Во всех .c заменил #include <libusb-1.0/libusb.h> на #include <libusb.h>
Добавил код в nooliterx.c, который позволяет стать ему полноценным демоном при запуске скриптом из /opt/etc/init.d

Сборка:

export LD_LIBRARY_PATH=/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/lib/:$LD_LIBRARY_PATH
export PATH=$PATH:/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/bin/
export SDL_INCLUDE=-I/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/include/
export SDL_LIB=-L/dop/current-toolchains/toolchain-mipsel_gcc4.1.2/lib/
make


Запускаю nooliterx как демона скриптом:

#!/bin/sh

prefix="/opt"
PATH=${prefix}/bin:${prefix}/sbin:/sbin:/bin:/usr/sbin:/usr/bin
NAME=nooliterx
DAEMON=${prefix}/sbin/${NAME}
#DAEMON_OPTS="-f ${prefix}/etc/noolite.conf"
DAEMON_OPTS=""

test -x $DAEMON || exit 0

if [ -z "$1" ] ; then
    case `echo "$0" | sed 's:^.*/\(.*\):\1:g'` in
        S??*) rc="start" ;;
        K??*) rc="stop" ;;
        *) rc="usage" ;;
    esac
else
    rc="$1"
fi

case "$rc" in
    start)
        echo "Starting: $NAME"
        $DAEMON $DAEMON_OPTS
        ;;
    stop)
        if [ -n "`pidof $NAME`" ]; then
            echo "Stopping: $NAME"
            killall $NAME 2> /dev/null
        fi
        ;;
    restart)
        "$0" stop
        sleep 1
        "$0" start
        ;;
    *)
        echo "Usage: $0 (start|stop|restart|usage)"
        ;;
esac

exit 0

MajorDoMo не планирую использовать, так как просто для управления светом система избыточна. Кроме того флешка с работающим mysql (обработка/запись) долго не проживёт.
Пока что nooliterx будет непосредственно вызывать noolitepc.

В дальнейшем планирую описать схему части системы на arduino.

Update 2.

http://www.dd-wrt.com/phpBB2/viewtopic.php?t=43358
Необходимо 3 модуля ядра для работы с arduino через usb (usb_serial, ftdi_sio, acm)
Разархивируем их в /opt
В стартовый скрипт в панели управления dd-wrt добавляем

insmod /opt/usb/usbserial.o
insmod /opt/usb/serial/ftdi_sio.o
insmod /opt/usb/acm.o

Arduino теперь общается с dir-320.

echo 'Something' > /dev/usb/acm/0
cat < /dev/usb/acm/0

Можно научить роутер отправлять уведомления о событиях через СМС в помощью календаря Google.
В моей прошивке dd-wrt какие-то глюки с часовыми поясами.
Так что время пришлось выставить на 4 часа назад (UTC).

Update 3.

В общем пока автоматизация с треском провалилась.
Я так и не смог подключить Нептун к Arduino. Внешнее управление на Нептун отлично реагирует за простое замыкание обычной кнопкой, но совершенно недекватно воспринимает перекидное реле, подключенное к Arduino.

Update 4.

Выкладываю первые результаты автоматизации света в гостиной и коридоре.

Свет в коридоре (помещение 2) включается при обнаружении движения и гаснет через 3 минуты после прекращения движения.
Свет в гостиной (помещение 9) включается при входе из коридора, выключается, если после срабатывании ИК-датчика в проеме в зале не было движения в течении минуты.
При включении/выключении света с пульта PU212 автоматическое управление отключается. При нажатии на пульте кнопки сценария - автоматический режим активируется вновь.

С noolitepc не получается передавать команду сразу по нескольким каналам. Приходится дополнительно группу светильников привязывать на отдельный канал. Например, у меня все силовые блоки дополнительно привязаны к 15 каналу для их одновременного выключения.

IMAG0868
Блок, который должен был работать с Нептуном (arduino Uno, приемник и передатчик 433MHz, перекидное реле, выводы для подключения Нептуна и выводы для подключения pir'ов).

IMAG0877
Он же в собранном виде.

IMAG0880
Блок для подключения к Dlink dir-320 (arduino Uno, приемник и передатчик 433MHz).

IMAG0907
Он же в подключенном состоянии.

IMAG0901
ИК-диоды в плинтусе.

IMAG0903
ИК-приемники (TSOP) в плинтусе.

IMAG0911
pir в гостиной.

Ниже приведёт код всех модулей. Он далеко не оптимален. Просто прототип...
По этой же причине комментариев в коде почти нет и они смешаны (русский и английский).

Демон, работающий с arduino (запускается в из startup commands) (/opt/etc/init.d/S100arduino). Команды пересылает на MajorDomo (скрипт его заменяющий в моём случае):

#!/opt/bin/bash
cd /
exec >/dev/null
exec 2>/dev/null

(
trap "" TERM

while [ true ]
do

stty -F /dev/usb/acm/0 cs8 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts raw


while read STR
do
    echo $STR >> /opt/var/log/arduino.log
    IFS=';' read -a LINE <<< $STR
    printf -v CMD "timeout 120 wget -q -T 10  http://localhost/objects/?script=switchNooLitePress&buf0=%d&;buf1=%d&buf2=%d&buf3=%d&buf4=%d&buf5=%d&buf6=%d&buf7=%d -O /dev/null &" ${LINE[0]} ${LINE[1]} ${LINE[2]} ${LINE[3]} ${LINE[4]} ${LINE[5]} ${LINE[6]} ${LINE[7]}
    $CMD
    echo $CMD >> /opt/var/log/arduino.log

done < /dev/usb/acm/0

done
) &

 

/opt/share/www/objects/index.php, работающий вместо MaJorDomo:

<?php
if($_GET['script']=="switchNooLitePress"){

  $ch_on=0;
  $ch_off=0;
  $room=0;
  $serial=false;
  $enable=false;

  if($_GET['buf1']==17){$ch_on=5;$ch_off=16;}
  elseif($_GET['buf1']==2){$ch_on=3;$ch_off=3;}
  elseif($_GET['buf1']==64){$ch_on=5;$ch_off=5;}
  elseif($_GET['buf1']==13){exec("echo \"C\" > /dev/usb/acm/0");}
  elseif($_GET['buf1']==14){exec("echo \"D\" > /dev/usb/acm/0");}
  elseif($_GET['buf1']==15){exec("echo \"E\" > /dev/usb/acm/0");}
  elseif($_GET['buf1']==16){exec("echo \"F\" > /dev/usb/acm/0");}

    if($_GET['buf2']==2 || $_GET['buf2']==3){
      if($_GET['buf1']==17)exec("echo \"A\" > /dev/usb/acm/0");
      exec("/opt/sbin/noolitepc -api -on_ch ".$ch_on);
      echo "ON";
    }
    if($_GET['buf2']==0 || $_GET['buf2']==1){
      if($_GET['buf1']==17)exec("echo \"B\" > /dev/usb/acm/0");
      exec("/opt/sbin/noolitepc -api -off_ch ".$ch_off);
      echo "OFF";
    }
}
?>

 

Скетч arduino, подключенного к dir-320:

// Dlink
#include <VirtualWire.h>
#include <EasyTransferVirtualWire.h>

#define IR_CLOCK_RATE    36000L

const int led_pin = 13; // Signal led
const int receive_pin = 4; // 433 receiver
const int transmit_pin = 5; // 433 transmitter
const int pir_pin_1 = 6;  // pir 1 (livingroom)
const int pir_pin_2 = 7;  // pir 2 (hallway)
const int pir_pin_3 = 8;  // pir 3
const int IRled_pin =  11;  //IR leds
const int tsop_pin_1 =  2;  //IR receiver
const int tsop_pin_2 =  3;  //IR receiver

int calibrationTime = 30; //Calibration time (10-60 sec - from datasheet)
int int_pir_1 = 0;
int int_pir_2 = 0;
int int_pir_3 = 0;
int int_tsop_pin_1 = 0;
int int_tsop_pin_2 = 0;
int occupants_zal = 0;
int occupants_zal_old = 0;
boolean detect_ir_zal = false;
boolean detect_hall = false;
unsigned long time_detect_led = 0;
unsigned long time_detect_hall = 0;
const unsigned long pause_0 = 500; // Delay between ir and pir in ms
const unsigned long pause_1 = 3000; // Delay between ir and pir in ms (on out)
const unsigned long pause_2 = 30000; // Waiting for pir in ms (on out)
const unsigned long pause_3 = 60000; // Delay before switch off in ms
const unsigned long pause_4 = 180000; // Delay before switch off in ms
//int i = 0;
//const int repeate = 2;

unsigned int unique_device_id = 0; // device ID
String Str;

boolean enable_zal = true;
boolean enable_hall = true;

//create object
EasyTransferVirtualWire ET;
char buf[120];

struct SEND_DATA_STRUCTURE{
  //наша структура данны. она должна быть определена одинаково на приёмнике и передатчике
  //кроме того, размер структуры не должен превышать 26 байт (ограничение VirtualWire)
  unsigned int device_id;
  unsigned int destination_id;  
  unsigned int packet_id;
  byte command;
  int data;
};

//переменная с данными нашей структуры
SEND_DATA_STRUCTURE mydata;

void setup()
{
    pinMode(led_pin, OUTPUT);
    pinMode(pir_pin_1, INPUT);
    pinMode(pir_pin_2, INPUT);
    pinMode(pir_pin_3, INPUT);
    
    // toggle on compare, clk/1
    TCCR2A = _BV(WGM21) | _BV(COM2A0);
    TCCR2B = _BV(CS20);
    // 36kHz carrier/timer
    OCR2A = (F_CPU/(IR_CLOCK_RATE*2L)-1);
    pinMode(IRled_pin, OUTPUT);

    pinMode(tsop_pin_1, INPUT);     
    pinMode(tsop_pin_2, INPUT);     
    
    Serial.begin(9600);

    delay(4000);
    digitalWrite(led_pin, LOW);

    //дадим датчику время на калибровку
    for(int i = 0; i < calibrationTime; i++)
    {
      //Во время калибровки будет мигать глаза
      i % 2 ? digitalWrite(led_pin, HIGH) : digitalWrite(led_pin, LOW);
      delay(1000);
    }
    //По окончанию калибровки погасим глаза
    digitalWrite(led_pin, LOW);   

    ET.begin(details(mydata));
    // Initialise the IO and ISR
    vw_set_tx_pin(transmit_pin);
    vw_set_rx_pin(receive_pin);
    vw_setup(2000);      // Bits per sec
    randomSeed(analogRead(0));
    vw_rx_start();       // Start the receiver PLL running
    
}

void loop()
{
  int_pir_1 = digitalRead(pir_pin_1);
  int_pir_2 = digitalRead(pir_pin_2);
  int_tsop_pin_1 = digitalRead(tsop_pin_1);
  int_tsop_pin_2 = digitalRead(tsop_pin_2);
 
  if(int_pir_2 == true)
  {
    time_detect_hall = millis();
    if (detect_hall == false)
    {
      if(enable_hall==true)serial_send(64, 2);     
      detect_hall = true;
    }
  }
  if(int_pir_2 == false && detect_hall == true && millis() - time_detect_hall > pause_4)
  {
    if(enable_hall==true)serial_send(64, 0);  
    detect_hall = false;   
  }
 
  if(occupants_zal == 0 && int_pir_1 == true)
  {
    occupants_zal = 1;     
    //Serial.println("Pir 1");
  }
  if(int_tsop_pin_1 == LOW || int_tsop_pin_2 == LOW)
  {
      detect_ir_zal = true;
      time_detect_led = millis();
      //Serial.println("IR");
  }  
  if(occupants_zal == 1 && detect_ir_zal == true && millis() - time_detect_led > pause_1)
  {
      if(int_pir_1 == true)
      {
        detect_ir_zal = false;
        //Serial.println("Cancel");
      }
      if(int_pir_1 == false && millis() - time_detect_led > pause_2)
      {
        occupants_zal = 0;
        detect_ir_zal = false;
        //Serial.println("Apply");
      }
  }
  if(occupants_zal_old != occupants_zal)
  {
      if(occupants_zal > 0)
      {
        if(enable_zal==true)serial_send(2, 2);
        occupants_zal_old = occupants_zal;
        //Serial.println("On");
      }
      else
      {
        if(millis() - time_detect_led > pause_3)
        {
          if(enable_zal==true)serial_send(2, 0);
          occupants_zal_old = occupants_zal;
          //Serial.println("Off");
        }
      }
  }
 
  if(ET.receiveData())  // получили пакет данных, обрабатываем
  {
    if (mydata.destination_id==unique_device_id)
    {
      digitalWrite(led_pin, HIGH);
      //sprintf(buf, cmd_rf, (int)mydata.device_id, (int)mydata.destination_id, (int)mydata.packet_id, (int)mydata.command, (int)mydata.data);
      //Serial.println(buf); // выводим строку со ссылкой для HTTP-запроса
      //Serial.println();        
      digitalWrite(led_pin, LOW);        
    }
  }
  else
  {
     if (Serial.available())
     {
       char value = Serial.read();
       if (value=='C') {
         enable_hall = false;
         //Serial.println("C");
       }
       if (value=='D') {
         enable_hall = true;
         detect_hall == false;
         //Serial.println("D");
       }
       if (value=='E') enable_zal = false;
       if (value=='F') enable_zal = true;
       
       if (value=='B') mydata.command = 0;
       if (value=='A') mydata.command = 1;
       if (value=='A' || value=='B')
       {
         
          digitalWrite(led_pin, HIGH);         
          mydata.device_id = unique_device_id;
          mydata.destination_id = 1;
          mydata.packet_id = random(65535);
          mydata.data = 0;
          ET.sendData(); // отправка данных
          //Serial.println("Sended");
          //Serial.println(value);
          digitalWrite(led_pin, LOW);
       }
     }
  }
 
}

void serial_send(int i1, int i2)
{
  sprintf(buf, "%u;%u;%u;%u;%u;%u;%u;%u",0,i1,i2,0,0,0,0,0);
  Serial.println(buf);  
}

 

Продолжение следует...

Konstantin Kuyukov, 2012-2018