Pull to refresh

Контроллер центральный домашний, всемогущий КЦД-В-2-12

Reading time 48 min
Views 165K
История появления на свет центрального домашнего контроллера довольно запутанна. Мне кажется, если на секунду отвлечься и представить его в виде зимнего леса (вид сверху), то можно будет увидеть беспорядочные тропинки, плохо замаскированные ямы и, возможно, бродящего где-то в глуши И. Сусанина.

Функционал наращивался постепенно: сначала подключил беспроводные розетки, потом замахнулся на выключатели света. Аппетиты росли — датчики протечки, задымления, дверей, метеодатчики, радиореле и управление AV-техникой. Мастерство росло не так быстро. Поэтому получилось то, что получилось: вещь, бесконечно далекая от гайдлайнов по программированию и устройству электронных схем, но вполне работоспособная.

И знаете что? Меня это устраивает.




Если вы вдруг пропустили содержание первой серии, на всякий случай напоминаю. Этот текст будет не столь зажигательным, поскольку больше инструкция. К тому же, в самом начале я планирую масштабный «отказ от гарантий». Дело в том, что я исключительно далек и от схемотехники, и от программирования. Поэтому то, что у меня получилось, получилось именно так, как получилось (я хотел использовать еще больше получилось, но не получилось).

Я, разумеется, попробую ответить на вопросы «почему так, а не так?», но во многих случаях это будет похоже, как если бы в спросили у блондинки, почему она включила правый поворотник, и ушла с крайнего правого ряда своей полосы на крайний правый ряд встречной. Практически все, что сейчас есть действует по основополагающему принципу «Работает? Не трогай!».

По этой же причине я покажу код и схему в оригинальной конфигурации с минимально возможными изменениями (в основном, анонимизация персональных данных). Это должно с высокой степенью вероятности позволить повторить ее в рабочем состоянии. А потом сможете все причесать и украсить, как захотите. Если, конечно, захотите повторять.

Пожалуйста, поймите, что я так и не дошел до модификации Arduino для загрузки скетчей через локальную сеть. И любая модификация кода оборачивается беготней с табуретками и головной болью с возвращением в работоспособное состояние датчика движения. Поэтому я априори буду в штыки воспринимать даже стоящие предложения. Скорее всего, потом остыну и не только приму к сведению, но и реализую ваши советы.

Поэтому заранее прошу прощения за возможно бурную реакцию.

БЫСТРЫЙ СТАРТ
Квикстарт

Назначение контроллера

Управление беспроводными исполняющими устройствами, использующими амплитудную модуляцию несущей частоты и протокол и/или чипы кодера-декодера SC2260/SC2262 или аналогичные, а также беспроводными выключателями света Livolo.

Это могут быть всевозможные радиореле, беспроводные розетки, беспроводные выключатели, светодиодные ленты с дистанционным управлением, домашние сигнализации и вообще все, на что в Китае хватает чипов и фантазии. Подобная периферия обычно выполняется в двух частотных решениях: 433 МГц или 315 МГц. В принципе, выбирать можно любую, но в России, кажется, официально разрешена 433 МГц, хотя и за 315 МГц, вещающих на десять метров никто, вероятно, карать не будет.

Возможно, у вас уже есть что-то из таких устройств, но если и нет, технология такова, что они очень доступны в отличие от, скажем, периферии Z-Wave, ZigBee, Xbee или X10. Хотя эти протоколы, конечно, более надежны и защищены.

Опознать «простую» периферию можно, к примеру, по пультам ДУ. Обычно это брелок с четырьмя (плюс-минус) кнопками. Часто максимальное количество кнопок — 8. Но это все косвенные признаки, поскольку нет никакой гарантии, что внутри будет именно что совместимый чип. Используемая в контроллере библиотека RC-Switch совместима со следующими чипами: SC5262 / SC5272, HX2262 / HX2272, PT2262 / PT2272, EV1527, RT1527, FP1527 и HS1527. Можете в поиске ориентироваться на эти названия.

Что касается выключателей Livolo, то тут никакого разнообразия. Только они, поскольку там придумали свой протокол. Если будете заказывать у них, обратите внимание — не все выключатели там беспроводные.

Принцип управления

В первую очередь контроллер рассчитан на исполнение команд через веб-интерфейс при помощи обычных POST-запросов. Элементарный вид запроса — это просто ввод адреса контроллера и команды в строке браузера. Любого, причем браузера. Таким образом, контроллером, и, следовательно, «подключенными» к нему устройствами можно управлять практически с любого современного телефона, планшета или компьютера — был бы доступ в интернет.

В простейшем случае запрос выглядит так: 192.168.10.48/?Switch1-On

Здесь «192.168.10.48» — адрес контроллера; "?" означает, что дальше — команда; «Switch1-On» — команда.

Команды жестко задаются в коде контроллера на момент программирования. Изменить их «на лету» нельзя. Полученная через веб-интерфейс команда сравнивается контроллером с имеющимся списком и, в случае совпадения, исполняется. В противном случае — игнорируется.

Контроллер также может реагировать на команды пультов управления (с указанными выше чипами) и выполнять сценарии или просто переключать (по сути — функция ретранслятора) технику при получении их сигналов. Сценарии или команды также жестко описываются в коде контроллера на момент программирования.

Сборка контроллера

Для сборки контроллера в минимальной конфигурации потребуется:

1) Arduino Uno
2) Ethernet-шилд с чипом Wiznet W5100
3) Приемник амплитудно-модулированного сигнала (ASK/OOK) 433 МГц (или частоты периферии). Примерно такой.
4) Передатчик амплитудно-модулированного сигнала (ASK/OOK) 433 МГц (или частоты периферии). Примерно такой.
5) Источник постоянного тока 9В с разъемом 5.5/2.1 мм (стандартный разъем на плате Arduino)
6) Макетные

Этого достаточно для управления периферией через интернет и получения команд с радиопультов.

Последовательность сборки:

1) Установите Ethernet-шилд на плату Arduino
2) Подключите передатчик: DATA к выводу 8 Arduino; GND — к минусу питания; VCC — к плюсу питания.
3) Подключите приемник: DATA к выводу 2 Arduino; GND — к минусу питания; VCC к выводу 5V Arduino.

Будьте внимательны: обычные ASK/OOK-передатчики работают в диапазоне от 3В до 12В, поэтому их можно напрямую подключить к 9В источнику питания. При этом обычные ASK/OOK-приемники рассчитаны на 5В, поэтому их необходимо подключить или к выводу 5V Arduino, или к стабилизатору 5В.

В характеристиках Arduino указано, что оптимальное напряжение внешнего питания 7В — 12В. Поэтому, в принципе, можете использовать любой из источников питания в этом диапазоне, если под руками нет 9В.

Начало эксплуатации

Перед началом эксплуатации адаптируйте код под свои периферийные устройства. Об этом — в конце данного текста, где, собственно, приводится и код. Не забудьте про нестандартные библиотеки.

Для базовых функций (управление беспроводной периферией) вам понадобятся:

1) Указать в коде параметры вашей сети.

2) Указать в коде коды управления периферийных устройств (загрузите в контроллер пример ReceiveDemo и нажимайте кнопки пульта ДУ устройства, чтобы их узнать; если хотите управлять выключателями Livolo и с родного пульта, и с контроллера, используйте вот этот код, чтобы узнать идентификатор пульта).

Команды обычных периферийных устройств в коде можно найти по названию процедуры txSwitch(). В скобках — код управления устройства. Команды Livolo можно узнать по названию процедуры livolo.sendButton(). Первое число в скобках — идентификатор пульта, второе — код кнопки (коды кнопок указаны в описании библиотеки).

3) Переименовать команды веб-интерфейса, как вам больше нравится (команды интерфейса можно найти в коде по названию процедуры processCommand.

4) По вкусу можете переписать строки состояния (можно найти в коде по комментарию "// строки состояния в PROGMEM", которые будут отображаться при исполнении команд. Обратите внимание: для экономии памяти строки заданы не для всех команд.

5) Рекомендую закомментировать содержимое процедуры sendMail(), если не планируете пользоваться уведомлениями по почте. Иначе — укажите почтовый адрес контроллера, логин и пароль его эккаунта в кодировке BASE64 и почтовый адрес получателя уведомлений. Напоминаю, что уведомления по почте состоят из строк состояния (п.4).

После того, как все готово, загрузите код в контроллер. Затем подключите контроллер к источнику питания и локальной сети. Теперь можете пробовать управлять им, набирая адрес контроллера и одну из команд, которые задали для веб-интерфейса.

В целом, все это должно позволить запустить контроллер. Ниже — описание полной конфигурации и особенностей настройки.


Функционал

Итак, центральный контроллер. Для начала повторю его функции:

1) Управление четырьмя радиорозетками;

2) Управление 8 кнопками четырех радиовыключателей света;

3) Перезагрузка маршрутизатора при пропадании интернета;

4) Включение и выключение веб-камеры;

5) Контроль 8 беспроводных датчиков ОПС (три датчика задымления, три датчика протечки, пара датчиков открытия двери);

6) Получение данных с двух метеодатчиков и передача их в интернет;

7) Управление ТВ, медиа-плеером и кондиционером;

8) Управление кормушкой для котов;

9) Контроль проводного датчика движения;

10) Уведомление о событиях по электронной почте.

Сервисные функции (сценарии):

1) Автоматическое управление светом в гардеробе;

2) Автоматическое управление светом в прихожей;

3) Автоматическое управление ночным освещением на кухне;

4) Автоматическое управление светом и музыкой в ванной (на момент написания — два варианта: самим контроллером и командами контроллеру в ванной);

5) Выключение всего, что выключается дистанционно, включение камеры и переключение датчика движения в режим охраны при выходе из дома; и выключение камеры с включением фонового света, света в прихожей и переключением датчика движения в предыдущий, перед охраной, режим по возвращении.

Зачем мне два блока питания

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

Иными словами, блок 9В питает контроллер, а также коммутируется его реле на питание маршрутизатора, а блок 5В применяется для питания веб-камеры и вспомогательного питания периферии, потому что на всех выводов и возможностей Arduino не хватает.

Уже сейчас я понимаю, что хватило было бы одного достаточно мощного 9В блока и одного 5В стабилизатора. И, возможно, позже я к этому вернусь. А вы можете сразу исключить лишнюю железку из списка.

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

Железки

Для приготовления контроллера понадобится (ссылки исключительно для примера):

1) Arduino Uno — 1 шт.
2) Ethernet-шилд с чипом Wiznet W5100 — 1 шт.
3) ASK/OOK-приемник 433 МГц — 1 шт.
4) ASK/OOK-передатчик 433 МГц — 1 шт.
5) Датчик движения HC-SR501 — 1 шт.
6) Блок (не менее 2 шт. в блоке) реле 5В с ключами — 1 шт.
7) Пьезокерамическая пищалка — 1 шт.
8) Источники питания. Как минимум — 5В для Arduino и ее периферии. Лучше — 9В и стабилизатор 5В для периферии.
9) Антенны для приемника и передатчика, если их нет.
10) Макетные или иные провода, разъемы и корпус по вкусу.

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

