背景:
自用,实现使用ESP01S模块和继电器构建自定义功能的Wi-Fi控制器,后续可用于自动窗帘、自动给植物浇水,自动开关灯,自动开关风扇等
所需材料:
- ESP01S模块
- 继电器模块(这里网上卖的有个坑,需要注意,下文提及)
- 杜邦线(用于连接模块)
- USB转串口模块
步骤:
1. 准备工作:
- 获取所需的组件和材料。
- 安装Arduino IDE并在其中安装ESP8266支持。
- 连接ESP01S模块和继电器模块,确保连接正确。通常网上卖的,GPIO 0用于控制继电。
- 这里遇到的坑,需要提及下的是网上很多店家把光耦贴反了,就拿出来发货,导致继电器功能失效,要把光耦位置贴回来,跟上图原理图一致,即1脚跟R6导线一端连在一起。
2. 编写Arduino代码:
#include <ESP8266WiFi.h> //默认,加载WIFI头文件
#include "PubSubClient.h" //默认,加载MQTT库文件
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <AceButton.h>
#include <ESP8266httpUpdate.h>
using namespace ace_button;
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
const char* mqtt_server = "bemfa.com"; //默认,MQTT服务器
const int mqtt_server_port = 9501; //默认,MQTT服务器
//*****可以修改的地方******//
String aptype = "002"; //001插座类型,002灯类型,003风扇类型,004传感器,005空调,006开关,009窗帘
String verSion = "1"; //1是mqtt协议,3是tcp协议,5是mqtt V2 版本,7是 tcp V2 版本
String adminID = ""; //默认空即可。企业id,企业用户可配置,用户会自动绑定到该企业下,获取id方法见接入文档5.17节
const int LED_Pin = 0; //单片机控制的继电器引脚,或者LED引脚值,可自行修改,其他开发板,修改为自己的引脚,例如esp8266-01修改为const int LED_Pin = 0;
const int LedBlink = 2; //指示灯引脚,可自行修改,如果没有指示灯,建议删除指示灯相关代码
const int buttonPin = 3; //定义按钮引脚,可自行修改
int failCount = 3; //定义失败连接次数
bool ledState = true; //led 状态
String upUrl = "http://bin.bemfa.com/b/1BcZjQyYTFjMzVjYjI4NGQzZTlhNDk3Yjk0NDFiN2QwZDI=CC3857002.bin"; //OTA固件链接,请替换为自己的固件链接,如果接收到msg=update,开始执行固件升级
//**********************//
String topicMac = "";
int httpCode = 0;
String UID = "";
String TOPIC = "";
#define HOST_NAME "bemfa"
char config_flag = 0;
#define MAGIC_NUMBER 0xAA
/**
* 结构体,用于存储配网信息
*/
struct config_type {
char stassid[32];
char stapsw[64];
char cuid[40];
char ctopic[32];
uint8_t reboot;
uint8_t magic;
};
config_type config;
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
//灯光函数及引脚定义
void turnOnLed();
void turnOffLed();
void doSmartconfig();
void saveConfig();
void initWiFi();
void loadConfig();
void restoreFactory();
void waitKey();
//按钮配置接口
AceButton ledButton(buttonPin);
//按键处理程序
void handleEvent(AceButton* button, uint8_t eventType,
uint8_t) {
String Msgstring;
String topicString;
switch (eventType) {
//当短按时
case AceButton::kEventReleased:
Serial.println(F("Button: Pressed"));
ledState = !ledState; //改变led状态
digitalWrite(LED_Pin, ledState); //写入状态
if (ledState == true) {
Serial.println("low press off");
Msgstring = "off";
} else {
Serial.println("low press on");
Msgstring = "on";
}
topicString = TOPIC + "/up";
client.publish(topicString.c_str(), Msgstring.c_str()); //推送消息
break;
//当长按时
case AceButton::kEventLongPressed:
Serial.println(F("Button: Long Pressed"));
Serial.println("Restore Factory....... ");
config.magic = 0x00;
config.reboot = 0;
strcpy(config.stassid, "");
strcpy(config.stapsw, "");
strcpy(config.cuid, "");
strcpy(config.ctopic, "");
saveConfig();
doSmartconfig();
int num = 0;
while (WiFi.status() != WL_CONNECTED && num < 120) { //检查是否连接成功
delay(500);
num = num + 1;
Serial.print(".");
}
getUid(topicMac, true);
break;
}
}
//当升级开始时,打印日志
void update_started() {
Serial.println("CALLBACK: HTTP update process started");
}
//当升级结束时,打印日志
void update_finished() {
Serial.println("CALLBACK: HTTP update process finished");
}
//当升级中,打印日志
void update_progress(int cur, int total) {
Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total);
digitalWrite(LedBlink, HIGH); //指示灯引脚
delay(100);
digitalWrite(LedBlink, LOW); //指示灯引脚
delay(100);
}
//当升级失败时,打印日志
void update_error(int err) {
Serial.printf("CALLBACK: HTTP update fatal error code %d\n", err);
}
/**
* 固件升级函数
* 在需要升级的地方,加上这个函数即可,例如setup中加的updateBin();
* 原理:通过http请求获取远程固件,实现升级
*/
void updateBin() {
Serial.println("start update");
WiFiClient UpdateClient;
ESPhttpUpdate.onStart(update_started); //当升级开始时
ESPhttpUpdate.onEnd(update_finished); //当升级结束时
ESPhttpUpdate.onProgress(update_progress); //当升级中
ESPhttpUpdate.onError(update_error); //当升级失败时
t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
switch (ret) {
case HTTP_UPDATE_FAILED: //当升级失败
Serial.println("[update] Update failed.");
break;
case HTTP_UPDATE_NO_UPDATES: //当无升级
Serial.println("[update] Update no Update.");
break;
case HTTP_UPDATE_OK: //当升级成功
Serial.println("[update] Update ok.");
break;
}
}
/**
* mqtt回调函数
*/
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
String Mqtt_Buff = "";
for (int i = 0; i < length; i++) {
Mqtt_Buff += (char)payload[i];
}
Serial.print(Mqtt_Buff);
Serial.println();
// Switch on the LED if an 1 was received as first character
if (Mqtt_Buff == "on") { //如果接收字符on,亮灯
turnOnLed(); //开灯函数
} else if (Mqtt_Buff == "off") { //如果接收字符off,亮灯
turnOffLed(); //关灯函数
} else if (Mqtt_Buff == "update") {
Serial.println("[update] Update Start......");
updateBin();
}
Mqtt_Buff = "";
}
/**
* 重新连接
*/
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(UID.c_str())) {
Serial.println("connected");
client.subscribe(TOPIC.c_str()); //订阅
failCount = 0;
} else {
failCount = failCount + 1;
if (failCount>2){
delayRestart(0);
}
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(2000);
}
}
}
/**
* 按键中断,仅在配网失败时使用
*/
static unsigned long buttonLastMillis = 0; //时间戳,用于计算防抖
void IRAM_ATTR checkSwitch() {
unsigned long newMillis = millis(); //获取当前时间戳
if (newMillis - buttonLastMillis > 30) { //检测短按,是否大于30ms
Serial.println("low press !!!!!!!!");
ledState = !ledState; //改变led状态
digitalWrite(LED_Pin, ledState); //写入状态
}
buttonLastMillis = newMillis; //重新计算防抖动
}
void getUid(String mac, bool reConfig) {
if (strcmp(config.cuid, "") == 0 || reConfig) {
http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://api.bemfa.com/api/device/v1/airkiss/?topic=" + mac + "&version=" + verSion + "&ad=" + adminID);
httpCode = http_bemfa_HTTPClient.GET();
if (httpCode > 0) {
String payload = http_bemfa_HTTPClient.getString();
//json数据解析
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
}
String code = doc["code"];
if (code == "5723200") {
String docUID = doc["uid"];
strcpy(config.cuid, docUID.c_str());
strcpy(config.ctopic, mac.c_str());
saveConfig();
} else {
Serial.println(" config ERROR.........");
}
http_bemfa_HTTPClient.end();
}
}
if (strcmp(config.ctopic, "") == 0) {
TOPIC = mac;
} else {
TOPIC = config.ctopic;
}
UID = config.cuid;
}
/**
* 存储配网信息
*/
void saveConfig() {
int rand_key;
uint8_t mac[6];
WiFi.macAddress(mac);
config.reboot = 0;
EEPROM.begin(256);
uint8_t* p = (uint8_t*)(&config);
for (int i = 0; i < sizeof(config); i++) {
EEPROM.write(i, *(p + i));
}
EEPROM.commit();
}
Ticker delayTimer;
void delayRestart(float t) {
delayTimer.attach(t, []() {
ESP.restart();
});
}
/**
* airkiss配网
*/
void doSmartconfig() {
Serial.print(" Smartconfig begin,Waiting for WeChat Config.....");
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.stopSmartConfig();
WiFi.beginSmartConfig();
int cnt = 0;
while (!WiFi.smartConfigDone()) {
digitalWrite(LedBlink, HIGH); //指示灯引脚
delay(150);
digitalWrite(LedBlink, LOW); //指示灯引脚
delay(150);
if (WiFi.smartConfigDone()) {
strcpy(config.stassid, WiFi.SSID().c_str());
strcpy(config.stapsw, WiFi.psk().c_str());
config.magic = 0xAA;
saveConfig();
}
cnt++;
if (cnt >= 600) {
delayRestart(0);
}
}
digitalWrite(LedBlink, LOW); //指示灯引脚
Serial.println("Smartconfig ok");
}
/**
* 初始化wifi信息,并连接路由器网络
*/
void initWiFi() {
if (WiFi.status() != WL_CONNECTED) {
WiFi.disconnect(); //断开连接
WiFi.mode(WIFI_STA); //STA模式
WiFi.begin(config.stassid, config.stapsw); //连接路由器
}
int num = 0;
while (WiFi.status() != WL_CONNECTED && num < 120) { //检查是否连接成功
delay(500);
num = num + 1;
Serial.print(".");
}
Serial.println("wifi config ok");
}
/**
* 加载存储的信息,并检查是否进行了反复5次重启恢复出厂信息
*/
uint8_t* p = (uint8_t*)(&config);
void loadConfig() {
uint8_t mac[6];
WiFi.macAddress(mac);
EEPROM.begin(256);
for (int i = 0; i < sizeof(config); i++) {
*(p + i) = EEPROM.read(i);
}
config.reboot = config.reboot + 1;
if (config.reboot >= 4) {
restoreFactory();
}
if (config.magic != 0xAA) {
config_flag = 1;
}
EEPROM.begin(256);
for (int i = 0; i < sizeof(config); i++) {
EEPROM.write(i, *(p + i));
}
EEPROM.commit();
delay(2000);
EEPROM.begin(256);
config.reboot = 0;
for (int i = 0; i < sizeof(config); i++) {
EEPROM.write(i, *(p + i));
}
EEPROM.commit();
delay(2000);
}
/**
* 恢复出厂设置,清除存储的wifi信息
*/
void restoreFactory() {
Serial.println("Restore Factory....... ");
config.magic = 0x00;
strcpy(config.stassid, "");
strcpy(config.stapsw, "");
strcpy(config.cuid, "");
strcpy(config.ctopic, "");
config.magic = 0x00;
saveConfig();
delayRestart(1);
while (1) {
delay(100);
}
}
/**
* 检查是否需要airkiss配网
*/
void waitKey() {
if (config_flag == 1) {
doSmartconfig();
}
}
void setup() {
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP); // 设置led引脚为输入引脚
attachInterrupt(digitalPinToInterrupt(buttonPin), checkSwitch, RISING); //设置中断
pinMode(LED_Pin, OUTPUT);
digitalWrite(LED_Pin, ledState); //写入默认状态
pinMode(LedBlink, OUTPUT);
digitalWrite(LedBlink, LOW); //指示灯引脚
Serial.println("Beginning...");
topicMac = WiFi.macAddress().substring(8); //取mac地址做主题用
topicMac.replace(":", ""); //去掉:号
topicMac = topicMac + aptype;
Serial.print("主题:");
Serial.println(topicMac);
loadConfig();
waitKey();
initWiFi();
getUid(topicMac, false);
//按键配置
ButtonConfig* buttonConfig = ButtonConfig::getSystemButtonConfig();
buttonConfig->setEventHandler(handleEvent);
buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress);
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterLongPress);
buttonConfig->setLongPressDelay(5000); //长按时间5秒
detachInterrupt(digitalPinToInterrupt(buttonPin)); //删除外部中断
client.setServer(mqtt_server, mqtt_server_port);
client.setCallback(callback);
digitalWrite(LedBlink, HIGH); //指示灯引脚
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
ledButton.check(); //按键检查
}
//打开灯泡
void turnOnLed() {
Serial.println("Turn ON");
digitalWrite(LED_Pin, LOW);
digitalWrite(LedBlink, LOW);
delay(500);
digitalWrite(LedBlink, HIGH);
}
//关闭灯泡
void turnOffLed() {
Serial.println("Turn OFF");
digitalWrite(LED_Pin, HIGH);
digitalWrite(LedBlink, LOW);
delay(500);
digitalWrite(LedBlink, HIGH);
}
3. 上传代码到ESP01S模块:
- 将ESP01S模块连接到计算机上,并选择正确的端口和开发板。
- 编译并上传代码到ESP01S。
4. 画板调试:
- 因为想直接接220v,所以画个板子,再买个220v交流转直流5v的模块,如下:
-
画板
焊接后模组