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

в 8:30, , рубрики: android, arduino, bluetooth, Песочница, Разработка под android, метки: , ,

Предисловие

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

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

  • Управлять режимами подсветки (скорость затухания, яркость свечения) удаленно, с 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.

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

Проблемы

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

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

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

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

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

Особых заморочек не было — благо документация есть, и весьма хорошая, как и громадное количество советов на 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/

Автор: alexpp

Источник

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


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