RBG контроллер на ESP как компонент умного дома

Публикация 15.02.2017

Всегда грезил идеей умного дома. Хоп-хоп и все, что угодно можно включать-выключать не отрывая попы от дивана. Мечта ленивца! А ведь не зря программисты считаются самой ленивой профессией, а? Давайте приблизимся к заветной мечте и сделаем управление RGB лентой в нашей квартире с телефона Android.

Я программист и я ленив. Повторяться о том как прошивать ESP с помощью Arduino IDE не буду, читайте в статье ESP + MQTT как основа умного дома там все подробно описано. Теперь перейдем к сути.

Зажигать RGB ленту будет плата NodeMCU, на ней стоит ESP8266-12 плюс стабилизатор и удобные выводы - идеальный вариант для отладки. ESP8266-01 лучше не использовать для таких задач т.к. она не имеет выводов, которые при старте не "моргнут" единицей. Управление осуществляется по протоколу MQTT с мобильного телефона на android. MQTT - открытый протокол поверх http веб-сокет соединения. Мы зарегистрировали аккаунт на cloudmqtt и все наши устройства объединены этим сервером-брокером.

Подключаем нашу ESP8266 к USB-UART шнурку и из Arduino IDE грузим скетч в контроллер:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <EEPROM.h>

// Подключение к wifi
const char *ssid = "xxxx"; // Имя wifi точки доступа
const char *pass = "xxxx"; // Пароль wifi точки доступа

// Подключение к mqtt брокеру
const char *mqtt_server = "m13.cloudmqtt.com"; // Имя сервера MQTT
const int mqtt_port = 14483; // Порт для подключения к серверу MQTT
const char *mqtt_user = "xxxx"; // Логи к серверу MQTT
const char *mqtt_pass = "xxxx"; // Пароль к серверу MQTT
const char *topic_name = "light/rgb1"; // Название mqtt топика для передачи цвета

// Конфигурация портов
const int r = 2; // GIPO управления каналом цвета R
const int g = 4; // GIPO управления каналом цвета R
const int b = 5; // GIPO управления каналом цвета B

// конфигурация eprom памяти
const int eprom_r = 0; // Область в памяти для R
const int eprom_g = 1; // Область в памяти для G
const int eprom_b = 2; // Область в памяти для B

#define BUFFER_SIZE 100

int tm = 300;
float temp = 0;
int c_r = 0; // Текущий цвет R
int c_g = 0; // Текущий цвет G
int c_b = 0; // Текущий цвет B

WiFiClient wclient;
PubSubClient client(wclient);

