ESP8266:Примеры/Веб-переключатель, управляемый при помощи EasyIoT Cloud

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Веб-переключатель, управляемый при помощи EasyIoT Cloud[1]

Эта статья рассказывает, как собрать веб-переключатель, управляемый через WiFi при помощи веб-сервиса EasyIoT Cloud. Для этого проекта нам понадобятся модуль с чипом ESP8266, твердотельное реле и программа IDE Arduino. Данные для точки доступа, к которой будет подключаться веб-переключатель, можно задать через браузерный интерфейс.

Управлять веб-переключателем можно, во-первых, дистанционно – через браузерный веб-интерфейс (с компьютера, планшета или мобильного телефона) или через официальное приложение.

Веб-переключатель подключен к интернету через WiFi. Кроме того, переключателем можно управлять локально, при помощи кнопки.

Данное руководство написано на примере последней версии MQTT-брокера EasyIoT Cloud. Эта версия имеет два улучшения по сравнению с предыдущей. Во-первых, для обновления состояния в интерфейсе используется информация от переключателя. То есть, если соединение с переключателем разорвано, состояние в пользовательском интерфейсе обновляться не будет. Во-вторых, данные для точки доступа, к которой будет подключаться веб-переключатель, теперь можно задать через браузер, открытый на смартфоне или компьютере.

Этот веб-переключатель работает по принципу «подключай и работай» – с настройками EasyIoT Cloud возиться не нужно, т.к. они выставляются автоматически.

Внимание!

ВНИМАНИЕ! Работая с этим проектом, вы будете иметь дело с СЕТЕВЫМ напряжением. ЭТО ОЧЕНЬ ОПАСНО!

Если у вас недостаточно опыта или квалификации для работы с СЕТЕВЫМ напряжением, настоятельно рекомендую не работать с этим проектом!

Не беритесь за этот проект, не имея ДОСТАТОЧНО ЗНАНИЙ о том, как работают цепи, использующие СЕТЕВОЕ напряжение!

Не беритесь за этот проект, если на линии с СЕТЕВЫМ напряжением не стоит правильный ПРЕДОХРАНИТЕЛЬ!

Максимальная сила тока для твердотельного реле в этом проекте – 2 ампера. Подходит только для комнатного освещения.

Введение

[Видео]

Цепь веб-переключателя в законченном виде

Необходимые компоненты

  • Один модуль с ESP8266
  • Одно твердотельное реле (2 ампера, 240 вольт)
  • Один понижающий преобразователь (на входе переменный ток, на выходе – постоянный; на выходе 3.3 вольта; 600 мА)
  • Одна кнопка
  • Один транзистор типа NPN (TO-92, 2N2222)
  • Один электролитический конденсатор на 1000 мкФ
  • Один резистор на 1 кОм
  • Один резистор на 47 кОм

Настройка EasyIoT Cloud

Настраивать EasyIoT Cloud не нужно, просто зарегистрируйтесь в сервисе EasyIoT Cloud. Чтобы получить доступ к веб-переключателю (будь то Android-приложение или браузерный интерфейс), понадобятся имя пользователя и пароль. Веб-переключатель будет автоматически добавлен в EasyIoT Cloud и появитя в браузерном интерфейсе или интерфейсе Android-приложения после подключения к питанию.

Программирование

Программа для веб-переключателя написана в IDE Arduino. Вам нужно будет установить в нее аддон для ESP8266, и о том, как это сделать, можно прочесть тут.

Дальнейшие действия зависят от того, какой модуль ESP8266 вы используете. Если ваш модуль не оснащен адаптером USB-Serial, то его нужно будет подключить. Схему подключения к адаптеру смотрите на картинке ниже. Не забудьте настроить его на 3,3 вольта. Иногда адаптеры не обеспечивают ESP8266 достаточным напряжением. В этом случае ESP8266 нужно питать только от внешнего источника питания (т.е. нужно отключить от адаптера линию Vcc).

