前几年开发的一个工业现场使用的遥控器项目,使用了esp8266,一个单色oled显示屏,12864,4线IIC接口。4个按钮。
基本功能包含:
1:从EEPROM读取wifi配置,并链接到目标wifi。
2:如果没有配置wifi,或者wifi链接不上,自动启动AP,手机链接设备热点,浏览器打开配置页面,进入web配网模式。
3:提供从后台查询当前设备(炼钢炉)工作状态,并通过按钮选择炉次,进行控制。
4:长时间不使用,进入轻度睡眠模式。按两个按键唤醒。
5:oled屏幕显示菜单,并当前选中项高亮显示。按钮切换菜单,进行操作。
这个项目时间太久了,弄完以后,他们又不用了(比较尴尬)。因此给代码贡献出来,有需要的可以参考其中的部分功能。如wifi配置,oled屏幕等。
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <WiFiUdp.h>
#include <ESP8266WebServer.h>//建立web服务用
#include <ESP8266mDNS.h>
#include <EEPROM.h> //文件读写用
#include <ESP8266HTTPClient.h>
ESP8266WebServer server(80);
String ssid = "";//wifi名称
String password = "";//密码
const char* wifiname = "TYMESCTL"; //热点名称
const char* wifipwd = "12345678"; //热点名称
String host = ""; //需要访问的域名
bool err = false; //是否有错误
DynamicJsonDocument doc(1024);//json对象
JsonArray arr;//存储返回的数组对象
WiFiClient client;//wifi链接对象,为http对象提供访问支持
JsonObject curData;//用来显示当前option的对象
HTTPClient http;//http对象
void showAP();//显示wifi信息,提供客户链接
void startAP();//开始AP
void readConfig();//读取配置信息
String httpGet(String url);//声明get函数
void connectWifi();//链接到wifi
void getRNO();//获取列表
void scanBtn();//扫描按钮状态
void showError();//显示错误
void showOff();//啥也u显示
void showMenu();//显示炉次
void showOption(JsonObject d, char t); //显示记录界面
void showMsg(String msg);//显示消息
void startY(String rno);//记录充氧开始时间
void endY(String rno);//记录充氧结束时间
void startP(String rno);//记录浇灌开始时间
void endP(String rno);//记录浇灌结束时间
void handleHtml();//返回html页面
void handleSave();//存储配置post
void clearConfig();//清除所有配置
void light_sleep();//进入睡眠,省电
//iic驱动方式
U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
char b1 = 16; //btn1pin
char b2 = 13; //btn2pin
char b3 = 15; //btn3pin
char b4 = 12; //btn4pin
char b5 = 14; //btn5pin
char b1state = 0;
char b2state = 0;
char b3state = 0;
char b4state = 0;
char b5state = 0;
char s1, s2, s3, s4, s5; //用量判断是不是上升沿的临时变量
char curselect = -1;//-1是没有选中
char curstep = 0; //当前操作步骤,0表示息屏,进入省电模式,1表示联网中,2表示选中炉号,3表示记录时间,10表示联网不成功,进入热点
char nulltime = 0;//空循环次数,每次1秒,10秒没有操作就息屏
String currno;//当前操作的rno;
bool issleep=false;
unsigned short delaytime = 1000; //默认延迟1秒
const String html = "<!DOCTYPE html><html>\
<head>\
<meta charset='utf-8'>\
<title>TYMESCTL Web Config</title>\
</head>\
<body>\
<h2>控制器配置</h2>\
<form action='/save' method='post'>\
<p> wifi名称<input name='ssid'></p>\
<p> wifi密码<input name='pwd'></p>\
<p> 接口地址<input name='host'></p>\
<p> <input type='submit' value='提交'></p>\
</form>\
</body>\
</html>";
/***
* 进入轻度睡眠模式
*/
void light_sleep() {
//if(issleep)return;
//issleep=true;
Serial.print("into light sleep mode\n");
wifi_set_opmode_current(NULL_MODE);//断开wifi
delay(3000);//断开ap时间
if(curstep==10){
Serial.print("set ap off\n");
WiFi.softAPdisconnect(true);
delay(3000);//断开ap时间
}
wifi_station_disconnect();
delay(3000);//断开wifi时间
/*
bool stopped;
do {
stopped = wifi_station_get_connect_status() == DHCP_STOPPED;
if (!stopped)
{
Serial.println("dhcp not stopped?");
delay(500);
}
} while (!stopped);
*/
Serial.println("---off...");
wifi_set_opmode_current(NULL_MODE);//断开wifi
delay(3000);//断开wifi时间
wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);//轻度睡眠
wifi_fpm_open(); // Enables force sleep
gpio_pin_wakeup_enable(b2, GPIO_PIN_INTR_HILEVEL);
wifi_fpm_do_sleep(0xFFFFFFF); // Sleep for longest possible time
Serial.print("sleep success \n");
nulltime=255;//ap模式下,必须到255才睡觉,所以直接给设置为255
//delaytime=3000;
}
void setup(void) {
EEPROM.begin(512);//开始山村读取模块,读取512字节
//0-20,存储wifi名称,遇到0结束
//20-40,存储wifi密码,遇到0结束
//40-100存储api接口,遇到0结束
u8g2.begin();
// u8g2.enableUTF8Print(); // enable UTF8 support for the Arduino print() function
u8g2.setFont(u8g2_font_unifont_t_chinese2); // use chinese2 for all the glyphs of "你好世界"
// u8g2.setFont(u8g2_font_gb24st_t_3);
// u8g2.drawStr(0, 15, "connecting");
//链接到wifi
Serial.begin(115200);
Serial.println();
readConfig();
if (ssid == "") startAP();
else connectWifi();
///io初始化
pinMode(b1, INPUT);
pinMode(b2, INPUT);
pinMode(b3, INPUT);
pinMode(b4, INPUT);
pinMode(b5, INPUT);
}
//读取配置
void readConfig() {
//0-20,存储wifi名称,遇到0结束
//20-40,存储wifi密码,遇到0结束
//40-120存储api接口,遇到0结束
//从EEPROM中逐个取出每一位的值,并链接
char t;
for (int i = 0; i < 20; i++) {
t = char(EEPROM.read(i));
//Serial.printf("i=%d t=%d \n ",i,t);
if (t == '\0' || t == 255 || t == 0 ) break;
ssid += t;
}
for (int i = 20; i < 40; i++) {
t = char(EEPROM.read(i));
if (t == '\0' || t == 255 || t == 0 ) break;
password += t;
}
for (int i = 40; i < 120; i++) {
t = char(EEPROM.read(i));
if (t == '\0' || t == 255 || t == 0 ) break;
host += t;
}
//Serial.print("SSID= "+ssid+"\n");
//Serial.println("PWD= "+password);
//Serial.println("HOST= "+host);
}
//链接到wifi
void connectWifi() {
// u8g2.setFont(u8g2_font_unifont_t_chinese2); // use chinese2 for all the glyphs of "你好世界"
//Serial.print("connecting to ");
//Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
int dly = 0;
int p = 0;
while (WiFi.status() != WL_CONNECTED) {
//Serial.print(".");
u8g2.firstPage();
do {
u8g2.drawStr(0, 20, ssid.c_str());
for (int i = 0; i <= p; i += 2) {
u8g2.drawStr(i * 2, 35, ".");
}
} while ( u8g2.nextPage() );
//u8g2.nextPage();
p++;
if (p > 40) {
p = 0;
dly++;//延时加
if (dly >= 3) {
break;//停止链接wifi
}
}
delay(50);
}
if (dly < 3) {
showMsg(" net ok");
//Serial.println("");
//Serial.println("WiFi connected");
//Serial.println("IP address: ");
// Serial.println(WiFi.localIP());
}
else {
startAP();
}
delay(1000);//延迟1秒
}
//开始热点配网
void startAP() {
curstep = 10; //当前是配网模式
WiFi.mode(WIFI_AP);//切换位热点模式
WiFi.softAP(wifiname, wifipwd);
showAP();
server.on("/", handleHtml);
server.on("/save", handleSave);
server.begin();
//Serial.println("start AP");
//Serial.print("AP name:" );
//Serial.println(wifiname);
//Serial.println("IP address: ");
//Serial.println(WiFi.softAPIP());
}
//显示ap信息
void showAP()
{
nulltime = 0;
u8g2.firstPage();
do {
u8g2.drawStr(20, 20, wifiname);
u8g2.drawStr(20, 40, wifipwd);
} while ( u8g2.nextPage() );
}
//扫描按钮输入状态
void scanBtn() {
// b1state=b2state=b3state=b4state=b5state=0;
s1 = digitalRead(b1);
s2 = digitalRead(b2);
s3 = digitalRead(b3);
s4 = digitalRead(b4);
s5 = digitalRead(b5);
// Serial.printf("s5==%d \n", s5);
if (s1 == 1 && b1state == 0)b1state = 1;
else b1state = 0;
if (s2 == 1 && b2state == 0)b2state = 1;
else b2state = 0;
if (s3 == 1 && b3state == 0)b3state = 1;
else b3state = 0;
if (s4 == 1 && b4state == 0)b4state = 1;
else b4state = 0;
if (s5 == 1 && b5state == 0)b5state = 1;
else b5state = 0;
}
//主循环
void loop(void) {
scanBtn();
// if(issleep)return;//如果是在睡眠,就等待
//获取按钮状态
Serial.printf("curtime= %d, delaytime= %d \n",nulltime,delaytime);
Serial.printf("state = %d %d %d %d %d \n",b1state,b2state,b3state,b4state,b5state);
if (b1state == 1 && b2state == 1) {
ESP.restart();//重启启动
return;
}
else if (b1state == 1 && b3state == 1) {
//Serial.print("will be clear config");
clearConfig();
ESP.reset();//重启
}
if (curstep == 10) {
//配网模式
server.handleClient();
nulltime++;
if (nulltime >= 30 ) {
delaytime = 3000;
//nulltime = 0; //重新计数
showOff();//清空屏幕
}
if(nulltime>=60){
light_sleep();//进入轻度睡眠
return;
}
delay(delaytime);
return;
}
//空闲判断
if (b1state == 0 && b2state == 0 && b3state == 0 && b4state == 0 && b5state == 0 ) {
nulltime++;
if (!issleep && nulltime >= 10) {
delaytime = 3000;
curstep = 0; //息屏
// nulltime = 0; //重新计数
showOff();//清空屏幕
}
if(!issleep && nulltime>=20){
light_sleep();//进入轻度睡眠
return;
}
delay(delaytime);//延迟1秒
return;//没有按键啥也不做
}//空闲判断结束
delaytime=1000;//延迟1秒
nulltime = 0;
issleep=false;
//判断是不是掉线了
if (WiFi.status() != WL_CONNECTED) {
Serial.print("reconnecting");
connectWifi();
}
//如果按下了1号键
if (b1state == 1) {
if (curstep == 2) {
//正在选中炉次
curselect += 1; //增加一个
if (curselect >= arr.size())curselect = 0;
showMenu();
}
else {
curstep = 2; //切换到步骤2,显示菜单
curselect = 0; //切换回第一个炉次
getRNO(); //获取菜单
if (!err) showMenu();
else curstep = 1; //返回第一步
}
}
else {
//其他按钮按下
if (curstep == 2 || curstep == 3) {
//其他按钮按下,只有目录和操作界面可以按
curstep = 3; //转到界面3
//String rno=arr[curselect]["rno"].as<String>();//获取rno
// Serial.print("rno="+rno);
if (b2state == 1) {
startY(currno);
}
else if (b3state == 1) {
endY(currno);
}
else if (b4state == 1) {
startP(currno);
}
else if (b5state == 1) {
endP(currno);
}
// delay(5000);
}
}//按键判断结束
// getRNO();
delay(delaytime);//延迟0.5秒
}
//充氧开始时间
void startY(String rno) {
String rsp = httpGet("/starty?rno=" + rno);
// Serial.print(rsp);
if (rsp == "") {
showError();
delay(1000);
return;
}
deserializeJson(doc, rsp);
//Serial.print("parse json");
JsonObject obj = doc.as<JsonObject>();
// Serial.print("to jsonobject");
showOption(obj, 1); //显示结果
}
void endY(String rno) {
String rsp = httpGet("/endy?rno=" + rno);
if (rsp == "") {
showError();
delay(1000);
return;
}
deserializeJson(doc, rsp);
JsonObject obj = doc.as<JsonObject>();
showOption(obj, 2); //显示结果
}
//充氧开始时间
void startP(String rno) {
String rsp = httpGet("/startp?rno=" + rno);
if (rsp == "") {
showError();
delay(1000);
return;
}
deserializeJson(doc, rsp);
JsonObject obj = doc.as<JsonObject>();
showOption(obj, 3); //显示结果
}
void endP(String rno) {
String rsp = httpGet("/endp?rno=" + rno);
if (rsp == "") {
showError();
delay(1000);
return;
}
deserializeJson(doc, rsp);
JsonObject obj = doc.as<JsonObject>();
showOption(obj, 4); //显示结果
}
//显示炉次
void showMenu()
{
int size = arr.size();//获取长度
//Serial.printf("allcount %d ,cur select %d\n",size,curselect);
u8g2.firstPage();
do {
for (int i = 0; i < size; i++) {
String rno = arr[i]["rno"].as<String>();
String startydt = arr[i]["starty"].as<String>();
String endydt = arr[i]["endy"].as<String>();
String startpdt = arr[i]["startp"].as<String>();
String endpdt = arr[i]["endp"].as<String>();
if (curselect == i) {
currno = rno; //记录当前rno;
u8g2.setFontMode(1);
u8g2.setDrawColor(1);
u8g2.drawBox(0, i * 15, 128, 16);
u8g2.setDrawColor(2);
}
else {
u8g2.setFontMode(0);
u8g2.setDrawColor(1);
}
// Serial.print(startydt+" "+endydt+" "+startpdt+" "+endpdt+" \n");
u8g2.drawStr(0, i * 15 + 13, rno.c_str()); //显示到屏幕
if (startydt == "null") {
// u8g2.drawStr(68, i * 15 + 13, " "); //显示到屏幕
}
else {
u8g2.drawStr(68, i * 15 + 13, "*"); //显示到屏幕
}
if (endydt == "null") {
// u8g2.drawStr(83, i * 15 + 13, " "); //显示到屏幕
}
else {
u8g2.drawStr(83, i * 15 + 13, "*"); //显示到屏幕
}
if (startpdt == "null") {
// u8g2.drawStr(98, i * 15 + 13, " "); //显示到屏幕
}
else {
u8g2.drawStr(98, i * 15 + 13, "*"); //显示到屏幕
}
if (endpdt == "null") {
//u8g2.drawStr(113, i * 15 + 13, " "); //显示到屏幕
}
else {
u8g2.drawStr(113, i * 15 + 13, "*"); //显示到屏幕
}
}
}
while ( u8g2.nextPage());
}
//显示操作界面
void showOption(JsonObject d, char t) {
//Serial.print("show optin \n");
String rno = d["data"]["rno"].as<String>();
String startydt = d["data"]["starty"].as<String>();
String endydt = d["data"]["endy"].as<String>();
String startpdt = d["data"]["startp"].as<String>();
String endpdt = d["data"]["endp"].as<String>();
String tm = d["message"].as<String>();
String tm1 = "";
if (tm.length() > 10) {
tm1 = tm.substring(10);
tm = tm.substring(0, 10);
}
Serial.print("option rno=" + rno + "tm=" + tm + " tm1=" + tm1 + "\n");
u8g2.firstPage();
do {
u8g2.drawStr(0, 13, rno.c_str()); //显示到屏幕
if (startydt == "null") {
//u8g2.drawStr(70, 13, " "); //显示到屏幕
}
else {
if (t == 1) u8g2.drawStr(62, 13, "[*]"); //显示到屏幕选择状态
else u8g2.drawStr(68, 13, "*"); //显示到屏幕
}
if (endydt == "null") {
//u8g2.drawStr(85, 13, " "); //显示到屏幕
}
else {
if (t == 2) u8g2.drawStr(77, 13, "[*]"); //显示到屏幕选择状态
else u8g2.drawStr(83, 13, "*"); //显示到屏幕
}
if (startpdt == "null") {
//u8g2.drawStr(100, 13, " "); //显示到屏幕
}
else {
if (t == 3) u8g2.drawStr(92, 13, "[*]"); //显示到屏幕选择状态
else u8g2.drawStr(98, 13, "*"); //显示到屏幕
}
if (endpdt == "null") {
//u8g2.drawStr(115, 13, " "); //显示到屏幕
}
else {
if (t == 4) u8g2.drawStr(107, 13, "[*]"); //显示到屏幕选择状态
else u8g2.drawStr(113, 13, "*"); //显示到屏幕
}
u8g2.drawStr(25, 28, tm.c_str()); //时间显示到屏幕
u8g2.drawStr(28, 43, tm1.c_str()); //时间显示到屏幕
} while (u8g2.nextPage());
}
//啥也不显示
void showOff()
{
u8g2.firstPage();
do {
} while ( u8g2.nextPage() );
}
//显示错误
void showError()
{
u8g2.firstPage();
do {
u8g2.drawStr(0, 30, " error");
} while ( u8g2.nextPage() );
}
//显示消息
void showMsg(String msg)
{
u8g2.firstPage();
do {
u8g2.drawStr(0, 30, msg.c_str());
} while ( u8g2.nextPage() );
}
//获取炉次
void getRNO()
{
showMsg(" geting data...");
String payload = httpGet("/t");
if (payload == "") {
showError();
return;
}
deserializeJson(doc, payload);
JsonObject obj = doc.as<JsonObject>();
arr = obj["data"].as<JsonArray>();//转换为数组
// delay(2000);
}
//http请求
String httpGet(String url) {
err = false;
if (http.begin(client, host + url)) { // HTTP
// Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
// Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = http.getString();
err = false;
return payload;
}
} else {
err = true;
//Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
return "";
}
http.end();
} else {
err = true;
// Serial.printf("[HTTP} Unable to connect\n");
return "";
}
}
//显示html页面
void handleHtml()
{
server.send(200, "text/html", html);
}
//清除所有配置
void clearConfig() {
for (int i = 0; i < 120; i++) {
EEPROM.write(i, 255);//标记为默认
}
EEPROM.commit();//提交,不提交无效
}
//保存配置
void handleSave()
{
for (uint8_t i = 0; i < server.args(); i++) {
String arg = server.argName(i);
String v = server.arg(i);
if (arg == "ssid")ssid = v;
else if (arg == "pwd")password = v;
else if (arg == "host")host = v;
// message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
// Serial.print("SSID= "+ssid+"\n");
//Serial.println("PWD= "+password);
//Serial.println("HOST= "+host);
clearConfig();//先清除一下
for (int i = 0; i < ssid.length(); i++) {
EEPROM.write(i, ssid[i]);
}
for (int i = 0; i < password.length(); i++) {
EEPROM.write(20 + i, password[i]);
}
for (int i = 0; i < host.length(); i++) {
EEPROM.write(40 + i, host[i]);
}
EEPROM.commit();
server.send(200, "text/plain", "config success");
}