Хочу поделиться опытом использования системы для умного дома NooLite совместно с Raspberry Pi Model B (далее RPI) в двухкомнатной квартире.
О системе 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 ради такого простого функционала — управлять освещением в квартире с мобильного телефона, а реализовал бы все используя пластиковый стаканчик, камешек и ниточку на Arduino с Wi-Fi шилдом или более простой и лаконичной реализации на AVR микроконтроллере».
Но, поскольку уже пылился на полке RPI и одолевало желание сделать «умное» освещение с использованием платформы RPI, решил взять за основу именно его. Также, возможность использования RPI дает хороший запас для расширения функционала «умного дома».

Входные данные


  • 2-х комнатная квартира П44Т без ремонта в новостройке
  • RPI (был приобретен два года назад интереса ради)
  • желание реализовать «умное» освещение

Некоторое время приглядывался к различным платформам и технологиям умного дома, в итоге выбор пал на систему NooLite, исходя из следующего:
  • адекватная цена по сравнению с аналогичными системами (Z-Wave, EnOcean, ZigBee)
  • доверяю товарам, произведенным в Белорусии
  • открытый протокол
  • множество примеров использования в интернете
  • широко распространенная и ратифицированная ГКРЧ частота 433 МГц

Обратил внимание и на платформу для домашней автоматизации Wiren Board на базе ARM9 с богатыми функциональными возможностями под все мыслимые и немыслимые нужды. Особенно порадовала киллер-фича — радиомодуль на 433 МГц, что в моем случае подходит для управления силовыми блоками NooLite. Но поскольку у меня уже есть RPI, будем отталкиваться от этого.
Для реализации «умного» освещения в квартире мне понадобилось решить следующие задачи:
  1. определить оптимальный сценарий освещения в двухкомнатной квартире
  2. определить метод интеграции с RPI и размещения слаботочки
  3. выбрать обертку для управления
  4. составить список покупок
  5. согласовать вышеперечисленное с женой
  6. купить необходимые компоненты
  7. привязать пульты и отладить интеграцию RPI > NooLite
  8. найти и купить подходящие выключатели для пультов
  9. разместить силовые блоки и выключатели
  10. разработать мобильное приложение под 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 минимальная
Воспользовавшись инструкциями предоставленными на сайте проекта:
  1. установливаем framework WebIOPi выполняя следующие шаги
    • Installation
    • Running WebIOPi (Daemon)
    • Auto start at boot

  2. настраиваем UART, изменяя 3 файла: /etc/inittab, /boot/cmdline.txt, /etc/webiopi/config. Шаги:
    • On-Board UART
    • WebIOPi Configuration

  3. воспроизводим действия описанные в разделе
    • 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.


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

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

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

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