. обычный передатчик. Надпись в расфокусе гласит FS1000A
image

Правда, мне понравился передатчик, купленный по случаю на DX. Он, кроме того, что дороже других, уже оснащен спиральной антенной и субъективно работает лучше, чем совсем дешевые передатчики, которые я пытался оснастить самодельными и спиральными «заводскими» антеннами.

. обычный суперрегенеративный приемник, на который я уже успел запаять «антенну»
image
А вот купленный там же супергетеродинный приемник, как я говорил выше, не впечатлил.

К вопросу антенн я, кстати, еще вернусь — это у меня было очень больное место. А пока напомню, что дешевые комплекты приемника и передатчика продаются без антенн. И это не значит, что так и должно быть. Это значит, что антенны вам придется или сделать самостоятельно, или купить готовые.

Пьезокерамическая пищалка получила это место потому, что ей не нужны дополнительные компоненты. Внутреннее сопротивление позволяет подключаться напрямую к Arduino. А еще она довольно громко орет и очень компактна.

И, чтобы два раза не вставать: Arduino Uno потому, что я выбирал первый контроллер. По этой же причине — сетевой шилд с W5100. Ведь судя по тогдашним мнениям, это были оптимальные вещи для начинающих. Я же не знал, что все зайдет так далеко.

. шилд вообще никак не мешает собирать прототип
image

Предназначение компонентов довольно прозрачно. Ethernet-шилд обеспечивает работу веб-сервера на Arduino и, соответственно, удаленное управление, конфигурирование и мониторинг состояния. Приемник слушает датчики, передатчик отправляет команды контроллера на исполнительные устройства (розетки, выключатели) и сервисные контроллеры.

Прикрепленный к контроллеру датчик движения используется для режимов охраны и автоматического управления светом. А пищалка информирует об успешном включении, приеме некоторых радиокоманд (например — подтверждает, что услышала кнопку включения кормушки, чтобы не давить ее до полного и окончательного удовлетворения) и применяется для настройки того же датчика движения (писк при срабатывании датчика здорово помогает правильно выставить направление, чувствительность и выявить ложные срабатывания).

Реле включают и выключают маршрутизатор и веб-камеру, что весьма актуально, если учесть, что у меня маршрутизатор скрыт мало того, что с глаз долой, так еще и на высоте выше моего роста. Результат — перезагрузка сети без проблем и физическое выключение камеры, когда она не используется, что дает некоторый психологический комфорт.

. удобный блок реле с прямым подключением к Arduino. Просто реле подключить нельзя — слишком велик потребляемый катушкой ток. А здесь на плате оптронная развязка и транзисторный ключ плюс светодиод-индикатор состояния
image

Сборка контроллера ничего экстраординарного не представляет. Только, разумеется, не забудьте сразу нацепить Ethernet-шилд на Arduino Uno и помните, что сетевой шилд использует пины 4, 10, 11, 12 и 13 Arduino Uno, поэтому они не используются в функциях контроллера. По счастью, у Uno остается вполне достаточно цифровых пинов и полный комплект аналоговых.

Текущая конфигурация отражена в скетче и, на всякий случай, здесь.

A0 — DATA датчика движения
2 — DATA приемника
5 — к управляющему входу блока реле маршрутизатора
6 — к управляющему входу блока реле камеры
8 — DATA передатчика
9 — к плюсу пищалки

Все GND (земля) всех компонентов и все VCC (плюсы) соединяем соответствующим образом. Т.е. земля общая у всех, а если напряжение питания различается, каждый компонент должен получать свое.

Для простоты и расширяемости я распаял на разъеме 5В сразу несколько (четыре, что ли) отрезка макетного провода с «мамами», чтобы можно было, если что быстро подключить к питанию новые компоненты. Причем вышло, как обычно у меня — по дурацки. Не сразу распаял, а когда понял, что питающих пинов Arduino не хватает.

Другая важная особенность — подключение периферийных устройств к реле. Камеру я подключил к нормально разомкнутым контактам, а маршрутизатор — к нормально замкнутым. Так что в исходном состоянии системы маршрутизатор всегда включен, а камера — выключена. Но есть у меня подозрение, что и здесь что-то недодумал, поскольку забыл проверить, что случается при потере питания контроллером. Просто имейте в виду эту особенность.

. в итоге должно получиться что-то вроде этого. А это, судя по всему, КЦД-В-1-1 (первая аппаратная и софтовая версия контроллера). Приемник для простоты замотан в черную изоленту — а то мало ли что.

image

Для ориентира: стоимость первой версии контроллера, включая корпус и разъемы, составила около $60. Это уже потом я пошел в разнос и стал экспериментировать с УЗ-датчиком и в отчаянии накупил кучу приемников-передатчиков и антенн, чтобы, наконец, решить проблемы неуверенного приема и передачи сигналов.

Уже все собрали? Поздравляю! Теперь самое время еще два раза проверить подключение сигнальных проводов и три — питания. Словами не передать, сколько раз я ошибался в рутинных операциях, особенно, если при перекомпоновке приходилось по нескольку раз менять подключение компонентов. Не хочу, чтобы то же самое случилось с вами.

Периферия

Действующий состав периферийных устройств и взаимодействие с ними — цепь случайных совпадений и, в общем, достояние истории. Так как я сначала купил радиорозетки и выключатели, то сначала танцевал от них, а потом — к новым исполнителям с учетом того, что уже было.

Радиорозетки — самые простые (мои были куплены в Leroy Merlin, под их же маркой). Существуют под массой разных имен, а суть одна: обычно в комплекте три розетки и пульт (почему-то на четыре розетки). На спине розеток — переключатель, который выбирает канал. Если все поставить на один, то будут включаться одной кнопкой на пульте, что подходит для организации групп. Причем команды включения и выключения разные, поэтому за один канал-группу отвечает пара кнопок. Это удобно — ведь обратной связи нет, поэтому при любых сомнениях можно просто повторить команду на включение (выключение) и уж с десятого раза она точно дойдет. Рабочая частота — 433 МГц.

. это, надеюсь, в последний раз, когда пощу розетки, больше не буду
image

Максимальная коммутируемая мощность — что-то около киловатта. Я их использую для управления фоновым светом и включения-выключения планшета и акустики на кухне.

.
image

Вторым номером идут двухкнопочные радиовыключатели Livolo (есть и другие варианты, но я обещал про свою конфигурацию). Их я купил как упомянутая выше блондинка. Увидел и все — ми-ми-ми и хочу-не-могу. Влюбился с первого взгляда. Тоже, как выяснилось, 433 МГц и тоже обратной связи нет.

.
image

Плюсы выключателей в том, что они идеально (на мой вкус) выглядят и исключительно просто (даже с моим радиусом кривизны рук) монтируются. Еще лучше то, что они ориентированы на двухпроводную систему подключения и не требуют дополнительного питания, поэтому органично заменяют обычные клавишные выключатели. У меня Livolo работают и с энергосберегающими лампами, и со светодиодными (12В с трансформатором и 220В) — ни те, ни другие не мерцают в выключенном состоянии.

. стандартная круглая коробка. Для эстетов бывают в квадратном UK-исполнении
image

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

В результате на руках имею одну несущую частоту и два идентичных по сути, но разных по исполнению и, как следствие, несовместимых радиопротокола. Один — для выключателей света, другой — для радиорозеток и датчиков. Вот последний и вынесем на обсуждение коллективом в первую очередь.

Розетки хотя, повторюсь, и просты до безобразия, но довольно эффективны. Внутри — простой преобразователь напряжения, обычный (точно такой же, как вы купите для Arduino) приемник, микросхема-декодер и реле. В пульте — кодирующая микросхема и передатчик. Особенность протокола в том, что он подразумевает многократное повторение одной и той же кодовой посылки (уникальный номер устройства), пока нажата кнопка пульта или пока устройство (например, датчик двери) находится в активном состоянии.

. не очень богатый, но функциональный внутренний мир розетки
image

Эта схема получила исключительно широкое распространение во всевозможных сигнализациях и исполнительных устройствах, и реализуется на нескольких наиболее распространенных чипах. Обычно они ищутся по названиям SC2260/SC2262 или PT2260/2262, а для Arduino существует библиотека RC-Switch, которая без проблем управляет всеми подобными совместимыми чипами.

Повторю квикстарт. Вообще, опознать «простую» периферию можно, к примеру, по пультам ДУ. Обычно это брелок с четырьмя (плюс-минус) кнопками. Часто максимальное количество кнопок — 8. Но это все косвенные признаки, поскольку нет никакой гарантии, что внутри будет именно что совместимый чип. Используемая в контроллере библиотека RC-Switch совместима со следующими чипами: SC5262 / SC5272, HX2262 / HX2272, PT2262 / PT2272, EV1527, RT1527, FP1527 и HS1527. Можете в поиске ориентироваться и на эти названия.

. SC2262 в пульте ее. Что и требовалось
image

Полагаю, что когда эта информация устаканилась в моей голове, и случилось озарение. Я тогда сделал три вещи: подключил приемник к Arduino, загрузил пример кода-приемника и нажал кнопку домашнего беспроводного звонка. Затем посмотрел на экран лаптопа и забил в поиск Aliexpress что-то на тему беспроводных датчиков. И почувствовал себя, как в магазине игрушек, честное слово.

Представьте: я понял, что смогу получать уведомления о том, что кто-то нажал на кнопку звонка, и могу подключить массу различных датчиков, причем вообще без хлопот. Потому что эти датчики совершенно автономны, очень долго работают от одной батарейки и не требуют вообще никаких проводов.

Пришлось собрать глаза в кучку, обуздать желания и заказать то, что действительно необходимо (ссылки архивные, как отправная точка):

1) Датчики задымления — 3 шт.
2) Датчики протечки — 3 шт.
3) Датчики двери — 2 шт.
4) Датчик движения — 1 шт.

Можете не совершать мою ошибку, и не заказывать датчик движения. Поясню. Эта штука эффективна исключительно в охранных системах (где активное движение — признак тревоги, а не обычной жизни), а в умном доме беспроводной датчик движения будет только мешаться. Все потому, что он «кричит» каждый раз при срабатывании, и тем самым, во-первых, отвлекает контроллер, а, во-вторых, создает помехи остальным датчикам.

Очевидно, что таких «отвлеченных моментов» будет крайне много для нормальной работы контроллера, ведь не секрет, что люди имеют обыкновение ходить по дому. А штатной возможности дистанционно выключать этот датчик на время нет.

