DirectControl - 通过WiFi远程控制电气负载






4.78/5 (7投票s)
使用ESP8266模块远程控制大功率电器
引言
这是一篇入门文章,展示了如何使用廉价的电子元件,例如流行的 ESP8266 无线模块,轻松控制大功率电器。
背景
ESP8266 是一个带有完整 TCP/IP 堆栈和微控制器编程能力的 Wi-Fi 模块。ESP8266 模块可以作为接入点 (Access Point) 模式或客户端 (Station) 模式运行,或者同时以两种模式运行(混合模式),尽管后者不稳定。它还拥有 1MB 的闪存。
模块框图
模式详细技术规格
- 802.11 b / g / n 无线标准
- STA / AP 模式支持
- TCP / IP 协议栈,一个套接字
- 支持标准 TCP / UDP 服务器和客户端
- 支持串口波特率配置:1200/2400/4800/9600/19200/38400/57600/74800/115200 bps
- 支持串口数据位:5/6/7/8 位
- 支持串口奇偶校验:无
- 支持串口停止位:1/2 位
- ESP8266 GPIO 引脚 0/2/4/5/9/10/12/13/14/15/16 / ADC / EN / * UART TX / UART RX
- WiFi 工作电流:连续传输操作:≈70mA (200mA MAX),空闲模式:<200uA
- 串口 WiFi 传输速率:110-460800bps
市面上有许多 ESP8266 模块的变种。我使用的是 ESP8266MOD,因为它有许多 GPIO 引脚从芯片引出,外部天线接口和坚固的金属外壳。
电路图
如果您熟悉电子学基础知识,您会发现构建此电路图非常容易。
您将需要:
- ESP8266MOD
- 3.3V 电源
- Fortek SSR40-DA-H 固态继电器
- 一系列排针连接器和电阻
- USB-UART 3.3V 电平转换器
准备开发环境
作为开发套件,我选择了Espressif ESP8266 非官方开发套件。
Espressif ESP8266 非官方开发套件的安装和配置说明
- 下载 Espressif ESP8266 非官方开发套件 (148Mb) 并安装。
- 下载并安装 Java Runtime x86 或 x64 (jre-8uXXX-windows-xxx.exe)
- 下载并安装 Eclipse Neon x86 或 Eclipse Neon x86_64 用于 C++ 开发。将压缩包解压到 C 盘根目录。
- 下载并安装 MinGW。运行 mingw-get-setup.exe,在安装过程中选择无 GUI,即取消勾选“... also install support for the graphical user interface”。
- 下载脚本以自动化 MinGW 包的安装。
- 运行 install-mingw-package.bat 文件。下载预加载的 MinGW 文件包可确保它们被安装,有时 MinGW 包所在的服务器不再可用,导致所需包未安装。
- 从 c:\eclipse\eclipse.exe 目录启动 Eclipse Neon。
- 在 Eclipse 中,选择 File -> Import -> General -> Existing Project into Workspace,在 Select root directory 行,选择项目根目录并导入。
- 您需要用这个文件替换文件 c:\Espressif\examples\ESP8266\common_cpp.mk:common_cpp.zip。这是为了确保正确的 C99 构建。
Using the Code
以下代码用于启动一个接入点
void ICACHE_FLASH_ATTR WifiConnectorInit()
{
WifiEnterWorkMode();
captdnsInit();
WebInit(); // htttpdInit
}
void ICACHE_FLASH_ATTR WifiEnterWorkMode()
{
wifi_set_opmode(SOFTAP_MODE);
struct softap_config apconfig;
if(wifi_softap_get_config(&apconfig))
{
wifi_softap_dhcps_stop();
os_memset(apconfig.ssid, 0, sizeof(apconfig.ssid));
os_memset(apconfig.password, 0, sizeof(apconfig.password));
os_sprintf((char*)apconfig.ssid, "iqhub%ld", (long int)56); //our ssid name
apconfig.ssid_len = os_strlen((char*)apconfig.ssid);
apconfig.authmode = AUTH_OPEN;
apconfig.ssid_hidden = 0;
apconfig.channel = 9;
apconfig.max_connection = 4;
apconfig.beacon_interval = 100;
if(!wifi_softap_set_config(&apconfig))
os_printf("%s", "ESP8266 not set AP config!\r\n");
struct ip_info ipinfo;
wifi_get_ip_info(SOFTAP_IF, &ipinfo);
IP4_ADDR(&ipinfo.ip, 192, 168, 4, 1);
IP4_ADDR(&ipinfo.gw, 192, 168, 4, 1);
IP4_ADDR(&ipinfo.netmask, 255, 255, 255, 0);
wifi_set_ip_info(SOFTAP_IF, &ipinfo);
wifi_softap_dhcps_start();
}
wifi_set_broadcast_if(3); //1:station; 2:soft-AP, 3:station+soft-APs
}
对于这个项目,我们的 URL 查询集将是这样的
HttpdBuiltInUrl builtInUrls[]={
{"*", cgiRedirectApClientToHostname, "esp.nonet"},
{"/", cgiRedirect, "/index.html"},
{"/index.html", cgiIndex, NULL},
{"/onButton", cgiOnButton, "/index.html"},
{"/offButton", cgiOffButton, "/index.html"},
{NULL, NULL, NULL}
};
void ICACHE_FLASH_ATTR WebInit() {
httpdInit(builtInUrls, 80);
}
CGI 脚本是在 Web 服务器上运行的程序,它们根据客户端请求动态生成网页。
为了响应这些客户端请求中的一部分,我们需要实现几个自定义 CGI 脚本。
static char * g_web_index = { //... }; // Our web page as byte array,
// contains some format notations
// such as "%s" to insert curr led state
static char * g_web_index_formated = NULL; // Buffer for formatted web pages
typedef struct {
int arrayPos;
char buff[128];
} LongPageSendState;
bool g_ledState = false;
int ICACHE_FLASH_ATTR cgiIndex(HttpdConnData *connData) {
LongPageSendState *state= (LongPageSendState *)connData->cgiData;
int len;
//If the browser unexpectedly closes the connection, the CGI will be called
//with connData->conn=NULL. We can use this to clean up any data. It's pretty relevant
//here because otherwise we may leak memory when the browser aborts the connection.
if (connData->conn==NULL) {
//Connection aborted. Clean up.
if (state!=NULL) os_free(state);
if(g_web_index_formated) {
os_free(g_web_index_formated);
g_web_index_formated = NULL;
}
return HTTPD_CGI_DONE;
}
if (state == NULL) {
//This is the first call to the CGI for this webbrowser request.
//Allocate a state structure.
state= (LongPageSendState *)os_malloc(sizeof(LongPageSendState));
g_web_index_formated = (char *)os_malloc(sizeof(g_web_index) + 10);
//Save the ptr in connData so we get it passed the next time as well.
connData->cgiData=state;
//Set initial pointer to start of string
state->arrayPos= 0;
state->buff[0] = 0;
//We need to send the headers before sending any data. Do that now.
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/html");
httpdEndHeaders(connData);
}
//Figure out length of string to send. We will never send more than 128 bytes in this example.
os_sprintf(g_web_index_formated, g_web_index, g_ledState ? "ON" : "OFF");
len = os_strlen(g_web_index_formated) - state->arrayPos; //Get remaining length
if (len>128) len=128; //Never send more than 128 bytes
if (len==0) {
os_free(state);
os_free(g_web_index_formated);
g_web_index_formated = NULL;
return HTTPD_CGI_DONE;
}
//substr
for(int i = 0; i < len; i++){
state->buff[i] = g_web_index_formated[i + state->arrayPos];
}
//Send that amount of data
httpdSend(connData, state->buff, len);
//Adjust stringPos to first byte we haven't sent yet
state->arrayPos+=len;
//See if we need to send more
if (len == 128) {
//we have more to send; let the webserver call this function again.
return HTTPD_CGI_MORE;
} else {
//We're done. Clean up here as well: if the CGI function returns HTTPD_CGI_DONE, it will
//not be called again.
os_free(state);
os_free(g_web_index_formated);
g_web_index_formated = NULL;
return HTTPD_CGI_DONE;
}
}
int ICACHE_FLASH_ATTR cgiOnButton(HttpdConnData *connData){
g_ledState = true;
ioLed(1);
return cgiRedirect(connData); // Redirect to /index.html
}
int ICACHE_FLASH_ATTR cgiOffButton(HttpdConnData *connData){
g_ledState = false;
ioLed(0);
return cgiRedirect(connData); // Redirect to /index.html
}
我们的网页 HTML 代码将是这样的
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:">
<style>html { text-align: center;} </style></head>
<body><h1>ESP8266 Web Server</h1>
<p>GPIO 5 - State %s</p>
<p><a href="onButton"><button class="button">ON</button></a></p>
<p><a href="offButton"><button class="button">OFF</button></a></p>
</body></html>
将 Web 转换为二进制格式
为了将 HTML 页面转换为 C++ 代码,我使用了一个实用程序,它将 HTML 文件路径作为命令行参数。
用法
BinToCArray.exe index.html
输出如下所示
这个实用程序的源代码非常简单
int main(int argc, char** argv) {
assert(argc == 2);
char* fn = argv[1];
FILE* f = fopen(fn, "r");
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
printf("char a[%ld] = {\n", size );
unsigned long n = 0;
while (!feof(f)) {
unsigned char c;
if (fread(&c, 1, 1, f) == 0) break;
printf("0x%.2X,", (int)c); //Print in hex
++n;
if (n % 10 == 0) printf("\n");
}
fclose(f);
printf("};\n");
}
通过这样做,我们可以将网页存储在 ESP8266 的 RAM 中。作为替代方案,我建议您将其写入头文件中,并在使用它的模块中包含它。
如何构建和刷写
- 在 Eclipse 中,选择 File -> Import -> General -> Existing Project into Workspace,在 Select root directory 行,选择项目根目录并导入。
- 要构建项目,请在菜单栏中选择 Project -> Build Project。
- 要刷写 ESP8266,请转到 Make Target 视图中的项目文件夹,然后构建“flash”目标。
- 您需要 USB-3.3V UART 转换器,市面上有许多可供选择,价格也很便宜。
- 在刷写之前,请确保 GPIO0 连接到地,然后重置芯片(断电再上电),刷写完成后断开与地的连接。
如何使用
如果刷写成功,模块将启动一个名为 iqhub56 的接入点,使用任何手机连接它,然后打开网页地址:http://192.168.4.1。您将看到一个小型网页,您可以在其中控制引脚输出。以下是一个展示该设备工作原理的小视频。
历史
- 2018 年 8 月 6 日 - 第一个版本