Началось все как всегда с малого. Я просто купил самую простую ленту за 300р. на 12В и подключил ее блок питания через умную розетку. Розетка работает как и положено, в дом с Алисой интегрирована, все работает все отлично, но меня не устраивал факт того что есть Блок питания и целый отдельный блок в виде умной розетки. Помимо этого нет возможности управлять яркостью.
Материалы
Итак сегодня мы будем это менять! Начнем с бюджета.
- Светодиодная лента я взял 5м плотностью 60 диодов на метр, однако можно взять и другой длины и плотности
- Линейный преобразователь постоянного тока есть много разных конфигураций, этот меня привлек своим размером.
- Блок питания тут не забывайте учесть сколько ватт потребляет ваша лента (можно вычитать в описании)
- Транзистор тут сразу комплект из 5-и шт.
- Набор резисторов Большой набор, на большое количество номиналов. Нам надо лишь два.
- Микроконтроллер NodeMCU на базе ESP8266 можно взять такой, или для большей компактности такой та-же ESP8266 но на более компактной плате.
Итак суммарно наш бюджет составил 1168р. на момент написания этого материала. Однако всегда можно разобрать какой-нибудь старый прибор, если у вас таковой имеется и вам не придется заказывать, как минимум, партию резисторов из-за двух штук. Можно еще порассуждать про всякие термоусадки для упаковки всей конструкции, но этот момент я оставлю на ваше усмотрение, тут как говорится кто во что горазд.
Сборка
Прейдем к части сборки. Из приборов нам потребуется паяльник и мультиметр. Для начала ознакомимся со схемой:
Начать надо с начала, как бы странно это не прозвучало. Берем провод с вилкой и подключаем его ко входу нашего блока питания. Выход +12В с этого блока припаиваем к соответствующим контактам нашего линейного преобразователя. Далее нам надо отрегулировать наш преобразователь. Берем маленькую, плоскую отвертку, подключаем щупы мультиметра на соответствующие контакты выхода преобразователя, включаем режим измерения постоянного тока, и крутим подстроечный резистор пока не накрутим около 3.3В. Если все получилось, двигаемся дальше. На микроконтроллере есть несколько пинов (ног) на +3.3В и GND (он-же земля, он-же минус) подключайтесь к тем, которые вам удобнее (все операции с подключением и пайкой делаем только после того как отключите блок питания из розетки. Для управления транзистором я выбрал ногу D5, но вам совершенно ничего не мешает выбрать любую другую ногу с поддержкой ШИМ (ВАЖНО! Именно с помощью ШИМ мы и будем управлять яркостью света).

Выше приведено схематичное изображение NodeMCU, тут вы можете наблюдать какие из пинов поддерживают ШИМ (знак ~ возле пина).
Когда спаяем все в кучу, очень удобно вывести контакты для ленты в какие-либо быстрозажимные клеммы, вариантов масса, под винт, с зажимом, тут опять все на вашей фантазии.
Программирование
Следующий важный этап – программирование. Для начала убедитесь что ваша Arduino IDE умеет работать с микроконтроллерами ESP
Нам понадобятся две библиотеки ESP8266WiFi.h и PubSubClient.h Первая как не трудно догадаться позволяет нашей ESP8266 работать с Wi-Fi и должна уже присутствовать у вас после установки поддержки плат ESP, а вторая, это клиент MQTT брокера. (Что такое MQTT брокер и почему нужен именно он, я расскажу в отдельной статье)
Ищем указанные библиотеки в менеджере библиотек нашей IDE:

Без лишних слов вот весь код управления нашим устройством:
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include "PubSubClient.h"
#define LED D5
#define BUTTON_PIN D2
// Переменные WI-FI
const char* ssid = "ИМЯ ВАШЕЙ СЕТИ";
const char* password = "ПАРОЛЬ ВАШЕЙ СЕТИ";
WiFiClient espClient;
// Переменные MQTT
const char* mqtt_server = "АДРЕС СЕРВЕРА MQTT";
const int mqtt_port = ПОРТ СЕРВЕРА MQTT;
const char* powerTopic = "LD03032024/power";
const char* levelTopic = "LD03032024/level";
const char* powerStatusTopic = "LD03032024/powerStatus";
const char* levelStatusTopic = "LD03032024/levelStatus";
PubSubClient client(espClient);
// Переменные для работы с кнопкой
const int SHORT_PRESS_TIME = 500;
const int LONG_PRESS_TIME = 1000;
bool pressedOnce = false;
int lastBtnState = LOW;
unsigned long pressedTime = 0;
unsigned long oldPressedTime = 0;
unsigned long releasedTime = 0;
unsigned long oldReleasedTime = 0;
long pressDuration;
// Переменные общего назначения
bool ledOn = false; // Включен ли диод
uint8_t ledPower = 255; // Переменная хранит данные о яркости в диапазоне от 0 до 255
int inputLedPower; // Переменная хранит данные о яркости в процентах (относительное число, поступившее от брокера)
uint32_t tmr; // хранит состояние таймера millis() для подсчета прошедшего времени
bool needUpdateStatus = false; // требуется обновление статуса MQTT (статусы будут опубликованы в loop())
void setup(){
Serial.begin(115200);
pinMode(LED, OUTPUT);
pinMode(D1,INPUT_PULLUP);
pinMode(BUTTON_PIN, INPUT_PULLUP);
analogWriteFreq(21000);
digitalWrite(LED, 1);
ledOn = true;
initWiFi();
delay(1000);
Serial.print("RSSI: ");
Serial.println(WiFi.RSSI());
clientConnect();
delay(1000);
client.publish(powerStatusTopic, "1");
client.publish(levelStatusTopic, "100");
}
void clientConnect(){
if (client.connected())
{
return;
}
while (!client.connected())
{
client.setServer(mqtt_server, mqtt_port);
client.setCallback(MQTTcallback);
Serial.println("Connecting to MQTT...");
if (client.connect("LD23032024", "ПОЛЬЗОВАТЕЛЬ MQTT", "ПАРОЛЬ MQTT"))
{
Serial.println("connected");
client.subscribe(levelTopic);
client.subscribe(powerTopic);
}
else
{
Serial.print("failed with state ");
Serial.println(client.state());
delay(2000);
}
}
}
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
// Автоматическое переподключение при потере соединения
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
}
void buttonHandler(){
int currentState = digitalRead(BUTTON_PIN);
if(lastBtnState == HIGH && currentState == LOW){ // Кнопка нажата
pressedTime = millis();
oldPressedTime = millis();
}
if (lastBtnState == LOW && currentState == LOW){ // Кнопка удерживается
if (millis() - oldPressedTime > 1500){
ledPower -= 5;
delay(50);
analogWrite(LED, ledPower);
needUpdateStatus = true;
Serial.print("ledPower: ");
Serial.println(ledPower);
}
}
else if(lastBtnState == LOW && currentState == HIGH) { // Кнопка отпущена
releasedTime = millis();
if (pressedOnce){ // Это двойное нажатие
digitalWrite(LED, HIGH);
ledPower = 255;
pressedOnce = false;
Serial.println("Is double click");
}else{
pressedOnce = true;
}
pressDuration = releasedTime - pressedTime;
oldReleasedTime = millis();
oldPressedTime = millis();
needUpdateStatus = true;
}
if (pressedOnce && millis() - oldReleasedTime > 500){
if(pressDuration < SHORT_PRESS_TIME){
ledOn = !ledOn;
ledPower = 255;
digitalWrite(LED, ledOn);
Serial.println("Short");
}else if(pressDuration >= LONG_PRESS_TIME && pressDuration <1400){
//Serial.println("Long");
}
pressedOnce = false;
needUpdateStatus = true;
Serial.print("ledPower: ");
Serial.println(ledPower);
Serial.print("ledOn: ");
Serial.println(ledOn);
}
lastBtnState = currentState;
}
void MQTTcallback(char* topic, byte* payload, unsigned int length) {
char payload_string[length + 1];
Serial.print("Message arrived in topic: ");
Serial.println(topic);
memcpy(payload_string, payload, length);
payload_string[length] = '\0';
inputLedPower = atoi(payload_string);
if (strcmp(topic, powerTopic) == 0){
ledPower = 255 * inputLedPower;
inputLedPower = ledPower;
ledOn = inputLedPower;
}else if (strcmp(topic, levelTopic) == 0){
ledPower = (255 / 100) * inputLedPower;
ledOn = true;
}
analogWrite(LED, ledPower);
needUpdateStatus = true;
Serial.print("Message:");
Serial.println(inputLedPower);
Serial.print("length:");
Serial.println(length);
Serial.println();
Serial.println("-----------------------");
Serial.print("ledPower: ");
Serial.println(ledPower);
Serial.print("ledOn: ");
Serial.println(ledOn);
Serial.println("-----------------------");
}
void loop() {
if (tmr + 60000 < millis() ){
clientConnect();
Serial.println("Client reconnect");
tmr = millis();
}
if (needUpdateStatus){
if (ledOn) {
client.publish(powerStatusTopic, "1");
}else{
client.publish(powerStatusTopic, "0");
}
char ldPower[3];
utoa(ledPower * 100 / 255, ldPower, 10);
client.publish(levelStatusTopic, ldPower);
Serial.println("Client status updated");
Serial.print("ledPower: ");
Serial.println(ldPower);
Serial.print("ledOn: ");
Serial.println(ledOn);
needUpdateStatus = false;
}
client.loop();
buttonHandler();
}Вот собственно и весь код, теперь все что вам надо, это вставить этот код в ваш проект, подключить плату ESP8266 и загрузить код на плату. Дожидаемся окончания загрузки. Все, наше устройство готово, При включении питания, на D5 устанавливается 1 или HIGH (можно просто сказать что это логическая единица, или +3,3В) В любом из случаев это откроет наш транзистор, и светодиодная лента включится.
Думаю надо немного рассказать по принцип работы с MQTT. Одним из важных понятий тут является Топик это грубо выражаясь Канал или Контекст, или еще какое-либо похожее слово (более всего по смыслу подходит Топик). Наше устройство постоянно опрашивает MQTT брокер на предмет Нет ли чего нового для меня в этом конкретном топике и если что-то новое есть, устройство послушно выполняет. По сути название топика это строка, но в большинстве случаев есть некоторые рекомендации по формированию такой строки к примеру можно использовать такой шаблон Дом/Комната/Устройство/ВидКоманд в коде я указал четыре топика:
const char* powerTopic = "LD03032024/power"; const char* levelTopic = "LD03032024/level"; const char* powerStatusTopic = "LD03032024/powerStatus"; const char* levelStatusTopic = "LD03032024/levelStatus";
Названия топиков не особо соответствует шаблону, но это исключительно для теста. Вам же я рекомендую назвать топики более осознано. Топик это топик включения и выключения, топик powerlevel для установки уровня яркости, топики powerStatus и levelStatus работает в обратную сторону, т.е. наше устройство не принимает а наоборот публикует сообщения в данных топиках, сообщая нашему брокеру о своем состоянии.
Итак, как теперь стало очевидно, нам потребуется некий MQTT брокер. Конечно иметь свой сервер будет весьма удобно, и я опишу как сделать себе такой дома, но для его работы с Алисой нам потребуется реализовать целую кучу отдельных технологий, что в принципе потянет на отдельную ветку статей. Сегодня мы ограничимся каким-либо публичным сервисом MQTT, допустим wqtt.ru
После регистрации заходим в контрольную панель, переходим в настройки, и добавляем новое устройство:

Укажем имя и расположение устройства, а на следующем этапе перейдем к настройка органов управления:

Тип органа управления сначала укажем выключатель.

Топик управления это тот что у нас в коде отвечает за включение (по коду подает HIGH на пин D5)
void MQTTcallback(char* topic, byte* payload, unsigned int length)
{
...
if (strcmp(topic, powerTopic) == 0){
ledPower = 256 * inputLedPower;
...вот область кода отвечающая за обработку команд этого топика.
Добавим еще один орган управление с типом Регулятор:

Тут в принципе все аналогично предыдущему этапу, за исключением того что данный орган управления передает в топик не 1 и 0 а диапазон значений от 1 до 100, тут кстати наглядно можно рассказать о еще одном участке кода:
}else if (strcmp(topic, levelTopic) == 0){
ledPower = (255 / 100) * inputLedPower;
...
}С точки зрения человека, нам понятнее управление яркостью в диапазоне от 1 до 100… допустим это процент яркости. Но у плат ESP разрешение ШИМ несколько побольше…
(по описанию все источники утверждают о диапазоне от 0 до 1023, однако на моей плате при таком значении ШИМ работал неправильно, полностью игнорируя почти весь диапазон, а верно он стал работать только при диапазоне от 0 до 255. Очевидно китайцы что-то неверно нахимичили со значениями по умолчанию, думаю analogWriteRange(range) заботливо прописанная в методе setup() без труда решит этот вопрос, но что-то мне подсказывает что даже 256 значений будет вполне достаточно)
Так вот, получается нам нужно наши удобные проценты, превратить в диапазон от 0 до 255, в приведенном коде мы путем расчета соотношения именно это и делаем, главное не забыть что опубликовать данные в топике состояния надо в привычных пользователь (нам) значениях.
Есть еще один нюанс, дело в том что мой транзистор при включении через топик LD03032024/level издавал весьма неприятный писк. И дело тут вот в чем. По умолчанию на ШИМ частота прямоугольной волны составляет 1 кГц. Это значение входит в слышимый диапазон человека, собственно потому мы его и слышим. Дабы избежать этого неприятного явления в методе setup() укажите частоту работы ШИМ вне пределов слышимого диапазона:
void setup(){
...
analogWriteFreq(21000);
digitalWrite(LED, 1);
...
Я указал 21 кГц. Ну и финальный этап – обновление устройств Яндекс:


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

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