Еще одна спорная вещь — датчик протечки. Конструктивно он состоит из двух деталей — передающего блока и, собственно, датчика влажности, подключенного длинным проводом и представляющего собой пару расположенных на небольшом расстоянии друг от друга токопроводящих дорожек. Это в идеале — и провод длинный, и дорожки.

По факту можете получить провод длиной сантиметров 20, а вместо платы с дорожками — коробочку, внутри которой термоклеем закреплен зачищенный огрызок того же провода. С одной стороны, принципиальной разницы нет, поскольку все работает. А с другой — крепить такое неудобно, да и пока эта коробка водой наполнится (а то еще и всплывет) — поздно будет пить Боржоми.

. внутри моих датчиков протечки и двери чип CS5211AGP, который не упоминается в списке совместимости RC-Switch, но работает с ней. Это чтобы вы понимали, насколько популярный протокол.
image

Другой вариант китайского креатива — пропихнуть под «датчиком воды» датчик переполнения резервуара. Там вместо дорожек — корпус с поплавком. В корпусе расположен геркон, в поплавке — магнит. Дальше, полагаю, объяснять не надо. Для регистрации протечек эта штука совершенно не подходит.

Отсюда вывод: признаки расово правильного датчика протечки — длинный провод к сенсору влажности и сенсор, выглядящий как плата с двумя вытравленными дорожками, расположенными на небольшом расстоянии друг от друга.

. расово верный датчик протечки, рядом с ним — датчик двери


Датчики задымления я разместил на кухне, в прихожей и в комнате. Датчики протечки — под раковиной на кухне, в сантехническом коробе в ванной и под стиральной машиной.

. датчик задымления на месте
image

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

. датчик двери на месте. Родную его антенну оторвал во время антенной истерики. Теперь там кусок провода совершенно произвольной длины. Тем не менее, работает. Потому и не трогаю. Этот датчик закреплен на торце полки в гардеробе. А мимо катается раздвижная дверь, на которую наклеен магнит.
image

Завершают список периферии три кнопки и веб-камера.

1) Кнопка беспроводного звонка (433 МГц) для уведомлений о звонящих в дверь.

2) Беспроводная кнопка «паники» от сигнализации (она используется для управления кормушкой котов).

. вот такая «паника»
image

3) Кнопка присутствия для переключения режимов охраны и света при уходе и возвращении домой. В целом аналогична кнопке «паники», но свою я переделал из кнопки звонка (купил 315 МГц, не посмотрев) и китайского пульта-брелка. Собственно, кнопку звонка покупал потому, что она была привлекательна внешне и удобна — большая мягкая клавиша. Сама переделка упоминания не стоит — лучше купить готовую (звонок или панику).

. Я дома. Симпатичнее паники по-любому


Принцип работы и подготовка периферии для контроллера

Принцип работы беспроводных датчиков следующий:

1) Датчик задымления издает звуковой сигнал и постоянно передает сигнал по радио, пока превышен порог задымления. Если честно, натурных испытаний не проводил — пользовался кнопкой теста. Орет сильно.

2) Датчик двери передает свой сигнал в течение двух секунд после срабатывания.

3) Датчик протечки передает свой сигнал все время, пока превышен порог влажности. Как высохнет — замолкает.

Автономность датчиков приличная. С момента покупки в июне прошлого года батарейки пришлось менять только в датчиках дверей — но они же и открываются не один раз в день.

4) Кнопки передают сигнал, пока нажаты.

Собственно сам сигнал представляет собой уникальную кодовую посылку длиной 24 бита. Обычно ее можно менять по желанию: на плате есть или обычные перемычки, или же площадки, которые можно перемкнуть припоем. Посылка монолитна в эфире, но структурно состоит из двух частей: адреса устройства и уникального номера. Адрес обычно используется в системе сигнализации, что позволяет логически объединять датчики в охранные зоны.

Мне эти зоны не нужны, поэтому я всю посылку считаю уникальным номером и слежу разве что за тем, чтобы эти номера были действительно уникальными. То есть — чтобы не повторялись среди моей периферии, и чтобы у соседей не было похожих (это можно вычислить по ложным срабатываниям, чего, впрочем, у меня не случалось).

Именно эта кодовая посылка используется для идентификации центральным контроллером сработавшего датчика. Иногда адрес и номер пишут на корпусе датчика. Но лично я узнал числовые имена датчиков с помощью Arduino, приемника и библиотеки RC-Switch. Нужно просто подключить приемник к контроллеру (по умолчанию DATA приемника к 2 цифровому пину) и загрузить пример ReceiveDemo из папки RC-Switch.

Затем по очереди активируем датчики и записываем кого как зовут. Листочек не выкидываем — пригодится при адаптации кода центрального контроллера.

Реализацией этого протокола в RC-Switch я воспользовался и для других вещей: придуманные уникальные номера использую как команды и транспорт данных для сервисных контроллеров. Например, центральный контроллер говорит 012345678, а сервисный контроллер получает этот код и знает, что ему нужно отправить команду на включение ТВ. Или вот, к примеру метеодатчик выдает трель вида 1281265739, а центральный контроллер знает, что 1281 — это имя комнатного датчика, 265 — это температура (только поделить на 10), а 739 — атмосферное давление.

То есть вполне себе двусторонний протокол, хотя и с небольшой скоростью и без коррекции ошибок.

С выключателями Livolo почти так же, но не совсем. Кодовая посылка все так же делится на две части, но первая — уникальный идентификатор пульта ДУ, а вторая — код нажатой на пульте кнопки. Так как выключатели обучаемые, то им совершенно неважно какой у пульта идентификатор (они его запомнят при знакомстве), а коды кнопок формируются по очень простому правилу.

Что мы в итоге имеем? Если есть пульт и хочется использовать его вместе с центральным контроллером, то необходимо выяснить уникальный идентификатор пульта. И здесь нам поможет вот этот код. Все аналогично RC-Switch: подключаем приемник, загружаем код, жмем на кнопку пульта и записываем идентификатор и код кнопки. Повторяем со всеми кнопками, которые собираемся использовать.

Если же пульта нет, и необходимости в нем тоже нет, тогда достаточно просто библиотеки для управления Livolo. В комплекте с ней — описание использования и пример, а также коды ряда кнопок. Таким образом можно сделать совершенно произвольное количество пультов и, подозреваю, произвольное количество кнопок, если соблюдать правила (16 бит — идентификатор пульта, 7 бит — код кнопки).

О том, как появилась эта библиотека я постараюсь рассказать в отдельном тексте. А для наиболее пытливых умов сообщаю, что хотя «виртуальные» пульты действительно работают, по какой-то причине подходят не все 16-битные идентификаторы пульта.

Наконец, веб-камера. У меня простая и купленная давным-давно D-Link DCS-930. Особенностей у нее нет, разве что угол обзора объектива увеличен с помощью широкоугольной насадки (рыбий глаз) для мобильного телефона.

Антеннам посвящается

Сначала я использовал в качестве антенны обычные отрезки макетных проводов длиной 173 мм — под четверть длины волны. И до определенного момента это работало, но с увеличением количества беспроводных периферийных устройств стали случаться чудеса. Периферия то работала, то не работала.

Было очевидно, что проблема или в коде, или в приемо-передающей части. Код я проверил, и ошибок найти не смог. Значит, дело в радиоканале. Чтение текстов, посвященных диапазону и типам антенн 433 МГц меня не обрадовало: оказалось, что связь на этой частоте довольно капризна, а волны имеют свойство здорово отражаться от ближайших поверхностей и складываться вплоть до самоликвидации.

Одновременно я выяснил, что, возможно, не слишком удачно выбрал длину. Один товарищ на DIY-форуме рекомендовал использовать «электрическую» длину проводника, что по его расчетам в среднем составляло 0,95 от расчетной. То есть, если 1/4 волны 433 МГц = 173 мм, то электрическая длина — 165 мм. Антенны я немедленно переделал, но особой эффективности не заметил.

На следующем этапе я решил попробовать спиральные антенны. Про них говорили, что они гораздо менее капризны, чем штыревые (кусок провода — суть аналог штыревой в отвратном исполнении), хотя и чуть менее эффективны. В спираль я скручивал те же 165 мм отрезки, но уже первого попавшегося под руку провода, который можно было скрутить, то есть классический электрический кабель сечением 1.5 мм2.

. во как мою антенну (в центре) скрутило
image

Удивительно, но эти штуки были уже гораздо стабильнее и лучше. Но все равно не очень, да и эстетика, опять же. Поэтому в итоге я заказал на Ebay пачку готовых спиральных антенн.

. а это заводская. Хотя все мы знаем, какие в Китае заводы
image

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

Поэтому резюме: крайне важны хорошие антенны и приемники-передатчики, а также, по возможности, продуманное расположение центрального контроллера относительно периферии.

Резюме второе: если что-то идет не так, возможно, что дело не в коде, а в радиоканале.

Планировка событий

На момент сборки у меня не было часов реального времени, а переключать режимы автосвета в зависимости от времени суток хотелось. А когда часы появились, то уже не было места на библиотеку для них. Поэтому я воспользовался очевидным (для себя) решением — аппом Tasker, который давным давно поселился в смартфоне.

Идеология простая.

1) Завожу профили в с контекстом, где указан необходимый временной интервал.
2) Для каждого профиля добавляю необходимые задачи (Task) с действиями из категории Сеть (Net) — HTTP Post.
3) В строке адреса для каждого POST-запроса указываю адрес контроллера, включая команду, которую нужно выполнить. Например: 192.168.10.48/?Switch1-on

Сценарий получается, если в одной задаче собрать несколько действий. Иными словами, часть логики выносится за рамки контроллера, что дает весьма ощутимые плюсы:

1) Реализация сценариев без расхода ресурсов контроллера
2) Элементарное добавление новых сценариев
3) Простое и быстрое изменение настроек
4) Всегда точное время

Минусы тоже есть. Если Android с логикой на борту разрядится или зависнет (а то и сеть пропадет) — сценарий не будет выполнен.

С помощью этого решения у меня переключаются режимы дневного и ночного автоматического света. То есть, «Автосвет день» = включение автоматического света в прихожей, разрешение музыки в ванной и выключение автоматического света на кухне. А «Автосвет ночь» — противоположное действие.

Кроме того я попросту вытащил некоторые полезные (для меня) функции контроллера в виде ярлыков задач на один из экранов смартфона. Так достаточно просто одного нажатия, как на обычную сенсорную кнопку. И никаких длинных адресов в браузере.

В конечном итоге эта «логика» переехала со смартфона в Android-брелок, подключенный к ТВ. Так и за сеть можно меньше переживать, и за питание.

Основные алгоритмы

