Управление декоративной подсветкой на Arduino с телефона

от автора

Предисловие

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

Хотелось, чтобы была возможность:

  • Управлять режимами подсветки (скорость затухания, яркость свечения) удаленно, с Android-телефона по синезубу или пульта ДУ домашней техники по ИК
  • Возможность легкого перепрограммирования режимов работы на самом устройстве
  • Стоимость — чем меньше, тем лучше
  • Доступность компонентов


Выбор был несложным — и я стал обладателем китайской копии Arduino Uno3 под названием DK-Duino Uno, купленной за 15 вечнозеленых на ебее. Одновременно с контроллером были приобретены собственно светодиоды (синие, по 3 бакса за сотню), модуль синезуба HC-05 за 12 долларов и блок питания на 12В/5А за 7 долларов — итого на круг вышло 37 долларов, или 1100 рублей. На ебее есть возможность купить наборы светодиодная лента 5м+контроллер+пульт ИК за 17 долларов, но данный вариант не подходил из-за необходимости направлять пульт на приемник контроллера, который планируется спрятать за мебель/плинтус.
Оригинальностью задумка не блистала, просто хотелось создать нечто удобное, долговечное и понятное в управлении всем жильцам дома.

Задумка

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

Что хотелось воплотить в системе подсветки

  • Изменение скорости и яркости свечения, вплоть до полного отключения
  • Режим неизменной яркости
  • Режим затухания без бега

Реализация — Arduino

Для управления выбрал телефон на Андроиде и синезуб, как наиболее простой способ, не завязанный на систему команд определенного пульта, не подверженный проблемам передачи команд через препятствия вроде стен-мебели-котов.
За основу была взята одна из многочисленных схем бегущего огня с затуханием, найденная на просторах бесконечного.
Максимум числа светодиодов — 6, по количеству PWM выходов выбранного Ардуино.
Что требовалось доделать: возможность подключения модуля синезуба для приема команд управления и отсыла данных о текущем состоянии на телефон для отображения.
С этим я успешно справился, результатом стал скетч для Ардуино:

#include <SoftwareSerial.h>  enum LedState { LED_ON, LED_OFF }; boolean isFadeMode = false;  #define LED_CNT 6 int ledPins[LED_CNT] = {11, 10, 9, 6, 5, 3}; // pwm pins int ledBrightnessesWave[LED_CNT] = {0, 50, 100, 150, 205, 255}; // represents initial brightness for wave mode int ledStepsWave[LED_CNT] = {-5, 5, 5, 5, 5, 5}; // represents initial change for wave mode int ledBrightnesses[LED_CNT]; // represents brightness for pin int ledSteps[LED_CNT]; // represents change, each pin gets its own change so it wont interfere with any other pin  int ledSpeed = 50; int maxLedBrightness = 255; int ledBrightnessesConst = 255; // represents brightness for pins in no wave or fade mode  LedState led_state;  #define rxPin 2 #define txPin 4  #define SPEED_PREFIX 'G' #define SPEED_PREFIX_MAX 'P' #define BRIGHTNESS_PREFIX 'Q' #define BRIGHTNESS_PREFIX_MAX 'Z'  #define MIN_SPEED_DELAY 5 #define MAX_SPEED_DELAY 100 #define MIN_BRIGHTNESS 5 #define MAX_BRIGHTNESS 255  // set up a new serial port SoftwareSerial mySerial =  SoftwareSerial(rxPin, txPin);  void setup() {   for (int i = 0; i < LED_CNT; i++)   {     pinMode(ledPins[i], OUTPUT); //set pwm pins to output      //copy initial values     ledBrightnesses[i] = ledBrightnessesWave[i];     ledSteps[i] = ledStepsWave[i];   }      led_state = LED_ON;      Serial.begin(9600);    pinMode(rxPin, INPUT);   pinMode(txPin, OUTPUT);    mySerial.begin(115200); }  int getBrightness(int b) {   return b; }  int valueToDelay(int value) {   return MIN_SPEED_DELAY + (MAX_SPEED_DELAY - MIN_SPEED_DELAY) * value / (SPEED_PREFIX_MAX - SPEED_PREFIX); }  int valueToBrightness(int value) {   return MIN_BRIGHTNESS + (MAX_BRIGHTNESS - MIN_BRIGHTNESS) * value / (BRIGHTNESS_PREFIX_MAX - BRIGHTNESS_PREFIX); }  void recalculateBrightness(int brValue) {   ledBrightnessesConst = valueToBrightness(brValue); }  void ledFade() {   if (led_state == LED_OFF)   {     for (int i = 0; i < LED_CNT; i++)     {       analogWrite(ledPins[i], ledBrightnessesConst); // update all pins     }          return;   }    String s = "###";   for (int i = 0; i < LED_CNT; i++)   {     analogWrite(ledPins[i], getBrightness(ledBrightnesses[i]));      int newBr = ledBrightnesses[i] + ledSteps[i];     if (newBr <= 0 || newBr >= maxLedBrightness)     {       ledSteps[i] =- ledSteps[i]; //change direction if exceeds max/min value     }     else     {       ledBrightnesses[i] = newBr;     }      s.concat(ledBrightnesses[i]);     if (i < LED_CNT - 1) //skip separator for last entity       s.concat("-");   }   s.concat("***");      char charBuf[1000];   s.toCharArray(charBuf, 1000);   mySerial.write(charBuf);   mySerial.flush();      delay(ledSpeed); }  void loop() {   if (mySerial.available())   {     char command = mySerial.read();     Serial.println("command is -> " + command);          boolean handled = false;     boolean changeFadeMode = false;          switch (command)     {       case 'a':         led_state = LED_OFF;         handled = true;         break;       case 'b':         led_state = LED_ON;         ledSpeed = 50;         handled = true;         //do these lines to set wave mode         changeFadeMode = true;         isFadeMode = true;         break;       case 'c':         led_state = LED_ON;         ledSpeed = 30;         handled = true;         //do these lines to set wave mode         changeFadeMode = true;         isFadeMode = true;         break;       case 'd':         led_state = LED_ON;         ledSpeed = 10;         handled = true;         //do these lines to set wave mode         changeFadeMode = true;         isFadeMode = true;         break;       case 'e':         led_state = LED_ON;         changeFadeMode = true;         handled = true;         break;       default:         break;     }          if (changeFadeMode)     {       if (isFadeMode)       {         Serial.println("Set fade mode off");         for (int i = 0; i < LED_CNT; i++)         {           //copy initial values           ledBrightnesses[i] = ledBrightnessesWave[i];           ledSteps[i] = ledStepsWave[i];         }       }       else       {         Serial.println("Set fade mode on");         for (int i = 0; i < LED_CNT; i++)         {           ledBrightnesses[i] = 0;           ledSteps[i] = 5;         }       }       isFadeMode = !isFadeMode;     }     else     {       //do nothing     }          if (!handled)     {       boolean isSpeedCommand = command >= SPEED_PREFIX && command <= SPEED_PREFIX_MAX;       boolean isBrCommand = command >= BRIGHTNESS_PREFIX && command <= BRIGHTNESS_PREFIX_MAX;              if (isSpeedCommand)       {         led_state = LED_ON;         int speedValue = command - SPEED_PREFIX; //from 0 to 9         ledSpeed = valueToDelay(speedValue);       }       if (isBrCommand)       {         led_state = LED_OFF;         int brValue = command - BRIGHTNESS_PREFIX; //from 0 to 9         recalculateBrightness(brValue);       }     }   }      ledFade(); } 

