wifimanager库快速使用手册
日志
2024/12/23:新增局域网OTA烧录功能
2024/12/20:记录wifimanager库的使用,以及部分拓展(网页控制,文件响应)
前言
esp32系列的产品我一直很喜欢使用,因为其模组外围电路简单,还能结合arudinoIDE开发。自带wifi蓝牙,可以说十分契合智能家居,小型DIY这样的项目。此篇文章记录一个广泛使用的esp系列芯片的wifi控制库。包括如下几个部分:
- wifimanager库函数的使用,例如:固定ip的设置(对DIY产品来说很重要)
- 结合webserver响应相关网页,以及基本通信逻辑,结构
- 关于配置网络时,参数读取的逻辑,使用SPFIS文件管理系统
我想写的精简一点,日后若遗忘可以快速查看。
wifimanager库基本使用
我将wifimanager库封装成函数,以便主程序逻辑清晰,以下是函数.cpp
与.h
源码
其中.cpp
文件中,相关函数注释已经标明
.CPP文件
#include "wifihelper.h"
WiFiManager wifiManager;
bool shouldSaveConfig = false;
char mqtt_id[100];
WebServer server(80);
const int ledPin = 2;
/*****************************wifi********************************/
void WifiManager_init() {
WiFiManagerParameter custom_mqtt_id("id", "客户端名称", mqtt_id, 100);
wifiManager.addParameter(&custom_mqtt_id);
wifiManager.setConfigPortalTimeout(180); // 设置门户配置超时时间
wifiManager.setConnectTimeout(10); // 设置 WiFi 连接超时时间为10秒
wifiManager.setDebugOutput(false); // 打印调试内容
wifiManager.setMinimumSignalQuality(30); // 设置最小信号强度
wifiManager.setRemoveDuplicateAPs(true); // 设置过滤重复的AP
wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 5, 1), IPAddress(192, 168, 5, 1), IPAddress(255, 255, 255, 0)); // 设置固定AP信息
wifiManager.setSTAStaticIPConfig(IPAddress(192, 168, 137, 100), IPAddress(192, 168, 137, 1), IPAddress(255, 255, 255, 0)); // 设置静态ip
wifiManager.setAPCallback(configModeCallback); // 设置进入AP模式的回调
wifiManager.setSaveConfigCallback(saveConfigCallback); // 设置点击保存的回调
if (!wifiManager.autoConnect("Apple", "12345678")) {
Serial.println("Failed to connect, Timeout reached");
ESP.restart(); // 超时未连接成功,重启设备(wifimanager对于密码错误不会进行提示)
} else {
Serial.print("Connected to:");
Serial.println(WiFi.SSID());
Serial.print("local IP:");
Serial.println(WiFi.localIP());
}
if (shouldSaveConfig) {
strcpy(mqtt_id, custom_mqtt_id.getValue());
Serial.println(mqtt_id);
}
}
void configModeCallback(WiFiManager *myWiFiManager) {
Serial.print("Creat AP SSID:");
Serial.println(myWiFiManager->getConfigPortalSSID());
Serial.print("AP Address:");
Serial.println(WiFi.softAPIP());
}
void saveConfigCallback() {
shouldSaveConfig = true;
}
/*****************************SPIFS********************************/
void spifs_init() {
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS Mount Failed");
return;
}
}
/*****************************webserver****************************/
void server_init() {
server.on("/", HTTP_GET, handleRoot);
server.on("/controlLED", HTTP_GET, handleControlLED);
server.begin();
}
// 首页路由,提供网页文件(发送文件到网页示例)
void handleRoot() {
File file = SPIFFS.open("/index.html", "r");
if (file) {
server.streamFile(file, "text/html");
file.close();
} else {
server.send(404, "text/plain", "File Not Found");
}
}
// 控制 LED 开关(网页控制引脚示例)
void handleControlLED() {
String state = server.arg("state");
if (state == "ON") {
digitalWrite(ledPin, HIGH);
server.send(200, "text/plain", "LED is ON");
} else if (state == "OFF") {
digitalWrite(ledPin, LOW);
server.send(200, "text/plain", "LED is OFF");
} else {
server.send(400, "text/plain", "Invalid state");
}
}
.H文件
#ifndef _WIFIHELPER_H
#define _WIFIHELPER_H
#include <WiFiManager.h>
#include <WiFi.h>
#include <SPIFFS.h>
#include <WebServer.h>
extern WiFiManager wifiManager;
extern bool shouldSaveConfig;
extern char mqtt_id[100];
extern WebServer server;
extern const int ledPin;
/*****************************wifi********************************/
void WifiManager_init();
void configModeCallback(WiFiManager *myWiFiManager);
void saveConfigCallback();
/*****************************SPIFS*******************************/
void spifs_init();
/*****************************webserver***************************/
void server_init();
void handleRoot();
void handleControlLED();
#endif
.INO文件
#include "wifihelper.h"
void setup() {
Serial.begin(115200);
WifiManager_init();
spifs_init();
server_init();
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
}
void loop() {
server.handleClient();
}
.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PID Control Simulation</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
}
h1 {
margin-bottom: 30px;
}
.slider-container {
margin: 20px 0;
}
.slider {
width: 100%;
}
.chart-container {
margin-top: 20px;
position: relative;
height: 300px;
width: 100%;
background-color: #fff;
border: 1px solid #ccc;
}
#chart {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.controls {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
.control {
text-align: center;
}
.label {
margin-bottom: 10px;
}
.value-display {
margin-top: 20px;
}
.value-display p {
font-size: 18px;
margin: 5px 0;
}
.parameter-display {
margin-top: 10px;
font-size: 14px;
color: #666;
}
.target-display {
margin-top: 20px;
font-size: 18px;
}
.start-btn, .stop-btn, .reset-btn {
padding: 10px 20px;
font-size: 16px;
margin-top: 20px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
.start-btn:disabled, .stop-btn:disabled, .reset-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>PID Control Simulation</h1>
<!-- Set Target Value -->
<div>
<label for="targetInput" class="label">Set Target Value</label>
<input type="number" id="targetInput" value="1" step="0.1" min="0" max="10">
</div>
<!-- PID control sliders -->
<div class="controls">
<div class="control">
<label for="kp" class="label">Kp (Proportional)</label>
<input type="range" id="kp" class="slider" min="0" max="2" step="0.1" value="1">
<p id="kpValue">Kp: 1</p>
</div>
<div class="control">
<label for="ki" class="label">Ki (Integral)</label>
<input type="range" id="ki" class="slider" min="0" max="1" step="0.01" value="0.1">
<p id="kiValue">Ki: 0.1</p>
</div>
<div class="control">
<label for="kd" class="label">Kd (Derivative)</label>
<input type="range" id="kd" class="slider" min="0" max="1" step="0.01" value="0.1">
<p id="kdValue">Kd: 0.1</p>
</div>
</div>
<!-- Display current target and system state -->
<div class="value-display">
<p id="targetValue">Target: 1</p>
<p id="currentValue">Current Value: 0</p> <!-- This will be updated -->
</div>
<!-- Graph container -->
<div class="chart-container">
<canvas id="chart"></canvas>
</div>
<!-- Display coordinates and parameter details -->
<div class="parameter-display">
<p>Time Step: <span id="timeStep">0.1</span> s</p>
<p>Max X value: <span id="maxXValue">800</span></p>
<p>Max Y value: <span id="maxYValue">1.5</span></p>
</div>
<!-- Custom X, Y axis max inputs -->
<div class="controls">
<div class="control">
<label for="maxXInput" class="label">Max X Value</label>
<input type="number" id="maxXInput" value="800" step="1" min="1" max="2000">
</div>
<div class="control">
<label for="maxYInput" class="label">Max Y Value</label>
<input type="number" id="maxYInput" value="1.5" step="0.1" min="0.1" max="3">
</div>
</div>
<!-- Start, Stop, Reset Control Buttons -->
<button id="startBtn" class="start-btn" onclick="startPIDControl()">Start PID Control</button>
<button id="stopBtn" class="stop-btn" onclick="stopPIDControl()" disabled>Stop PID Control</button>
<button id="resetBtn" class="reset-btn" onclick="resetGraph()" disabled>Reset Graph</button>
<script>
let Kp = 1, Ki = 0.1, Kd = 0.1;
let target = 1;
let systemState = 0;
let previousError = 0;
let integral = 0;
let timeStep = 0.1;
let maxX = 800; // Default max X value
let maxY = 1.5; // Default max Y value
let isRunning = false; // To control when the PID loop starts
let animationFrameId; // Store animation frame id for stopping the loop
let canvas = document.getElementById('chart');
let ctx = canvas.getContext('2d');
let chartData = { x: [], y: [] };
// Canvas settings
const width = canvas.width = 1200; // Increase canvas width for wider chart display
const height = canvas.height = 300;
const margin = 40;
// Update PID parameters
function updatePID() {
Kp = parseFloat(document.getElementById('kp').value);
Ki = parseFloat(document.getElementById('ki').value);
Kd = parseFloat(document.getElementById('kd').value);
// Display updated PID values
document.getElementById('kpValue').innerText = `Kp: ${Kp}`;
document.getElementById('kiValue').innerText = `Ki: ${Ki}`;
document.getElementById('kdValue').innerText = `Kd: ${Kd}`;
}
// Listen to slider changes
document.getElementById('kp').addEventListener('input', updatePID);
document.getElementById('ki').addEventListener('input', updatePID);
document.getElementById('kd').addEventListener('input', updatePID);
// Update the target value based on user input
document.getElementById('targetInput').addEventListener('input', function() {
target = parseFloat(this.value);
document.getElementById('targetValue').innerText = `Target: ${target}`;
});
// Update X and Y max values
document.getElementById('maxXInput').addEventListener('input', function() {
maxX = parseInt(this.value);
document.getElementById('maxXValue').innerText = `Max X: ${maxX}`;
});
document.getElementById('maxYInput').addEventListener('input', function() {
maxY = parseFloat(this.value);
document.getElementById('maxYValue').innerText = `Max Y: ${maxY}`;
});
// Reset the graph and restore the initial conditions
function resetGraph() {
// Stop PID control before resetting
stopPIDControl();
// Clear the chart and reset values
chartData = { x: [], y: [] };
previousError = 0;
integral = 0;
systemState = 0;
// Reset the UI to show the initial values
document.getElementById('currentValue').innerText = `Current Value: 0`;
document.getElementById('targetValue').innerText = `Target: ${target}`;
// Clear the chart area
ctx.clearRect(0, 0, width, height);
// Optionally re-enable buttons after reset
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('resetBtn').disabled = false;
}
// Draw the graph with X and Y axis
function drawGraph() {
ctx.clearRect(0, 0, width, height);
// Draw left X axis
ctx.beginPath();
ctx.moveTo(margin, height - margin);
ctx.lineTo(margin, margin);
ctx.strokeStyle = 'black';
ctx.stroke();
// Draw right X axis
ctx.beginPath();
ctx.moveTo(width - margin, height - margin);
ctx.lineTo(width - margin, margin);
ctx.strokeStyle = 'black';
ctx.stroke();
// Draw Y axis
ctx.beginPath();
ctx.moveTo(margin, height - margin);
ctx.lineTo(width - margin, height - margin);
ctx.strokeStyle = 'black';
ctx.stroke();
// Draw the graph data
ctx.beginPath();
for (let i = 0; i < chartData.x.length; i++) {
let x = margin + (chartData.x[i] / maxX) * (width - 2 * margin);
let y = height - margin - (chartData.y[i] / maxY) * (height - 2 * margin);
ctx.lineTo(x, y);
}
ctx.strokeStyle = 'blue';
ctx.stroke();
}
// Run the PID loop
function runPID() {
let currentValue = systemState;
// Calculate error
let error = target - currentValue;
integral += error * timeStep;
let derivative = (error - previousError) / timeStep;
// Calculate PID output
let output = Kp * error + Ki * integral + Kd * derivative;
// Update system state (simulating control response)
systemState += output * timeStep;
// Save data for graph
chartData.x.push(chartData.x.length);
chartData.y.push(systemState);
// Ensure we do not exceed maxX
if (chartData.x.length > maxX) {
stopPIDControl(); // Stop PID control once max X is reached
return;
}
// Update the display of current value
document.getElementById('currentValue').innerText = `Current Value: ${systemState.toFixed(2)}`;
// Draw the updated graph
drawGraph();
// Update the previous error
previousError = error;
if (isRunning) {
animationFrameId = requestAnimationFrame(runPID);
}
}
// Start PID control
function startPIDControl() {
isRunning = true;
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('resetBtn').disabled = false;
runPID();
}
// Stop PID control
function stopPIDControl() {
isRunning = false;
cancelAnimationFrame(animationFrameId);
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('resetBtn').disabled = false;
}
</script>
</body>
</html>