Сейчас я пройдусь по коду и постараюсь описать хотя бы основные алгоритмы работы контроллера, чтобы стало немного яснее, что же я все-таки имел в виду.

Включение

Успешное включение контроллера подтверждает короткий двойной звуковой сигнал. Это означает, что контроллер перешел в дежурный режим. Одновременно на сервисный адрес почты отправляется уведомление «System started».

При включении устанавливаются следующие основные параметры:

— «Я дома» — истина
— Автосвет в прихожей — выключен
— Автосвет на кухне — выключен
— Автосвет в ванной — включен
— Время поворота сектора кормушки — 15 секунд
— Веб-камера — выключена
— Состояние датчиков задымления и протечки — нет тревоги
— Музыка в ванной — выключена, разрешена
— Датчик движения в прихожей — включен
— Режим отладки для датчика движения — выключен

Периодические действия

С интервалом в две минуты контроллер проверяет наличие интернета: выполняет подключение к Яндексу. Если подключились — не будет ничего. А если нет — выполняется перезагрузка маршрутизатора выключением и включением.

Всего выполняется две перезагрузки подряд. Если и после этого интернет не появился — таймаут в час. Потом снова проверка.

Второе периодическое действие — отправка погоды в интернет с интервалом в один час на сервис "Народный мониторинг". Используется протокол HTTP, метод POST. Для использования этой функции вам понадобится эккаунт на сайте сервиса.

Отправляются показания любого из датчиков при условии, что датчики передали все параметры, которые могут передать. Если датчик не передал один из параметров, считается, что показаний нет (отказ датчика).

Если показания передали оба датчика, в интернет передаются оба набора одновременно.

Данные от метеодатчиков передаются в контроллер по радиоканалу с помощью примитивного протокола на основе RC-Switch:

1) Комнатный датчик передает числа вида:

а) Давление: 1210XXXX, где XXXX — атмосферное давление в hPa с точностью до целых.
б) Температура: 1310SXXX, где S — 1 (ниже нуля) или 0 (выше нуля) в зависимости от знака температуры, а XXX — температура с точностью до десятых, умноженная на 10.

2) Уличный датчик передает числа вида 161HSXXX, где H — признак влажности, S — знак температуры, XXX — значение климатического параметра с точностью до десятых, умноженное на 10.

Если H = 1, контроллер считает, что переданы показания влажности. Если H = 0, контроллер думает, что получил температуру.

Оба параметра передаются каждым метеодатчиком с небольшим интервалом.

Готовый POST-запрос для двух датчиков выглядит примерно таким образом:

POST http://narodmon.ru/post.php HTTP/1.0
Host: narodmon.ru
Content-Type: application/x-www-form-urlencoded
Content-Length:51

ID=MAC00&MAC01=24.6&MAC02=1025


Здесь MAC00 — логический адрес головного устройства, MAC01 — логический адрес датчика температуры, MAC02 — логический адрес датчика давления. При этом физически датчики температуры могут быть одним или разными устройствами. Например, мой домашний датчик использует сенсор BMP085, который одновременно измеряет температуру и атмосферное давление. При этом для него назначается два адреса (MAC01 и MAC02), а адрес головного устройства это вещь, в общем, не привязанная ни к чему. Это просто адрес некоего виртуального владельца отдельных датчиков.

Действия по датчику движения

Если датчик выключен — действия не выполняются. Иначе выполняются следующие действия.

1) Если выключено автоматическое управление светом и если дома кто-то есть, датчик не выполняет никаких функций.

2) Если включен автосвет в прихожей, однократный проход вызывает включение света в прихожей на 1 минуту. Повторный проход в течение этой минуты продлевает время работы света до 3 минут. Каждый последующий проход аналогично продлевает время работы света до трех минут. Если в течение трех минут движение не зафиксировано, свет выключается.

3) Если включен автосвет на кухне, проход вызывает включение света на кухне на 10 минут. Каждый повторный проход не изменяет время работы света, но приводит к выдаче команды на его включение. Причина в том, что иногда свет на кухне не включается с первой попытки (радиоканал или занятость контроллера). Тогда, если еще из коридора видно, что свет не включился, достаточно сделать шаг назад — под датчик.

4) В отсутствие дома людей срабатывание датчика приводит к уведомлению об этом событии по электронной почте.

5) При включенном режиме отладки датчика движения каждое зафиксированное движение подтверждается звуковым сигналом. Если одновременно включен автосвет на кухне, дополнительно при каждом срабатывании датчика на 30 секунд включается свет на кухне. Это для более наглядной отладки и заодно было для отладки автосвета на кухне.

Действия по датчику двери в гардеробе

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

Особенность алгоритма в том, что включение света производится с задержкой в 1.7 секунды. Это необходимо, так как датчик «кричит» порядка 2 секунд, и если выдать команду на включение света сразу же, то контроллер может успеть «поймать» конец трели датчика, и еще раз переключит свет. Задержка не создает особого дискомфорта, зато предотвращает выключение света сразу после включения.

Аналогичный алгоритм работает и в случае использования автосвета по датчику двери в ванной.

Действия по кнопке «Я дома»

Нажатие кнопки при выходе из дома приводит к выполнению следующего сценария:

1) Пауза в 1 минуту
2) Выключение всех радиорозеток
3) Выключение всех радиовыключателей света
4) Выключение автосвета в прихожей, если он был включен
5) Выключение музыки в ванной
6) Переключение датчика движения в режим охраны с уведомлением о срабатываниях по почте
7) Включение веб-камеры с уведомлением по почте

Нажатие кнопки по возвращении домой выполняет другой сценарий:

1) Выключение веб-камеры с уведомлением по почте
2) Включение фонового света в комнате
3) Включение фонового света на кухне
4) Включение автосвета в прихожей, если он был включен перед уходом; или включение света в прихожей, если автосвет в прихожей был выключен на момент ухода из дома

Кнопка работает по принципу триггера: каждое нажатие переключает режим на противоположный (дома-не дома).

Действия по кнопке «Кормушка»

Кормушка (модифицированная из обычной Feed-Ex) представляет собой миску с четырьмя отделениями-секторами, закрытыми крышкой. Вырез в крышке открывает доступ только к одному сектору. Сектора заполняются кормом через один. Таким образом, каждый поворот на сектор или открывает корм, или прекращает к нему доступ.

. кормушка в исходном состоянии
image

Это не живодерство, а насущная необходимость, поскольку одного кота нужно держать на диете, и нельзя оставлять корм в доступе все время.

. принцип заполнения
image

При нажатии на кнопку «Кормушка» выполняется поворот кормушки на один сектор (по таймеру) и задержка в этом состоянии на 2.5 минуты, чтобы коты успели дойти и поесть.

Через 2.5 минуты выполняется поворот кормушки еще на сектор (пустой). Таким образом кормление прекращается.

Если кнопка нажата повторно до истечения 2.5-минутного интервала, выполняется немедленный поворот на сектор (закрытие), а поворот по таймеру отменяется. Это процедура экстренного закрытия, если кто-то рядом с кормушкой и видит, что к ней приложился не авторизованный для этой операции кот.

Действия по срабатыванию датчика двери в ванной

При срабатывании датчика выдается команда на переключение света и включение музыки, если включение музыки разрешено. При повторном срабатывании датчика снова выдается команда на переключение света и на выключение музыки.

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

Действия по срабатыванию датчиков задымления и протечки


1) При срабатывании датчика задымления контроллер уведомляет по почте письмом с указанием места расположения датчика

2) При срабатывании датчика протечки контроллер уведомляет по почте письмом с указанием места расположения датчика, одновременно выдается 10 звуковых сигналов с интервалом в 10 секунд, так как датчики протечки не имеют функции звукового уведомления

3) Срабатывание любого датчика считается критическим событием, после которого прием сигналов от датчиков блокируется до выполнения сброса состояния датчиков через веб-интерфейс.

Причина блокировки в том, что при срабатывании датчики передают сигнал постоянно. Это значит, что контроллер (без блокировки) будет реагировать на него сразу, как освободится от предыдущего уведомления по почте. Особого смысла я в этом не вижу, поэтому — блокировка до ручного сброса.

Действия при получении команд для аудио- видеотехники

Контроллер передает с помощью библиотеки RC-Switch числовую команду для медиаконтроллера. О медиаконтроллере — в будущих выпусках :)

Отправка почты

В отсутствие парка почтовых голубей в специально оборудованных клетках с электромеханическими замками, у всех возникает понятное желание воспользоваться для отправки писем простым и довольно удобным протоколом SMTP. Однако очень многие почтовые сервисы перешли на авторизацию, защищенную SSL, а у Arduino попросту нет на это сил.

Решение? Идем на компромисс с безопасностью, ищем и эксплуатируем почтовый сервер, способный на открытую авторизацию. Именно поэтому я использую Mail.Ru — он отвечает требованиям.

Я прекрасно знаю недостатки текущей реализации — она просто останавливает всю активность на время отправки письма за счет задержек delay, необходимых, чтобы гарантированно дождаться ответа сервера. Интерактивная схема с чтением полученных от сервера строк была бы немного быстрее, но у меня сразу не получилось ее «завести». А вот этот алгоритм, подхваченный на форумах Arduino.cc, заработал сразу.

По этой, кстати, причине, уведомление о срабатывании датчика движения выдается с 30-секундной задержкой. Так сделано для того, чтобы было время нажать на кнопку «Я дома» при возвращении. Если этой задержки нет — при входе сразу же отправляется письмо, и контроллер перестает реагировать на внешние раздражители в течение примерно десятка секунд.

В итоге для почтовых уведомлений вам понадобится:

1) Ящик на Mail.Ru
2) Логин в кодировке BASE64
3) Пароль в кодировке BASE64

Для того, чтобы закодировать свои логин и пароль можно воспользоваться одним из многочисленных (тысячи их) онлайновых сервисов.

Веб-интерфейс

Страничка по адресу контроллера отображает последнее значимое состояние (согласно списку строк состояния в скетче), а также последние полученные данные метеодатчиков.

Скетч всемогущий

Ну что тут скажешь? Смотрите и трепещите, поскольку зрелище это столь же увлекательное, сколь, полагаю, ужасное — с точки зрения любого более-менее адекватного программиста.

Функции, переменные и проч, и проч, добавлялись по мере необходимости, что объясняет абсолютную нелинейность кода. Тем не менее я постарался максимально подробно комментировать практически все, что делал. В первую очередь — для себя. Потому что через неделю уже забываю что, зачем и почему именно так, а не иначе.

Для корректной работы скетча требуется несколько нестандартных библиотек. Не забудьте скопировать их в папку libraries вашего каталога среды Arduino:

1) RC-Switch для управления розетками, радиореле и обмена с метео- и сервисными датчиками.

2) Livolo для управления одноименными выключателями света.

3) SimpleTimer для отсчета интервалов времени.

