Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi)

в 9:50, , рубрики: android, android studio, DIY, diy или сделай сам, noolite, Raspberry Pi, rpi, smart home, webiopi

Хочу поделиться опытом использования системы для умного дома NooLite совместно с Raspberry Pi Model B (далее RPI) в двухкомнатной квартире.

О системе NooLite неоднократно писали на хабре:

В данной статье я расскажу про:

  • установку и настройку WebIOPi на предустановленную Raspbian в контексте использования UART для передачи команд на модуль MT1132 NooLite;
  • макросы в фреймворке WebIOPi для связывания запросов в стиле HTTP REST с GPIO UART;
  • написание простого клиента на платформе Android для управления светом через REST дырки

Функционал

  • backend (обертка управления): RPI + WebIOPi (UART+REST) + NooLite модуль;
  • исполняющая часть: силовые блоки NooLite с нагрузкой — освещение в квартире;
  • front end (клиентская часть, управление): мобильный клиент Android, WEB интерфейс, консоль bash shell (python script)

Возможно, кто-то подумает: «я бы не стал заморачиваться с RPI ради такого простого функционала, — управлять освещением в квартире с мобильного телефона, а реализовал бы все используя пластиковый стаканчик, камешек и ниточку на Arduino с WiFi шилдом или более простой и лаконичной реализации на AVR микроконтроллере».

Но, поскольку уже пылился на полке RPI и одолевало желание сделать «умное» освещение с использованием платформы RPI, решил использовать за основу именно его. Также, возможность использования RPI дает хороший запас для расширения функционала «умного дома».

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

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

Некоторое время приглядывался к различным платформам и технологиям умного дома, в итоге выбор на систему 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 [1140р X 1 шт] 1140р;
  • Силовые блоки NL [1240р Х 11 шт] 13640р;
  • Пульты управления стационарные NL [1340р Х 7 шт] 9380р;
  • Пульт управления брелок NL [1550р Х 1 шт] 1550р;
  • Датчик движения NL [1550р Х 1 шт] 1550р;
  • Монтажные коробки, саппорты, клавишные/кнопочные модули (выключатели), рамки (приобретается индивидуально);
  • друзья с 3D принтером — бесплатно

Итого: ~27000р + контроллер от 3000р.

Сценарий освещения в квартире

Планировка угловой двухкомнатной распашонки П44Т с эркером приведена ниже.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 1

Планируется управлять освещением с помощью NooLite во всех помещениях кроме лоджии, ванной и туалета.

Коридор

2 точки освещения с релейным режимом (включение/выключение).

Одна не очень яркая точка освещения должна управляться датчиком движения PM111. Это позволит в темное время суток освещать путь ночному скитальцу в поисках огней ночного голода.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 2

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

Тепловой датчик движения переходит в дежурный режим (начинает срабатывать на движение тепловых объектов) при установке необходимого уровня освещенности помещения и чувствительности. Обычно в светлое время суток нет необходимости мигать светом в коридоре чтобы пройти.

Из инструкции к датчику:

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

Свет в туалете и ванной управляется классическим стационарным выключателем. Свет и вентилятор в туалете подключены через реле времени F&F PO-415 (тоже белорусы) на DIN рейке.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 3

В результате замыкания управляющего контакта 6 контакты реле 11, 12 замыкаются. Размыкание управляющего контакта 6 вызывает отсчет установленного времени, по истечении которого работа PO-415 прекращается.

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

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

Итого по коридору:

  • 2 силовых блока релейного типа на 200 Вт (SU111-200 в релейном режиме);
  • 1 стационарный 3 кнопочный выключатель (Пульт PK313):
    • кнопка 1 — вкл/выкл свет точка освещения 1;
    • кнопка 2 — вкл/выкл свет точка освещения 2;
    • кнопка 3 — выкл свет во всей квартире (сценарий)

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 4

Кухня

3 точки освещения:

  • светильник над столом (SU111-200 релейный режим);
  • точечное освещение в натяжном потолке (SU111-200 релейный режим);
  • светодбодная лента для подсветки рабочей поверхности кухни (SU111-200 релейный режим)

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 5

