基于ESP8266的电费余额查询(实现杭电i-hdu上网认证)
本帖最后由 zyzand 于 2018-5-10 16:00 编辑前段时间,学校发了通知,说寝室要安智能电表了,由原来的每月底后付费改成预付费,说是没余额会自动断电,通过微信平台查询和缴纳电费。打开查电费的界面看了一下,发现缴费的界面是需要在微信里才能打开的,但查询的界面能直接在普通浏览器打开:
(最近刚安装上,学校实行保电模式,所以暂时没电费也能用哈)
正好实验室有几块ESP8266和几块OLED。我想能不能拿ESP8266来显示当前的实时电费信息呢?这样就避免舍友吃鸡的时候突然断电的尴尬了。
电费信息的解析
研究了一下查询电费的方式,发现只要更改地址就能查到不同楼层的电费信息,返回的html的源码如下
代码还是比较短的,有1M内存的ESP8266完全能应付得来。
思路也很简单,就只先用http库获取到网页源码,然后使用关键信息前后的字符截取出相应的信息。解析信息的方法如下。(代码可能写的不太规范,欢迎大神指点)
struct priceinf {
float price;
char timestr;
int year;
int month;
int day;
int hh;
int mm;
int ss;
char lou;
char qinshi;
};
/*
返回信息解析
传入:html代码
返回:解析出来的信息(priceinf 结构体)
未解析到则返回空的结构体
*/
priceinf getPrice(String s) {
priceinf dat;
int datStart = 0, datEnd = 0;
String datstr;
char buf;
char datsign[] = "<span class=\"price\"";
datStart = s.indexOf(datsign) + strlen(datsign) + 23;
if (datStart == strlen(datsign) + 23 - 1) { //没有找到price
memset(&dat, 0, sizeof(dat));
return dat;
}
datEnd = s.indexOf("</span>", datStart) - 2; //减2是为了减去字符“元”
datstr = s.substring(datStart, datEnd);
dat.price = datstr.toFloat();
char timesign[] = "<font style=\"color:#2d9fd3\"><b>";
datStart = s.indexOf(timesign) + strlen(timesign);
datEnd = s.indexOf("</b></font>", datStart); //结尾
datstr = s.substring(datStart, datEnd);
datstr.toCharArray(dat.timestr, 20);
dat.timestr = 0;
dat.year = datstr.substring(0, 4).toInt();
dat.month = datstr.substring(5, 7).toInt();
dat.day = datstr.substring(8, 10).toInt();
dat.hh = datstr.substring(11, 13).toInt();
dat.mm = datstr.substring(14, 16).toInt();
dat.ss = datstr.substring(17, 19).toInt();
char lousign[] = "楼幢:";
datStart = s.indexOf(lousign) + strlen(lousign);
datEnd = s.indexOf("</p>", datStart); //结尾
datstr = s.substring(datStart, datEnd);
datstr.toCharArray(dat.lou, 20);
dat.lou = 0;
char qinshisign[] = "寝室号:";
datStart = s.indexOf(qinshisign) + strlen(qinshisign);
datEnd = s.indexOf("</p>", datStart); //结尾
datstr = s.substring(datStart, datEnd);
datstr.toCharArray(dat.qinshi, 10);
dat.qinshi = 0;
//Serial.println(s);
Serial.print("time=");
Serial.println(dat.timestr);
Serial.print("price=");
Serial.println(dat.price);
Serial.print("lou=");
Serial.println(dat.lou);
Serial.print("qinshi=");
Serial.println(dat.qinshi);
Serial.println();
return dat;
}
这个函数能实现对截止时间,楼,寝室号,剩余电费的解析,并把解析到的信息储存在结构体里。
然后把信息用OLED显示出来,不过这块OLED默认是SPI的,而我用的ESP-01最多只有3个能自定义的io,完全不够用。所以要改成IIC接口。按照说明改一下背后的电阻,然后将RES接VCC,CS、DC接GND,这样D0就是SCL,D1是SDA了。分别连接到ESP8266的GPOP0和GPIO2上。
把OLED 屏幕的软件IIC库导入到Arduino里,测试一下还挺好用。
其他功能
到这里这个程序就基本完成了,难度不是很大。但感觉这个程序好简单啊,ESP8266的潜能还没全发挥出来,于是,当做练习,在这个程序里加入了SmartConfig,用来应对应对没有已知密码的wifi的情况,然后让他能开机时从指定网址先获取查询电费的网址,并能从指定网址进行OTA空中升级。这样就不止能查询自己的宿舍,并且就算送给别人用也能随时对程序进行修改。
获取查询网址和升级信息
获取查询网址和升级信息的函数,为了方便可管理多个终端,在开机的时候会先获取本芯片的唯一SN号,然后程序会访问自己SN号对应的网址,这样就不用为每一个芯片单独烧写固件了,同一个固件就能去访问不同的网址:
int site_Get() {
int ret = 0;
if ((WiFiMulti.run() == WL_CONNECTED)) {
HTTPClient http;
Serial.printf("Connect to www.zyzand.com...\n");
http.begin("www.zyzand.com", 80, (String)"/IoT/clients/" + SN + "/index.php"); //HTTP
int httpCode = http.GET();
int i = 5;//重试次数
while (i-- > 0 && httpCode != 200) {
Serial.printf("code: %d Try again...", httpCode);
delay(1000);
httpCode = http.GET();
}
//从www.zyzand.com获取信息失败
if (httpCode != 200) {
Serial.printf(" GET Fail!");
ret = 2;
}
//成功则进行数据解析
else{
Serial.println("GET data:");
String payload = http.getString();
Serial.println(payload);
int nflag = payload.indexOf("\n");
Serial.println(nflag);
Serial.println(payload.indexOf("\n", nflag));
dat1 = payload.substring(0, nflag);
dat2 = payload.substring(nflag + 1, payload.indexOf("\n", nflag + 1));
Serial.print("dat1 = ");
Serial.println(dat1);
Serial.print("dat2 = ");
Serial.println(dat2);
//OTA
if (strcmp(dat1.c_str(), "updata") == 0) {
OTA();
}
}
}
else {//WiFi连接失败
Serial.print("WiFi failed\n");
ret = 1;
}
//失败时的处理
if (ret != 0) {
dat1 = "zyzand.com";
dat2 = (String)"/IoT/clients/" + SN + "/error/index.php";
}
return ret;
}
在测试OTA空中升级的时候,遇到的问题值得提一下。开始时空中升级总是返回flash配置错误:
Flash config wrong real
猜测是在编译固件时选择的Flash不对,我的ESP-01的flash型号是EN25Q80B-104,网上查了一下说是8Mbit(1M)的。于是在编译时选择Flash Size为1M(128k SPIFFS),这样再把编译好的固件上传到网站上就能空中升级了。
通过杭电i-hdu上网认证
我所在的学校有校园免费wifi,连接是不需要密码的,但是连接后会自动跳转到一个网页,需要进行认证才能访问外网。一直对他的认证方法感兴趣,于是用Fiddler抓了一下包,发现用来认证信息的只是一个post请求,也不是很复杂。在电脑上模拟成功,用ESP8266试了一下,也成功通过。
/*
通过i-HDU认证,请自己修改postDate中的学号和密码
*/
int hdulogin() {
const char * host = "2.2.2.2";
const int httpPort = 80;
WiFiClient client;
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
return 1;
}
delay(10);
String postDate = "opr=pwdLogin&userName=你的学号&pwd=学号对应的密码&rememberPwd=1";//将从串口接收的数据发送到服务器,readLine()方法可以自行设计
if (postDate.length() && postDate != "0") {
String data = (String)postDate;
int length = data.length();
String postRequest = (String)("POST ") + "/ac_portal/login.php HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: Keep Alive\r\n" +
"Content-Length: " + length + "\r\n" +
"Accept: */*\r\n" +
"Origin: http://2.2.2.2\r\n" +
"Content-Type: application/x-www-form-urlencoded; charset=UTF-8" + "\r\n" +
"User-Agent: zyzandESP8266\r\n" +
"\r\n" +
data + "\r\n";
//Serial.println(postRequest);
Serial.println();
client.print(postRequest);
delay(600);
//处理返回信息
String line = client.readStringUntil('\n');
while (client.available() > 0) {
line += client.readStringUntil('\n');
}
//Serial.println(line);
client.stop();
if (line.indexOf("logon success") != -1 || line.indexOf("不需要") != -1) { //认证成功
return 0;
}
else {
return 2;
}
}
}
最后自己焊接了个小板子,用AMS1117-3.3进行降压:
OLED库(软件IIC):
程序源码:(界面比较简单,但功能是全的,有需要可以自己改一下,加入其它功能)
ps:极客工坊能不能增加ESP8266的版块啊,这芯片比arduino强大太多了,80M的主频,最高16M的内存,还有强大的WIFi,最关键的是还能用arduino的语言编程,用起来简直就是开挂版的Arduino,再也不用担心内存不足的问题了。但是感觉网上Arduino编的ESP8266资源比较散。。。 恩。。。。有道理,我们也在弄32,看来有必要8266和ESP32单独弄出来。 哇 这数据玩的很6啊~~~~从HTML里面取数据 很不错 #include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
char ssid[] = "GTWY1";// WiFi名 SSID (name)
char pass[] = "58025952"; // WiFi密码
const unsigned long BAUD_RATE = 115200;//**
String payload = ""; //获取数据储存变量
String webadd = "http://wthrcdn.etouch.cn/WeatherApi?citykey=101010100"; //接口地址
int time1 = 0;//WIFI等待时间(500ms)
int sta,sta2;
String com;
void setup()
{
delay(1000);
Serial.begin(BAUD_RATE);//**
WiFi.begin(ssid, pass);//连接WIFI
}
void loop(){
http();
}
/*****************************************http数据获取*******************************************/
void http(){
HTTPClient http;
http.begin(webadd);
int httpCode = http.GET();//返回的代号200为正常
if(httpCode > 0) {
payload = http.getString();//获取XML数据
int a = payload.indexOf("shidu");
com = payload.substring(a,a+12);
Serial.println(payload);//**
Serial.println(httpCode);//**
Serial.println(com);//**
Serial.println(a);//**
// }
}
http.end();
}
你好,可以帮忙看一个为什么获取不到数据吗 本帖最后由 zyzand 于 2018-6-18 21:57 编辑
benclee 发表于 2018-6-18 19:43
#include
#include
char ssid[] = "GTWY1";// WiFi名 SSID (name)
是不是wifi没连上?
建议在setup最后加上这段代码:
while ((WiFiMulti.run() != WL_CONNECTED) ) { //等待wifi连接
delay(200);
Serial.print(".");
}
还有每次获取完数据要加上delay延迟几秒,不然访问太快可能会获取不到数据。 谢谢。
刚试了一下。还是不行
int httpCode = http.GET()能返回200,
payload = http.getString();返回的全是空白
int a = payload.indexOf("shidu");返回的是-1
本帖最后由 zyzand 于 2018-6-18 22:46 编辑
benclee 发表于 2018-6-18 22:08
谢谢。
刚试了一下。还是不行
int httpCode = http.GET()能返回200,
你好,我看了一下你的网站,并实验了一下你的程序,发现串口返回的的确是空白的,但我获取了字符串长度后发现有1000多字节。用串口输出调试信息后发现,服务器返回的数据是经过gzip压缩的:
这是用电脑接收到的数据:
所以ESP8266在用串口打印数据时遇到了意外的字符(\0),停止打印,看到的就是没有数据了。 这个有解决的办法吗 benclee 发表于 2018-6-18 23:47
这个有解决的办法吗
关于HTTP协议我也不是很了解,我在网上找了些资料。
在ESP8266发送请求时已经通过这句Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0说明自己不支持gzip了,但服务器好像开启了强制压缩,无论怎么改返回的都是压缩过的信息。
然后gzip是一种开源的压缩方式,算法比较复杂,没找到通过单片机实现的代码。有通过C语言实现的,理论上可以移植到8266,比较麻烦。
现在看来最简单的方法可能是通过另一台服务器先对gzip的数据进行解析,然后让ESP8266从这台服务器获取数据。 本帖最后由 zyzand 于 2018-6-19 00:30 编辑
benclee 发表于 2018-6-18 23:47
这个有解决的办法吗
你可以先用我的服务器,可能会不稳定,这个网页会从http://wthrcdn.etouch.cn/WeatherApi?citykey=101010100获取数据,然后返回解压后的数据,改网址的话要联系我。
http://www.zyzand.com/IoT/function/benclee-ungzip.php 明白了。十分感谢 用你的服务器转发一下就能收到数据了。十份感谢 建议成本价人手一个,然后就跟选课系统一样崩了,上啥预付费:lol
页:
[1]