Адаптация кода, если я ничего не путаю, включает в себя следующие изменения:

1) Определения пинов. Если вы захотите подключить периферию Arduino по-другому — придется поменять, а если подключите, как в коде — то и трогать не надо. Для удобства пины определены в секции переменных, остальной код шерстить не нужно.

2) Параметры сети.

3) Параметры почты (процедура sendMail).

4) Секция обработчика сигналов беспроводных датчиков. Здесь необходимо заменить номера датчиков на свои.

5) Команды периферийных устройств в коде. Их можно найти по названию процедуры «передатчика» txSwitch (это для розеток, радиореле и сервисных контроллеров) и по названию функции библиотеки livolo (для выключателей света).

6) Секция параметров погодных и климатических данных на сервис "Народный мониторинг".

7) Секция обработки команд веб-интерфейса. Здесь можно поменять имена команд на те, что вам больше нравятся. А можно не менять.

Критически важные пункты: 2 — 6. Если не настроите сеть и почту (для почты еще понадобится эккаунт на почтовом сервисе с SMPT и открытой авторизацией) — не будет дистанционного управления и уведомлений по почте. Если не измените идентификаторы датчиков на свои — контроллер их не «услышит». А если не поменяете команды розеток — то не сможете ими управлять. Для Livolo, впрочем, коррекция кодов не является жизненно важной, потому что если поставить выключатель в режим обучения, то он подхватит команду «на лету».

Аналогично, если захотите выдавать погоду в интернет, необходимо зарегистрироваться на сайте «Народного мониторинга» вбить MAC своего головного устройства (контроллера) и датчиков. Адреса можно взять произвольные. Берете, к примеру, старую сетевую карту или любой другой сетевой девайс, ищете наклейку с MAC и берете его как адрес головного устройства. А потом меняете последние (или какие вообще нравятся) разряды, чтобы получить необходимое количество адресов. Для текущего кода всего нужно пять адресов: головное устройство и четыре датчика (каждый параметр считается датчиком, а их четыре: температура внутри и снаружи, давление и влажность).

Ядро скетча составляют веб-сервер с обработчиком команд веб-интерфейса и обработчик сигналов от беспроводных датчиков. Все остальное — что-то вроде периферийной обвязки.

Последнее: я постарался ничего не испортить при обезличивании. Но если испортил — прошу прощения, и будем разбираться вместе.

А теперь слайды
/* 
15.08.2013 - новая процедура выдачи команд на розетки
22.09.2013 - новая процедура управления светом в коридоре (всегда включение на минуту, при повторном проходе таймаут 3 мин)
26.09.2013 - апдейт управления светом в коридоре, вместо счетчика простые признаки света
27.09.2013 - флаги вместо выключения приема, потому что при выключенном приемнике нет реакции на другие события и вообще это неправильно
29.09.2013 - увеличено время работы кормушки, увеличено время таймаута в ванной до 1,6 сек., увеличено время передачи Livolo (180 вместо 150)
30.09.2013 - минимум переменных
06.10.2013 - снова выключение приемника вместо флагов 27.09.2013, а то свет в шафу работает отвратно
26.10.2013 - новые процедуры отправки климатических данных и управления Livolo
28.10.2013 - удален вывод в OWM, автосвет в коридоре и ванной переведен на медиаконтроллер в комнате
18.11.2013 - добавлена куча выключений приема RC-Switch, чтобы прерывания не мешали коду; автосвет: добавлено включение света на кухне при каждом проходе
22.11.2013 - вернул задержки при выключении освещения, добавил дублирование "выкл. все" Livolo в swOff 
29.11.2013 - вернул управление светом в гардеробе и ванной в центральный контроллер
27.01.2014 - управление светом в ванной отдельным контроллером, новые команды для web (musicOn, musicOff)

A0 - PIR-датчик зеленый (минус - коричневый/оранжевый, плюс - красный)
A1 - серый (свободный)
0 - свободный
1 - свободный
2 - приемник
3 - резерв под датчик движения в коридоре
4 - Ethernet
5 - модем
6 - камера
7 - свободный
8 - передатчик
9 - пищалка
11, 12, 13 - Ethernet
*/

#include <SPI.h>
#include <Ethernet.h>
#include <avr/pgmspace.h> // для PROGMEM
#include <RCSwitch.h> //   http://code.google.com/p/rc-switch/
#include <SimpleTimer.h> // http://playground.arduino.cc//Code/SimpleTimer
// #include <Twitter.h> //http://playground.arduino.cc/Code/TwitterLibrary
#include <livolo.h>



// инициализация сети
byte ip[] = { 192,168,10, 48 };                        // IP Address
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // MAC Address
byte servergoogle[] = { 213, 180, 204, 3 }; // Yandex
byte servermail[] = { 94, 100, 177, 1 }; // Mail.Ru
byte gateway[] = { 192, 168, 10, 1 };
byte subnet[] = { 255, 255, 255, 0 };
byte mydns[] = { 192, 168, 10, 1 };

// строки состояния в PROGMEM
prog_char statusString_0[] PROGMEM = "Room softlight ON";
prog_char statusString_1[] PROGMEM = "Kitchen softlight ON";
prog_char statusString_2[] PROGMEM = "Humidifier ON";
prog_char statusString_3[] PROGMEM = "Socket #4 ON";
prog_char statusString_4[] PROGMEM = "Room softlight OFF";
prog_char statusString_5[] PROGMEM = "Kitchen softlight OFF";
prog_char statusString_6[] PROGMEM = "Humidifier OFF";
prog_char statusString_7[] PROGMEM = "Socket #4 OFF";
prog_char statusString_8[] PROGMEM = "Room main light #1";
prog_char statusString_9[] PROGMEM = "Room main light #2";
prog_char statusString_10[] PROGMEM = "Hall light";
prog_char statusString_11[] PROGMEM = "Wardrobe light";
prog_char statusString_12[] PROGMEM = "Bathroom light";
prog_char statusString_13[] PROGMEM = "Kitchen main light";
prog_char statusString_14[] PROGMEM = "All main light OFF";
prog_char statusString_15[] PROGMEM = "System started";
prog_char statusString_16[] PROGMEM = "Camera ON";
prog_char statusString_17[] PROGMEM = "Camera OFF";
prog_char statusString_18[] PROGMEM = "Router reset";
prog_char statusString_19[] PROGMEM = "Doorbell";
prog_char statusString_20[] PROGMEM = "Water leak: KITCHEN";
prog_char statusString_21[] PROGMEM = "OK";
prog_char statusString_22[] PROGMEM = "Motion";
prog_char statusString_23[] PROGMEM = "";
prog_char statusString_24[] PROGMEM = "Water leak: WASHER";
prog_char statusString_25[] PROGMEM = "Water leak: MAIN";
prog_char statusString_26[] PROGMEM = "Smoke: KITCHEN";
prog_char statusString_27[] PROGMEM = "Smoke: HALL";
prog_char statusString_28[] PROGMEM = "Smoke: ROOM";
prog_char statusString_29[] PROGMEM = "Sensors RESET";
prog_char statusString_30[] PROGMEM = "Kitchen auto light ON";
prog_char statusString_31[] PROGMEM = "Kitchen auto light OFF";
prog_char statusString_32[] PROGMEM = "Hall auto light ON";
prog_char statusString_33[] PROGMEM = "Hall auto light OFF";
prog_char statusString_34[] PROGMEM = "Bathroom auto light ON";
prog_char statusString_35[] PROGMEM = "Bathroom auto light OFF";

// табличка указателей на строки состояния
PROGMEM const char *statusString[] = 	   
{   
  statusString_0,
  statusString_1,
  statusString_2,
  statusString_3,
  statusString_4,
  statusString_5,
  statusString_6,
  statusString_7,
  statusString_8,
  statusString_9,
  statusString_10,
  statusString_11,
  statusString_12,
  statusString_13,
  statusString_14,
  statusString_15,
  statusString_16,
  statusString_17,
  statusString_18,
  statusString_19,
  statusString_20,
  statusString_21,
  statusString_22,
  statusString_23,
  statusString_24,
  statusString_25,
  statusString_26,
  statusString_27,
  statusString_28,
  statusString_29,
  statusString_30,
  statusString_31,
  statusString_32,
  statusString_33,
  statusString_34,
  statusString_35};

char statusStringBuf[40];    // буфер для чтения строк состояния


byte currentStatus=21; // статус на старте
boolean camStatus = false; // состояние камеры
boolean iamhome = true; // инициализация кнопки Я дома
boolean water1= false; // датчики протечки. Нужно, чтобы отправить письмо только один раз, а не слать, пока датчик в воде
boolean water2= false;
boolean water3= false;
boolean smoke1= false; // датчики дыма аналогично воде
boolean smoke2= false;
boolean smoke3= false;

// автомузыка в ванной
boolean me = true; // признак разрешенной музыки
boolean mOn = false; // признак включенной музыки

// блок автосвета

boolean kitchenLight = false; // автосвет на кухне
boolean kitchenLightOn = false; // состояние света на кухне
boolean hallLight = false; // автосвет в коридоре
boolean hallLightOn = false; // состояние света в коридоре
boolean bgLight = true; // включение фонового света одновременно с верхним в коридоре

int hallTimerID; // идентификатор основного таймера света в коридоре
int firstLightID; // идентификатор первого (минутного) таймера света в коридоре
boolean firstLight = false; // признак включения первого (минутного) интервала света в коридоре

#define modemPin  5 // пин реле модема и маршрутизатора
#define camPin  6 // пин реле камеры
#define PIRPin  A0 // пин датчика движения
byte prevPIR = 0; // состояние датчика движения на старте

boolean hallLightReEnable = false; // для включения автосвета в коридоре после кнопки Я дома

boolean usB = false; // флаг таймаута датчика после срабатывания, чтобы не запускалось много таймеров для отправки уведомлений
boolean feedB = false; // флаг работающей кормушки, чтобы не реагировала на повторные нажатия пока работает
boolean mR = false; // признак рестарта модема
boolean aL = false; // признак для звукового оповещения при протечках для однократного срабатывания
boolean sR = false; // признак рестарта системы
boolean aCl= true; // признак автозакрывания кормушки

int feedID; // идентификатор таймаута на закрытие кормушки
int feedTimer = 15000; // предустановка времени поворота кормушки на один сектор
boolean turnIt = false; // признак завершенного поворота при автоповороте кормушки
boolean debug = false; // пищалка на датчик движения
boolean PIRenabled = true; // чтобы включать и выключать датчик движения дистанционно

EthernetServer server(80);                           // Server Port 80
EthernetClient mail; // на выход для Твиттера и почты
 
SimpleTimer myTime; // создаем объект таймера для проверки соединения, игнорирования RC Switch на время работы датчика, кнопки Я дома

