1.问题或需求描述:
ESP8266模块实时查询股票基金数据,计算并显示收益。脱机运行,使用0.96寸 OLED屏显示信息,有电有网即可使用。
2.测试环境:
开发环境:VS2017, Arduino IDE(1.8.12)
软件环境:U8g2库(0.96 寸OLED驱动库),Regexp(Lua风格的轻量型正则表达式库)
硬件环境:ESP8266 开发板(本实验选用 NodeMcu), 0.96 寸 IIC OLED屏
3.结果展示
4.解决方法或原理:
备注:
- 数据刷新频率主要取决于数据站点的刷新频率,本程序默认10秒查询一次,已远超过数据站点的刷新频率,建议不要再提高刷新频率,否则有可能被禁止访问。
- 考虑到股市休市情况无需进行高频查询,所以增加了间隙休眠功能。原设计为每10秒查询一次网络时间以用于判定当前是否开市,但仍因访问频率过高被禁,故而改为只在启动时查询一次网络时间之后通过累加耗时而计算当前时间。
- 如2所述,虽然考虑到股市休市情况,但并未对节假日开启全天休眠,若读者需要此功能请自行实现。
- 本文旨在说明 ESP8266 获取和显示Web站点信息技术原理。
#include <dummy.h>
#include <Arduino.h>
#include <Regexp.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
ESP8266WiFiMulti WiFiMulti;
#define MAX_SLEEP_TIMES 10 //Sleep is allowed during non-business hours
double share = 796.10f; //Please set the shares held
#ifndef STASSID
#define STASSID "ssid"
#define STAPSK "password"
#endif
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
#pragma region variables
//Don't modify the following variables
double earningsLast = 0.0f;
ulong loopTimes = 0;
ulong lastTimeMs = 0;
bool queryFund = false;
bool nettimeAvailable = false;
byte hour = 0; //calculated time: hour
byte minute = 0; //calculated time: minute
byte nettimeHour = 0; //network time: hour
byte nettimeMinute = 0; //network time: minute
#pragma endregion
#define DEBUG 0
#if DEBUG
#define DBG_PRINT(...) Serial.print(__VA_ARGS__)
#define DBG_PRINTLN(...) Serial.println(__VA_ARGS__)
#define DBG_PRINTF(...) Serial.printf(__VA_ARGS__)
#else
#define DBG_PRINT(...)
#define DBG_PRINTLN(...)
#define DBG_PRINTF(...)
#endif
void (*Reboot)() = 0;
void UpdateNowTime()
{
const ulong nowtimeMs = millis();
if (nowtimeMs < lastTimeMs) Reboot();
const ulong timeSecond = (ulong)(abs(nowtimeMs - lastTimeMs)/1000) % 86400;
uint h = (uint)(timeSecond / 3600);
uint m = (uint)((timeSecond % 3600) / 60);
m += nettimeMinute;
h = h + nettimeHour + (int)(m / 60);
m %= 60;
h = (byte)(h % 24);
hour = (byte)(h & 0xff);
minute = (byte)(m & 0xff);
DBG_PRINTF("h:%d, m:%d\n", hour, minute);
}
void Text(int x, int y, const char* str, byte mode = 0)
{
if (mode) u8g2.clearBuffer();
u8g2.drawStr(x, y, str);
u8g2.sendBuffer();
}
void MatchCallback(const char* match, const unsigned int length, const MatchState& ms)
{
char cap[32] = { NULL };
if (ms.level == 3)
{
/*
Example:
Capture 0 = 3.6708
Capture 1 = 2.12
Capture 2 = 2022-03-21 11:15
*/
double dwjz = strtod(ms.GetCapture(cap, 0), NULL);
double gszzl = strtod(ms.GetCapture(cap, 1), NULL);
double principal = share * dwjz;
double earnings = principal * gszzl / 100.0f;
Text(2, 10, ms.GetCapture(cap, 2), 1); //eg:2022-03-21 11:15
sprintf(cap, "%.3f, %.3f", gszzl, earnings);
Text(2, 22, cap); //eg:2.12, 100
sprintf(cap, "%.3f, %.3f", principal + earnings, earnings - earningsLast);
Text(2, 34, cap); //eg:10000, -1.25
earningsLast = earnings;
}
}
void MatchCallback2(const char* match, const unsigned int length, const MatchState& ms)
{
char cap[36] = { NULL };
if (!queryFund)
{
memcpy(cap, match, length);
Text(2, 10, cap, 1);
}
if (ms.level == 2)
{
lastTimeMs = millis();
nettimeHour = (byte)(strtoul(ms.GetCapture(cap, 0), NULL, 10) & 0xff);
nettimeMinute = (byte)(strtoul(ms.GetCapture(cap, 1), NULL, 10) & 0xff);
DBG_PRINTF("nettime: h:%d, m:%d\n", nettimeHour, nettimeMinute);
}
}
void setup()
{
WiFiClient client;
HTTPClient http;
MatchState ms;
String payload;
int count = 0;
http.setReuse(false);
http.setUserAgent("Chrome/86.0.4240.111");
u8g2.begin();
u8g2.setFont(u8g2_font_6x13_tf);
u8g2.setPowerSave(0);
Serial.begin(115200);
#if DEBUG
Serial.setDebugOutput(true);
#endif // DEBUG
WiFi.mode(WIFI_STA);
WiFiMulti.addAP(STASSID, STAPSK);
Text(2, 10, "System starting...", 1);
delay(200);
Text(2, 10, "Connecting to WiFi...", 1);
while (WiFiMulti.run() != WL_CONNECTED)
{
delay(200);
}
Text(2, 10, "Get net time...", 1);
lastTimeMs = millis();
if (http.begin(client, "http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"))
{
int httpCode = http.GET();
if (httpCode > 0)
{
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
{
payload = http.getString();
DBG_PRINTLN(payload.c_str());
ms.Target(const_cast<char*>(payload.c_str()));
count = ms.GlobalMatch("%d%d%d%d%-%d%d%-%d%d%s(%d%d):(%d%d):%d%d", MatchCallback2); //get now time hour and minute
if (count == 0)
{
Serial.println(payload.c_str());
Text(2, 46, "No matched");
DBG_PRINTLN(F("No matched"));
}
}
else
{
DBG_PRINTF("Httpcode Error: %d\n", httpCode);
}
}
else
{
DBG_PRINTF("Httpcode Error: %d\n", httpCode);
}
}
else
{
DBG_PRINTLN(F("Connect http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json fail"));
}
http.end();
client.stop();
}
void loop()
{
WiFiClient client;
HTTPClient http;
MatchState ms;
String payload;
int count = 0;
http.setReuse(false);
http.setUserAgent("Chrome/86.0.4240.111");
// wait for WiFi connection
while (WiFiMulti.run() == WL_CONNECTED)
{
UpdateNowTime();
if ((hour >= 13 && hour < 15) || hour == 10 ||
(hour == 9 && minute >= 15) || (hour == 11 && minute <= 45) ||
(hour == 12 && minute >= 55) || (hour == 15 && minute <= 15)
)
{
queryFund = true;
loopTimes = 0;
}
else
queryFund = false;
if (queryFund || (loopTimes % MAX_SLEEP_TIMES) == 0)
{
if (http.begin(client, "http://fundgz.1234567.com.cn/js/400015.js"))
{
int httpCode = http.GET();
if (httpCode > 0)
{
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
{
payload = http.getString();
DBG_PRINTLN(payload.c_str());
ms.Target(const_cast<char*>(payload.c_str()));
count = ms.GlobalMatch("dwjz\":\"(%d%.%d%d%d%d)\"%,\"gsz\":\"%d%.%d%d%d%d\"%,\"gszzl\":\"(%-?%d%.%d%d)\"%,\"gztime\":\"(%d%d%d%d%-%d%d%-%d%d%s%d%d:%d%d)\"", MatchCallback);
if (count == 0)
{
Serial.println(payload.c_str());
Text(2, 46, "No matched2");
DBG_PRINTLN(F("No matched2"));
}
}
else
{
DBG_PRINTF("Httpcode2 Error: %d\n", httpCode);
}
}
else
{
DBG_PRINTF("Httpcode2 Error: %d\n", httpCode);
}
}
else
{
DBG_PRINTLN(F("Connect http://fundgz.1234567.com.cn/js/400015.js fail"));
}
http.end();
client.stop();
}
else
{
Text(2, 10, "Closed market.", 1);
Text(2, 22, "Opening hours:", 0);
Text(2, 34, "09:30-11:30", 0);
Text(2, 46, "13:00-15:00", 0);
Text(2, 58, "Powerd by firswof", 0);
DBG_PRINTLN(F("Closed market"));
}
loopTimes++;
DBG_PRINTLN("loop++");
delay(10000);
}
DBG_PRINTLN("loop2++");
delay(10000);
}