В зависимости от принятой команды контроллер либо изменяет скорость, либо яркость свечения светодиодов. Так же можно запрограммировать любые произвольные действия на принятую команду — в примере я отключаю подсветку и меняю скорость.
В каждом цикле контроллер шлет по синезубу на сопряженное устройство строку вида ###0-50-100-150-200-250***, где цифры — значения яркости шести светодиодов, с максимумом в 255.

Схема:

Проблемы

С чем столкнулся: невозможность приема многосимвольных команд от телефона. Например, посылая команду s150 (по задумке — установить скорость 150 попугаев при максимуме в 255), я получал на Ардуино вместо переданной строки полный бред вида s###, где вместо шарпа шли любые ASCII-символы. Возможно, это связано с использованием класса SoftwareSerial для подключения синезуба. Курение гугла показало, что я не один такой счастливчик с этой проблемой, но нет ни одного решения, кроме как изменение режимов модуля HC-05. Заморачиваться с командами AT не было желания, поэтому решил использовать только односимвольные команды из определенных диапазонов, тем более для поставленной задачи этого вполне хватало. Например — для скорости 10 значений и команды от G до P, для яркости — диапазон от Q до Z соответственно.

Реализация — Android

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

Скриншоты приложения:

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

// Get a BluetoothSocket to connect with the given BluetoothDevice try {     // MY_UUID is the app's UUID string, also used by the server code     tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } 

Вместо этого пришлось лезть в рефлекшн и дергать приватный метод:

try {     Class class1 = device.getClass();     Class aclass[] = new Class[1];     aclass[0] = Integer.TYPE;     Method method = class1.getMethod("createRfcommSocket", aclass);     Object aobj[] = new Object[1];     aobj[0] = Integer.valueOf(1);      tmp = (BluetoothSocket)method.invoke(device, aobj); } 

С чем связана эта проблема — неизвестно, но возникает она у многих.

Еще одна проблема возникла при парсинге принимаемой от микроконтроллера строки. Вместо строк вида ###0-50-100-150-200-250***###0-50-100-150-200-250***… иногда приходили строки с ошибочным количеством элементов, неправильными разделителями или маркерами начала/окончания последовательности. Решил проблему написанием анализатора принятой строки и выкидыванием неверных последовательностей. После этого была несложно реализовать вьюшку с отображением текущего состояния светодиодов.

Итоги

Работающее приложение для телефона и скетч для Ардуино, работающая схема на макетке.
Видео примера работы схемы:

Что дальше:

  • Собственно окончательная пайка схемы и разводка всех проводов-светодиодов по комнате
  • Подключение модулей объема-присутствия для включения/отключения системы в зависимости от присутствия человека в комнате
  • Подключение модуля часов — зачем подсветка днем?

Все исходники доступны здесь: code.google.com/p/arduinopad/

ссылка на оригинал статьи http://habrahabr.ru/post/165861/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *