Мини-кейс 2: Умная лампа
Умная лампа - RGB-матрица с слайдерами выбора оттенка, насыщенности и яркости.
Перед выполнением прочтите
Создание мини-проекта
Сделаем мини-проект с RGB модулем, который управляется через интерфейс RainMaker. Представим, что наш RGB модуль будет умной лампочкой, которая может менять цвет.
Нужно собрать устройство. К
GPIO 17
подключить модуль RGB матрицы Troyka контактомD1
. Распиновка платы на родительской странице "О ESP32". Питание для него от пина3.3v
.Светодиодная RGB матрица 4×4 (Troyka-модуль) Создаём новый файл в Arduino IDE с кодом:
#include <RMaker.h> #include <WiFi.h> #include <WiFiProv.h> #include <Adafruit_NeoPixel.h> // Библиотека для работы с RGB-матрицей #define SERVICE_NAME "PROV_2" // Имя устройства при подключении по bluetooth, нужно для настройки, когда плата ещё не настроена на WI-FI точку #define POP "abcd2" // ? Пароль для устройства при подключении по bluetooth #define BOOT_BTN_PIN 0 // GPIO пин кнопки BOOT #define RGB_MATRIX_PIN 17 // GPIO пина, к которому подключена RGB-матрица #define MATRIX_LED_DEFAULT_STATE false // Состояние матрицы при старте #define MATRIX_LED_COUNT 16 // Количество светодиодов в матрице #define DEFAULT_LED_HUE 0 // Значение слайдера hue для LED по умолчанию #define DEFAULT_LED_SATURATION 255 // Значение слайдера насышенности для LED по умолчанию #define DEFAULT_LED_BRIGHTNESS 64 // Значение слайдера яркости для LED по умолчанию Adafruit_NeoPixel ledMatrix = Adafruit_NeoPixel(MATRIX_LED_COUNT, RGB_MATRIX_PIN, NEO_GRB + NEO_KHZ800); // Создаём объект класса Adafruit_NeoPixel int matrix_led_gpio = RGB_MATRIX_PIN; // Переменная вашего светодиода bool matrix_led_state = MATRIX_LED_DEFAULT_STATE; // Переменная для хранения состояния вашего rgb-модуля int led_hue = DEFAULT_LED_HUE; // Значение оттенка цвета вашей лампы int led_saturation = DEFAULT_LED_SATURATION; // Значение насыщенности вашей ламы int led_brightness = DEFAULT_LED_BRIGHTNESS; // Значение яркости вашей ламы // Фреймворк предоставляет некоторые стандартные типы устройств, такие как выключатель, лампочка, вентилятор, датчик температуры. // Но вы также можете определить пользовательские устройства, используя объект базового класса 'Device', как показано здесь Device my_device("SmartLED", ESP_RMAKER_DEVICE_OTHER, &matrix_led_gpio); void sysProvEvent(arduino_event_t *sys_event) { switch (sys_event->event_id) { case ARDUINO_EVENT_PROV_START: // Для ESP32 Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on BLE\n", SERVICE_NAME, POP); printQR(SERVICE_NAME, POP, "ble"); // Для ESP32S2 //Serial.printf("\nProvisioning Started with name \"%s\" and PoP \"%s\" on SoftAP\n", service_name, pop); //printQR(service_name, pop, "softap"); break; } } // Функция обработки изменений void write_callback(Device *device, Param *param, const param_val_t val, void *priv_data, write_ctx_t *ctx) { const char *device_name = device->getDeviceName(); const char *param_name = param->getParamName(); if(strcmp(param_name, ESP_RMAKER_DEF_POWER_NAME) == 0) { // Если параметр с именем Power matrix_led_state = val.val.b; // Записываем значение состояния вашего светодиода в переменную, обратите внимание, что b - это тип bool Serial.printf("Received param %s = %s for %s\n", param_name, (matrix_led_state? "true" : "false"), device_name); if (!matrix_led_state) { ledMatrix.clear(); // Очистить матрицу ledMatrix.show(); // Отправить изменения на матрицу } else { uint32_t rgbcolor = ledMatrix.ColorHSV(led_hue, led_saturation, led_brightness); ledMatrix.fill(rgbcolor); // Закрасить матрицу ledMatrix.show(); // Отправить изменения на матрицу } param->updateAndReport(val); } else if (strcmp(param_name, ESP_RMAKER_DEF_HUE_NAME) == 0) { // Если параметр с именем Hue led_hue = map(val.val.i, 0, 360, 0, 65535); // Записываем значение в переменную, i - это тип int и перевести в нужный дапазон по инфе с библиотеки if (matrix_led_state) { // Если тумблер матрицы вкл, тогда установим новый hue uint32_t rgbcolor = ledMatrix.ColorHSV(led_hue, led_saturation, led_brightness); ledMatrix.fill(rgbcolor); // Закрасить матрицу ledMatrix.show(); // Отправить изменения на матрицу } Serial.printf("Received param %s = %d for %s\n", param_name, led_hue, device_name); param->updateAndReport(val); } else if (strcmp(param_name, ESP_RMAKER_DEF_SATURATION_NAME) == 0) { // Если параметр с именем Saturation led_saturation = val.val.i; // Записываем значение яркости вашей лампы в переменную, i - это тип int if (matrix_led_state) { // Если тумблер матрицы вкл, тогда установим новую яркость uint32_t rgbcolor = ledMatrix.ColorHSV(led_hue, led_saturation, led_brightness); ledMatrix.fill(rgbcolor); // Закрасить матрицу ledMatrix.show(); // Отправить изменения на матрицу } Serial.printf("Received param %s = %d for %s\n", param_name, led_saturation, device_name); param->updateAndReport(val); } else if (strcmp(param_name, ESP_RMAKER_DEF_BRIGHTNESS_NAME) == 0) { // Если параметр с именем Brightness led_brightness = val.val.i; // Записываем значение яркости вашей лампы в переменную, i - это тип int if (led_brightness == 0) { // На слайдере установлено 0 matrix_led_state = false; // Установить переменную состояния светодиода на выключен ledMatrix.clear(); // Очистить матрицу ledMatrix.show(); // Отправить изменения на матрицу my_device.updateAndReportParam(ESP_RMAKER_DEF_POWER_NAME, false); // Изменяем состояние тумблера на выкл в RainMaker Serial.printf("Toggle State to false.\n"); } else if (matrix_led_state) { // Если тумблер лампы вкл, тогда установим новую яркость uint32_t rgbcolor = ledMatrix.ColorHSV(led_hue, led_saturation, led_brightness); ledMatrix.fill(rgbcolor); // Изменить заливку ledMatrix.show(); // Отправить изменения на матрицу } Serial.printf("Received param %s = %d for %s\n", param_name, led_brightness, device_name); param->updateAndReport(val); } } void setup() { Serial.begin(115200); // Инициализируем скорость Serial pinMode(BOOT_BTN_PIN, INPUT); // Настраиваем пин кнопки BOOT на INPUT ledMatrix.begin(); // Инициализация RGB-матрицы if (!MATRIX_LED_DEFAULT_STATE) { // Если матрица изначально должна быть выключена ledMatrix.clear(); // Очистить матрицу ledMatrix.show(); // Отправить изменения на матрицу } Node my_node; // Объявляем Node my_node = RMaker.initNode("ESP RainMaker Node"); // Инициализируем Node // Создаём устройство для управления светодиодом my_device.addNameParam(); // Устнаваливаем имя устройства my_device.addPowerParam(MATRIX_LED_DEFAULT_STATE); // Устанавливаем стандатное значение my_device.assignPrimaryParam(my_device.getParamByName(ESP_RMAKER_DEF_POWER_NAME)); // Устанавливаем имя параметра Power // Создаём и добавляем слайдер hue Param hue_param(ESP_RMAKER_DEF_HUE_NAME, ESP_RMAKER_PARAM_HUE, value(DEFAULT_LED_HUE), PROP_FLAG_READ | PROP_FLAG_WRITE); // Указываем название создаваемого параметра, тип параметра, стандартное значение и задаём права hue_param.addBounds(value(0), value(360), value(1)); // Знач-е диапазона от 0 до 360, в данном случае праметр только для отображения, т.к. значение будет приходить всё-равно от 0 до 360. Если не указать будет от 0 и до 0 hue_param.addUIType(ESP_RMAKER_UI_HUE_SLIDER); // Устанавливаем тип отображения как HUE-слайдер my_device.addParam(hue_param); // Добавляем параметр устройству // Создаём и добавляем слайдер saturation Param saturation_param(ESP_RMAKER_DEF_SATURATION_NAME, ESP_RMAKER_PARAM_SPEED, value(DEFAULT_LED_SATURATION), PROP_FLAG_READ | PROP_FLAG_WRITE); // Указываем название создаваемого параметра, тип параметра, стандартное значение и задаём права saturation_param.addBounds(value(0), value(255), value(1)); // Значение от 0 до 255 saturation_param.addUIType(ESP_RMAKER_UI_SLIDER); // Устанавливаем тип отображения как слайдер my_device.addParam(saturation_param); // Добавляем параметр устройству // Создаём и добавляем слайдер brightness Param brightness_param(ESP_RMAKER_DEF_BRIGHTNESS_NAME, ESP_RMAKER_PARAM_SPEED, value(DEFAULT_LED_BRIGHTNESS), PROP_FLAG_READ | PROP_FLAG_WRITE); // Указываем название создаваемого параметра, тип параметра, стандартное значение и задаём права brightness_param.addBounds(value(0), value(255), value(1)); // Значение от 0 до 255 brightness_param.addUIType(ESP_RMAKER_UI_SLIDER); // Устанавливаем тип отображения как слайдер my_device.addParam(brightness_param); // Добавляем параметр устройству my_device.addCb(write_callback); // Устанавливаем функцию на обработчик my_node.addDevice(my_device); // Добавить девайс на ноду // Опционально RMaker.enableOTA(OTA_USING_PARAMS); // Включить беспроводное обновление RMaker.setTimeZone("Europe/Moscow"); // Установить часовой пояс https://rainmaker.espressif.com/docs/time-service.html RMaker.enableTZService(); // Кроме того, включите службу часового пояса и позвольте приложениям телефона установить соответствующий часовой пояс RMaker.enableSchedule(); // Включить расписание для нашего устройства RMaker.start(); // Запуск сервиса на устройстве WiFi.onEvent(sysProvEvent); WiFiProv.beginProvision(NETWORK_PROV_SCHEME_BLE, WIFI_PROV_SCHEME_HANDLER_FREE_BTDM, WIFI_PROV_SECURITY_1, POP, SERVICE_NAME); } void loop() { // Обрабатываем нажатие на кнопка BOOT if(digitalRead(BOOT_BTN_PIN) == LOW) { delay(100); // Для исключения дребезка на тактовой кнопке int startTime = millis(); // Время на номент нажатия кнопки while(digitalRead(BOOT_BTN_PIN) == LOW) delay(50); // Цикл, который создаёт паузу для ожидания отжатия кнопки int endTime = millis(); // Время на момент отжатия кнопки int pressTime = endTime - startTime; // Время, на которое кнопка была нажата // Условия по времени нажатия if (pressTime > 10000) { // Если кнопка была зажата на более 10 секунд, тогда сбросить всё Serial.printf("Reset to factory.\n"); RMakerFactoryReset(2); } else if (pressTime > 3000) { // Иначе если кнопка была зажата больше чем на 3 секунды, но меньше 10, тогда сбросить настройки WI-FI Serial.printf("Reset Wi-Fi.\n"); RMakerWiFiReset(2); } else { // В остальных случаях // ToDo } } delay(10); }
Добавьте библиотеку
Adafruit_NeoPixel
. Вы можете это сделать через пункт"Управление библиотками"
или скачать из интернета и добавить как zip библиотеку.В инструментах вы должны выбрать:
Плата: ESP32 Arduino > ESP32 Dev Module;
Partition Scheme: RainMaker.
И выберите порт платы. Загрузите прошивку. Никаких пинов для перевода контроллера в режим прошивки зажимать не нужно.
Далее дайдите в монитор порта, установить скорость
115200
. Выведется информация о том, как подключить плату к сервису. Там должна быть строка с QR-кодом и ссылка. Скопируйте ссылку и вставьте в адресную строку браузера.Откройте приложение
RainMaker
. В приложении RainMaker нажать на кнопку с иконкойПЛЮС
, запуститься сканер QR-кода.Отсканировать сканером QR-код с страницы с браузера.
В приложении
RainMaker
выбрать точку доступа для устройства, дать пароль и подождать подключения и всяких других настроек.На главной странице
RainMaker
появится или обновится ваш девайс.
Проблема с настрокой точки доступа:
Если необходимо сменить WI-FI точку для устройства, но в приложении не отображает список доступных точек, тогда необходимо на экране нажать на "Join Other Network
" и ввести название точки и пароль к ней самостоятельно.
Дополнительное задание:
Необходимо сделать, чтобы кнопка
BOOT
на плате отключала/включала (меняла состояние) нашу "Умную лампочку". Пример можно подсмотреть в прошлом мини-кейсе. * Важное замечание о том, что когда меняете состояние на устройстве, то ещё нужно в сервисе RainMaker также поменять состояние:my_device.updateAndReportParam(PARAM_NAME, value); // Изменяем состояние тумблера в RainMaker
Установить для девайса тип "Lightbulb" с картинкой, на которой изображена лампочка. * Подсказка, UI типы вы найдёте тут.
Необходимо для слайдера
Hue
поменять UI типHue Slider
наHue Circle
.Для параметра насыщенности и яркости поменять тип параметра на более подходящий -
Saturarion Slider
иBrightness Slider
.Сделать ещё вариант заливки матрицы с UI типом Dropdown (выпадающий список). Пример использования
Dropdown
есть в стандартном примереRMakerCustomAirCooler
. Первый режим (допустим с названиемFill
) дожен будет у вас закрашивать все светодиоды моментально как это было изначально, а второй (с именемColorWipe
) с помощью функцииColorWipe
будет закрашивать матрицу постепенно. Посмотрите какие функции доступны в библиотеке для управление матрицой.// Переделанная функция заполнения каждого сегмента из примера Амперки void ColorWipe(uint32_t color, uint8_t wait) { for (uint16_t i = 0; i < ledMatrix.numPixels(); i++) { // Заполняем текущий сегмент выбранным цветом //ledMatrix.setPixelColor(i, color); // Этот метод также работает ledMatrix.fill(color, i, 1); ledMatrix.show(); delay(wait); // Ждём } }
*Подсказка реализации:
Добавьте глобальную переменную modes с определёнными названиями режимов:
static const char* fill_modes[] = { "Fill", "ColorWipe" }; // Массив со значениями режимов
Добавьте define со значением режима по умолчанию, например равную 1:
#define DEFAULT_FILL_MODE 1 // Значение режима заливки по умолчанию
А также добавьте, глобальную переменную, в которую будете записывать значение режима при выборе:
int fill_mode = DEFAULT_FILL_MODE; // Переменая хрененения режим заливки
...
Добавьте в функцию
setup
новый параметр:Param mode_param("Mode", ESP_RMAKER_PARAM_MODE, value(fill_modes[DEFAULT_FILL_MODE - 1]), PROP_FLAG_READ | PROP_FLAG_WRITE); mode_param.addValidStrList(fill_modes, 2); // Подставляем массив возможных режимов и размер массива mode_param.addUIType(ESP_RMAKER_UI_DROPDOWN); // Устанавливаем тип UI my_device.addParam(mode_param);
...
В функции - обработчике изменений
write_callback
необходимо добавить обработку этого нового параметра, что если полученное значение == "Fill", тогда установить певый режим, иначе, если значение равно "ColorWipe, тогда установить второй режим:... } else if (strcmp(param_name, "Mode") == 0) { const char* mode = val.val.s; if (strcmp(mode, "Fill") == 0) { fill_mode = 1; } else if (strcmp(mode, "ColorWipe") == 0) { fill_mode = 2; } Serial.printf("Received param %s = %d for %s\n", param_name, mode, device_name); param->updateAndReport(val); }
...
В местах кода, где необходимо управлять матрицой (включать/выключать или изменять параметры) необходимо написать логическое условие какой режим будет выполнятся... первый (изначальный) или второй (новый - функция ColorWipe). В силу того, что эти строки будут повторяться, то советую оптимизировать код, создав одну функцию, которую будете вызывать в необходимых местах кода.
Last updated
Was this helpful?