#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>
#include <time.h>
#include <Ticker.h>
#include <ArduinoZlib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
#define BUTTON_UP 12
// U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
U8G2_SSD1306_128X64_NONAME_2_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
// 全屏绘制
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock=*/SCL, /* data=*/SDA, /* reset=*/U8X8_PIN_NONE);
// npt
#define NTP_SERVER "pool.ntp.org"
#define UTC_OFFSET 28800 // 8h
#define UTC_OFFSET_DST 0
// #define NTP_SERVER "time.windows.com"
/* #define NTP_SERVER "ntp.sjtu.edu.cn"
#define UTC_OFFSET 0
#define UTC_OFFSET_DST 0 */
// time
struct tm timeinfo;
char *g_cur_time;
// 天气定时器
Ticker timer;
bool use_gzip = false;
bool time_state = false;
bool weather_state = false;
const char *ssid = "Wokwi-GUEST";
const char *password = "";
/* const char *ssid = "dazhong";
const char *password = "dazhong2016"; */
// 与主程序共享的变量要加上 volatile 关键字
// volatile bool connected = false;
enum connect_action
{
toNone = 0,
toConnect = 1,
toWait = 2,
toUnConnect = 3
};
enum connect_state
{
unconnect = 0,
connecting = 1,
connected = 2
};
volatile connect_action connectAction = toNone;
volatile connect_state connectState = unconnect;
// https://devapi.qweather.com/v7/weather/now?location=101100201&key=5d4cb1285d6e4233abfa8293aa4b4182
String url = "https://devapi.qweather.com/v7/weather/now";
String location = "101100201"; // 山西大同
String key = "5d4cb1285d6e4233abfa8293aa4b4182";
String url_all = url + "?location=" + location + "&key=" + key;
/* String url = "https://api.seniverse.com/v3/weather/now.json";
String location = "WX0S40QDCME0"; // 山西大同
String key = "S8ImEoNoO9kDj9pN4";
String language = "zh-Hans";
String unit = "c"; */
void setU8g2Oled();
void execConnect();
void execTime();
void execWeather();
void pressAButton();
void drawLayout();
void spinner();
void a();
// 外部中断
void setExternalInterrupt();
// 定义外部中断函数
void handle_interrupt()
{
// connected = !connected;
switch (connectState)
{
case unconnect:
connectAction = toConnect;
break;
case connecting:
connectAction = toWait;
break;
case connected:
connectAction = toUnConnect;
break;
default:
connectAction = toNone;
break;
}
}
void setup()
{
Serial.begin(115200);
use_gzip = true;
// 配置输入按键
pinMode(BUTTON_UP, INPUT_PULLUP);
// 初始化oled
setU8g2Oled();
setExternalInterrupt();
// 配置天气周期性定时器
timer.attach(10, execWeather);
}
void loop()
{
drawLayout();
pressAButton();
if (connectState == connected)
{
if (!weather_state)
{
// execGzipWeather();
execWeather();
}
if (!time_state)
{
execTime();
}
}
delay(500); // this speeds up the simulation
}
void pressAButton() // connect to WiFi
{
if (connectAction == toNone || connectAction == toWait)
{
// connect2WiFi();
// Serial.println((String) "按钮状态1:" + digitalRead(BUTTON_UP) + ",connectState:" + connectState);
}
else if (connectAction == toConnect)
{
connectState = connecting;
// Serial.println((String) "按钮状态2-1:" + digitalRead(BUTTON_UP) + ",connectState:" + connectState);
execConnect();
connectState = connected;
connectAction = toNone;
// Serial.println((String) "按钮状态2-2:" + digitalRead(BUTTON_UP) + ",connectState:" + connectState);
}
else if (connectAction == toUnConnect)
{
WiFi.disconnect(true); // 断开之前的连接
connectState = unconnect;
connectAction = toWait;
}
delay(250); // this speeds up the simulation
}
void execTime()
{
// 如果获取失败,就开启联网模式,获取时间
if (!getLocalTime(&timeinfo))
{
Serial.println("从NPT服务器获取时间");
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
return;
}
time_state = true;
// 格式化输出:2021-10-24 23:00:44 Sunday
sprintf(g_cur_time, "%Y年/%m月/%d日 %Z", &timeinfo);
Serial.println(g_cur_time);
// Serial.println(&timeinfo, "%F %T %A");
/* time_t now;
struct tm *p;
now = time(NULL); // 或time(&now); // 获取从1970至今过了多少秒,存入time_t类型的timep
p = localtime(&now); // 用localtime将秒数转化为struct tm结构体
Serial.printf("%d/%d/%d %02d:%02d:%02d\n", 1950 + p->tm_year, 1 + p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec); // 2019/2/28 14:18:31 */
/* Serial.println(&timeinfo, "%Y年/%m月/%d日 %Z");
Serial.println(&timeinfo, "%H:%M:%S");
char *s;
sprintf(s, "Name: %d, Age: %d, Salary: %d", &timeinfo.tm_year, &timeinfo.tm_year, &timeinfo.tm_year);
printf("%s\n", s); // 输出:Name: Alice, Age: 30, Salary: 50000.50 */
// sprintf(s, "%Y年/%m月/%d日 %Z", &timeinfo);
// Serial.println((String) "s=" + s);
}
void drawLayout()
{
u8g2.firstPage();
do
{
// u8g2.setFont(u8g2_font_unifont_t_chinese2);
u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
u8g2.drawUTF8(0, 13, "年月日");
u8g2.drawLine(0, 16, 128, 16);
u8g2.drawLine(0, 51, 128, 51);
u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
if (connectState != connected)
{
u8g2.drawUTF8(0, 62, "connecting......");
continue;
}
else if (connectState == connected)
{
/* String tip_ip = "IP:";
String ip = WiFi.localIP().toString();
tip_ip = tip_ip + ip;
u8g2.drawUTF8(0, 63, tip_ip.c_str()); */
u8g2.setFont(u8g2_font_open_iconic_embedded_2x_t); // 选择内置open_iconic_embedded_2x图标
u8g2.drawGlyph(90, 15, 0x0050); // 选择wifi图标
u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
}
if (!weather_state)
{
u8g2.drawUTF8(0, 62, "Loading weather......");
continue;
// Serial.println("Loading time......");
}
else
{
u8g2.setFont(u8g2_font_open_iconic_weather_2x_t); // 选择内置Weather图标
u8g2.drawGlyph(112, 15, 0x0041); // 选择0x45图标
}
if (!time_state)
{
u8g2.drawUTF8(0, 62, "Loading time......");
continue;
// Serial.println("Loading time......");
}
// u8g2.drawUTF8(0, 62, "A:天 B:温 C:燃气 D:体检");
} while (u8g2.nextPage());
u8g2.drawUTF8(0, 62, "A:天 B:温 C:燃气 D:体检");
}
void setU8g2Oled()
{
// 初始化 oled 对象
u8g2.begin();
// 开启中文字符集支持
u8g2.enableUTF8Print();
// 设置oled字体
// u8g2.setFont(u8g2_font_unifont_t_chinese2);
// 设置字体方向
u8g2.setFontDirection(0);
delay(100);
}
/// 外部中断
void setExternalInterrupt()
{
pinMode(BUTTON_UP, INPUT_PULLUP);
/* 外部中断配置 attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) 包括 3 个参数:
pin:GPIO 端口号;
ISR:中断服务程序,没有参数与返回值的函数;
mode:中断触发的方式,支持以下触发方式:
LOW 低电平触发
HIGH 高电平触发
RISING 上升沿触发
FALLING 下降沿触发
CHANGE 电平变化触发
*/
attachInterrupt(digitalPinToInterrupt(BUTTON_UP), handle_interrupt, FALLING);
// Serial.println("setExternalInterrupt");
}
void execConnect()
{
// 检测是否链接成功
WiFi.begin(ssid, password, 6);
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
// spinner();
delay(5000);
}
/* u8g2.clear();
u8g2.setCursor(0, 15);
u8g2.print("WiFi Connected");
u8g2.setCursor(0, 30);
// 直接print WiFi.localIP会是一串数字,转string后才是标准ip格式
u8g2.print((String) "IP: " + WiFi.localIP().toString().c_str());
u8g2.sendBuffer(); */
// u8g2.setCursor(0, 45);
// u8g2.print("IP: ");
// // println WiFi.localIP 就不用转string 打印标准ip格式
// u8g2.println(WiFi.localIP());
// u8g2.sendBuffer();
connectState = connected;
}
void execGzipWeather()
{
uint8_t *outbuf;
int len = -1;
uint8_t buff[2048] = {0};
HTTPClient https;
if (use_gzip)
https.addHeader("Accept-Encoding", "gzip");
https.begin(url_all); // 准备启用连接
int httpCode = https.GET(); // 发起GET请求
Serial.printf("[HTTPS] GET code=%d,size=%d\r\n", httpCode, len);
// Serial.printf("size=%d\n", len);
WiFiClient *stream = https.getStreamPtr();
Serial.println("stream ok");
if (https.connected() && (len > 0 || len == -1))
{
// get available data size
size_t size = stream->available(); // 还剩下多少数据没有读完?
Serial.printf("avail size=%d\n", size);
if (size)
{
size_t readBytesSize = stream->readBytes(buff, size);
if (len > 0)
{
len -= readBytesSize;
}
outbuf = (uint8_t *)malloc(sizeof(uint8_t) * 15000);
uint32_t outprintsize = 0;
int result = ArduinoZlib::libmpq__decompress_zlib(buff, readBytesSize, outbuf, 15000, outprintsize);
Serial.write(outbuf, outprintsize);
free(outbuf);
}
}
https.end();
}
void execWeather()
{
/* Serial.print("Content-Type = ");
Serial.println(https.header("Content-Type"));
Serial.print("Content-Length = ");
Serial.println(https.header("Content-Length"));
https.addHeader("Accept-Encoding", "gzip");
const char *headerKeys[] = {"Content-Type", "Content-Length"};
https.collectHeaders(headerKeys, 2); // 准备需要接收的响应头内容 */
HTTPClient https;
if (use_gzip)
https.addHeader("Accept-Encoding", "gzip");
https.begin(url_all); // 准备启用连接
int httpCode = https.GET(); // 发起GET请求
if (httpCode > 0) // 如果状态码大于0说明请求过程无异常
{
if (httpCode == HTTP_CODE_OK) // 请求被服务器正常响应,等同于httpCode == 200
{
uint8_t buff[128] = {0};
int len = https.getSize(); // 读取响应正文数据字节数,如果返回-1是因为响应头中没有Content-Length属性
WiFiClient *stream = https.getStreamPtr(); // 获取响应正文数据流指针
while (https.connected() && (len > 0 || len == -1)) // 当前已连接并且有数据可读
{
size_t size = stream->available(); // 获取数据流中可用字节数
if (size)
{
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); // 读取数据到buff
uint32_t outprintsize = 0;
uint8_t *outbuf = (uint8_t *)malloc(sizeof(uint8_t) * 15000);
uint32_t readBytesSize = static_cast<uint32_t>(c);
int result = ArduinoZlib::libmpq__decompress_zlib(buff, readBytesSize, outbuf, 15000, outprintsize);
Serial.write(buff, c);
if (len > 0)
{
len -= c;
}
}
delay(1);
}
}
}
else
{
Serial.printf("[HTTP] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end(); // 结束当前连接
return;
// 获取响应状态码
Serial.printf("HTTP 状态码: %d", httpCode);
// 获取响应正文
String payload = https.getString();
Serial.println("响应数据");
Serial.println(payload);
https.end();
return;
// 创建 DynamicJsonDocument 对象
JsonDocument doc;
// 解析 JSON 数据
deserializeJson(doc, payload);
// 从解析后的 JSON 文档中获取值
String city = "大同";
String weather = doc["now"]["text"].as<String>();
unsigned int windScale = doc["now"]["windScale"].as<unsigned int>();
int temperature = doc["now"]["temp"].as<int>();
Serial.println("城市: " + city);
Serial.println("天气: " + weather);
Serial.println("风速: " + windScale);
Serial.println("温度: " + temperature);
/* // unsigned int temp = doc["now"]["temp"].as<unsigned int>();
String city = doc["results"][0]["location"]["name"].as<String>();
String weather = doc["results"][0]["now"]["text"].as<String>();
int temperature = doc["results"][0]["now"]["temperature"].as<int>();
Serial.printf("城市: %s\n", city);
Serial.printf("天气: %s\n", weather);
Serial.printf("温度: %d\n", temperature); */
weather_state = true;
Serial.printf("温度2222222222: %d\n", temperature);
}
void spinner()
{
static int8_t counter = 10;
u8g2.firstPage();
do
{
/* all graphics commands have to appear within the loop body. */
u8g2.drawBox(110, 50, counter, counter); // 画填充矩形w,h,宽度,高度 2 u8g2.drawFrame(x, y, w, h); // 画空心矩形w,h,宽度,高度
counter++;
if (counter == 15)
{
counter = 10;
}
Serial.println("x");
delay(30);
} while (u8g2.nextPage());
}