// создаем объект RCSwitch
RCSwitch mySwitch = RCSwitch();

#define  txPin  8 // пин передатчика

byte lineDead = 0; // признаки для перезагрузки маршррутизатора
byte lineRest = 0;

// УПРАВЛЕНИЕ Livolo, создаем объект livolo
Livolo livolo(8);

// БЛОК ПОГОДЫ

boolean outData = false; // признак актуальности данных внешнего метеодатчика
boolean inData = false; // признак актуальности данных внутреннего метеодатчика

int humidityRH=0;
int tempC=0;
int tempIn=0;
int pressurehPa=0;
int weatherData = 0;
byte narodLength;
byte owmLength;

byte servernarodmon[] = { 91, 122, 49, 168 }; // narodmon.ru


// В PROGMEM строки обмена с серверами для конструирования POST-запроса

prog_char openWeatherString_0[] PROGMEM = "POST http://narodmon.ru/post.php HTTP/1.0";
prog_char openWeatherString_1[] PROGMEM = "Host: narodmon.ru";
prog_char openWeatherString_2[] PROGMEM = "Content-Type: application/x-www-form-urlencoded";
prog_char openWeatherString_3[] PROGMEM = "Content-Length: ";
prog_char openWeatherString_4[] PROGMEM = "ID=MAC00"; // 15 narodmon начало (MAC головного устройства)
prog_char openWeatherString_5[] PROGMEM = "&MAC01="; // 14 температура снаружи
prog_char openWeatherString_6[] PROGMEM = "&MAC02="; // 14 влажность
prog_char openWeatherString_7[] PROGMEM = "&MAC03="; // 14 температура внутри
prog_char openWeatherString_8[] PROGMEM = "&MAC04="; // 14 давление

// табличка указателей на строки обмена с серверами

PROGMEM const char *openWeatherString[] = 	   
{ openWeatherString_0,
  openWeatherString_1,
  openWeatherString_2,
  openWeatherString_3,
  openWeatherString_4,  
  openWeatherString_5,	
  openWeatherString_6,
  openWeatherString_7,
  openWeatherString_8};
  
  char sendDataStringBuf[47]; 



/**
 * Setup
 */
void setup() {
 pinMode(PIRPin, INPUT);
 myTime.setInterval(120000, checkLine); // запуск проверки соединения с Интернетом каждые две минуты
 myTime.setInterval(3600000, resetGood); // отправка погоды каждые 60 минут
 Ethernet.begin(mac, ip, mydns, gateway, subnet); // инициализация сети
 server.begin(); // старт веб-сервера
  mySwitch.enableTransmit(txPin); // инициализация передатчика в RC-Swich
  mySwitch.enableReceive(0);  // инициализация приемника в RC-Switch
  pinMode(camPin, OUTPUT); // инициализация реле камеры
  pinMode(modemPin, OUTPUT); // инициализация реле модема
  digitalWrite(camPin, HIGH);
  digitalWrite(modemPin, HIGH);
  pinMode(txPin, OUTPUT); //инициализация передатчика
  sR = true; // признак рестарта системы
  delay(10000); // устаканиваемся
  sendMail(15); // пишем себе письмо при каждом старте  sendMail(); 
  makeSound(); // пищалка
  delay(1000);
  makeSound();

}

// отправка погоды

void resetGood() { // раз в 60 (или по-другому) минут отправляем погоду и сбрасываем признаки актуальности

  sendWeather();          
  outData = false;
  inData = false;
  narodLength=0;
  owmLength=0;
  humidityRH=0;
  tempC=0;
  tempIn=0;
  pressurehPa=0;
  
}

void sendWeather() { // подготовка параметров для конструирования POST-запроса с погодой

if (inData == true && outData == true) {
  selectData(5, 8, 71+owmLength+narodLength);
 	
} else { if (outData == true) {
            selectData(5, 6, 43+owmLength);
          }
          if (inData == true) {
            selectData(7, 8, 43+narodLength); 
          }
	}
}


void selectData (byte startPos, byte endPos, byte lenPos) { // заголовки owm или narodmon, конечная позиция счетчика, длина фиксированных строк, отправка POST-запроса с погодой

  if (mail.connect(servernarodmon, 80)) {
  for (byte sendDataCount = 0; sendDataCount < 3; sendDataCount++) {
    strcpy_P(sendDataStringBuf, (char*)pgm_read_word(&(openWeatherString[sendDataCount]))); // читаем строку POST-запроса из PROGMEM
    mail.println(sendDataStringBuf);
    }
 strcpy_P(sendDataStringBuf, (char*)pgm_read_word(&(openWeatherString[3]))); // читаем строку PROGMEM
  mail.print(sendDataStringBuf); 
  mail.println(lenPos);
  mail.println();
 strcpy_P(sendDataStringBuf, (char*)pgm_read_word(&(openWeatherString[4]))); // читаем строку PROGMEM
  mail.print(sendDataStringBuf);  
  for (byte i=startPos; i<endPos+1; i++) {
    strcpy_P(sendDataStringBuf, (char*)pgm_read_word(&(openWeatherString[i]))); // читаем строку PROGMEM
		mail.print(sendDataStringBuf);
		switch (i) {
			case 5:
				mail.print(tempC/10.0, 1);
			break;
			case 6:
				mail.print(humidityRH/10.0, 1);
			break;
			case 7:
				mail.print(tempIn/10.0, 1);
			break;
			case 8:
				mail.print(pressurehPa);
			break;			
			}
	}
	mail.println();
  }
  mail.stop();
}

// ПИЩАЛКА

void makeSound() {
  tone(9, 900, 450);
  // delay(100);
  // tone(9, 900, 450);
}  



// автосвет

void autoLights() {
		if (kitchenLight == true) { // если автосвет на кухне разрешен
			txSwitch(136101); // включаем свет
                    if (kitchenLightOn == false) {
			if (debug == true) {myTime.setTimeout(30000, kitchenLightOff);} // для отладки 30-секундное включение света и пищалка
			else {
			myTime.setTimeout(600000, kitchenLightOff); // таймер на рабочее выключение света
			}
                    }
                    kitchenLightOn = true;
		}
        
		if (hallLight == true) { // если автосвет в коридоре разрешен

			if (hallLightOn == true)	{ // если включен "длинный свет" и случился проход	
					myTime.restartTimer(hallTimerID); // сбрасываем таймер "длинного" света для продления времени освещения еще на 3 мин
				}

			if (firstLight == true && hallLightOn == false) { // если после первого прохода случился второй
					hallLightOn = true; // выставляем признак включенного "длинного" света
					myTime.deleteTimer(firstLightID); // выключаем таймер "короткого" света
					hallTimerID=myTime.setTimeout(180000, hallLightsOff); // заводим таймер "длинного" света: если ничего не случится, свет будет выключен через 3 мин.
				}

			if (firstLight == false) { // если первый проход под датчиком
                                        mySwitch.disableReceive();
					livolo.sendButton(8500, 24);  // включаем свет
					firstLight = true; // выставляем признак прохода
					firstLightID=myTime.setTimeout(60000, hallLightsOff); // заводим таймер света на минуту: если ничего не случится, свет будет выключен через 1 мин.
                                        mySwitch.enableReceive(0);
				}
				
					
		}			
}


// выключение света в коридоре
void hallLightsOff() {
 mySwitch.disableReceive();
 livolo.sendButton(8500, 24);
 hallLightReset();
 mySwitch.enableReceive(0);
}
 
// выключение света на кухне по таймеру
void kitchenLightOff() {
 txSwitch(136100);
 kitchenLightOn = false;
}

// Отправка уведомления о срабатывании датчика движения в режиме охраны
void sendMotion(){
  sendMail(22);
  usB = false;
}

// Уведомление о переключении режима "Я дома"
void sendHome() {
  
  sendMail(17);
  
}

// сброс автосвета в коридоре в исходное состояние
void hallLightReset() {
  
  hallLightOn = false; 
  firstLight = false;
  myTime.deleteTimer(hallTimerID); 
  myTime.deleteTimer(firstLightID);

  }

 // звуковая сигнализация о протечке
void soundAlarm() {
 
 if (aL == false) {
   myTime.setTimer(10000, makeSound, 10);
   aL = true;
 }
} 
 
// процедура повтора команд на розетки - для более стабильного включения/выключения
void txSwitch(long cmd) {
  mySwitch.disableReceive();
    for (byte txC=0; txC<1; txC++) {
    mySwitch.send(cmd, 24);
  mySwitch.enableReceive(0);
    }
}

void feedAuto() { // поворот кормушки на сектор
    feedOn();
    myTime.setTimeout(feedTimer, feedOff);
}

void feedOn() { // включение кормушки

  if (feedB == false) {
    txSwitch(46151);
    feedB = true;}
}

void feedOff() { // выключение кормушки

  if (feedB == true) {
    txSwitch(46150);
    feedB = false;}
}

// закрытие кормушки
void feedClose() {
  feedAuto();
  turnIt = false;
}

// выключение света при выходе из дома и включение камеры
void swOff() {
    if (hallLight==true) {hallLight = false; hallLightReEnable=true; hallLightReset();} // если был включен автосвет в коридоре - снова включим его по возвращении домой
    livolo.sendButton(8500, 106); // Выключить все Livolo    
    delay(550);
    txSwitch(133028); // выключение розетки #1
    delay(550);
    txSwitch(136100);  // выключение розетки #2
    delay(550);
    txSwitch(130740);  // выключение розетки #3
    delay(550);
    txSwitch(131588);  // выключение розетки #4
    delay(550);
    txSwitch(1386100); // выключение музыки
    mySwitch.disableReceive();
    digitalWrite(camPin, LOW); // включение камеры
    camStatus = true;
    delay(550);
    livolo.sendButton(8500, 106); // Выключить все Livolo
    sendMail(16); // письмо Не дома
    usB = false;
    mySwitch.enableReceive(0);
}

// свет в гардеробе
void wardrobeOn() {
  livolo.sendButton(8500, 80);
  mySwitch.enableReceive(0);
}  

// свет в ванной
void bathroomOn() {
  livolo.sendButton(8500, 108); // переключаем свет
  if (me == true) { // если музыка разрешене
    if (mOn == false) { // если музыка выключена
      mOn=true; // установка признака включенной музыки
      txSwitch(1386101);} // включение музыки
    else {txSwitch(1386100); // иначе выключаем музыку
          mOn=false;} // снимаем признак включенной музыки
  }
  mySwitch.enableReceive(0); // включение RC Switch теперь в главном цикле
}

/**
 * Loop
 */
