О системе 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.
#!/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».
Может на Хабре кто-нибудь раскроет полезные кейсы использования.
# 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.
Интерфейс
Получился очень аскетичный интерфейс:
<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;
}
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.
Силовой блок на потолке во время отладки
Отладка на месте
Электрощит с размещенной внутри слаботочкой
Спасибо за внимание. Хорошего дня!
Комментарии (9)
alexpp
11.08.2015 14:20Завидую, что есть доступ к проводке и потолку, чтобы можно было разместить блоки управления и т.п. А что делать, если есть натяжной потолок, и не хочется его испортить — загадка.
2b3q
11.08.2015 14:37+1alexpp, силовой блок NL не большой, в инструкции есть вариант размещения в стакане люстры и за подвесным потолком:
doom369
11.08.2015 17:04+1На Блинк смотрели? По логике можно быстро интегрировать.
2b3q
11.08.2015 17:44doom369, не смотрел. Спасибо за ссылку. Буду иметь в виду. Интересная платформа. Порадовало быстрое создание dashboard элементов инфографики и управления для Android и iOS из коробки. Кстати, WebIOPi тоже можно наружу выбросить без Real IP через внешний сервис и привязать RPI как ноду. Но пока не вижу смысла выбрасывать наружу освещение в доме, может только управление вводным автоматом.
Jey
13.08.2015 19:59+1Вы можете дополнить свою систему USB-приёмником и беспроводными датчиками от той же Ноотехники (датчик движения у вас уже есть + можно добавить датчик температуры/влажности).
2b3q
14.08.2015 11:48Кажется, вы создатель MajorDoMo, знакомый никнэйм. Классная открытая платформа.
Касательно добавления датчиков влажности и температуры, думал, но остановился пока только на управлении освещением, поскольку изначально ставил цель «управлять светом». Сейчас так уже не думаю, когда цель достигнута появляется новая цель и это видимо бесконечный процесс, тема действительно интересная и многогранная. Стоит только представить «как прикрутить эти датчики», сразу лезут мысли «как хранить? FS, SQL DB, MongoDB», «как выводить данные? вьюшки с графиками и гистограммами в WebIOPi, дашборды, готовые библиотеки формирования графиков, а может внешние сервисы», «куда выводить? только на HTTP сервере или еще в смартфон». А потом приходит другая мысль «велосипед?» Есть же готовые решения, в том числе и MajorDoMo.
В любом случае, когда-нибудь расширю функционал. Хочется и NFC триггеры добавить — приложил карточку от метро к NFC ридеру, включился усилитель и заиграла музыка из любимого плэй листа… Приложил другую — дом/квартира сконфигурировала подходящую атмосферу — паттерны освещения, кондиционирования, и тд.
tekub
Большое спасибо, что поделились опытом!
Я как-раз решался какую связку использовать…
2b3q
Рад, что публикация оказалась полезной!