Если вы используете адаптерную плату вроде той, что выпускает команда NodeMCU, просто подключите ее к компьютеру через USB-кабель. Этот способ рекомендуется для новичков.

Саму программу можно скачать с GitHub. Вам также понадобится библиотека для клиента MQTT; она называется «esp-mqtt», и ее можно скачать отсюда.

Скачав, установите ее в папку «libraries» IDE Arduino. Программа использует MQTT-брокер, встроенный в EasyIoT Cloud.

В программе нужно будет поменять две строчки, показанные ниже. Это логин и пароль к аккаунту в EasyIoT Cloud. Вместо "usrname" и "pssw" впишите собственные логин и пароль.

#define EIOTCLOUD_USERNAME "usrname"
#define EIOTCLOUD_PASSWORD "pssw"

Кроме того, прямо в программе можно поменять данные для точки доступа (SSID и пароль), но это необязательно, потому что их также можно поменять в веб-интерфейсе EasyIoT Cloud.

Вначале программа считывает настройки EEPROM. Затем пытается подключиться к точке доступа. Если подключиться к точке доступа не удается, через 10 секунд программа переключает ESP8266 в режим точки доступа, чтобы вы могли указать данные для точки доступа, к которой должен подключиться ESP8266. Если подключение к точке доступа прошло успешно, программа проверяет ID модуля. Если ID модуля равно «0», это значит, что переключатель не настроен на EasyIoT Cloud. В этом случае программа добавляет модуль в EasyIoT Cloud и сохраняет ID модуля в настройки EEPROM. После успешной настройки модуль подписывается на топик, содержащий данные о статусе переключателя.

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

//#define DEBUG

#define AP_SSID     "Geek"
#define AP_PASSWORD "G33k"  


#define EIOTCLOUD_USERNAME "usrname"
#define EIOTCLOUD_PASSWORD "psswd"


// создаем экземпляр класса MQTT:
#define EIOT_CLOUD_ADDRESS "cloud.iot-playground.com"
MQTT myMqtt("", EIOT_CLOUD_ADDRESS, 1883);

#define CONFIG_START 0
#define CONFIG_VERSION "v01"

#define AP_CONNECT_TIME 10 //s

char ap_ssid[16];
char ap_pass[16];

WiFiServer server(80);


struct StoreStruct {
  // это для проверки того, ваши ли это настройки:
  char version[4];
  // переменные для настроек:
  uint moduleId;  // ID модуля
  bool state;     // состояние
  char ssid[20];
  char pwd[20];
} storage = {
  CONFIG_VERSION,
  // модуль по умолчанию - «0»
  0,
  0, // выкл.
  AP_SSID,
  AP_PASSWORD
};

bool stepOk = false;
int buttonState;

boolean result;
String topic("");
String valueStr("");

boolean switchState;
const int buttonPin = 0;  
const int outPin = 2;  
int lastButtonState = LOW; 
int cnt = 0;

void setup() 
{  
#ifdef DEBUG
  Serial.begin(115200);
#endif
  delay(1000);

  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH);
  
  EEPROM.begin(512);

  switchState = false;  

  loadConfig();
  
  pinMode(BUILTIN_LED, OUTPUT); 
  digitalWrite(BUILTIN_LED, !switchState); 
#ifndef DEBUG
  pinMode(outPin, OUTPUT); 
  digitalWrite(outPin, switchState);
#endif  


#ifdef DEBUG
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");  //  "Подключение к "
  Serial.println(AP_SSID);
#endif  
  WiFi.begin(storage.ssid, storage.pwd);

  int i = 0;
  
  while (WiFi.status() != WL_CONNECTED && i++ < (AP_CONNECT_TIME*2) ) {
    delay(500);
#ifdef DEBUG
    Serial.print(".");
#endif
  }

  if (!(i < (AP_CONNECT_TIME*2)))
  {
    AP_Setup();
    AP_Loop();
    ESP.reset();
  }