void setup_wifi() {

  delay(10);
  
  // Подключаемся к WiFi
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  // Повторять пока нет MQTT соединения
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Попытка подключения к MQTT
    if (client.connect("arduinoClient", mqtt_user, mqtt_pass)) {
      Serial.println("connected");
      // Подписываемся по топики (которые мы будем слушать от брокера)
      client.subscribe(topic_name);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  
  delay(10);
  
  // Конфигурируем GPIO при старте
  pinMode(r, OUTPUT);
  pinMode(g, OUTPUT);
  pinMode(b, OUTPUT);

  // Читаем последний заданный цвет из энергонезависимой памяти EEPROM
  // Необходимо вызвать функцию EEPROM.begin(size) каждый раз перед началом чтения или записи.
  // Размер (указывается в байтах) соответствует размеру данных, которые вы намереваетесь использовать в EEPROM.
  // Размер данных должен быть в диапазоне от 4 до 4096 байт.
  EEPROM.begin(4);
  c_r = EEPROM.read(eprom_r);
  c_g = EEPROM.read(eprom_g);
  c_b = EEPROM.read(eprom_b);
  EEPROM.commit();
  
  setColor();

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

// Функция получения данных от MQTT сервера
void callback(char* topic, byte* payload, unsigned int length)
{
  // Присланное значение цвета
  String value = "";
  for (int i=0; i<length; i++) {
    value = value + ((char)payload[i]);
  }

  Serial.print("The topic: ");
  Serial.println(topic);

  // Проверяем из нужного ли нам топика пришли данные 
  if (String(topic) == String(topic_name))
  {
    if (value != "") {
      Serial.print("HEX color: ");
      Serial.println(value);

      // Конвертируем пришедшие данные из hex #00AAFF в отдельные integer цвета
      long number = (long) strtol( &value[1], NULL, 16);
      c_r = number >> 16;
      c_g = number >> 8 & 0xFF;
      c_b = number & 0xFF;

      setColor();

      // Запишем новые цвета в память, что бы при следующем включении цвет был такой же
      EEPROM.begin(4);
      EEPROM.write(eprom_r, c_r);
      EEPROM.write(eprom_g, c_g);
      EEPROM.write(eprom_b, c_b);
      EEPROM.commit();
    }
  }
}

void setColor()
{
  // Передадим значения на GPIO
  analogWrite(r, c_r);
  analogWrite(g, c_g);
  analogWrite(b, c_b);

  Serial.print("Set color RGB(");
  Serial.print(c_r);
  Serial.print(", ");
  Serial.print(c_g);
  Serial.print(", ");
  Serial.print(c_b);
  Serial.println(")");
}

Детально описывать код не буду т.к. в нем много комментариев. Принцип работы такой:

  • Подключиться к роутеру;
  • Подключиться к брокеру через интернет;
  • Сообщить брокеру, что мы слушаем топики "light/rgb1". Они нам будут задавать цвет.
  • Прочитать из памяти последний сохраненный цвет;
  • Запустить цикл в котором мы задаем текущий цвет на GPIO через встроенный шим и ожидаем поступления топика. Если он будет, то конвертируем цвет в нужным нам формат, сохраняем его в память и меняем шим модуляцию на GPIO.

В коде используется библиотека "PubSubClient.h". Прикладываю ее ниже, что бы не возникло проблем с совместимостью версий.

Схема подключения

Для управления RGB лентой нам потребуется три MOSFET транзистора IRLB8721.

Они выдерживают хорошие токи и напряжение до 30 вольт, но не стоит их крепить к одному радиатору - корпус - средний вывод. Для стабильности работы подтягиваем управляющие GPIO и RST, и GPIO0 к земле через резисторы 10 кОм.

В схему включен стабилизатор напряжения на 3.2 вольта т.к. плата тут ESP8266-12. Она рассчитана на 3.2 Вольта.

Приложение MQTT для телефона

Телефон станет таким же клиентом брокера как и все наши устройства "Умного дома", Скачиваем один из множества бесплатных клиентов под ваш Android - IoT MQTT Dashboard. Вводим данные для доступа к брокеру.

Создаем новый элемент управления "Select color", указываем топик с именем "light/rgb1" - на него будет подписано наше устройство. Формат передаваемого цвета - HEX (#abcdef).

Готово! Теперь можно на телефоне задать цвет RGB подсветки и он будет послушно меняться на нашем устройстве. Аналогичным образом с небольшими изменениями в коде можно сделать датчик потопа с электро кранами, систему управления котлом и контролем температуры, охранную систему и многое другое. Лишь бы воображения хватило!

Для любителей анимации предлагаю версию с плавным изменением цвета:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <EEPROM.h>

// Подключение к wifi
const char *ssid = "xxxx"; // Имя wifi точки доступа
const char *pass = "xxxx"; // Пароль wifi точки доступа

// Подключение к mqtt брокеру
const char *mqtt_server = "m13.cloudmqtt.com"; // Имя сервера MQTT
const int mqtt_port = 14483; // Порт для подключения к серверу MQTT
const char *mqtt_user = "xxxx"; // Логи к серверу MQTT
const char *mqtt_pass = "xxxx"; // Пароль к серверу MQTT
const char *topic_name = "light/rgb1"; // Название mqtt топика для передачи цвета

// Конфигурация портов
const int r = 2; // GIPO управления каналом цвета R
const int g = 4; // GIPO управления каналом цвета R
const int b = 5; // GIPO управления каналом цвета B

// Конфигурация eprom памяти
const int eprom_r = 0; // Область в памяти для r
const int eprom_g = 1; // Область в памяти для g
const int eprom_b = 2; // Область в памяти для b

// Анимация
const int duration = 200; // продолжительность анимации
unsigned long previous_millis = 0;
unsigned long current_millis = 0;
const long interval = 10;
// Временное значения
float ca_r = 0;
float ca_g = 0;
float ca_b = 0;
// Коэффициенты для анимации
float ck_r = 0;
float ck_g = 0;
float ck_b = 0;
int animate_duration = duration; // Триггер запуска анимации

#define BUFFER_SIZE 100

int tm = 300;
float temp = 0;
int c_r = 0; // Текущий цвет R
int c_g = 0; // Текущий цвет G
int c_b = 0; // Текущий цвет B

WiFiClient wclient;
PubSubClient client(wclient);

void setup_wifi() {
  // Подключаемся к WiFi
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    animate();
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void reconnect() {
  // Повторять пока нет MQTT соединения
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Попытка подключения к MQTT
    if (client.connect("arduinoClient", mqtt_user, mqtt_pass)) {
      Serial.println("connected");
      // Подписываемся по топики (которые мы будем слушать от брокера)
      client.subscribe(topic_name);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  
  delay(10);
  
  // Конфигурируем GPIO при старте
  pinMode(r, OUTPUT);
  pinMode(g, OUTPUT);
  pinMode(b, OUTPUT);

  // Читаем последний заданный цвет из энергонезависимой памяти EEPROM
  // Необходимо вызвать функцию EEPROM.begin(size) каждый раз перед началом чтения или записи.
  // Размер (указывается в байтах) соответствует размеру данных, которые вы намереваетесь использовать в EEPROM.
  // Размер данных должен быть в диапазоне от 4 до 4096 байт.
  EEPROM.begin(4);
  c_r = EEPROM.read(eprom_r);
  c_g = EEPROM.read(eprom_g);
  c_b = EEPROM.read(eprom_b);
  EEPROM.commit();
  
  setColor();
  previous_millis = millis();
  current_millis = millis();

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  animate();
  
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

// Функция получения данных от MQTT сервера
void callback(char* topic, byte* payload, unsigned int length)
{
  // Присланное значение цвета
  String value = "";
  for (int i=0; i<length; i++) {
    value = value + ((char)payload[i]);
  }

  Serial.print("The topic: ");
  Serial.println(topic);

  // Проверяем из нужного ли нам топика пришли данные 
  if (String(topic) == String(topic_name))
  {
    if (value != "") {
      Serial.print("HEX color: ");
      Serial.println(value);

      // Конвертируем пришедшие данные из hex #00AAFF в отдельные integer цвета
      long number = (long) strtol( &value[1], NULL, 16);
      c_r = number >> 16;
      c_g = number >> 8 & 0xFF;
      c_b = number & 0xFF;

      setColor();

      // Запишем новые цвета в память, что бы при следующем включении цвет был такой же
      EEPROM.begin(4);
      EEPROM.write(eprom_r, c_r);
      EEPROM.write(eprom_g, c_g);
      EEPROM.write(eprom_b, c_b);
      EEPROM.commit();
    }
  }
}

void animate()
{
  current_millis = millis();
  
  if (animate_duration > 0 && current_millis - previous_millis >= interval) {
    previous_millis = current_millis;
    
    ca_r = ca_r + ck_r;
    ca_g = ca_g + ck_g;
    ca_b = ca_b + ck_b;
    
    if (ca_r < 0) ca_r = 0;
    if (ca_g < 0) ca_g = 0;
    if (ca_b < 0) ca_b = 0;

    animate_duration = animate_duration - 1;
    
    // Передадим значения на GPIO
    analogWrite(r, ca_r);
    analogWrite(g, ca_g);
    analogWrite(b, ca_b);

    Serial.print("Animate RGB(");
    Serial.print(ca_r);
    Serial.print(", ");
    Serial.print(ca_g);
    Serial.print(", ");
    Serial.print(ca_b);
    Serial.println(")");
  }
}

void setColor()
{
  animate_duration = duration; // Триггер запуска анимации
  
  ck_r = ((c_r - ca_r) / duration);
  ck_g = ((c_g - ca_g) / duration);
  ck_b = ((c_b - ca_b) / duration);
  
  Serial.print("Set color RGB(");
  Serial.print(c_r);
  Serial.print(", ");
  Serial.print(c_g);
  Serial.print(", ");
  Serial.print(c_b);
  Serial.println(")");
}

Отладку удобнее всего проводить на NodeMCU. При подключении обратите внимание на распиновку вашей платы. Не все контакты поддерживают цифровой выход. Используйте каналы с индексом D.

Печатная плата, если есть желание сорбрать в железе.

Файлы для скачивания:
* комментарии публикуются после модерации
16.04.2020 19:35
Добрый вечер, на сколько метров хватит хватит? RGB 5050 RGB 12v 14.4 Ватт на метр. Имеется 25-30 метров, вроде транзисторов таких с головой хватит при хорошем теплоотводе? Что лучше использовать для отвода? Спасибо больше)
03.05.2018 22:19
Спасибо, исправил ошибку.
28.04.2018 09:26
у Вас ошибочка во втором скетче
ca_r = ca_r + ck_r;
ca_g = ca_r + ck_r;
ca_b = ca_r + ck_r;
22.11.2017 13:50
Это идентификатор websocket клиента. В данном решении можно использовать любую строку.
22.11.2017 13:11
Добрый день. Подскажите, что такое "arduinoClient" в строке: if (client.connect("arduinoClient", mqtt_user, mqtt_pass)) .
Спасибо
04.10.2017 17:20
Все зависит от самих ошибок.
Может быть версия библиотек разная. Попробуйте скачать библиотеку publicent отсюда http://diytimes.ru/post/23 (файл в конце статьи). В обновленной версии изменился синтаксис.
Стоит проверить настройки ArduinoIDE, может быть выбран не тот тип процессора или еще что-то. По настройкам смотрите статью http://diytimes.ru/post/19 в ней все подробно расписано.
18.09.2017 19:30
Не могу скомпилировать данный скрипт много ошибок. В чем может быть проблема??