主要记录项目开发过程遇到的bug,项目进度、以及各个功能模块的实现
物联网鱼缸毕设
需求一、主要硬件模块1、原理图:(1)、温度传感器(18b20)(2)、继电器(3)、NODE MCU 、8266模块及自动下载电路、串口通信部分驱动代码:效果与网页交换数据网页端请求数据2、主要外围设备连接供电二、网页端1.WIFI配网写入网页文件地址WIFI 配置部分后台代码扫描WIFI 并处理成jsonWIFI配置处理部分WIFI 配置部分网页端存储WIFI SSID、PassWord到EEPROM中2.网页数据显示、控制3、网页端数据请求项目进度需求
关键字:物联网、网页与硬件交互、传感器数据上传、配网
智能鱼缸实现Wifi联网、网页后台控制、传感器数据上传、基本的鱼缸DIY搭建
提示:以下是本篇文章正文内容,下面案例可供参考
一、主要硬件模块
1、原理图:
(1)、温度传感器(18b20)
(2)、继电器
原理图
(3)、NODE MCU 、8266模块及自动下载电路、串口通信
8266模块
自动下载电路
使用usb接口供电兼串口下载/串口通信
板载外围的电路:
其它版本的NodeMCU板子 PCB图、和本次使用100%兼容
部分驱动代码:
#include <Arduino.h>//单总线18b20驱动库#include <OneWire.h>#include <DallasTemperature.h>#define ONE_WIRE_BUS 13//8266连接GPIO13即D7,Arduino连接port 2// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)OneWire oneWire(ONE_WIRE_BUS);// Pass our oneWire reference to Dallas Temperature. DallasTemperature sensors(&oneWire);void setup(void){Serial.begin(9600);Serial.println("Dallas Temperature IC Control Library Demo");// Start up the librarysensors.begin();}void loop(void){// call sensors.requestTemperatures() to issue a global temperature // request to all devices on the busSerial.print("Requesting temperatures...");sensors.requestTemperatures(); // Send the command to get temperaturesSerial.println("DONE");// After we got the temperatures, we can print them here.// We use the function ByIndex, and as an example get the temperature from the first sensor only.float tempC = sensors.getTempCByIndex(0);// Check if reading was successfulif(tempC != DEVICE_DISCONNECTED_C) {Serial.print("Temperature for the device 1 (index 0) is: ");Serial.println(tempC);} else{Serial.println("Error: Could not read temperature data");}}
效果
与网页交换数据
else if (parm == "wd") //温度{//更新temptrue数据read_temperature();esp8266_server.send(200, "text/plain", String(temperature));}
网页端请求数据
function wendu() {var xmlhttp;if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();}xmlhttp.onreadystatechange = function () {if (xmlhttp.status == 200) {document.getElementById("wd").innerHTML = xmlhttp.responseText + "℃";}}xmlhttp.open("GET", "/sjhq?sjhq=" + "wd");xmlhttp.send();}
2、主要外围设备连接
因为使用的都是模块,所以就懒得画原理图、弄封装了。这里直接记录8266端口与外围设备的接线:
继电器使用5v电源,光耦隔离,高电平有效。OLED使用3.3V供电。由于IO口不够使用,使用D0引脚,D0是MCU睡眠唤醒引脚,8266上电时默认会有一个跳变信号,所以继电器会动作一下,8266本来实际有效引脚就很少。
供电
推荐使用USB手机插头或者充电宝供电,不推荐使用8266板载micro usb供电
二、网页端
1.WIFI配网
主要实现步骤:ESP8266先运行在AP模式,手机或者电脑连接热点后,弹出强制门户网站(WiFi配网页面),点击网页上配网按钮,即可扫描周围全部WIFI热点,点击热点名称即可把信息输入SSID文本框,用户只需用输入密码即可。最后点击连接WIFI按钮,ESP8266收到网页的SSID、Password信息后,连接此WIFI,如果连接成功,自动跳转到主页面否则重新刷新该页面,网页上显示密码错误。
写入网页文件地址
使plantform io上传
WIFI 配置部分后台代码
else{//未配置过WIFIif (wifiStatus == false) //未连接{// digitalWrite(2, LOW);WiFi_Status = "0";EEPROM.write(WiFi_State_Addr, 0);mit();WiFi.disconnect(true); //断开连接Serial.println("");Serial.print("Start WiFi config \n softAP starting --> ");Serial.println(AP_name);Serial.print("http://");Serial.println(url);WiFi.mode(WIFI_AP_STA); //配置为热点模式WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));WiFi.softAP(AP_name); //热点地址}}
扫描WIFI 并处理成json
//获取WiFi加密类型并返回String wifi_type(int typecode){if (typecode == ENC_TYPE_NONE)return "Open";if (typecode == ENC_TYPE_WEP)return "WEP ";if (typecode == ENC_TYPE_TKIP)return "WPA ";if (typecode == ENC_TYPE_CCMP)return "WPA2";if (typecode == ENC_TYPE_AUTO)return "WPA*";return "NONE";}//扫描WiFi,返回jsonvoid wifiScan(){String req_json = "";Serial.println("Scan WiFi");int n = WiFi.scanNetworks();if (n > 0){int m = 0;req_json = "{\"req\":[";for (int i = 0; i < n; i++){if ((int)WiFi.RSSI(i) >= Signal_filtering){m++;req_json += "{\"ssid\":\"" + (String)WiFi.SSID(i) + "\"," + "\"encryptionType\":\"" + wifi_type(WiFi.encryptionType(i)) + "\"," + "\"rssi\":" + (int)WiFi.RSSI(i) + "},";}}req_json.remove(req_json.length() - 1);req_json += "]}";esp8266_server.send(200, "text/json;charset=UTF-8", req_json);Serial.print("Found ");Serial.print(m);Serial.print(" WiFi! >");Serial.print(Signal_filtering);Serial.println("dB");Serial.println("scan all wifi :");Serial.println(req_json);}}
WIFI配置处理部分
//用于WiFi配置,接收ssid、password两个参数void wifiConfig(){// 判断请求是否存在这两个参数以及是否未配置if (esp8266_server.hasArg("ssid") && esp8266_server.hasArg("password") && WiFi_Status == "0"){int ssid_len = esp8266_server.arg("ssid").length();int password_len = esp8266_server.arg("password").length();// 判断ssid和密码长度是否合理if ((ssid_len > 0) && (ssid_len < 33) && (password_len > 7) && (password_len < 65)){// 把参数获得的String赋值到ssidString ssid_str = esp8266_server.arg("ssid");String password_str = esp8266_server.arg("password");const char *ssid = ssid_str.c_str();const char *password = password_str.c_str();Serial.print("SSID: ");Serial.println(ssid);Serial.print("Password: ");Serial.println(password);// 开始连接WiFiWiFi.begin(ssid, password);Serial.print("Connenting");//判断是否连接成功,8秒超时unsigned long millis_time = millis();while ((WiFi.status() != WL_CONNECTED) && (millis() - millis_time < 8000)){delay(500);Serial.print(".");}// 判断是否连接成功if (WiFi.status() == WL_CONNECTED){// 连接成功,返回1digitalWrite(2, HIGH);Serial.println("");Serial.println("Connected successfully!");Serial.print("IP Address: ");Serial.println(WiFi.localIP());Serial.print("http://");Serial.println(Hostname);esp8266_server.send(200, "text/plain", "1");delay(300);WiFi_Status = "1";// 记录状态到EEPROM:已配置EEPROM.write(WiFi_State_Addr, '1');mit();//存储WIFI SSID、Password到EEPROM中writeString(10, ssid_str, password_str);Serial.println("save ok");delay(50);//关闭热点WiFi.softAPdisconnect();delay(2000);ESP.restart();}else{esp8266_server.send(200, "text/plain", "0");// 连接失败, 返回0Serial.println("Connenting failed!");}}else{// WiFi密码格式错误Serial.println("Password format error");esp8266_server.send(200, "text/plain", "Password format error");}}else{// 请求参数错误Serial.println("Request parameter error");esp8266_server.send(200, "text/plain", "Request parameter error");}}
WIFI 配置部分网页端
网页端使用html显示,点击扫描按钮,发送处理请求到ESP8266端,ESP8266调用函数,处理成json数据返回给html,使用Ajax接收数据,接收完毕后会显示所有WIFI列表,用户点击某个WIFI名称后输入密码,此时Ajax返回数据给ESP8266,8266开始调用连接WIFI函数,无论是否连接成功都会返回给网页端数据给html页面上,给用户显示,成功后8266会重启。
存储WIFI SSID、PassWord到EEPROM中
这两个普通操作函数我写了大几个小时、可能那天状态不是很好,各方面测试都试过、包括中文也可以存储到EEPROM中。
/**@brief:把SSID 、Password写入EEPROM中*地址8:ssid_str 长度 地址9:pws_str长度,*从地址10开始为ssid_str、pws_str连续存储内容*@return: psw_start 密码地址起始位置*/uint8_t writeString(int start,String ssid_str,String psw){//String ssid_str = "micro";//String psw = "arduino01";uint8_t ssid_str_len = ssid_str.length();uint8_t psw_len = psw.length();EEPROM.write(8,ssid_str_len);EEPROM.write(9,psw_len);mit();delay(30);//写入ssid、psw字符串int ssid_start_addre;int ssid_end_addre;int psw_start;int psw_end_addre;//读取ssid的长度、psw的长度//uint8_t ssid_len = EEPROM.read(8);//uint8_t psw_len = EEPROM.read(9);//写入ssid_strint i;for(i = start;i < start+ssid_str_len;i++){EEPROM.write(i,ssid_str[i-start]);delay(30);}ssid_end_addre = i;psw_start = i+1;mit();//写入psw,在ssid_str结束的下一个位置for(i = psw_start;i < psw_start+psw_len; i++){EEPROM.write(i,psw[i-psw_start]);delay(30);}mit();psw_end_addre = i;return psw_start;}/**@brief:从地址10开始读取SSID、PAssword*@Param:uint8_t: flag:0 返回ssid_str 1:返回psw_str*@return String**/String getStringInfo(uint8_t flag){//从EEprom中读ssid_strString temp_str = "";int i,psw_start;uint8_t ssid_str_len,pws_str_len;ssid_str_len = EEPROM.read(8);pws_str_len = EEPROM.read(9);Serial.println(ssid_str_len);Serial.println(pws_str_len);if (flag == 0) //ssid_str{for(i = 10;i < 10+ssid_str_len;i++){temp_str += char(EEPROM.read(i));}}else if(flag == 1){//psw_strpsw_start = 10+ssid_str_len+1;for(i = psw_start;i < pws_str_len+psw_start;i++){temp_str += char(EEPROM.read(i));}}return temp_str;}
2.网页数据显示、控制
代码如下(示例):
//处理index所有数据获取请求void sjhq(){String parm = esp8266_server.arg("sjhq");if (parm == "wd") //温度{Serial.println("respond wen du request.");//更新temptrue数据read_temperature();esp8266_server.send(200, "text/plain", String(temperature));}else if (parm == "light") //灯光按钮{Serial.println("light clicked...");dg = !dg;Serial.println("light:");Serial.println(dg?"on":"off");digitalWrite(Light, dg);esp8266_server.send(200, "text/plain", "ok");}else if (parm == "sxhs"){sxh = !sxh;digitalWrite(ShuiXunHuan, sxh);Serial.println("水循环");esp8266_server.send(200, "text/plain", "ok");}else if(parm == "zy"){//增氧zeng_yang = !zeng_yang;digitalWrite(ZengYang,zeng_yang);Serial.println("ZengYang:"+zeng_yang);esp8266_server.send(200,"text/plain", "ok");}else if(parm == "zengyangStatus"){//获取增氧开关状态Serial.println("respond zengyangStatus request.");esp8266_server.send(200, "text/plain", String(digitalRead(ZengYang)?1:0));}else if(parm == "timeFeed"){String value = esp8266_server.arg("value");Serial.println("select timer value:"+value);//电机转3s后停止//digitalWrite(ZengYang,HIGH);digitalWrite(Feed,HIGH);delay(3000);digitalWrite(Feed,LOW);//digitalWrite(ZengYang,LOW);esp8266_server.send(200,"text/plain", "ok");}}
3、网页端数据请求
下面只展示两个处理、其它大同小异
function wendu() {var xmlhttp;if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();}xmlhttp.onreadystatechange = function () {if (xmlhttp.status == 200) {document.getElementById("wd").innerHTML = xmlhttp.responseText + "℃";}}xmlhttp.open("GET", "/sjhq?sjhq=" + "wd");xmlhttp.send();}function shuixunhuan() {var xmlhttp;if (window.XMLHttpRequest) {xmlhttp = new XMLHttpRequest();}xmlhttp.onreadystatechange = function () {if (xmlhttp.status == 200) {if (xmlhttp.responseText == "1") {document.getElementById("sxh").innerHTML = "开启"} else {document.getElementById("sxh").innerHTML = "关闭"// document.getElementById("sxh").innerHTML = xmlhttp.responseText;}}}xmlhttp.open("GET", "/sjhq?sjhq=" + "sxh");xmlhttp.send();}
项目进度
3月17日主要完成了18B20温度传感器软硬件的实现,并调试成功。使用了文件系统并上传网页到ESP8266闪存里、Web 服务器3月18日
今天上课上到下午4点,又排队做核酸检测,头疼。什么都没做3月19日
今天实现了WIFI配网的移植,主要有Ajax技术,但存储WIFI功能还未实现3月20日
今天实现了WIFI配网成功后把WIFI SSID、Password存储到EEPROM中,重启后自动连接此WIFI。3月21日
今天上了一天课,啥都没做。3月22日
今天上课到下午6点,晚上把index.html主页内容显示到8266上,并成功显示了温度数据,每3秒更新一次。但8266连接热点后不能域名解析了。3月23日
今天没做3月24日
今天没做3月25日
今天没做3月26日
今天上午刚收到快递,快递等的实在是太久了!今天把外围设备都焊接调试好了,只逻辑代码了,还需要解决默认进入后台页面地址问题,明天准备接一个OLED屏幕,把每次的ip地址显示出来,直接使用地址来访问页面。3月27日
今天上午实现了OLED屏幕显示,最开始使用u8glib库,但有bug。后来换成另外一个库成功跑通。下午、晚上实现了OLED上显示系统ip、系统提示基本信息。3月28日
今天总体测试,排除可能存在的bug、优化使用体验。3月29日
今天没做3月30日
今天没做3月31日
晚上最后测试。打包快递。