#ifdef DEBUG
  Serial.println("");
  Serial.println("WiFi connected");  //  "Подключились к WiFi"
  Serial.println("IP address: ");    //  "IP-адрес: "
  Serial.println(WiFi.localIP());

  Serial.println("Connecting to MQTT server");  
             //  "Подключение к MQTT-серверу"
#endif
  // задаем ID клиента;
  // генерируем название клиента на основе MAC-адреса 
  // и последних 8 бит счетчика микросекунд;
  String clientName;
  uint8_t mac[6];
  WiFi.macAddress(mac);
  clientName += macToStr(mac);
  clientName += "-";
  clientName += String(micros() & 0xff, 16);
  myMqtt.setClientId((char*) clientName.c_str());

#ifdef DEBUG
  Serial.print("MQTT client id:");  //  "ID MQTT-клиента"
  Serial.println(clientName);
#endif
  // настраиваем функции обратного вызова:
  myMqtt.onConnected(myConnectedCb);
  myMqtt.onDisconnected(myDisconnectedCb);
  myMqtt.onPublished(myPublishedCb);
  myMqtt.onData(myDataCb);
  
  //////Serial.println("connect mqtt...");
                   //  "Подключение к MQTT..."
  myMqtt.setUserPwd(EIOTCLOUD_USERNAME, EIOTCLOUD_PASSWORD);  
  myMqtt.connect();

  delay(500);

#ifdef DEBUG
  Serial.print("ModuleId: ");  //  "ID модуля: "
  Serial.println(storage.moduleId);
#endif

  // если нужно, создаем модуль: 
  if (storage.moduleId == 0)
  {
    // создаем модуль:
#ifdef DEBUG
    Serial.println("create module: /NewModule");
               //  "Создание модуля: /NewModule"
#endif
    myMqtt.subscribe("/NewModule");
    waitOk();
      
    // создаем параметр Sensor.Parameter1:
#ifdef DEBUG    
    Serial.println("/"+String(storage.moduleId)+ "/Sensor.Parameter1/NewParameter");    
#endif
    myMqtt.subscribe("/"+String(storage.moduleId)+ "/Sensor.Parameter1/NewParameter");
    waitOk();

    // задаем параметр IsCommand:
#ifdef DEBUG    
    Serial.println("/"+String(storage.moduleId)+ "/Sensor.Parameter1/IsCommand");    
#endif
    valueStr = "true";
    topic  = "/"+String(storage.moduleId)+ "/Sensor.Parameter1/IsCommand";
    result = myMqtt.publish(topic, valueStr);
    delay(100);

    // задаем тип модуля:
#ifdef DEBUG        
    Serial.println("Set module type"); 
               //  "Настройка типа модуля: /NewModule"

#endif
    valueStr = "MT_DIGITAL_OUTPUT";
    topic  = "/" + String(storage.moduleId) + "/ModuleType";
    result = myMqtt.publish(topic, valueStr);
    delay(100);

    // сохраняем ID нового модуля:
    saveConfig();
  }

  //switchState = storage.state;
  //storage.state = !storage.state;

  storage.state = switchState;

  Serial.println("Suscribe: /"+String(storage.moduleId)+ "/Sensor.Parameter1"); 
  myMqtt.subscribe("/"+String(storage.moduleId)+ "/Sensor.Parameter1");
}

void loop() {
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
#ifdef DEBUG        
    Serial.print(".");
#endif
  }


  int reading = digitalRead(buttonPin);

  if (reading != lastButtonState) {
    if (reading == LOW)
    {
      switchState = !switchState;            
#ifdef DEBUG
      Serial.println("button pressed");
                 //  "Кнопка нажата"
#endif
    }

#ifdef DEBUG
      Serial.println("set state: ");
                 //  "Задано состояние: "
      Serial.println(valueStr);
#endif

    lastButtonState = reading;
  }
  
  digitalWrite(BUILTIN_LED, !switchState); 