void loop() {
if (lineRest > 30) {lineDead = 0; lineRest = 0;} // обнуляем счетчики количества безуспешных попыток соединения и таймаута ожидания соединения

myTime.run(); // старт и проверка таймеров SimpleTimer
  
    char* command = httpServer(); // запуск вебсервера

if (PIRenabled == true) { // если включен датчик движения
	if (prevPIR == 0 && analogRead(PIRPin) > 500) { // если сработал датчик - переход из 0 в 1
		prevPIR=1; // сохраняем признак для переключения
		if (debug == true) {makeSound();} // пищалка для отладки по срабатыванию датчика
		if (iamhome == false) { //  если никого нет дома
			if (usB == false) { // если не установлена отсрочка срабатывания, чтобы исключить блокировку при отправке почты по возвращении домой
				usB = true;
				myTime.setTimeout(35000, sendMotion);
			}
		} else { // если мы дома
			if (hallLight == true || kitchenLight == true) { // если разрешен автосвет
				autoLights(); // запускаем процедуру автосвета
			} 
 
  
		}
    } else if (prevPIR == 1 && analogRead(PIRPin) == 0) {
		prevPIR = 0; // переключение признака срабатывания датчика
		}
}

// и поехали по кругу
// СЕКЦИЯ обработки сигналов беспроводных датчиков

  if (mySwitch.available()) { // проверяем датчики
    int value = mySwitch.getReceivedValue();
    if (value != 0) {
      switch (mySwitch.getReceivedValue()) {
 	  
      case 1418288: // Water leak KITCHEN протечка на кухне
      if (water1 == false) { // флаг для уведомлений только в том случае, если событие происходит в первый раз
        sendMail(20); // уведомление по почте
        soundAlarm(); // звуковой сигнал
        water1 = true;} // флаг произошедшего события
      break;
    
	case 3470133: // Water leak WASHER протечка под стиральной машиной
      if (water2 == false) {
        sendMail(24);
        soundAlarm(); 
        water2 = true;}
      break;

      case 3592421: // Water leak MAIN протечка в сантехкоробе
      if (water3 == false) {
        sendMail(25);
        soundAlarm();
        water3 = true;}
      break;

      case 1084887: // Smoke KITCHEN задымление на кухне
      if (smoke1 == false) {
        sendMail(26);
        smoke1 = true;}
      break;

      case 2184536: // Smoke HALL задымление в прихожей
      if (smoke2 == false) {
        sendMail(27);
        smoke2 = true;}
      break;

      case 12602757: // Smoke ROOM задымление в комнате
      if (smoke3 == false) {
        sendMail(28);
        smoke3 = true;}
      break;

      case 3395440: // Doorbell ringing звонок в дверь
      sendMail(19);
      break;

      case  15421849: // беспроводной датчик движения на кухне - автосвет (альтернативный вариант)
      if (PIRenabled == false) {autoLights();} // использовать беспроводной датчик на кухне только если выключен проводной у контроллера
      break;      

      case 15741424: // кнопка Кормушка
      mySwitch.disableReceive();
//      tone(9, 400, 450);
	  if (feedB == false) { // если кормушка не вращается
              tone(9, 400, 450);
		if (aCl == true) { // если включено автозакрытие
			if (turnIt == true) { // кнопка уже была нажата
				myTime.deleteTimer(feedID); // отключение таймера
				feedAuto(); // поворот сектора
				turnIt = false; // сброс признака повторного нажатия кнопки
			} else { // если кнопка нажата в первый раз
				feedAuto(); // поворот сектора
				feedID=myTime.setTimeout(150000+feedTimer, feedClose); // таймер на второй поворот
				turnIt = true; // установка признака повторного нажатия
				}
		} else {
			feedAuto();
			}
	  }
      mySwitch.enableReceive(0);
      break;
      
      // свет в гардеробе
    case 6041743: // автосвет в гардеробе - датчик
        mySwitch.disableReceive();
	myTime.setTimeout(1700, wardrobeOn); // сбрасываем флаг занятости после срабатывания датчика
      break;  

     case 1548296: // автосвет в ванной - датчик (альтернативный вариант)
          mySwitch.disableReceive(); // выключаем RC Switch, чтобы исключить повторные срабатывания и вообще, чтобы ничего не мешало
          // if (bathLight == true) {
          myTime.setTimeout(1700, bathroomOn); // включаем свет после окончания работы датчика
          // }
      break;        

	  case 5391052: // Нажата кнопка Я дома 
                        mySwitch.disableReceive();
			if (iamhome == false) {
				iamhome = true;
				makeSound();
				livolo.sendButton(8500, 106); // Выключаем все Livolo, чтобы не выключить свет в коридоре вместо включения
				digitalWrite(camPin, HIGH); // выключаем камеру
				camStatus = false;
				if (bgLight == true) { // включаем фоновый свет, если нужно
					txSwitch(133029); // фоновый свет в комнате
					// delay(350);
					txSwitch(136101); // фоновый свет на кухне
				}
				// delay(350);    
				txSwitch(131589); // планшет и звук
				if (hallLightReEnable == true) {
					hallLight = true;
					hallLightReEnable = false; // сбрасываем признак включения автосвета после кнопки я дома
				} else
					{livolo.sendButton(8500, 24);} // если автосвет был включен - включаем его снова; если нет - просто включаем свет в коридоре
				myTime.setTimeout(15000, sendHome); // письмо Я дома
				delay(1000); // на случай удержания кнопки
                                mySwitch.enableReceive(0);
		} else {
		iamhome = false;
  		makeSound();
                delay(1500); // на случай удержания кнопки
		myTime.setTimeout(60000, swOff);
		}
          
	   break;
    }      

	// получение метеоданных
	
if (mySwitch.getReceivedValue()/100000 == 161) {
    
    weatherData = mySwitch.getReceivedValue() - 16100000;
	if (weatherData > 10000) { // пришла влажность
		humidityRH = (weatherData - 10000);
		}
	else { // пришла температура
			if (weatherData > 1000) { // минусовая температура
				tempC = -(weatherData - 1000);
				owmLength=9;
				}
			else { // плюсовая температура
					tempC = weatherData;
					owmLength=8;
				  }
		}
}

  if (mySwitch.getReceivedValue()/100000 == 121) {
    pressurehPa = (mySwitch.getReceivedValue() - 12100000)/1.33; // пришло давление
	}

	if (mySwitch.getReceivedValue()/100000 == 131) {
    weatherData = mySwitch.getReceivedValue() - 13100000;
		if (weatherData > 1000) { // минусовая температура
			tempIn = -(weatherData - 1000);
			narodLength=8;
			}
		else { // плюсовая температура
				tempIn = weatherData;
				narodLength=7;				
			  }
	}

if (tempIn!=0 && pressurehPa!=0) {inData = true;}
if (tempC!=0 && humidityRH!=0) {outData = true;}


   } 
      mySwitch.resetAvailable();
  //    mySwitch.enableReceive(0); // включение RC Switch
  }  

}

/**
 * СЕКЦИЯ обработки команд веб-интерфейса
 */
