Хочу поделиться опытом использования системы для умного дома NooLite совместно с Raspberry Pi Model B (далее RPI) в двухкомнатной квартире.
О системе NooLite неоднократно писали на хабре:
В данной статье я расскажу:
Возможно, кто-то подумает: «я бы не стал заморачиваться с RPI ради такого простого функционала — управлять освещением в квартире с мобильного телефона, а реализовал бы всеиспользуя пластиковый стаканчик, камешек и ниточку на Arduino с Wi-Fi шилдом или более простой и лаконичной реализации на AVR микроконтроллере».
Но, поскольку уже пылился на полке RPI и одолевало желание сделать «умное» освещение с использованием платформы RPI, решил взять за основу именно его. Также, возможность использования RPI дает хороший запас для расширения функционала «умного дома».
Некоторое время приглядывался к различным платформам и технологиям умного дома, в итоге выбор пал на систему NooLite, исходя из следующего:
Обратил внимание и на платформу для домашней автоматизации Wiren Board на базе ARM9 с богатыми функциональными возможностями под все мыслимые и немыслимые нужды. Особенно порадовала киллер-фича — радиомодуль на 433 МГц, что в моем случае подходит для управления силовыми блоками NooLite. Но поскольку у меня уже есть RPI, будем отталкиваться от этого.
Для реализации «умного» освещения в квартире мне понадобилось решить следующие задачи:
Пункты 1 и 4—6 могут повторяться в цикле.
Цена вопроса:
Итого: ~27 000 ₽ + контроллер от 3 000 ₽.
Планировка угловой двухкомнатной р��спашонки П44Т с эркером приведена ниже.

Планируется управлять освещением с помощью NooLite во всех помещениях кроме лоджии, ванной и туалета.
2 точки освещения с релейным режимом (включение/выключение).
Одна не очень яркая точка освещения должна управляться датчиком движения PM111. Это позволит в темное время суток освещать путь ночному скитальцу в поисках огней ночного голода.

Обе точки освещения должны управляться стационарным выключателем расположенным рядом с входной дверью. Выключатель должен предусматривать сценарий «выключить свет во всей квартире».
Тепловой датчик движения переходит в дежурный режим (начинает срабатывать на движение тепловых объектов) при установке необходимого уровня освещенности помещения и чувствительности. Обычно в светлое время суток нет необходимости мигать светом в коридоре чтобы пройти.
Из инструкции к датчику:
Свет в туалете и ванной управляется классическим стационарным выключателем. Свет и вентилятор в туалете подключены через реле времени F&F PO-415 (тоже белорусы) на DIN рейке.

Стационарный коридорный выключатель должен быть кнопочного типа, поскольку вызов сценария предполагает нажатие кнопки (сценарий не имеет состояний ВКЛ/ВЫКЛ).
Также кнопка необходима если нам понадобиться выключить уже включенный датчиком движения свет, когда таймер датчика настроен не выключать свет несколько минут после срабатывания. Клавиша в этом случае не подходит.
Итого по коридору:

Три точки освещения:
Точки освещения управляются 3-х кнопочным выключателем (пульт PK311), расположенным у входа на кухню.

Две точки освещения в релейном режиме (SU111-200):
Каждая точка освещения управляется 2-мя выключателями:

Возможно, кто-то спросит «почему на балконе не поставить 2-х клавишный выключатель как и в гостиной?» или «почему не поставить во всей квартире кнопки?».
Так уж вышло, сначала предполагалось на балконе установить 3-х кнопочный выключатель с одной сценарной кнопкой, но потом передумали при выборе дизайна самих выключателей. Также предполагалось, что в коридоре и на кухне будут кнопки, а в гостиной комнате, спальне и на балконе — клавишные выключатели, чтобы сохранить дизайн.
Позже захотелось диммировать свет выключателем у кровати, поэтому все пошло наперекосяк. Чтобы сохранить дизайн выключателей, пришлось объездить множество строительных рынков, посетить дюжину интернет сайтов, об этом далее в разделе «найти и купить подходящие выключатели для пультов».
Секция 1 (кровать)
Обычно силовые блоки располагаются в каждой точке освещения. Одна точка освещения — один силовой блок. В моем сценарии освещения есть одно отступление от традиционной логики. В спальне «секция 1» предполагается установить светильник с двумя силовыми блоками, один из которых будет диммироваться.
Секция 2 (эркер)
Помимо стационарных выключателей все точки освещения должны управляться с карманного брелка в качестве резервного пульта и RPI. Для брелка:

Скажу сразу, с интеграцией все просто. У NooLite есть модуль MT1132. Модуль получает управляющий пакет по UART от некоторого контроллера, в моем случае это RPI, передает команду по радиоканалу на исполнительные устройства (силовые блоки) и отвечает стройкой «OK\r\n» все описано в инструкции к модулю.

Поскольку UART линии RPI работают с TTL уровнями 3,3 В, значит будем использовать 3,3 В в качестве U питания модуля.

Можно использовать три линии на MT1132: VCC, GND и RX, для передачи управляющего пакета. Я так и делал при отладке — не читал ответ «OK\r\n» от MT1132, мне достаточно было наблюдать за индикатором привязываемого силового блока. При успешном принятии команды «привязки» на силовом блоке инициируется частое мерцание встро��нного светодиода.
Еще один момент, мощность радиопередатчика при U питания от 3,3 В до 3,3 мВт, при U питания от 5 В до 5 мВт. Максимальное расстояние до силового блока 70 м.
Управляющий пакет состоит из 12 байт:
ST, B0, B1, B2, B3, B4, B5, B6, B7, B8, CS, SP
ST — стартовый байт, всегда равен 85
B0..B8 — payload (управляющие команды)
CS — контрольная сумма. Младший байт от суммы первых 10 байт (с ST по B8)
SP — стоповый байт
По инструкции выписал необходимые мне управляющие байты:
B1 — управляющая команда со значениями:
B4 — адрес канала (от 0 до 31). Всего 32 канала.
остальные байты по умолчанию за исключением контрольной суммы.
B0 — настройка режима передачи модуля.
Если в B0 передается значение 80 (0x50) значит:

В итоге составил вот такую таблицу с необходимыми мне управляющими пакетами.
Было решено установить слаботочку в электрощит внутреннего монтажа:

Prerequisites:
Подготавливая образ, словил прикольную хардварную багу на Macbook Air, во время записи образа Raspbian-wheezy под рутом (хотя по мне это фича — дополнительная защита от записи, лишний раз подумаешь, прежде чем перезапишешь данные):
Консоль вернула:
Cо снятым lock на SD карте, под рутом и правильными модами на /dev/disk2….
Потом нашел трэд по этой теме, кому-то помогало дуть в слот SD на маке, кому-то слотоприкладство. Бить макбук в наше время расточительство, и мне в итоге помог небольшой зазор: если SD карту вставить не до конца, все сработает.
Во время отладки пришлось колхозить, поскольку не было подходящих мама-мама коннекторов.

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

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

Настройка WebIOPi минимальная
Воспользовавшись инструкциями предоставленными на сайте проекта:

Установка джампера для тестирования UART петли.
Используя ранее составленную таблицу с управляющими пакетами, напишем простенький Python-скрипт управления модулем из RPI SHELL.
Для управления MT1132 из консоли shell выполняем следующие команды
Прикручиваем REST API
Фреймворк WebIOPi предусматривает управление через HTTP запросы в стиле REST, что облегчает прикручивание мобильных клиентов и упрощает взаимодействие «клиент-сервер».
Копипаст возможностей REST API фреймворка по ссылке выше:
Наш кейс «Call a macro on the server». Описание нежирное, но достаточное для эксперимента:
HTTP POST /macros/(macro)/(args)
Поскольку (macro) еще не подготовлен проверим REST «Get full GPIO state/configuration», а для этого нужно сделать запрос HTTP GET /*.
Открываем любой REST API Client, я использовал DHC клиент для Chrome браузера. Пробуем выполнить REST запрос получения текущего времени.

Конфигурация WebIOPi (/etc/webiopi/config)

В разделе [DEVICES]:
Добавляем устройство serial (UART GPIO) — это и есть наш модуль MT1132, где
ttyAMA0 — это девайс (порт), который видит ядро Raspbian,
baudrate — это скорость в бодах обмена информации через этот UART интерфейс.
9600 бод / (8 + 1 старт бит + 1 стоп бит) = 960 байт/с.
В разделе [SCRIPTS] (custom scripts):
Добавляем строку myscrypt = /home/pi/smarthome/python/mt1132.py — для подключения нашего скрипта к фреймворку.
В разделе [REST] (настройки управления GPIO через REST API. Опционально):
gpio-post-value = false — запрещаем изменение логических уровней LOW/HIGH на пинах GPIO через REST запросы;
gpio-post-function = false — запрещаем изменение настройки IN/OUT на пинах GPIO через REST запросы.
Остальные настройки оставляем без изменений.
[COAP]:
Не стал трогать, отключать. Пока представления не имею с чем его едят и где он применяется. Зафиксировал только в голове вот эту строку «СoAP — is a specialized web transfer protocol for use with constrained nodes and constrained networks in the Internet of Things, designed for machine-to-machine (M2M) applications such as smart energy and building automation».
Может на Хабре кто-нибудь раскроет полезные кейсы использования.
Отправляем REST запрос «привязки адреса 3-го канала NooLite MT1132 к силовому блоку, на котором инициирована привязка» через DHC.
При успешной отправке управляющего пакета модуль MT1132 ответит по UART TX в GPIO UART RX «OK». Силовой блок в случае успеха запомнит адрес канала и замигает интенсивно встроенным зеленым светодиодом.

Изначально я не планировал писать приложение под Андройд, в силу того что представления не имел, предполагал ограничиться браузером. Потом случайно забрел на канал Start Android. Автор, Дмитрий, подробно рассказывает вместе с Андрюхой, как быстро стартануть свой первый проект под Андройд платформы. Огромное спасибо автору за проект и вложенный труд!
Посмотрев и выполнив не более двадцати уроков, я приступил к созданию своего простого приложения для управления освещением. Для разработки использовал IDE Android Studio, как подсказывает Гугл — based on IntelliJ IDEA.
Интерфейс
Получился очень аскетичный интерфейс:

К кнопкам привязаны методы onClick.
Switch определяет ID view элемента кнопки, выводит TOAST сообщение на экран и делает HTTP запрос на RPI через HttpURLConnection внутри AsyncTask (вызов ParseTask().execute();).
Взаимодействие с backend
HttpURLConnection — класс для взаимодействия по HTTP протоколу.
Метод doInBackground класса AsyncTask — выполняет тяжелые задачи в отдельном бэкграунд-потоке и возвращает результат обратно в UI поток.
HttpURLConnection выполняется в методе doInBackground.
Такой прием часто встречается в интернете для реализации обмена данными через HTTP протокол.
Подставляет константу из файла strings.xml в ресурсах проекта
Таким образом полный URL для включения канала 4 выглядит так
Исходя из сценария освещения, в данном проекте используются стационарные встраиваемые пульты/выключатели NooLite клавишные и кнопочные, нужно предусмотреть возможность установки выключателей с тремя модулями (кнопками/клавишами), сохранив дизайн выключателей (все выключатели должны выглядеть одинаково).
При выборе выключателей предусмотреть возможность:
Под данные условия подошли выключатели bticino livinglight. На фото ниже собранные модули с пультом NooLite.


Силовой блок на потолке во время отладки

Отладка на месте

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

Спасибо за внимание. Хорошего дня!
О системе NooLite неоднократно писали на хабре:
- NooLite — система радиоуправления освещением, или первый шаг к умному дому
- NooLite-2, или умный дом для чайников
- поискать на Хабре по тегу «noolite» или набрать в поисковой строке гугла «site:habrahabr.ru noolite»
В данной статье я расскажу:
- об установке и настройке WebIOPi на предустановленную Raspbian в контексте использования UART для передачи команд на модуль MT1132 NooLite
- о макросах в фреймворке WebIOPi для связывания запросов в стиле HTTP REST с GPIO UART
- о написании простого клиента на платформе Android для управления светом через REST дырки
Функционал
- бекенд (обертка управления): RPI + WebIOPi (UART+REST) + NooLite модуль
- исполняющая часть: силовые блоки NooLite с нагрузкой — освещение в квартире
- фронтенд (клиентская часть, управление): мобильный клиент Android, WEB-интерфейс, консоль bash shell (python script)
Возможно, кто-то подумает: «я бы не стал заморачиваться с RPI ради такого простого функционала — управлять освещением в квартире с мобильного телефона, а реализовал бы все
Но, поскольку уже пылился на полке RPI и одолевало желание сделать «умное» освещение с использованием платформы RPI, решил взять за основу именно его. Также, возможность использования RPI дает хороший запас для расширения функционала «умного дома».
Входные данные
- 2-х комнатная квартира П44Т без ремонта в новостройке
- RPI (был приобретен два года назад интереса ради)
- желание реализовать «умное» освещение
Некоторое время приглядывался к различным платформам и технологиям умного дома, в итоге выбор пал на систему NooLite, исходя из следующего:
- адекватная цена по сравнению с аналогичными системами (Z-Wave, EnOcean, ZigBee)
- доверяю товарам, произведенным в Белорусии
- открытый протокол
- множество примеров использования в интернете
- широко распространенная и ратифицированная ГКРЧ частота 433 МГц
Обратил внимание и на платформу для домашней автоматизации Wiren Board на базе ARM9 с богатыми функциональными возможностями под все мыслимые и немыслимые нужды. Особенно порадовала киллер-фича — радиомодуль на 433 МГц, что в моем случае подходит для управления силовыми блоками NooLite. Но поскольку у меня уже есть RPI, будем отталкиваться от этого.
Для реализации «умного» освещения в квартире мне понадобилось решить следующие задачи:
- определить оптимальный сценарий освещения в двухкомнатной квартире
- определить метод интеграции с RPI и размещения слаботочки
- выбрать обертку для управления
- составить список покупок
- согласовать вышеперечисленное с женой
- купить необходимые компоненты
- привязать пульты и отладить интеграцию RPI > NooLite
- найти и купить подходящие выключатели для пультов
- разместить силовые блоки и выключатели
- разработать мобильное приложение под Android
Пункты 1 и 4—6 могут повторяться в цикле.
Цена вопроса:
- RPI [1 шт]
- Модуль NooLite MT1132 [1 140 ₽ X 1 шт] 1 140 ₽
- Силовые блоки NL [1 240 ₽ Х 11 шт] 13 640 ₽
- Пульты управления стационарные NL [1 340 ₽ Х 7 шт] 9 380 ₽
- Пульт управления брелок NL [1 550 ₽ Х 1 шт] 1 550 ₽
- Датчик движения NL [1 550 ₽ Х 1 шт] 1 550 ₽
- Монтажные коробки, саппорты, клавишные/кнопочные модули (выключатели), рамки (приобретается индивидуально)
- друзья с 3D принтером — бесплатно
Итого: ~27 000 ₽ + контроллер от 3 000 ₽.
Сценарий освещения в квартире
Планировка угловой двухкомнатной р��спашонки П44Т с эркером приведена ниже.

Планируется управлять освещением с помощью NooLite во всех помещениях кроме лоджии, ванной и туалета.
Коридор
2 точки освещения с релейным режимом (включение/выключение).
Одна не очень яркая точка освещения должна управляться датчиком движения PM111. Это позволит в темное время суток освещать путь ночному скитальцу в поисках огней ночного голода.

Обе точки освещения должны управляться стационарным выключателем расположенным рядом с входной дверью. Выключатель должен предусматривать сценарий «выключить свет во всей квартире».
Тепловой датчик движения переходит в дежурный режим (начинает срабатывать на движение тепловых объектов) при установке необходимого уровня освещенности помещения и чувствительности. Обычно в светлое время суток нет необходимости мигать светом в коридоре чтобы пройти.
Из инструкции к датчику:
Если освещенность возле датчика PM111 выше установленной регулятором «Освещенность», то датчик находится в режиме ожидания. При этом его ток потребления минимален (менее 1 мкА), а тепловой сенсор движения отключен. Когда освещенность опускается ниже заданного уровня, датчик переходит в дежурный режим.
Свет в туалете и ванной управляется классическим стационарным выключателем. Свет и вентилятор в туалете подключены через реле времени F&F PO-415 (тоже белорусы) на DIN рейке.

В результате замыкания управляющего контакта 6 контакты реле 11, 12 замыкаются. Размыкание управляющего контакта 6 вызывает отсчет установленного времени, по истечении которого работа PO-415 прекращается.
Стационарный коридорный выключатель должен быть кнопочного типа, поскольку вызов сценария предполагает нажатие кнопки (сценарий не имеет состояний ВКЛ/ВЫКЛ).
Также кнопка необходима если нам понадобиться выключить уже включенный датчиком движения свет, когда таймер датчика настроен не выключать свет несколько минут после срабатывания. Клавиша в этом случае не подходит.
Итого по коридору:
- два силовых блока релейного типа на 200 Вт (SU111-200 в релейном режиме)
- один стационарный 3-х кнопочный выключатель (Пульт PK313):
- кнопка 1 — вкл/выкл свет точка освещения 1
- кнопка 2 — вкл/выкл свет точка освещения 2
- кнопка 3 — выкл свет во всей квартире (сценарий)

Кухня
Три точки освещения:
- светильник над столом (SU111-200 релейный режим)
- точечное освещение в натяжном потолке (SU111-200 релейный режим)
- светодиодная лента для подсветки рабочей поверхности кухни (SU111-200 релейный режим)
Точки освещения управляются 3-х кнопочным выключателем (пульт PK311), расположенным у входа на кухню.

Гостиная и балкон
Две точки освещения в релейном режиме (SU111-200):
- основное освещение в гостиной
- свет на балконе
Каждая точка освещения управляется 2-мя выключателями:
- 2х кнопочный стационарный выключатель на балконе (Пульт PK313):
- кнопка 1 — вкл/выкл свет на балконе
- кнопка 2 — вкл/выкл свет в гостиной
- 2-х клавишный стационарный выключатель в гостиной (Пульт PK314):
- клавиша 1 — вкл/выкл свет в гостиной
- клавиша 2 — вкл/выкл свет на балконе

Возможно, кто-то спросит «почему на балконе не поставить 2-х клавишный выключатель как и в гостиной?» или «почему не поставить во всей квартире кнопки?».
Так уж вышло, сначала предполагалось на балконе установить 3-х кнопочный выключатель с одной сценарной кнопкой, но потом передумали при выборе дизайна самих выключателей. Также предполагалось, что в коридоре и на кухне будут кнопки, а в гостиной комнате, спальне и на балконе — клавишные выключатели, чтобы сохранить дизайн.
Позже захотелось диммировать свет выключателем у кровати, поэтому все пошло наперекосяк. Чтобы сохранить дизайн выключателей, пришлось объездить множество строительных рынков, посетить дюжину интернет сайтов, об этом далее в разделе «найти и купить подходящие выключатели для пультов».
Спальня
Секция 1 (кровать)
Обычно силовые блоки располагаются в каждой точке освещения. Одна точка освещения — один силовой блок. В моем сценарии освещения есть одно отступление от традиционной логики. В спальне «секция 1» предполагается установить светильник с двумя силовыми блоками, один из которых будет диммироваться.
Секция 2 (эркер)
Помимо стационарных выключателей все точки освещения должны управляться с карманного брелка в качестве резервного пульта и RPI. Для брелка:
- кнопка А (включить/выключить свет в коридоре и на кухне)
- кнопка B (включить/выключить свет в ��остиной и на балконе)
- кнопка С (включить/выключить свет в спальне)

Определение места размещения слаботочки и метода интеграции NooLite с для RPI
Скажу сразу, с интеграцией все просто. У NooLite есть модуль MT1132. Модуль получает управляющий пакет по UART от некоторого контроллера, в моем случае это RPI, передает команду по радиоканалу на исполнительные устройства (силовые блоки) и отвечает стройкой «OK\r\n» все описано в инструкции к модулю.
| Напряжение питания модуля (VCC Uпит) | 2,7—5,5 В |
|---|---|
| диапазон входного напряжения на Rx | 0 — Uпит |
| TTL HIGH LVL (логическая единица) при Uпит=5 В | 2 — Uпит (5 В) |
| TTL LOW LVL (логический ноль) при Uпит=5 В | 0—0,8 В |
| TTL HIGH LVL (логическая единица) при Uпит=3,3 В | 2—3,3 В |
| TTL LOW LVL (логический ноль) при Uпит=3,3 В | 0—0,8 В |
| Скорость UART | 9600 бод |

Поскольку UART линии RPI работают с TTL уровнями 3,3 В, значит будем использовать 3,3 В в качестве U питания модуля.

Можно использовать три линии на MT1132: VCC, GND и RX, для передачи управляющего пакета. Я так и делал при отладке — не читал ответ «OK\r\n» от MT1132, мне достаточно было наблюдать за индикатором привязываемого силового блока. При успешном принятии команды «привязки» на силовом блоке инициируется частое мерцание встро��нного светодиода.
Еще один момент, мощность радиопередатчика при U питания от 3,3 В до 3,3 мВт, при U питания от 5 В до 5 мВт. Максимальное расстояние до силового блока 70 м.
Про управляющий пакет
Управляющий пакет состоит из 12 байт:
ST, B0, B1, B2, B3, B4, B5, B6, B7, B8, CS, SP
ST — стартовый байт, всегда равен 85
B0..B8 — payload (управляющие команды)
CS — контрольная сумма. Младший байт от суммы первых 10 байт (с ST по B8)
SP — стоповый байт
По инструкции выписал необходимые мне управляющие байты:
B1 — управляющая команда со значениями:
- 0 — выключить нагрузку
- 2 — включить нагрузку
- 4 — включить или выключить нагрузку
- 9 — отвязка (запустить процедуру стирания адреса из памяти силового блока)
- 15 — привязка (записать адрес модуля в силовой блок)
B4 — адрес канала (от 0 до 31). Всего 32 канала.
остальные байты по умолчанию за исключением контрольной суммы.
B0 — настройка режима передачи модуля.
Если в B0 передается значение 80 (0x50) значит:
- количество повторов — 2
- битрейт 2—1000 бит/сек
- режим 0 — передать команду

В итоге составил вот такую таблицу с необходимыми мне управляющими пакетами.
Размещение в электрощите
Было решено установить слаботочку в электрощит внутреннего монтажа:
- RPI крепится на DIN рейку с помощью крепления напечатанного у друзей на 3D принтере
- модуль MT1132 для управления силовыми блоками по радиоканалу
- две ��озетки ~220 на DIN для подключения блоков питания роутера и RPI

Отладка интеграции и выбор обертки для управления
Prerequisites:
- RPI
- модуль MT1132
- провода мама-мама для соединения пинов MT1132 и RPI GPIO
- SD карта с любым предустановленым образом RPI
- доступ к SHELL (bash, csh, korn, etc.) консоли RPI (прямой клавомониторный или удаленный по SSH)
- подготовленные управляющие пакеты
- библиотека для работы с UART через GPIO header в RPI
Подготавливая образ, словил прикольную хардварную багу на Macbook Air, во время записи образа Raspbian-wheezy под рутом (хотя по мне это фича — дополнительная защита от записи, лишний раз подумаешь, прежде чем перезапишешь данные):
dd bs=1m if=2015-02-16-raspbian-wheezy.img of=/dev/disk2Консоль вернула:
dd: /dev/disk2: Permission denied Cо снятым lock на SD карте, под рутом и правильными модами на /dev/disk2….
Потом нашел трэд по этой теме, кому-то помогало дуть в слот SD на маке, кому-то слотоприкладство. Бить макбук в наше время расточительство, и мне в итоге помог небольшой зазор: если SD карту вставить не до конца, все сработает.
Во время отладки пришлось колхозить, поскольку не было подходящих мама-мама коннекторов.

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

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

Настройка WebIOPi минимальная
Воспользовавшись инструкциями предоставленными на сайте проекта:
- установливаем framework WebIOPi выполняя следующие шаги
- Installation
- Running WebIOPi (Daemon)
- Auto start at boot
- настраиваем UART, изменяя 3 файла: /etc/inittab, /boot/cmdline.txt, /etc/webiopi/config. Шаги:
- On-Board UART
- WebIOPi Configuration
- воспроизводим действия описанные в разделе
- Serial Loopback trick (отправляем «строку» сами себе по UART и читаем через WEB интерфейс WEBIOPi в разделе Serial Monitor)
- видео в разделе Serial Monitor отображает процесс

Установка джампера для тестирования UART петли.
Используя ранее составленную таблицу с управляющими пакетами, напишем простенький Python-скрипт управления модулем из RPI SHELL.
RPI Shell script for MT1132 module
#!/usr/bin/python
import sys, getopt
def main(argv):
ch = ''
cmd = ''
try:
opts, args = getopt.getopt(argv,"h:",["ch=","cmd="])
except getopt.GetoptError:
print 'mt1132.py --ch <channel_number> --cmd <ON/OFF/BIND/UNBIND>'
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print 'mt1132.py --ch <channel_number> --cmd <ON/OFF/BIND/UNBIND>'
sys.exit()
elif opt in ("--ch"):
ch = arg
elif opt in ("--cmd"):
cmd = arg
print 'Channel: ', ch
print 'Command: ', cmd
if cmd=='ON' and ch!='':
if ch=='0':
print 'Switch ON channel 0'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00a7, 0x00aa])
elif ch=='1':
print 'Switch ON channel 1'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00a8, 0x00aa])
elif ch=='2':
print 'Switch ON channel 2'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00a9, 0x00aa])
elif ch=='3':
print 'Switch ON channel 3'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00aa, 0x00aa])
elif ch=='4':
print 'Switch ON channel 4'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00ab, 0x00aa])
elif ch=='5':
print 'Switch ON channel 5'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00ac, 0x00aa])
elif ch=='6':
print 'Switch ON channel 6'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00ad, 0x00aa])
elif ch=='7':
print 'Switch ON channel 7'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00ae, 0x00aa])
elif ch=='8':
print 'Switch ON channel 8'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00af, 0x00aa])
elif ch=='9':
print 'Switch ON channel 9'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00b0, 0x00aa])
elif ch=='10':
print 'Switch ON channel 10'
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00b1, 0x00aa])
if cmd=='OFF' and ch!='':
if ch=='0':
print 'Switch OFF channel 0'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00a5, 0x00aa])
elif ch=='1':
print 'Switch OFF channel 1'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00a6, 0x00aa])
elif ch=='2':
print 'Switch OFF channel 2'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00a7, 0x00aa])
elif ch=='3':
print 'Switch OFF channel 3'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00a8, 0x00aa])
elif ch=='4':
print 'Switch OFF channel 4'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00a9, 0x00aa])
elif ch=='5':
print 'Switch OFF channel 5'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00aa, 0x00aa])
elif ch=='6':
print 'Switch OFF channel 6'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00ab, 0x00aa])
elif ch=='7':
print 'Switch OFF channel 7'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00ac, 0x00aa])
elif ch=='8':
print 'Switch OFF channel 8'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00ad, 0x00aa])
elif ch=='9':
print 'Switch OFF channel 9'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00ae, 0x00aa])
elif ch=='10':
print 'Switch OFF channel 10'
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00ae, 0x00aa])
if cmd=='BIND' and ch!='':
if ch=='0':
print 'BIND channel 0'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00b4, 0x00aa])
elif ch=='1':
print 'BIND channel 1'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00b5, 0x00aa])
elif ch=='2':
print 'BIND channel 2'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00b6, 0x00aa])
elif ch=='3':
print 'BIND channel 3'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00b7, 0x00aa])
elif ch=='4':
print 'BIND channel 4'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00b8, 0x00aa])
elif ch=='5':
print 'BIND channel 5'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00b9, 0x00aa])
elif ch=='6':
print 'BIND channel 6'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00ba, 0x00aa])
elif ch=='7':
print 'BIND channel 7'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00bb, 0x00aa])
elif ch=='8':
print 'BIND channel 8'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00bc, 0x00aa])
elif ch=='9':
print 'BIND channel 9'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00bd, 0x00aa])
elif ch=='10':
print 'BIND channel 10'
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00be, 0x00aa])
if cmd=='UNBIND' and ch!='':
if ch=='0':
print 'UNBIND channel 0'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00ae, 0x00aa])
elif ch=='1':
print 'UNBIND channel 1'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00af, 0x00aa])
elif ch=='2':
print 'UNBIND channel 2'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00b0, 0x00aa])
elif ch=='3':
print 'UNBIND channel 3'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00b1, 0x00aa])
elif ch=='4':
print 'UNBIND channel 4'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00b2, 0x00aa])
elif ch=='5':
print 'UNBIND channel 5'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00b3, 0x00aa])
elif ch=='6':
print 'UNBIND channel 6'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00b4, 0x00aa])
elif ch=='7':
print 'UNBIND channel 7'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00b5, 0x00aa])
elif ch=='8':
print 'UNBIND channel 8'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00b6, 0x00aa])
elif ch=='9':
print 'UNBIND channel 9'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00b7, 0x00aa])
elif ch=='10':
print 'UNBIND channel 10'
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00b8, 0x00aa])
if __name__ == "__main__":
main(sys.argv[1:])
Для управления MT1132 из консоли shell выполняем следующие команды
mt1132.py --ch <channel_number> --cmd <ON/OFF/BIND/UNBIND>
Прикручиваем REST API
Фреймворк WebIOPi предусматривает управление через HTTP запросы в стиле REST, что облегчает прикручивание мобильных клиентов и упрощает взаимодействие «клиент-сервер».
Копипаст возможностей REST API фреймворка по ссылке выше:
- Get GPIO function
- Set GPIO function
- Get GPIO value
- Set GPIO value
- Output a single pulse
- Output bit sequence
- Output PWM with a duty cycle ratio
- Output PWM with an angle for servos
- Call a macro on the server
- Get full GPIO state/configuration
Наш кейс «Call a macro on the server». Описание нежирное, но достаточное для эксперимента:
HTTP POST /macros/(macro)/(args)
- Returns the value returned by the macro
Поскольку (macro) еще не подготовлен проверим REST «Get full GPIO state/configuration», а для этого нужно сделать запрос HTTP GET /*.
Открываем любой REST API Client, я использовал DHC клиент для Chrome браузера. Пробуем выполнить REST запрос получения текущего времени.

Конфигурация WebIOPi (/etc/webiopi/config)

В разделе [DEVICES]:
Добавляем устройство serial (UART GPIO) — это и есть наш модуль MT1132, где
ttyAMA0 — это девайс (порт), который видит ядро Raspbian,
baudrate — это скорость в бодах обмена информации через этот UART интерфейс.
9600 бод / (8 + 1 старт бит + 1 стоп бит) = 960 байт/с.
В разделе [SCRIPTS] (custom scripts):
Добавляем строку myscrypt = /home/pi/smarthome/python/mt1132.py — для подключения нашего скрипта к фреймворку.
В разделе [REST] (настройки управления GPIO через REST API. Опционально):
gpio-post-value = false — запрещаем изменение логических уровней LOW/HIGH на пинах GPIO через REST запросы;
gpio-post-function = false — запрещаем изменение настройки IN/OUT на пинах GPIO через REST запросы.
Остальные настройки оставляем без изменений.
[COAP]:
Не стал трогать, отключать. Пока представления не имею с чем его едят и где он применяется. Зафиксировал только в голове вот эту строку «СoAP — is a specialized web transfer protocol for use with constrained nodes and constrained networks in the Internet of Things, designed for machine-to-machine (M2M) applications such as smart energy and building automation».
Может на Хабре кто-нибудь раскроет полезные кейсы использования.
Добавляем в ранее подготовленный скрипт макросы для REST запросов.
# Channel 0
@webiopi.macro
def ch0(cmd):
if cmd=='on':
serial.writeBytes([0x55, 0x50, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00a7, 0x00aa])
webiopi.sleep(1)
resp = 'Channel: 0, Cmd: ' + cmd + ', Status: ' + serial.readString()
if cmd=='off':
serial.writeBytes([0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00a5, 0x00aa])
webiopi.sleep(1)
resp = 'Channel: 0, Cmd: ' + cmd + ', Status: ' + serial.readString()
if cmd=='unbind':
serial.writeBytes([0x55, 0x50, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00ae, 0x00aa])
webiopi.sleep(1)
resp = 'Channel: 0, Cmd: ' + cmd + ', Status: ' + serial.readString()
if cmd=='bind':
serial.writeBytes([0x55, 0x50, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00b4, 0x00aa])
webiopi.sleep(1)
resp = 'Channel: 0, Cmd: ' + cmd + ', Status: ' + serial.readString()
return resp
Тестируем REST запросы с макросами
Отправляем REST запрос «привязки адреса 3-го канала NooLite MT1132 к силовому блоку, на котором инициирована привязка» через DHC.
При успешной отправке управляющего пакета модуль MT1132 ответит по UART TX в GPIO UART RX «OK». Силовой блок в случае успеха запомнит адрес канала и замигает интенсивно встроенным зеленым светодиодом.

Простой клиент под Андройд
Изначально я не планировал писать приложение под Андройд, в силу того что представления не имел, предполагал ограничиться браузером. Потом случайно забрел на канал Start Android. Автор, Дмитрий, подробно рассказывает вместе с Андрюхой, как быстро стартануть свой первый проект под Андройд платформы. Огромное спасибо автору за проект и вложенный труд!
Посмотрев и выполнив не более двадцати уроков, я приступил к созданию своего простого приложения для управления освещением. Для разработки использовал IDE Android Studio, как подсказывает Гугл — based on IntelliJ IDEA.
Интерфейс
Получился очень аскетичный интерфейс:

main.xml AS IS
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/hl7"
android:padding="2dp"
android:clickable="false">
<!-- Шапка-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/r1c1"
android:textSize="18sp"
android:layout_width="110dp"
android:layout_height="match_parent"
android:padding="4dp"
android:layout_margin="1dp"
android:text="Помещение"
android:background="@color/hl4"
android:layout_weight="1"
android:gravity="center"
android:textColor="@color/white" />
<TextView
android:textColor="@color/white"
android:textSize="18sp"
android:id="@+id/r1c2"
android:layout_width="120dp"
android:layout_height="match_parent"
android:padding="4dp"
android:text="Точка освещения"
android:layout_margin="1dp"
android:background="@color/hl4"
android:layout_weight="2"
android:gravity="center"/>
<TextView
android:textColor="@color/white"
android:textSize="18sp"
android:id="@+id/r1c3"
android:layout_width="150dp"
android:layout_height="match_parent"
android:padding="4dp"
android:text="Управление"
android:layout_margin="1dp"
android:background="@color/hl4"
android:layout_weight="3"
android:gravity="center"/>
</LinearLayout>
<!-- кухня -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="match_parent"
android:textColor="@color/black"
android:textSize="16sp"
android:text="@string/room1txt"
android:id="@+id/room1"
android:layout_weight="1"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center"
android:background="@color/holotheme_color"
android:textStyle="bold" />
<LinearLayout
android:orientation="vertical"
android:background="@color/hl8"
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:layout_margin="1dp"
android:gravity="center|top">
<TextView
android:layout_width="match_parent"
android:textColor="@color/hl9"
android:layout_height="36dp"
android:text="@string/place1txt"
android:id="@+id/r1switch1"
android:textSize="13sp"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
<TextView
android:textSize="13sp"
android:layout_width="match_parent"
android:layout_height="36dp"
android:textColor="@color/hl9"
android:text="@string/place5txt"
android:id="@+id/r1switch2"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
<TextView
android:textSize="13sp"
android:layout_width="match_parent"
android:layout_height="36dp"
android:textColor="@color/hl9"
android:text="@string/place2txt"
android:id="@+id/r1switch3"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
</LinearLayout>
<LinearLayout
android:background="@color/hl8"
android:orientation="vertical"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:layout_margin="1dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r1b1on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r1b1off" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВКЛ"
android:id="@+id/r1b2on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r1b2off" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r1b3on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r1b3off" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- Гостиная -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="match_parent"
android:textSize="16sp"
android:text="@string/room2txt"
android:id="@+id/room2"
android:layout_weight="1"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center"
android:background="@color/holotheme_color"
android:textColor="@color/black"
android:textStyle="bold"/>
<TextView
android:layout_width="120dp"
android:layout_height="match_parent"
android:textSize="13sp"
android:text="@string/place1txt"
android:textColor="@color/hl9"
android:id="@+id/r2switch1"
android:layout_weight="2"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center|left"
android:background="@color/hl8"/>
<LinearLayout
android:background="@color/hl8"
android:orientation="horizontal"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:layout_margin="1dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r2b1on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="36dp"
android:text="ВЫКЛ"
android:id="@+id/r2b1off" />
</LinearLayout>
</LinearLayout>
<!-- Балкон -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="match_parent"
android:textSize="16sp"
android:text="@string/room3txt"
android:id="@+id/room3"
android:layout_weight="1"
android:layout_margin="1dp"
android:padding="4dp"
android:textColor="@color/black"
android:gravity="center"
android:background="@color/holotheme_color"
android:textStyle="bold"/>
<TextView
android:layout_width="120dp"
android:layout_height="match_parent"
android:textSize="13sp"
android:textColor="@color/hl9"
android:text="@string/place1txt"
android:id="@+id/r3switch1"
android:layout_weight="2"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center|left"
android:background="@color/hl8"
android:clickable="true"/>
<LinearLayout
android:background="@color/hl8"
android:orientation="horizontal"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:layout_margin="1dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r3b1on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:text="ВЫКЛ"
android:id="@+id/r3b1off" />
</LinearLayout>
</LinearLayout>
<!-- Комната с эркером -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="match_parent"
android:textSize="16sp"
android:text="@string/room4txt"
android:id="@+id/room4"
android:layout_weight="1"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center"
android:background="@color/holotheme_color"
android:textColor="@color/black"
android:textStyle="bold" />
<LinearLayout
android:orientation="vertical"
android:background="@color/hl8"
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:layout_margin="1dp"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="37dp"
android:text="@string/place6txt"
android:textColor="@color/hl9"
android:id="@+id/r4switch1"
android:textSize="13sp"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
<TextView
android:layout_width="match_parent"
android:layout_height="37dp"
android:text="@string/place8txt"
android:textColor="@color/hl9"
android:id="@+id/r4switch3"
android:textSize="13sp"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
<TextView
android:textSize="13sp"
android:layout_width="match_parent"
android:textColor="@color/hl9"
android:layout_height="37dp"
android:text="@string/place7txt"
android:id="@+id/r4switch2"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
</LinearLayout>
<LinearLayout
android:background="@color/hl8"
android:orientation="vertical"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:layout_margin="1dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="37dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r4b1on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r4b1off" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r4b3on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r4b3off" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r4b2on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r4b2off" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- Коридор -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="110dp"
android:layout_height="match_parent"
android:textSize="16sp"
android:text="@string/room5txt"
android:id="@+id/room5"
android:layout_weight="1"
android:layout_margin="1dp"
android:padding="4dp"
android:gravity="center"
android:textColor="@color/black"
android:background="@color/holotheme_color"
android:textStyle="bold" />
<LinearLayout
android:orientation="vertical"
android:background="@color/hl8"
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:layout_margin="1dp"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="36dp"
android:text="@string/place3txt"
android:textColor="@color/hl9"
android:id="@+id/r5switch1"
android:textSize="13sp"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
<TextView
android:textSize="13sp"
android:layout_width="match_parent"
android:layout_height="37dp"
android:textColor="@color/hl9"
android:text="@string/place4txt"
android:id="@+id/r5switch2"
android:padding="4dp"
android:gravity="center_vertical|left"
android:background="@drawable/hover1"
android:clickable="true"/>
</LinearLayout>
<LinearLayout
android:background="@color/hl8"
android:orientation="vertical"
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:layout_margin="1dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="36dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r5b1on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r5b1off" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="37dp">
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="ВКЛ"
android:id="@+id/r5b2on" />
<Button
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"
android:text="ВЫКЛ"
android:id="@+id/r5b2off" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
К кнопкам привязаны методы onClick.
@Override
public void onClick(View v) {
// define the button switch that invoked the listener by id
switch (v.getId()) {
// Buttons room1 кухня
case R.id.r1b1on: // основной
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room1txt)+" > "+r1sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch4url) + "on";
new ParseTask().execute();
break;
Switch определяет ID view элемента кнопки, выводит TOAST сообщение на экран и делает HTTP запрос на RPI через HttpURLConnection внутри AsyncTask (вызов ParseTask().execute();).
Взаимодействие с backend
HttpURLConnection — класс для взаимодействия по HTTP протоколу.
Метод doInBackground класса AsyncTask — выполняет тяжелые задачи в отдельном бэкграунд-потоке и возвращает результат обратно в UI поток.
HttpURLConnection выполняется в методе doInBackground.
Такой прием часто встречается в интернете для реализации обмена данными через HTTP протокол.
webiopiurl = getString(R.string.ch4url) + "on";
Подставляет константу из файла strings.xml в ресурсах проекта
<string name="ch4url">http://192.168.1.154:8000/macros/ch4/</string>
Таким образом полный URL для включения канала 4 выглядит так
http://192.168.1.154:8000/macros/ch4/on
// HTTP Query to backend in REST style
private class ParseTask extends AsyncTask<Void, Void, String> {
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
String result = "";
String BASIC_AUTH = "Basic "
+ Base64.encodeToString((getString(R.string.login) + ":" + getString(R.string.pwd)).getBytes(), Base64.NO_WRAP);
@Override
protected String doInBackground(Void... params) {
// выполняем запрос в REST стиле
try {
URL url = new URL(webiopiurl);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Authorization", BASIC_AUTH);
urlConnection.connect();
// получаем ответ от backend webiopi
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
result = buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
код MainActivity.java AS IS
package ru.bbq.smarthome_App;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends ActionBarActivity implements View.OnClickListener {
// Add logger
public static String LOG_TAG = "my_log";
public static String webiopiurl = "";
// switch buttons
TextView r1sw1;
TextView r1sw2;
TextView r1sw3;
TextView r2sw1;
TextView r3sw1;
TextView r4sw1;
TextView r4sw2;
TextView r4sw3;
TextView r5sw1;
TextView r5sw2;
// ON OFF buttons
Button r1b1on;
Button r1b1off;
Button r1b2on;
Button r1b2off;
Button r1b3on;
Button r1b3off;
Button r2b1on;
Button r2b1off;
Button r3b1on;
Button r3b1off;
Button r4b1on;
Button r4b1off;
Button r4b2on;
Button r4b2off;
Button r4b3on;
Button r4b3off;
Button r5b1on;
Button r5b1off;
Button r5b2on;
Button r5b2off;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find View-elements and buttons
r1sw1 = (TextView) findViewById(R.id.r1switch1); // room1switch1
r1sw2 = (TextView) findViewById(R.id.r1switch2); // room1switch2
r1sw3 = (TextView) findViewById(R.id.r1switch3); // room1switch3
r2sw1 = (TextView) findViewById(R.id.r2switch1); // room2switch1
r3sw1 = (TextView) findViewById(R.id.r3switch1); // room3switch1
r4sw1 = (TextView) findViewById(R.id.r4switch1); // room4switch1
r4sw2 = (TextView) findViewById(R.id.r4switch2); // room4switch2
r4sw3 = (TextView) findViewById(R.id.r4switch3); // room4switch3
r5sw1 = (TextView) findViewById(R.id.r5switch1); // room5switch1
r5sw2 = (TextView) findViewById(R.id.r5switch2); // room5switch2
r1b1on = (Button) findViewById(R.id.r1b1on); // room1 button1 ON
r1b1off = (Button) findViewById(R.id.r1b1off); // room1 button1 OFF
r1b2on = (Button) findViewById(R.id.r1b2on); // room1 button2 ON
r1b2off = (Button) findViewById(R.id.r1b2off); // room1 button2 OFF
r1b3on = (Button) findViewById(R.id.r1b3on); // room1 button3 ON
r1b3off = (Button) findViewById(R.id.r1b3off); // room1 button3 OFF
r2b1on = (Button) findViewById(R.id.r2b1on); // room2 button1 ON
r2b1off = (Button) findViewById(R.id.r2b1off); // room2 button1 OFF
r3b1on = (Button) findViewById(R.id.r3b1on); // room3 button1 ON
r3b1off = (Button) findViewById(R.id.r3b1off); // room3 button1 OFF
r4b1on = (Button) findViewById(R.id.r4b1on); // room4 button1 ON
r4b1off = (Button) findViewById(R.id.r4b1off); // room4 button1 OFF
r4b2on = (Button) findViewById(R.id.r4b2on); // room4 button2 ON
r4b2off = (Button) findViewById(R.id.r4b2off); // room4 button2 OFF
r4b3on = (Button) findViewById(R.id.r4b3on); // room4 button3 ON
r4b3off = (Button) findViewById(R.id.r4b3off); // room4 button3 OFF
r5b1on = (Button) findViewById(R.id.r5b1on); // room5 button1 ON
r5b1off = (Button) findViewById(R.id.r5b1off); // room5 button1 OFF
r5b2on = (Button) findViewById(R.id.r5b2on); // room5 button2 ON
r5b2off = (Button) findViewById(R.id.r5b2off); // room5 button2 OFF
//assign listeners to buttons
r1b1on.setOnClickListener(this);
r1b1off.setOnClickListener(this);
r1b2on.setOnClickListener(this);
r1b2off.setOnClickListener(this);
r1b3on.setOnClickListener(this);
r1b3off.setOnClickListener(this);
r2b1on.setOnClickListener(this);
r2b1off.setOnClickListener(this);
r3b1on.setOnClickListener(this);
r3b1off.setOnClickListener(this);
r4b1on.setOnClickListener(this);
r4b1off.setOnClickListener(this);
r4b2on.setOnClickListener(this);
r4b2off.setOnClickListener(this);
r4b3on.setOnClickListener(this);
r4b3off.setOnClickListener(this);
r5b1on.setOnClickListener(this);
r5b1off.setOnClickListener(this);
r5b2on.setOnClickListener(this);
r5b2off.setOnClickListener(this);
}
// Define On ClickView method
@Override
public void onClick(View v) {
// define the button switch that invoked the listener by id
switch (v.getId()) {
// Buttons room1 кухня
case R.id.r1b1on: // основной
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room1txt)+" > "+r1sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch4url) + "on";
new ParseTask().execute();
break;
case R.id.r1b1off: // основной
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room1txt)+" > "+r1sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch4url) + "off";
new ParseTask().execute();
break;
case R.id.r1b2on: // кухня точечный
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room1txt)+" > "+r1sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch3url) + "on";
new ParseTask().execute();
break;
case R.id.r1b2off: // кухня точечный
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room1txt)+" > "+r1sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch3url) + "off";
new ParseTask().execute();
break;
case R.id.r1b3on: // LED кухня
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room1txt)+" > "+r1sw3.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch2url) + "on";
new ParseTask().execute();
break;
case R.id.r1b3off: // LED кухня
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room1txt)+" > "+r1sw3.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch2url) + "off";
new ParseTask().execute();
break;
// Buttons room2 гостиная
case R.id.r2b1on:
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room2txt)+" > "+r2sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch6url) + "on";
new ParseTask().execute();
break;
case R.id.r2b1off:
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room2txt)+" > "+r2sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch6url) + "off";
new ParseTask().execute();
break;
// Buttons room3 балкон
case R.id.r3b1on:
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room3txt)+" > "+r3sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch5url) + "on";
new ParseTask().execute();
break;
case R.id.r3b1off:
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room3txt)+" > "+r3sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch5url) + "off";
new ParseTask().execute();
break;
// Buttons room4 bedroom
case R.id.r4b1on: // над кроватью релейный режим
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room4txt)+" > "+r4sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch8url) + "on";
new ParseTask().execute();
break;
case R.id.r4b1off: // над кроватью релейный режим
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room4txt)+" > "+r4sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch8url) + "off";
new ParseTask().execute();
break;
case R.id.r4b2on: // у эркера в спальне
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room4txt)+" > "+r4sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch7url) + "on";
new ParseTask().execute();
break;
case R.id.r4b2off: // у эркера в спальне
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room4txt)+" > "+r4sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch7url) + "off";
new ParseTask().execute();
break;
case R.id.r4b3on: // над кроватью dimmer
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room4txt)+" > "+r4sw3.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch9url) + "on";
new ParseTask().execute();
break;
case R.id.r4b3off: // над кроватью dimmer
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room4txt)+" > "+r4sw3.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch9url) + "off";
new ParseTask().execute();
break;
// Buttons room5 corridor
case R.id.r5b1on:
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room5txt)+" > "+r5sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch1url) + "on";
new ParseTask().execute();
break;
case R.id.r5b1off:
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room5txt)+" > "+r5sw1.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch1url) + "off";
new ParseTask().execute();
break;
case R.id.r5b2on:
Toast.makeText(this, getString(R.string.bon)+" '"+getString(R.string.room5txt)+" > "+r5sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch0url) + "on";
new ParseTask().execute();
break;
case R.id.r5b2off:
Toast.makeText(this, getString(R.string.boff)+" '"+getString(R.string.room5txt)+" > "+r5sw2.getText()+"'", Toast.LENGTH_SHORT).show();
webiopiurl = getString(R.string.ch0url) + "off";
new ParseTask().execute();
break;
}
}
// HTTP Query to backend in REST style
private class ParseTask extends AsyncTask<Void, Void, String> {
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
String result = "";
String BASIC_AUTH = "Basic "
+ Base64.encodeToString((getString(R.string.login) + ":" + getString(R.string.pwd)).getBytes(), Base64.NO_WRAP);
@Override
protected String doInBackground(Void... params) {
// выполняем запрос в REST стиле
try {
URL url = new URL(webiopiurl);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Authorization", BASIC_AUTH);
urlConnection.connect();
// получаем ответ от backend webiopi
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
result = buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
protected void onPostExecute(String str) {
super.onPostExecute(str);
// ответ обрабатываем, как простую строку plain text (не JSON)
// выводим результат в log
Log.d(LOG_TAG, str);
// делаем TOAST - выводим ответ webiopi вместе со статусом модуля MT1132
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
}
}
}
Найти и купить подходящие выключатели для пультов
Исходя из сценария освещения, в данном проекте используются стационарные встраиваемые пульты/выключатели NooLite клавишные и кнопочные, нужно предусмотреть возможность установки выключателей с тремя модулями (кнопками/клавишами), сохранив дизайн выключателей (все выключатели должны выглядеть одинаково).
При выборе выключателей предусмотреть возможность:
- установки от двух до трех модулей в саппорт выключателей
- установки кнопочных и клавишных модулей
Под данные условия подошли выключатели bticino livinglight. На фото ниже собранные модули с пультом NooLite.


Силовой блок на потолке во время отладки

Отладка на месте

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

Спасибо за внимание. Хорошего дня!
