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

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

Перевод: Максим Кузьмин (Cubewriter) Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Cat poo.png Черновик


Веб-переключатель, управляемый при помощи 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).

Esp8266-reflash-firmware.jpg

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

ESP8266 internet connected switch (EasyIoT Cloud MQTT API V1) - improved 1.jpg

Саму программу можно скачать с 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 подключен к кнопке – для локального управления веб-переключателем.

Web-switch-scheme.jpg

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

ESP8266 internet connected switch (EasyIoT Cloud MQTT API V1) - improved 1 1.png


ESP8266 internet connected switch (EasyIoT Cloud MQTT API V1) - improved 1 2.png

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

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

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

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

ESP8266 internet connected switch (EasyIoT Cloud MQTT API V1) - improved 2 1.png

См.также

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