void processCommand(char* command) {
  if        (strcmp(command, "Switch1-on") == 0) { // первая розетка
    txSwitch(133029);
    currentStatus=0;
  } else if (strcmp(command, "Switch1-off") == 0) {
    txSwitch(133028);
    currentStatus=4;
  } else if (strcmp(command, "Switch2-on") == 0) { // вторая розетка
    txSwitch(136101);
    currentStatus=1;
  } else if (strcmp(command, "Switch2-off") == 0) {
    txSwitch(136100);
    currentStatus=5;
  } else if (strcmp(command, "Switch3-on") == 0) { // третья розетка
    txSwitch(130741);
    currentStatus=2;
  } else if (strcmp(command, "Switch3-off") == 0) {
    txSwitch(130740);
    currentStatus=6;
  } else if (strcmp(command, "Switch4-on") == 0) { // четвертая розетка
    txSwitch(131589);
    currentStatus=3;
  } else if (strcmp(command, "Switch4-off") == 0) {
    txSwitch(131588);
    currentStatus=7;
  } else if (strcmp(command, "router") == 0) {
    modRestart();
  } else if (strcmp(command, "webCamOn") == 0) {
    if (!camStatus) {
	digitalWrite(camPin, LOW); // включение камеры, только если выключена. По умолчанию реле в режиме NO (камера всегда выключена)
	camStatus = true;
    currentStatus=16;
	sendMail(currentStatus);
	}
  } else if (strcmp(command, "webCamOff") == 0) {
    if (camStatus) {
	digitalWrite(camPin, HIGH); // выключение камеры, только если включена
	camStatus = false;
    currentStatus=17;
    sendMail(currentStatus);
	}
  } else if (strcmp(command, "Livolo1") == 0) {
    livolo.sendButton(8500, 0); // Livolo кнопка 1 комната
    currentStatus=8;
  } else if (strcmp(command, "Livolo2") == 0) {
    livolo.sendButton(8500, 96); // Livolo 2 комната
    currentStatus=9;
  } else if (strcmp(command, "Livolo4") == 0) {
    livolo.sendButton(8500, 24); // Livolo 4 прихожая
    currentStatus=10;
  } else if (strcmp(command, "Livolo5") == 0) {
    livolo.sendButton(8500, 80); // Livolo 5 гардероб
    currentStatus=11;
  } else if (strcmp(command, "Livolo7") == 0) {
    livolo.sendButton(8500, 108); // Livolo 7 ванная
    currentStatus=12;
  } else if (strcmp(command, "Livolo3") == 0) {
    livolo.sendButton(8500, 120); // Livolo 3 кухня
    currentStatus=13;
  } else if (strcmp(command, "Livolo11") == 0) {
    livolo.sendButton(8500, 106); // Выключить все Livolo
    currentStatus=14;
  } else if (strcmp(command, "resetSensors") == 0) {
    water1=false; // сброс признаков датчиков
    water2=false; // сброс признаков датчиков
    water3=false; // сброс признаков датчиков
    smoke1=false; // сброс признаков датчиков
    smoke2=false; // сброс признаков датчиков
    smoke3=false; // сброс признаков датчиков    
    aL=false;
    currentStatus=29;
  } else if (strcmp(command, "autoLightKitchenOn") == 0) {
    kitchenLight = true; kitchenLightOn = false; txSwitch(136100);// включено автоматическое управление фоновым светом на кухне
    currentStatus=30;
  } else if (strcmp(command, "autoLightKitchenOff") == 0) {
    kitchenLight = false; txSwitch(136100);// выключено автоматическое управление фоновым светом на кухне
    currentStatus=31;
  } else if (strcmp(command, "autoLightHallOn") == 0) {
    hallLight = true; hallLightReset(); // включено автоматическое управление фоновым светом в коридоре
    currentStatus=32;
  } else if (strcmp(command, "autoLightHallOff") == 0) {
    hallLight = false; // выключено автоматическое управление фоновым светом в коридоре
    currentStatus=33;
  } else if (strcmp(command, "autoBathLightOn") == 0) { // автосвет в ванной
    txSwitch(1486221);
  } else if (strcmp(command, "autoBathLightOff") == 0) {
    txSwitch(1486220);
  } else if (strcmp(command, "autoMusicOn") == 0) { // автомузыка в ванной
    txSwitch(1486231);
  } else if (strcmp(command, "autoMusicOff") == 0) {
    txSwitch(1486230);
  } /* else if (strcmp(command, "blight") == 0) {
    bathLight =!bathLight; // включено автоматическое управление светом в ванной
    if (bathLight == true) {currentStatus=34;} else {currentStatus=35;}
  } */ else if (strcmp(command, "LGTV-on") == 0) { // блок команд для медиаконтроллера (управление ТВ, плеером и кондиционером)
    txSwitch(88100);
  } else if (strcmp(command, "LGTV-back") == 0) {
    txSwitch(88116);
  }  else if (strcmp(command, "LGTV-volup") == 0) {
    txSwitch(88132);
  }  else if (strcmp(command, "LGTV-voldown") == 0) {
    txSwitch(88164);
  }  else if (strcmp(command, "LGTV-chup") == 0) {
    txSwitch(88128);
  }  else if (strcmp(command, "LGTV-chdown") == 0) {
    txSwitch(88256);
  }  else if (strcmp(command, "LGTV-mute") == 0) {
    txSwitch(88512);
  }  else if (strcmp(command, "LGTV-menu") == 0) {
    txSwitch(89100);
  }  else if (strcmp(command, "LGTV-ok") == 0) {
    txSwitch(89108);
  }  else if (strcmp(command, "LGTV-up") == 0) {
    txSwitch(89116);
  }   else if (strcmp(command, "LGTV-down") == 0) {
    txSwitch(89132);
  }  else if (strcmp(command, "LGTV-left") == 0) {
    txSwitch(89164);
  }  else if (strcmp(command, "LGTV-right") == 0) {
    txSwitch(89128);
  }  else if (strcmp(command, "LGTV-av") == 0) {
    txSwitch(89256);
  }  else if (strcmp(command, "GMiniMedia-on") == 0) {
    txSwitch(89512);
  }  else if (strcmp(command, "GMiniMedia-off") == 0) {
    txSwitch(87108);
  }  else if (strcmp(command, "GMiniMedia-play") == 0) {
    txSwitch(87116);
  }  else if (strcmp(command, "GMiniMedia-back") == 0) {
    txSwitch(87132);
  }  else if (strcmp(command, "GMiniMedia-volup") == 0) {
    txSwitch(87164);
  }  else if (strcmp(command, "GMiniMedia-voldown") == 0) {
    txSwitch(87128);
  }  else if (strcmp(command, "GMiniMedia-ff") == 0) {
    txSwitch(87256);
  }  else if (strcmp(command, "GMiniMedia-rew") == 0) {
    txSwitch(87512);
  }  else if (strcmp(command, "GMiniMedia-menu") == 0) {
    txSwitch(92108);
  }  else if (strcmp(command, "GMiniMedia-set") == 0) {
    txSwitch(92116);
  }  else if (strcmp(command, "GMiniMedia-up") == 0) {
    txSwitch(92132);
  }  else if (strcmp(command, "GMiniMedia-down") == 0) {
    txSwitch(92164);
  }  else if (strcmp(command, "GMiniMedia-left") == 0) {
    txSwitch(92128);
  }  else if (strcmp(command, "GMiniMedia-right") == 0) {
    txSwitch(92256);
  }  else if (strcmp(command, "GMiniMedia-ok") == 0) {
    txSwitch(92512);
  }  else if (strcmp(command, "LGAC-cool") == 0) {
    txSwitch(95108);
  }  else if (strcmp(command, "LGAC-heat") == 0) {
    txSwitch(95116);
  }  else if (strcmp(command, "LGAC-on") == 0) {
    txSwitch(95132);
  }  else if (strcmp(command, "LGAC-off") == 0) {
    txSwitch(95164);
  }  else if (strcmp(command, "catFeedl") == 0) { // коррекция таймера поворота кормушки +0.5 сек 
      feedTimer=feedTimer+500;
  }  else if (strcmp(command, "catFeeds") == 0) { // коррекция таймера поворота кормушки  -0.5 сек
      feedTimer=feedTimer-500;
  }  else if (strcmp(command, "catFeedrst") == 0) {
      feedTimer=15000; feedB=false; turnIt = false; // сброс таймера поворота кормушки в исходное значение
  }   else if (strcmp(command, "musicEnabled") == 0) { // переключение разрешения и запрета музыки в ванной
       me=!me;
    if (me == false) { // при отключении флага разрешенной музыки одновременно выключаем музыку
      txSwitch(1386100);
      mOn=false;}
  }  else if (strcmp(command, "catFeed") == 0) {
      feedOn(); // включаем кормушку
  }  else if (strcmp(command, "catFeedstop") == 0) {
    feedOff(); // выключаем кормушку
  }  else if (strcmp(command, "catFeedAuto") == 0) { // поворот кормушки на сектор
    feedAuto();
  }  else if (strcmp(command, "catFeedCloseOn") == 0) { // включение функции автоповорота по таймеру
      aCl=true;
	  turnIt = false;
  }   else if (strcmp(command, "catFeedCloseOff") == 0) { // выключение функции автоповорота по таймеру
      aCl=false;
      turnIt = false;
  }   else if (strcmp(command, "debugPIR") == 0) { // режим отладки для датчика движения и автосвета на кухне
    debug=!debug;
  }   else if (strcmp(command, "enablePIR") == 0) { // включение датчика движения
    PIRenabled=true;
  }   else if (strcmp(command, "disablePIR") == 0) { // отключение датчика движения
    PIRenabled=false;
  }   else if (strcmp(command, "backLightOff") == 0) { // запрещение фонового света
    bgLight=false;
  }   else if (strcmp(command, "backLightOn") == 0) { // разрешение фонового света
    bgLight=true;
  } /*   else if (strcmp(command, "rst") == 0) {
    resetFunc(); // сброс
  }*/
}

// ВЕБ-сервер

void httpResponseHome(EthernetClient c) {
  c.println("HTTP/1.1 200 OK");
  c.println("Content-Type: text/html");
  c.println();
  c.println("<html>");
  c.println("<head>");
  strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[currentStatus])));
    c.println(statusStringBuf);
    c.print(";");    
    c.print(tempC/10.0, 1);
    c.print(";");    
    c.print(humidityRH/10.0, 1);
    c.print(";");        
    c.print(pressurehPa);
    c.print(";");        
    c.println(tempIn/10.0, 1);    
  c.println("</body>");
  c.println("</html>");
}

/**
 * HTTP Redirect to homepage
 */
void httpResponseRedirect(EthernetClient c) {
  c.println("HTTP/1.1 301 Found");
  c.println("Location: /");
  c.println();
}

/**
 * Process HTTP requests, parse first request header line and 
 * call processCommand with GET query string (everything after
 * the ? question mark in the URL).
 */
char*  httpServer() {
  EthernetClient client = server.available();
  if (client) {
    char sReturnCommand[32];
    int nCommandPos=-1;
    sReturnCommand[0] = '\0';
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if ((c == '\n') || (c == ' ' && nCommandPos>-1)) {
          sReturnCommand[nCommandPos] = '\0';
          if (strcmp(sReturnCommand, "\0") == 0) {
              httpResponseHome(client);
          } else {
            processCommand(sReturnCommand);
            httpResponseRedirect(client);
          }
          break;
        }
        if (nCommandPos>-1) {
          sReturnCommand[nCommandPos++] = c;
        }
        if (c == '?' && nCommandPos == -1) {
          nCommandPos = 0;
        }
      }
      if (nCommandPos > 30) {
//        httpResponse414(client);
        sReturnCommand[0] = '\0';
        break;
      }
    }
    if (nCommandPos!=-1) {
      sReturnCommand[nCommandPos] = '\0';
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
    
    return sReturnCommand;
  }
  return '\0';
}

// перезагрузка маршрутизатора

void modRestart() {
  
digitalWrite(modemPin, LOW); // рестарт модема. По умолчанию реле в режиме NC (модем всегда включен), на время рестарта - переключение
    delay(10000);
    digitalWrite(modemPin, HIGH);  
    mR = true; // признак рестарта для уведомления по почте
}

// проверка интернета

void checkLine(){
  if (lineDead < 2) {
    if (mail.connect(servergoogle, 80)) {
	// связь есть, закрываем сеанс
	mail.stop();
         if (mR == true) { // чтобы не задерживать работу для отправки почты после рестарта. При рестарте ставим признак, здесь - почта, признак сбрасываем
           sendMail(18);
           mR = false;
           }
         if (sR == true) { // чтобы не задерживать работу для отправки почты после рестарта. При рестарте ставим признак, здесь - почта, признак сбрасываем
           sendMail(15);
           sR = false;
           }

	} else {
		// связи нет, рестарт
                modRestart();
                lineDead = lineDead + 1; // увеличиваем счетчки неуспешных попыток соединения
	}
  } // linedead if
   else { lineRest = lineRest + 1; } // каждый цикл - минута таймаута без проверки соединения

}

// отправка почты

void sendMail(byte statusStringN){
  
    if (mail.connect(servergoogle, 80)) {
    mail.stop();
        strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[statusStringN]))); // читаем строку состояния из PROGMEM
//                twitter.post(statusStringBuf); // в твиттер
 		mail.connect(servermail, 25);// идем на smtp.mail.ru		
		mail.println("EHLO ArduinoMail"); // привет 
                delay(1500); // wait for a response	
		mail.println("AUTH LOGIN"); // старт авторизации
                delay(1500); // wait for a response		
		mail.println("ВАШ логин в BASE64"); // логин
                delay(1500); // wait for a response                delay(wait); // wait for a response		
		mail.println("ВАШ пароль в BASE64"); // пароль
                delay(1500); // wait for a response        
		mail.println("MAIL From: адрес присвоенный контроллеру @mail.ru"); // identify sender, this should be the same as the smtp servergoogle you are using
                delay(1500); // wait for a response
                mail.println("RCPT To: адрес@получателя"); // identify recipient
                delay(1500); // wait for a response
                mail.println("DATA"); // identify recipient 
                delay(1500); // wait for a response
                mail.println("To: адрес@получателя"); // identify recipient
                mail.print("Subject: "); // insert subject
                mail.println(statusStringBuf);
                mail.println(statusStringBuf); // insert body
//              client.println(portmap.externalIp());
                mail.println("."); // end email
                mail.println("QUIT"); // terminate connection 
                delay(1500); // wait for a response
                mail.println();
                mail.stop();  
              } else {mail.stop();}
}


Tags:
Hubs:
+72
Comments 46
Comments Comments 46

Articles