#ifndef DEBUG  
  digitalWrite(outPin, switchState); 
#endif
  if (switchState != storage.state)
  {
    storage.state = switchState;
    // сохраняем состояние кнопки:
    saveConfig();

    valueStr = String(switchState);
    topic  = "/"+String(storage.moduleId)+ "/Sensor.Parameter1";
    result = myMqtt.publish(topic, valueStr, 0, 1);    
#ifdef DEBUG
      Serial.print("Publish ");  //  "Публикация "

      Serial.print(topic);
      Serial.print(" ");
      Serial.println(valueStr);
#endif  
    delay(200);
  }  
}

void waitOk()
{
  while(!stepOk)
    delay(100);
 
  stepOk = false;
}

String macToStr(const uint8_t* mac)
{
  String result;
  for (int i = 0; i < 6; ++i) {
    result += String(mac[i], 16);
    if (i < 5)
      result += ':';
  }
  return result;
}


/*
 * 
 */
void myConnectedCb() {
#ifdef DEBUG
  Serial.println("connected to MQTT server");
             //  "Подключение к MQTT-серверу"
#endif
  if (storage.moduleId != 0)
    myMqtt.subscribe("/" + String(storage.moduleId) + "/Sensor.Parameter1");
}

void myDisconnectedCb() {
#ifdef DEBUG
  Serial.println("disconnected. try to reconnect...");
             //  "Соединение разорвано. Пытаемся переподключиться..."
#endif
  delay(500);
  myMqtt.connect();
}

void myPublishedCb() {
#ifdef DEBUG  
  Serial.println("published.");  //  "Опубликовано."

#endif
}

void myDataCb(String& topic, String& data) {  
#ifdef DEBUG  
  Serial.print("Received topic:");  //  "Полученный топик:"
  Serial.print(topic);
  Serial.print(": ");
  Serial.println(data);
#endif
  if (topic ==  String("/NewModule"))
  {
    storage.moduleId = data.toInt();
    stepOk = true;
  }
  else if (topic == String("/"+String(storage.moduleId)+ "/Sensor.Parameter1/NewParameter"))
  {
    stepOk = true;
  }
  else if (topic == String("/"+String(storage.moduleId)+ "/Sensor.Parameter1"))
  {
    switchState = (data == String("1"))? true: false;
#ifdef DEBUG      
    Serial.print("switch state received: ");
             //  "Полученное состояние переключателя: "
    Serial.println(switchState);
#endif
  }
}

void loadConfig() {
  // чтобы убедиться, что настройки есть, и это ВАШИ настройки!
  // если настройки найдены не будут, будут использованы 
  // настройки по умолчанию:
  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2])
    for (unsigned int t=0; t<sizeof(storage); t++)
      *((char*)&storage + t) = EEPROM.read(CONFIG_START + t);
}

void saveConfig() {
  for (unsigned int t=0; t<sizeof(storage); t++)
    EEPROM.write(CONFIG_START + t, *((char*)&storage + t));

  EEPROM.commit();
}



void AP_Setup(void){
  Serial.println("setting mode");  //  "Режим настройки"

  WiFi.mode(WIFI_AP);

  String clientName;
  clientName += "Thing-";
  uint8_t mac[6];
  WiFi.macAddress(mac);
  clientName += macToStr(mac);
  
  Serial.println("starting ap");  //  "Запуск точки доступа"
  WiFi.softAP((char*) clientName.c_str(), "");
  Serial.println("running server");  //  "Запуск сервера"
  server.begin();
}

