#include <WiFi.h>
#include <PubSubClient.h>
#include "GyverStepper.h"
// -------------------- НАСТРОЙКИ Wi-Fi И MQTT --------------------
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com"; // публичный брокер HiveMQ
const char* mqtt_topic_command = "shutter/command";
const char* mqtt_topic_status = "shutter/status";
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// -------------------- ПИНЫ ДРАЙВЕРА --------------------
#define STEP_PIN 4
#define DIR_PIN 16
#define EN_PIN 2
// -------------------- ПАРАМЕТРЫ МЕХАНИКИ --------------------
const float STEPS_PER_MM = 5.0; // сколько шагов на 1 мм (рассчитайте под вашу механику)
const float FULL_STROKE_MM = 1000.0; // полный ход шторы в мм
// Вычисляем положения в шагах
const long TOP_POS = 0; // полностью закрыто (верх)
const long BOTTOM_POS = (long)(FULL_STROKE_MM * STEPS_PER_MM); // полностью открыто (низ)
// -------------------- СОЗДАНИЕ ОБЪЕКТА STEPPER --------------------
GStepper<STEPPER2WIRE> stepper(200, STEP_PIN, DIR_PIN, EN_PIN);
// -------------------- ПЕРЕМЕННЫЕ СОСТОЯНИЯ --------------------
enum State { IDLE, MOVING_UP, MOVING_DOWN };
State state = IDLE;
long targetPercent = 0; // текущая целевая позиция в процентах (0-100)
long currentPercent = 0; // последняя известная позиция в процентах (для публикации)
unsigned long lastStatusPub = 0; // для периодической публикации статуса
const long STATUS_INTERVAL = 60000; // публиковать статус раз в минуту
// -------------------- ФУНКЦИИ ПРЕОБРАЗОВАНИЯ --------------------
// Проценты -> шаги
long percentToSteps(int percent) {
percent = constrain(percent, 0, 100);
return map(percent, 0, 100, TOP_POS, BOTTOM_POS);
}
// Шаги -> проценты
int stepsToPercent(long steps) {
steps = constrain(steps, TOP_POS, BOTTOM_POS);
return map(steps, TOP_POS, BOTTOM_POS, 0, 100);
}
// -------------------- ПУБЛИКАЦИЯ СТАТУСА --------------------
void publishStatus() {
if (!mqttClient.connected()) return;
int percent = stepsToPercent(stepper.getCurrent());
char buf[10];
sprintf(buf, "%d", percent);
mqttClient.publish(mqtt_topic_status, buf);
Serial.print("Status published: ");
Serial.println(percent);
}
// -------------------- ОБРАБОТКА MQTT КОМАНД --------------------
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
// Преобразуем payload в строку
char message[length + 1];
for (unsigned int i = 0; i < length; i++) {
message[i] = (char)payload[i];
}
message[length] = '\0';
Serial.println(message);
// Проверяем, что топик соответствует командному
if (strcmp(topic, mqtt_topic_command) != 0) return;
// Разбор команды
if (strcmp(message, "OPEN") == 0) {
// Полностью открыть (100%)
targetPercent = 100;
stepper.setTarget(percentToSteps(targetPercent));
stepper.enable();
state = MOVING_DOWN; // открытие = движение вниз (к BOTTOM_POS)
Serial.println("Command: OPEN");
}
else if (strcmp(message, "CLOSE") == 0) {
// Полностью закрыть (0%)
targetPercent = 0;
stepper.setTarget(percentToSteps(targetPercent));
stepper.enable();
state = MOVING_UP;
Serial.println("Command: CLOSE");
}
else if (strncmp(message, "SET", 3) == 0) {
// Пропускаем "SET" и любые пробелы
char* numPart = message + 3;
while (*numPart == ' ') numPart++; // пропускаем пробелы
int percent = atoi(numPart); // преобразуем число
percent = constrain(percent, 0, 100);
targetPercent = percent;
stepper.setTarget(percentToSteps(targetPercent));
stepper.enable();
// определяем направление
long curSteps = stepper.getCurrent();
long targetSteps = percentToSteps(targetPercent);
if (targetSteps > curSteps) state = MOVING_DOWN;
else if (targetSteps < curSteps) state = MOVING_UP;
else state = IDLE;
Serial.print("Command: SET ");
Serial.println(percent);
}
else if (strcmp(message, "UP") == 0) {
// Приоткрыть на 10% (увеличить процент)
int curPercent = stepsToPercent(stepper.getCurrent());
int newPercent = curPercent + 10;
if (newPercent > 100) newPercent = 100;
targetPercent = newPercent;
stepper.setTarget(percentToSteps(targetPercent));
stepper.enable();
state = MOVING_DOWN;
Serial.println("Command: UP (+10%)");
}
else if (strcmp(message, "DOWN") == 0) {
// Прикрыть на 10% (уменьшить процент)
int curPercent = stepsToPercent(stepper.getCurrent());
int newPercent = curPercent - 10;
if (newPercent < 0) newPercent = 0;
targetPercent = newPercent;
stepper.setTarget(percentToSteps(targetPercent));
stepper.enable();
state = MOVING_UP;
Serial.println("Command: DOWN (-10%)");
}
else if (strcmp(message, "STOP") == 0) {
// Аварийная остановка
stepper.brake();
state = IDLE;
Serial.println("Command: STOP");
}
else if (strcmp(message, "STATUS") == 0) {
// Запрос статуса
publishStatus();
}
}
// -------------------- ПОДКЛЮЧЕНИЕ К MQTT --------------------
void reconnectMQTT() {
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Shutter-";
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println("connected");
mqttClient.subscribe(mqtt_topic_command);
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// -------------------- НАСТРОЙКА --------------------
void setup() {
Serial.begin(115200);
// Подключение к Wi-Fi
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Настройка MQTT
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setCallback(mqttCallback);
// Настройка мотора
stepper.setRunMode(FOLLOW_POS); // режим следования к позиции
stepper.setMaxSpeed(400); // максимальная скорость (шагов/сек)
stepper.setAcceleration(300); // ускорение
stepper.autoPower(true); // автоотключение при остановке
// Важно! Предполагаем, что при старте штора вручную установлена в верхнее положение (закрыто)
stepper.setCurrent(TOP_POS);
stepper.disable();
Serial.println("System ready. Send MQTT commands to " + String(mqtt_topic_command));
Serial.print("Bottom position (steps): ");
Serial.println(BOTTOM_POS);
}
// -------------------- ОСНОВНОЙ ЦИКЛ --------------------
void loop() {
// Поддержка MQTT
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
// Обязательный вызов tick() для мотора
stepper.tick();
// Обработка состояний движения и публикация при достижении цели
switch (state) {
case MOVING_UP:
case MOVING_DOWN:
if (!stepper.tick()) { // tick() вернёт false, когда цель достигнута
state = IDLE;
stepper.disable(); // отключаем мотор (autoPower тоже это сделает)
Serial.println("Target reached");
publishStatus(); // публикуем новый статус
}
break;
case IDLE:
// Ничего не делаем
break;
}
// Периодическая публикация статуса (раз в минуту)
if (millis() - lastStatusPub > STATUS_INTERVAL) {
lastStatusPub = millis();
publishStatus();
}
}