Гостиная и балкон

2 точки освещения в релейном режиме (SU111-200):

  • основное освещение в гостиной;
  • свет на балконе.

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

  • 2х кнопочный стационарный выключатель на балконе (Пульт PK313):
    • кнопка 1 — вкл/выкл свет на балконе;
    • кнопка 2 — вкл/выкл свет в гостиной
  • 2х клавишный стационарный выключатель в гостиной (Пульт PK314):
    • клавиша 1 — вкл/выкл свет в гостиной;
    • клавиша 2 — вкл/выкл свет на балконе

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 6

Возможно, кто-то спросит «почему на балконе не поставить 2х клавишный выключатель как и в гостиной? или почему не поставить во всей квартире кнопки?».

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

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

Спальня

Секция 1 (кровать)
Обычно силовые блоки располагаются в каждой точке освещения. Одна точка освещения — один силовой блок. В моем сценарии освещения есть одно отступление от традиционной логики. В спальне «секция 1» предполагается установить светильник с двумя силовыми блоками один из которых будет диммироваться.

Секция 2 (эркер)
Помимо стационарных выключателей все точки освещения должны управляться с карманного брелка в качестве резервного пульта и RPI. Для брелка:

  • кнопка А (включить/выключить свет в коридоре и на кухне);
  • кнопка B (включить/выключить свет в гостиной и на балконе);
  • кнопка С (включить/выключить свет в спальне)

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 7

Определение места размещения слаботочки и метода интеграции NooLite с для RPI

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

Напряжение питания модуля (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 бод

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 8

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 9

Можно использовать 3 линии на MT1132: VCC, GND и RX, для передачи управляющего пакета. Я так и делал при отладке, — не читал ответ «OKrn» от 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 — передать команду

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 10

В итоге составил вот такую таблицу с необходимыми мне управляющими пакетами.

Размещение в электрощите

Было решено установить слаботочку в электрощит внутреннего монтажа:

  • RPI крепится на DIN рейку с помощью крепления напечатанного у друзей на 3D принтере;
  • модуль MT1132 для управления силовыми блоками по радиоканалу;
  • 2 розетки ~220 на DIN для подключения блоков питания роутера и RPI

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 11

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

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 карту вставить не до конца, все сработает.

Во время отладки пришлось колхозить, поскольку не было подходящих мама-мама коннекторов.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 12

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 13

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

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

Общая архитектура взаимодействия

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 14

Настройка 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 отображает процесс

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 15

Установка джампера для тестирования 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 клиент для хром браузера. Пробуем выполнить REST запрос получения текущего времени.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 16

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 17

В разделе [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». Силовой блок в случае успеха запомнит адрес канала и замигает интенсивно встроенным зеленым светодиодом.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 18

Простой клиент под Андройд

Изначально я не планировал писать приложение под андройд, в силу того что представления не имел, предполагал ограничиться браузером. Потом случайно забрел на канал Start Android. Автор Дмитрий подробно рассказывает вместе с Андрюхой, как быстро стартануть свой первый проект под Андройд платформы. Огромное спасибо автору за проект и вложенный труд!

Посмотрев и выполнив не более 20 уроков я приступил к созданию своего простого приложения для управления освещением. Для разработки использовал IDE Android Studio, как подсказывает гугл, — based on IntelliJ IDEA.

Интерфейс

Получился очень аскетичный интерфейс:

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 19

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 клавишные и кнопочные, то нужно предусмотреть возможность установки выключателей с 3-мя модулями (кнопками/клавишами) сохранив дизайн выключателей (все выключатели должны выглядеть одинаково).

При выборе выключателей предусмотреть возможность:

  • установки от 2х до 3х модулей в саппорт выключателей;
  • установки кнопочных и клавишных модулей

Под данные условия подощли выключатели bticino livinglight. На фото ниже собранные модули с пультом NooLite.

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 20

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 21

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 22

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 23

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

Управляем освещением в квартире (NooLite, Raspberry Pi и WebIOPi) - 24

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

Автор: 2b3q

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js