void AP_Loop(void){

  bool  inf_loop = true;
  int  val = 0;
  WiFiClient client;

  Serial.println("AP loop");

  while(inf_loop){
    while (!client){
      Serial.print(".");
      delay(100);
      client = server.available();
    }
    String ssid;
    String passwd;
    // считываем первую строчку запроса:
    String req = client.readStringUntil('\r');
    client.flush();

    // подготавливаем ответ, начинаем со стандартного заголовка:
    String s = "HTTP/1.1 200 OK\r\n";
    s += "Content-Type: text/html\r\n\r\n";
    s += "<!DOCTYPE HTML>\r\n<html>\r\n";

    if (req.indexOf("&") != -1){
      int ptr1 = req.indexOf("ssid=", 0);
      int ptr2 = req.indexOf("&", ptr1);
      int ptr3 = req.indexOf(" HTTP/",ptr2);
      ssid = req.substring(ptr1+5, ptr2);
      passwd = req.substring(ptr2+10, ptr3);    
      val = -1;
    }

    if (val == -1){
      strcpy(storage.ssid, ssid.c_str());
      strcpy(storage.pwd, passwd.c_str());
      
      saveConfig();
      //storeAPinfo(ssid, passwd);
      s += "Setting OK";
      s += "<br>"; // переходим к следующей строчке
      s += "Continue / reboot";
      inf_loop = false;
    }

    else{
      String content="";
      // показываем значения всех входных аналоговых контактов:
      content += "<form method=get>";
      content += "<label>SSID</label><br>";
      content += "<input  type='text' name='ssid' maxlength='19' size='15' value='"+ String(storage.ssid) +"'><br>";
      content += "<label>Password</label><br>";
      content += "<input  type='password' name='password' maxlength='19' size='15' value='"+ String(storage.pwd) +"'><br><br>";
      content += "<input  type='submit' value='Submit' >";
      content += "</form>";
      s += content;
    }
    
    s += "</html>\n";
    // отправляем ответ клиенту:
    client.print(s);
    delay(1);
    client.stop();
  }
}

Самый простой способ проверить программу – воспользоваться адаптерной платой NodeMCU ESP8266. Для нее не нужно никаких предварительных настроек – просто подключите ее к USB-порту компьютера. Если вы используете эту плату, вы также можете убрать знак комментария у строчки #define DEBUG, расположенной в начале программы; это позволит видеть отладочные сообщения. Кнопка FLASH на ESP8266 будет служить кнопкой ручного изменения состояния переключателя. Состояние светодиода будет отображаться при помощи встроенного светодиода.

[Видео]

Схема

В нашем случае используется модуль ESP-01, но вы можете использовать любой другой модуль с чипом ESP8266. Контакт GPIO2 подключен к транзистору NPN, чтобы управлять твердотельным реле. Максимальная сила тока для нашего твердотельного реле – это 2 ампера. Этого хватит для комнатного освещения, но для проектов, где требуется больше питания (к примеру, для нагревателей) – вряд ли.

Для подачи питания используется понижающий преобразователь, снижающий напряжение до 3,3 вольт. Очень важно поставить на 3,3-вольтовую линию конденсатор на 1000 мкФ. Если забыть о нем, то цепь работать не будет.

Контакт GPIO0 подключен к кнопке – для локального управления веб-переключателем.

После включения питания переключатель будет автоматически добавлен в EasyIoT Cloud, а также отобразится в браузерном интерфейсе и Android-приложении.


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

Настройка веб-переключателя

Будучи включенным, веб-переключатель пытается подключиться к точке доступа. Если спустя 10 секунд подключиться к точке доступа так и не удается, веб-переключатель переходит в режим точки доступа. Возьмите мобильный телефон и поищите точку доступа с названием «Thing-xx-xx-xx-xx», где «xx-xx-xx-xx» – это набор случайных цифр. Подключитесь к этой точке доступа, а затем введите в браузере IP-адрес «192.168.4.1».

Откроется страница, где вы сможете настроить название SSID и пароль к ней. Указав нужные данные, кликните по кнопке «Submit». После этого веб-переключатель перезагрузится и подключится к указанной точке доступа.

См.также

Внешние ссылки