ESP32抓取心知天气、苏宁API并且将数据显示到0.96寸OLED显示

声明:文章仅为个人学习分享,不能保证所有注释无误,代码处已给出注释,文章不再赘述

一、开发环境以及设备

  • VScode上的ESPIDF
  • 合宙ESP32C3开发板
  • 四针0.96OLED显示屏
  • 一根type-c(用于烧录程序)
  • 4个杜邦线(用于OLED显示屏和ESP32C3开发板连接)
  • 可连网开热点设备(不限于移动手机或者笔记本电脑等)
  • 心知天气免费API(自行申请)

二、 结果展示

在这里插入图片描述
在这里插入图片描述

三、 内容

1、 定时器

用于1秒计时

mTimer.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "mTimer.h"

bool time_1s_falg = false;      /*用于表示1S到达*/

/**
 * @brief 将计数值发送到消息队列中
 * 
 * @param timer 定时器句柄
 * @param edata 报警事件数据
 * @param user_data 用户注册回调函数时数据
 * @return true 未使用,默认返回true
 * @return false 
 */
static bool IRAM_ATTR Timierng_Callback_Func(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
    time_1s_falg = true;
    return true;
}

void Timer_Init(uint64_t count_us)
{
    gptimer_handle_t gptimer = NULL;                /*通用定时器句柄*/
    gptimer_config_t timer_config = {               /*配置结构体*/
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,         /*选择APB作为默认时钟源*/
        .direction = GPTIMER_COUNT_UP,              /*设置计数方向为向上计数*/
        .resolution_hz = 1000000,                   /*设置计数器频率1M*/
    };
    /*创建一个新的定时器*/
    ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
    /*设置事件回调函数*/
    gptimer_event_callbacks_t cbs = {
        .on_alarm = Timierng_Callback_Func,
    };
    /*注册事件回调函数*/
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
    ESP_ERROR_CHECK(gptimer_enable(gptimer));   /*使能定时器*/
    gptimer_alarm_config_t alarm_config = {
        .reload_count = 0,
        .alarm_count = count_us,
        .flags.auto_reload_on_alarm = 1,
    };
    /*使警报配置生效*/
    ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
    /*使定时器开始工作*/
    ESP_ERROR_CHECK(gptimer_start(gptimer));
}


mTimer.h

#ifndef __MTIMER_H__
#define __MTIMER_H__

#include <stdio.h>

extern bool time_1s_falg;

void Timer_Init(uint64_t count_us);

#endif

2、 oled显示

I2C通讯,用于显示时间、天气、地点、温度

oled.c

#include "oled.h"
#include "mfont.h"
#include "esp_err.h"
#include "driver/i2c.h"

#define     I2C_SCL                 6                       /*时钟线*/
#define     I2C_SDA                 5                       /*数据线*/
#define     I2C_FREQ_HZ             400000                  /*频率:400K*/
#define     I2C_ADDR                0X3C                    /*OLED地址*/
#define     I2C_PORT                0                       /*I2C通道*/
#define     I2C_MASTER_TIMEOUT_MS   100                     /*发出超时等待时间*/
#define     OLED_CMD                0                       /*写命令*/
#define     OLED_DATA               1                       /*写数据*/


/**
 * @brief I2C初始化
 * 
 * @return esp_err_t ESP_OK:表示初始化成功
 */
static esp_err_t I2C_Init(void)    /*大多数特定的 ESP-IDF 函数使用 esp_err_t 类型来返回错误代码ESP_OK 表示为成功*/
{
    int i2c_port = I2C_PORT;
    /*I2C初始化配置*/
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .scl_io_num = I2C_SCL,
        .sda_io_num = I2C_SDA,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,    /*使能上拉*/
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_FREQ_HZ};

    i2c_param_config(i2c_port, &conf);
    /*参数:I2C端口号、模式、主机模式无需填写(写零)、主机模式无需填写(写零)、中断标志(0:忽略中断);返回ESP_OK表示成功安装驱动*/
    return i2c_driver_install(i2c_port, conf.mode, 0, 0, 0);
}

/**
 * @brief 写字节
 * 
 * @param dat 数据
 * @param mode 1:内容 0:命令
 * @return esp_err_t 
 */
static esp_err_t OLED_WR_Byte(uint8_t dat, uint8_t mode)
{
    int ret;
    uint8_t cmd;
    if(1 == mode)
        cmd = 0x40;
    else
        cmd = 0x00;
    uint8_t write_buf[2] = {cmd, dat};
    /*参数:I2C端口号、I2C设备7位地址、发送的字节、写入缓冲区大小、发出超时之前等待最大时长tick*/
    ret = i2c_master_write_to_device(I2C_PORT, I2C_ADDR , write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
    return ret;
}


/**
 * @brief 清屏
 * 
 */
void OLED_Clear(void)
{
    uint8_t i, n;
    for (i = 0; i < 8; i++)
    {
        OLED_WR_Byte(0xb0 + i, OLED_CMD);
        OLED_WR_Byte(0x00, OLED_CMD);
        OLED_WR_Byte(0x10, OLED_CMD);
        for (n = 0; n < 128; n++)
            OLED_WR_Byte(0, OLED_DATA);
    }
}
/**
 * @brief OLED初始化
 * 
 */
void OLED_Init(void)
{
    ESP_ERROR_CHECK(I2C_Init());
    OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
	OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
	OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WR_Byte(0x00,OLED_CMD);//-not offset
	OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
	OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
	OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
	OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
	OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
	OLED_Clear();
	OLED_WR_Byte(0xAF,OLED_CMD);
}
/**
 * @brief 坐标设置
 * 
 * @param x 列:0~127
 * @param y 页:0~7
 */
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
    OLED_WR_Byte(0xb0 + y, OLED_CMD);
    OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
    OLED_WR_Byte((x & 0x0f), OLED_CMD);
}

/**
 * @brief 显示字符
 * 
 * @param x 起始坐标:0~127
 * @param y 起始坐标:0~7
 * @param chr 显示的字符
 * @param Char_Size 大小:8或16
 */
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size)
{
    uint8_t c = 0;
    uint8_t i = 0;
    c = chr - ' ';
    if (x > 127)
    {
        x = 0;
        y = y + 2;
    }
    if (Char_Size == 16)
    {
        OLED_Set_Pos(x, y);
        for (i = 0; i < 8; i++)
            OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
        OLED_Set_Pos(x, y + 1);
        for (i = 0; i < 8; i++)
            OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
    }
    else
    {
        OLED_Set_Pos(x, y);
        for (i = 0; i < 6; i++)
            OLED_WR_Byte(F6x8[c][i], OLED_DATA);
    }
}
/**
 * @brief 显示字符串
 * 
 * @param x 起始坐标:0~127
 * @param y 起始坐标:0~7
 * @param chr 显示的字符串
 * @param Char_Size 大小:8或16
 */
void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t Char_Size)
{
    uint8_t j = 0;
    while (chr[j] != '\0')
    {
        OLED_ShowChar(x, y, chr[j], Char_Size);
        x += 8;
        if (x > 120)
        {
            x = 0;
            y += 2;
        }
        j++;
    }
}
/**
 * @brief 显示汉字
 * 
 * @param x 起始坐标:0~127
 * @param y 起始坐标:0~7
 * @param no 对应汉字序列号
 */
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no)
{
    uint8_t t, adder = 0;
    OLED_Set_Pos(x, y);
    for (t = 0; t < 16; t++)
    {
        OLED_WR_Byte(Hzk[2 * no][t], OLED_DATA);
        adder += 1;
    }
    OLED_Set_Pos(x, y + 1);
    for (t = 0; t < 16; t++)
    {
        OLED_WR_Byte(Hzk[2 * no + 1][t], OLED_DATA);
        adder += 1;
    }
}

/**
 * @brief 显示图片,注意:起始坐标加上实际大小不要从超过显示最大范围
 * 
 * @param x0 起始坐标0~127
 * @param y0 0~7
 * @param x1 长0~127
 * @param y1 宽0~63
 * @param BMP 所显示图片
 */
void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t BMP[])
{
    uint32_t j = 0;
    uint8_t x, y;
    y1 = y1 / 8 + ((y1 % 8)?1 : 0);     /*计算后宽实际值还是0~7,为了方便调用时填入像素点长和宽值看起来和谐*/
    for (y = y0; y < y1 + y0; y++)
    {
        OLED_Set_Pos(x0, y);
        for (x = x0; x < x1 + x0; x++)
        {
            OLED_WR_Byte(BMP[j++], OLED_DATA);
        }
    }
}
/**
 * @brief 求m的n次方
 * 
 * @param m 底数
 * @param n 指数
 * @return uint32_t 
 */
uint32_t oled_pow(uint8_t m,uint8_t n)
{
	uint32_t result=1;	 
	while(n--)result*=m;    
	return result;
}	
/**
 * @brief 显示数字,依据显示位数自动补零在前面
 * 
 * @param x0 起始坐标0~127
 * @param y0 0~7
 * @param num 数字
 * @param len 显示所占的长度(不应小于实际大小)
 * @param size2 字体大小16、8
 */
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size2)
{
    uint8_t t, temp, m = 0;
    if (8 == size2)
        m = 2;
    for (t = 0; t < len; t++)
    {
        temp = (num / oled_pow(10, len - t - 1)) % 10;
        if (temp == 0)
        {
            OLED_ShowChar(x + (size2 / 2 + m) * t, y, '0', size2);
        }
        else
        {
            OLED_ShowChar(x + (size2 / 2 + m) * t, y, temp + '0', size2);
        }
    }
}


oled.h

#ifndef __OLED_H__
#define __OLED_H__

#include <stdio.h>

/*OLED初始化*/
void OLED_Init(void);
/*清屏函数*/
void OLED_Clear(void);
/*设置坐标*/
void OLED_Set_Pos(uint8_t x, uint8_t y);
/*显示字符*/
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size);
/*显示字符串*/
void OLED_ShowString(uint8_t x,uint8_t y,uint8_t *chr,uint8_t Char_Size);
/*显示汉字*/
void OLED_ShowCHinese(uint8_t x,uint8_t y,uint8_t no);
/*显示图片*/
void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t BMP[]);
/*显示数字*/
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size2);

#endif

3、 main函数

程序功能简述

  • 每次连上WiFi时候抓取一次时间,然后不再抓取,通过定时器来计时
  • 每10秒钟抓取一次心知天气的天气信息
  • 无论时间还是天气,再将数据处理后第一时间显示到OLED屏上
/**
 * @file main.c
 * @author Acy (2496188164@qq.com)
 * @brief 上电获取一次时间,每隔一定时间获取指定地点一次天气和温度,并再OLED显示屏上显示 
 *        时间处理上一开始就打开了定时器,应当再获取到现在时间时先清空当前计数值,否则程序会有-1S~1S的误差
 *        通过不断创建任务来替代while循环不断打开然后关闭http客户端(while循环按一定频率抓取天气,10S抓取一次时,大约十分钟左右就会报空间不足mbedtls_ssl_setup returned -0x7F00)
 *        注意:注释的语句若没有特殊说明则为调试过程所用
 *             UTF-8编码:汉字3个字节
 *
 * @version 0.1
 * @date 2024-01-11
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include <stdio.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_ip_addr.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "cJSON.h"
#include "mTimer.h"
#include "oled.h"
#include "mfont.h"

/*苏宁时间戳:http://quan.suning.com/getSysTime.do*/
/*我的心知天气:https://api.seniverse.com/v3/weather/now.json?key=SXadqEfe17oFmTEg_&location=qinzhou&language=zh-Hans&unit=c*/

#define     WIFI_STA_NAME                   "Acy_PC"                /*WIFI名字*/
#define     WIFI_STA_PASSWORD               "1234567890"            /*WIFI密码*/
#define     WEATHER_DATA_LEN_MAX            384                     /*获取心知天气数据数组长度*/
#define     TIME_DATA_LEN_MAX               248                     /*获取苏宁时间戳的数组长度*/
#define     WEATHER_GET_INTERVAL_TIME_S     10                      /*获取心知天气的间隔时间*/

/*oled显示屏时间显示*/
typedef struct
{
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
}time_struct;

/*中文数组下标,用于可同拼音不同声调下*/
typedef enum{
    CHINESE_TIAN = 0,   /*天*/
    CHINESE_QI,         /*气*/
    CHINESE_QING,       /*晴*/
    CHINESE_WU_4,       /*雾*/
    CHINESE_MAI,        /*霾*/
    CHINESE_YU,         /*雨*/
    CHINESE_XIAO,       /*小*/
    CHINESE_ZHONG,      /*中*/
    CHINESE_DA,         /*大*/
    CHINESE_BAO,        /*暴*/
    CHINESE_DUO,        /*多*/
    CHINESE_YUN,        /*云*/
    CHINESE_YIN,        /*阴*/
    CHINESE_C,          /*℃*/
    CHINESE_BEI,        /*北*/
    CHINESE_JING,       /*京*/
    CHINESE_NAN,        /*南*/
    CHINESE_QIN,        /*钦*/
    CHINESE_ZHOU,       /*州*/
    CHINESE_NING,       /*宁*/
    CHINESE_WU_2,       /*梧*/
}CHINESE_DATA_CODE;

/*需要与compare_location地点顺序一致,判断代码中通过遍历compare_location字符串来判断对应的区域代号*/
typedef enum
{
    BEIJING = 0,
    QINZHOU,
    NANJING,
    NANNING,
    WUZHOU
}WEATHER_LOCATION_CODE;
/*部分天气图标数组下标*/
typedef enum
{
    WEATHER_DUOYUN = 0,     /*多云*/
    WEATHER_QINGTIAN,       /*晴天*/
    WEATHER_YINTIAN,        /*阴天*/
    WEATHER_WEIZHI,         /*未知(这里未知代表未知天气或者未知天气图标)*/
    WEATHER_MAI,            /*霾*/
    WEATHER_WU,             /*雾*/
    WEATHER_XIAOYU,         /*小雨*/
    WEATHER_ZHONGYU,        /*中雨*/
    WEATHER_DAYU,           /*大雨*/
    WEATHER_BAOYU,          /*暴雨*/
}WEATHER_CODE;
/*用于处理城市数据结构体*/
typedef struct
{
    WEATHER_LOCATION_CODE location_code;        /*地点对应代号值*/
    WEATHER_CODE weather_code;                  /*天气对应代号值*/
    int8_t temperature;                         /*温度*/
}weather_struct;

/*用于调试日志ESP_LOGE*/
static const char *Wifi_Tag = "wifi_sta";       
static const char *Http_Tag = "Http_Task";
static uint8_t wifi_retry_count = 0;            /*wifi重连次数*/

static bool get_time_flag = false;              /*是否需要重新获取时间(断网重连)*/
static bool get_weather_flag = false;           /*是否可以创建获取天气任务*/
static bool wifi_connect_status = false;        /*是否连接上WiFi(获取到WiFi的AP即可,无论该WiFi是否有网)*/
static uint8_t current_num = 0;                 /*当前数字,不同数字代表不同城市,这里用来遍历不同地点的url配置*/

/*
注意:添加抓取地点时,需要同时在location和compare_location中添加
      地名只允许两个字地名,未进行不同长度地名显示处理
*/
const char *const location[5] = {
    "beijing", "qinzhou", "nanjing", "nanning", "wuzhou"        /*抓取地点*/
};
const char compare_location[] = "北京钦州南京南宁梧州";         /*用于比较抓取到的地点*/

time_struct oled_time = {               /*显示时间结构体默认值*/
    .year = 2019,
    .month = 8,
    .day = 30,
    .hour = 10,
    .minute = 11,
    .second = 12,
};

weather_struct oled_weather = {         /*当地天气代号结构体,默认值*/
    .location_code = BEIJING,
    .temperature = 25,
    .weather_code = WEATHER_WEIZHI,
};


/*抓取天气信息任务*/
static void Http_Clinet_Weather_Task(void *pvParameters);
/*抓取时间信息任务*/
static void Http_Clinet_Time_Task(void *pvParameters);
/*主要运行的任务*/
static void Main_Task(void *pvParameters);
/*wifi事件处理函数*/
static void Event_Handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data);
/*主要程序入口函数*/
static void Program_Entry(void);
/*时间处理显示到oled函数*/
static void Timer_Deal_Func(void);
/*月份处理函数*/
static void February_Deal(void);        /*二月处理*/
static bool Is_Leap_Year(void);         /*是否为闰年*/
/*天气信息处理显示到oled函数*/
static void Weather_Data_Deal(const char *location_name, const uint8_t weather_code, const int8_t weather_temperature);    
/*显示温度函数*/   
static void OLED_Show_Temperature(void);        

void app_main(void)
{
    OLED_Init();        /*OLED初始化*/
    /*默认显示数据*/
    /*年月日时分秒*/
    OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
    OLED_ShowChar(72, 3, ':', 16);
    OLED_ShowNum(80, 3, oled_time.minute, 2, 16);       
    OLED_ShowChar(96, 3, ':', 16);
    OLED_ShowNum(104, 3, oled_time.second, 2, 16);
    OLED_ShowNum(54, 7, oled_time.year, 4, 8);
    OLED_ShowChar(80, 7, '/', 8);
    OLED_ShowChar(104, 7, '/', 8);
    OLED_ShowNum(88, 7, oled_time.month, 2, 8);
    OLED_ShowNum(112, 7, oled_time.day, 2, 8);
    /*地点*/
    OLED_ShowCHinese(48, 0, CHINESE_BEI);
    OLED_ShowCHinese(64, 0, CHINESE_JING);
    OLED_ShowChar(84, 0, ':', 16);
    /*天气情况*/
    OLED_ShowCHinese(96, 0, CHINESE_QING);
    OLED_ShowCHinese(112, 0, CHINESE_TIAN);
    /*图标*/
    OLED_DrawBMP(0, 0, 48, 48, (uint8_t*)weather_logo[oled_weather.weather_code]);
    /*温度*/
    OLED_Show_Temperature();
    /*1S定时器初始化*/
    Timer_Init(1000000);
    /*一直运行的任务*/
    xTaskCreate(Main_Task, "oled_timer_client", 8096, NULL, 2, NULL);
    /*主要程序入口*/
    Program_Entry();
}

/**
 * @brief 获取时间任务
 * 
 * @param pvParameters 未使用传参
 */
static void Http_Clinet_Time_Task(void *pvParameters)
{
    get_time_flag = true;                               /*允许再次创建抓取时间任务*/
    cJSON *cjson_root = NULL;
    cJSON *cjson_child = NULL;
    char time_array[TIME_DATA_LEN_MAX] = {0};
    char temp_year_arry[4];
    char temp_time_arry[2];
    int data_len = 0;
    esp_http_client_config_t config_time = 
    {
        .url = "http://quan.suning.com/getSysTime.do",
    };
    esp_http_client_handle_t client_time = esp_http_client_init(&config_time);          /*创建http客户端句柄*/
    esp_http_client_set_method(client_time, HTTP_METHOD_GET);                           /*设置http请求方法为GET*/
    esp_err_t err = esp_http_client_open(client_time, 0);  
    if(ESP_OK != err)
    {
        ESP_LOGE(Http_Tag, "Failed to open HTTP connection: %s", esp_err_to_name(err));
    }
    else
    {
        data_len = esp_http_client_fetch_headers(client_time);
        if(data_len < 0)
        {
            ESP_LOGE(Http_Tag,  "HTTP client_weather fetch headers failed!");
        }
        else
        {
            int data_read = esp_http_client_read_response(client_time, time_array, 250);
            if(data_read >= 0)
            {
                /*解析数据*/
                cjson_root = cJSON_Parse(time_array);                           /*获取解析根*/
                cjson_child = cJSON_GetObjectItem(cjson_root, "sysTime2");      /*解析的键results*/
                if(NULL!= cjson_child)
                {
                    get_time_flag = false;                                          /*不允许再次抓取任务*/
                    ESP_LOGI(Http_Tag, "北京时间:%s", cjson_child->valuestring);
                    /*年-月-日*/
                    memcpy(temp_year_arry, cjson_child->valuestring, 4);            /*前4个为年*/
                    oled_time.year = atoi(temp_year_arry);
                    memcpy(temp_time_arry, (cjson_child->valuestring) + 5, 2);      /*第6、7个数据为月*/
                    oled_time.month = atoi(temp_time_arry);
                    memcpy(temp_time_arry, (cjson_child->valuestring) + 8, 2);     
                    oled_time.day = atoi(temp_time_arry);
                    /*时:分:秒*/
                    memcpy(temp_time_arry, (cjson_child->valuestring) + 11, 2);     
                    oled_time.hour = atoi(temp_time_arry);
                    memcpy(temp_time_arry, (cjson_child->valuestring) + 14, 2);     
                    oled_time.minute = atoi(temp_time_arry);
                    memcpy(temp_time_arry, (cjson_child->valuestring) + 17, 2);     
                    oled_time.second = atoi(temp_time_arry);
                    ESP_LOGI(Http_Tag, "%04d-%02d-%02d %02d:%02d:%02d", oled_time.year, oled_time.month, oled_time.day, 
                                                            oled_time.hour, oled_time.minute, oled_time.second);
                    OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
                    OLED_ShowNum(80, 3, oled_time.minute, 2, 16);       
                    OLED_ShowNum(104, 3, oled_time.second, 2, 16);
                    OLED_ShowNum(54, 7, oled_time.year, 4, 8);
                    OLED_ShowChar(80, 7, '/', 8);
                    OLED_ShowChar(104, 7, '/', 8);
                    OLED_ShowNum(88, 7, oled_time.month, 2, 8);
                    OLED_ShowNum(112, 7, oled_time.day, 2, 8);
                }
            }
            else
            {
                ESP_LOGE(Http_Tag, "Failed to read response");
            }
        }
    }
    esp_http_client_close(client_time);     /*关闭连接*/
    esp_http_client_cleanup(client_time);   /*释放资源*/
    cJSON_Delete(cjson_root);               /*删除cjson_root结构体*/
    ESP_LOGE(Http_Tag, "删除本次任务(时间获取)");
    vTaskDelete(NULL);                      /*将自己删除*/
}

/**
 * @brief 获取天气任务
 * 
 * @param pvParameters 未使用传参
 */
static void Http_Clinet_Weather_Task(void *pvParameters)
{
    get_weather_flag = 1;
    cJSON *cjson_root = NULL;
    cJSON *cjson_results = NULL;
    cJSON *cjson_now = NULL;
    cJSON *cjson_temperature = NULL;
    cJSON *cjson_weather = NULL;
    cJSON *cjson_code = NULL;
    cJSON *cjson_location = NULL;
    cJSON *cjson_name = NULL;
    char weather_array[WEATHER_DATA_LEN_MAX] = {0};
    char url[100] = "https://api.seniverse.com/v3/weather/now.json?key=SXadqEfe17oFmTEg_&location=";
    strcat(url, location[current_num]);
    strcat(url, (char *)"&language=zh-Hans&unit=c");
    esp_http_client_config_t config_weather = 
    {
        .url = url,
    };
    esp_http_client_handle_t client_weather = esp_http_client_init(&config_weather);
    esp_http_client_set_method(client_weather, HTTP_METHOD_GET);
    esp_err_t err = esp_http_client_open(client_weather, 0);
    if(ESP_OK != err)
    {
        ESP_LOGE(Http_Tag, "Failed to open HTTP connection: %s", esp_err_to_name(err));
    }
    else
    {
        int data_len = esp_http_client_fetch_headers(client_weather);
        if(data_len < 0)
        {
            ESP_LOGE(Http_Tag,  "HTTP client_weather fetch headers failed!");
        }
        else
        {
            int data_read = esp_http_client_read_response(client_weather, weather_array, WEATHER_DATA_LEN_MAX);
            if(data_read >= 0)
            {
                /*一些调试过程中未处理的数据*/
                // ESP_LOGI(Http_Tag, "HTTP GET Status = %d, content_length = %lld",
                // esp_http_client_get_status_code(client_weather),
                // esp_http_client_get_content_length(client_weather));
                // printf("data:%s", weather_array);
                // ESP_LOGI(Http_Tag, "data:%s", weather_array);
                /*解析数据*/
                cjson_root = cJSON_Parse(weather_array);                            /*获取解析根*/
                cjson_results = cJSON_GetObjectItem(cjson_root, "results");         /*解析的键results*/
                cjson_now = cJSON_GetArrayItem(cjson_results, 0);                   /*键results数组第0个值*/
                cjson_now = cJSON_GetObjectItem(cjson_now, "now");                  /*键results数组第0个值中的键now*/
                cjson_temperature = cJSON_GetObjectItem(cjson_now, "temperature");
                cjson_weather = cJSON_GetObjectItem(cjson_now, "text");
                cjson_code = cJSON_GetObjectItem(cjson_now, "code");
                cjson_location = cJSON_GetArrayItem(cjson_results, 0);                   /*键results数组第0个值*/
                cjson_location = cJSON_GetObjectItem(cjson_location, "location");        /*键results数组第0个值中的键location*/
                cjson_name =  cJSON_GetObjectItem(cjson_location, "name");
                if(NULL!= cjson_temperature)
                    ESP_LOGI(Http_Tag, "%s温度:%s",cjson_name->valuestring, cjson_temperature->valuestring);
                if(NULL != cjson_weather)
                    ESP_LOGI(Http_Tag, "%s天气:%s",cjson_name->valuestring, cjson_weather->valuestring);
                Weather_Data_Deal(cjson_name->valuestring, atoi(cjson_code->valuestring), atoi(cjson_temperature->valuestring));
            }
            else
            {
                ESP_LOGE(Http_Tag, "Failed to read response");
            }
        }
    }
    esp_http_client_close(client_weather);                  /*关闭连接*/
    esp_http_client_cleanup(client_weather);                /*记得调用该函数清空内存(释放资源)*/
    cJSON_Delete(cjson_root);                               /*释放内存,删除cJSON实体和所有子实体*/
    ESP_LOGE(Http_Tag, "删除本次任务(天气获取)");
    get_weather_flag = true;                                /*允许再次创建任务*/
    vTaskDelete(NULL);
}

/**
 * @brief 
 * 
 * @param arg           传递的参数
 * @param event_base    基本事件
 * @param event_id      事件ID
 * @param event_data    事件数据
 */
static void Event_Handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if(WIFI_EVENT == event_base)
    {
        // wifi_event_sta_connected_t *wifi_sta_event = (wifi_event_sta_connected_t *)event_data;
        switch (event_id)
        {
        case WIFI_EVENT_STA_START:
            ESP_LOGI(Wifi_Tag, "connect to AP:%s ", WIFI_STA_NAME);
            /*
            成功连接到目标AP后产生WIFI_EVENT_STA_CONNECTED,失败则WIFI_EVENT_STA_DISCONNECTED;
            中间省略,具体参考官网;若IP地址成功获取 产生IP_EVENT_STA_GOT_IP事件
            */
            esp_wifi_connect();                         
            break;
        case WIFI_EVENT_STA_CONNECTED:
            ESP_LOGI(Wifi_Tag, "connected");
            wifi_retry_count = 0;
            // wifi_sta_event = (wifi_event_sta_connected_t *)event_data;
            // memcpy(sta_bssid, wifi_sta_event->bssid, 6);
			// memcpy(sta_ssid, wifi_sta_event->ssid, wifi_sta_event->ssid_len);
			// sta_ssid_len = wifi_sta_event->ssid_len;
			break;
        case WIFI_EVENT_STA_DISCONNECTED:
            // ESP_LOGI(Wifi_Tag, "掉线了 ");
            get_weather_flag = false;           /*允许下次连上网后重新打开获取天气任务*/
            wifi_connect_status = false;        /*不允许创建抓取时间任务*/
            wifi_retry_count++;
            ESP_LOGI(Wifi_Tag, "The %dth:retry to connect to AP:%s ", wifi_retry_count,  WIFI_STA_NAME);
            esp_wifi_connect();
            break;
        default:
            break;
        }
    }
    else if(IP_EVENT == event_base)
    {
        ip_event_got_ip_t *ip_event = (ip_event_got_ip_t *)event_data;
        /*连接上网后*/
        if(IP_EVENT_STA_GOT_IP == event_id)
        {
            ESP_LOGI(Wifi_Tag, "got ip:" IPSTR, IP2STR(&ip_event->ip_info.ip));
            wifi_retry_count = 0;
            if(!get_weather_flag)
                xTaskCreate(Http_Clinet_Weather_Task, "http_client_weather", 8192, NULL, 3, NULL);      /*创建抓取天气任务*/
            if(!get_time_flag)                  /*断网重连后会执行*/
                xTaskCreate(Http_Clinet_Time_Task, "http_client_time", 8192, NULL, 3, NULL);            /*创建抓取时间任务*/
            wifi_connect_status = true;
        }
    }
    return;
}

static void Program_Entry(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());                              /*初始化默认的NVS(非易失性存储)分区*/
    ESP_ERROR_CHECK(esp_netif_init());                              /*创建LwIP核心任务,并初始化LwIP相关工作*/
    ESP_ERROR_CHECK(esp_event_loop_create_default());               /*创建默认事件循环*/
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();   /*创建有 TCP/IP 堆栈的默认网络接口实例绑定 station */
    assert(sta_netif);
    
    /*
    esp_event_handler_register:参数1:要为其注册处理程序的事件的基本 ID    参数2:要为其注册处理程序的事件的 ID
                                参数3:调度事件时调用的处理程序函数         参数4:数据,除了事件数据之外,在调用处理程序时传递给处理程序
                                IP_EVENT_STA_GOT_IP:当 DHCP 客户端成功从 DHCP 服务器获取 IPV4 地址或 IPV4 地址发生改变时,将引发此事件
    */
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &Event_Handler, NULL));
	ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &Event_Handler, NULL));   
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));                           /*创建 Wi-Fi 驱动程序任务,并初始化 Wi-Fi 驱动程序。*/
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));              /*将 Wi-Fi 模式配置为 station 模式*/
    wifi_config_t wifi_config= 
    {
        .sta = 
        {
            .ssid = WIFI_STA_NAME,                                  /*WiFi名*/
			.password = WIFI_STA_PASSWORD,                          /*WiFi密码*/
			.bssid_set = 0,                                         /*对于站位配置,bssid_set需要为0;并且只有当用户需要检查AP的MAC地址时,它才需要为1。*/
		}
	};

    ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );  /*设置STA的配置*/
    ESP_ERROR_CHECK( esp_wifi_start() );                                /*启动wifi驱动程序 会产生WIFI_EVENT_STA_START 事件*/
    ESP_LOGI(Wifi_Tag, "wifi init sta finished!");
}
/**
 * @brief 一直运行的任务
 * 
 * @param pvParameters 
 */
static void Main_Task(void *pvParameters)
{
    uint8_t i = 0;
    bool blink_flag = false;
    while(1)
    {
        if(time_1s_falg)
        {
            blink_flag = !blink_flag;
            /*如果联网状态则抓取地点的天气*/
            if((true == get_weather_flag) && (0 == i % WEATHER_GET_INTERVAL_TIME_S) && (true == wifi_connect_status))
            {
                current_num++;
                if(current_num > 4)
                    current_num = 0;
                i = 0;
                get_weather_flag = false;
                xTaskCreate(Http_Clinet_Weather_Task, "http_client_weather", 8192, NULL, 3, NULL);
            }
            /*如果本次连上Wifi之后没抓取过时间*/
            if((true == get_time_flag) && (true == wifi_connect_status))   
            {
                 xTaskCreate(Http_Clinet_Time_Task, "http_client_time", 8192, NULL, 3, NULL);
            }
            time_1s_falg = false;
            oled_time.second++; 
            i++;
            Timer_Deal_Func();
            ESP_LOGI(Http_Tag, "%04d-%02d-%02d %02d:%02d:%02d", oled_time.year, oled_time.month, oled_time.day, 
                                                                oled_time.hour, oled_time.minute, oled_time.second);
        }
        if(blink_flag)
        {
            OLED_ShowChar(72, 3, ':', 16);
            OLED_ShowChar(96, 3, ':', 16);
        }
        else
        {
            OLED_ShowChar(72, 3, ' ', 16);
            OLED_ShowChar(96, 3, ' ', 16);
        }
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);                      /*将自己删除*/
}
/**
 * @brief 时间处理函数
 * 
 */
static void Timer_Deal_Func(void)
{
    if(oled_time.second >= 60)
    {
        oled_time.second = 0;
        oled_time.minute++;
        if(oled_time.minute >= 60)
        {
            oled_time.minute = 0;
            oled_time.hour++;
            if(oled_time.hour >= 24)
            {
                /*这里可以重新抓取一下时间*/
                oled_time.day++;
                oled_time.hour = 0;
                switch(oled_time.month)
                {
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 10:
                case 12:
                    /*处理大月*/
                    if(oled_time.day > 31)
                    {
                        oled_time.month++;
                        oled_time.day = 1;
                    }
                    break;
                case 2:
                    /*处理平月*/
                    February_Deal();
                    break;
                case 4:
                case 6:
                case 9:
                case 11:
                    /*处理小月*/
                    if(oled_time.day > 30)
                    {
                        oled_time.month++;
                        oled_time.day = 1;
                    }
                    break;
                default:
                    break;
                }
                if(oled_time.month > 12)
                {
                    oled_time.month = 1;
                    oled_time.year++;
                    OLED_ShowNum(54, 7, oled_time.year, 4, 8);
                }
                OLED_ShowNum(88, 7, oled_time.month, 2, 8);
                OLED_ShowNum(112, 7, oled_time.day, 2, 8);
            }
            OLED_ShowNum(56, 3, oled_time.hour, 2, 16);
        }
        OLED_ShowNum(80, 3, oled_time.minute, 2, 16);           /*分钟更新*/
    }
    OLED_ShowNum(104, 3, oled_time.second, 2, 16);
    
}

/**
 * @brief 判断是否未润连
 * 
 * @return true 闰年
 * @return false 平年
 */
static bool Is_Leap_Year(void)
{
    return (0 == oled_time.year % 4 && 0 != oled_time.year % 100) || (0 == oled_time.year % 400);
}
/**
 * @brief 二月处理函数
 * 
 */
static void February_Deal(void)
{
    if(Is_Leap_Year())
    {
        /*闰年处理*/
        if(oled_time.day > 29)
        {
            oled_time.month++;
            oled_time.day = 1;
        }
    }
    else
    {
        /*平年处理*/
        if(oled_time.day > 28)
        {
            oled_time.month++;
            oled_time.day = 1;
        }
    }
}
/**
 * @brief 处理地点和温度
 * 
 * @param location_name 地点字符串
 * @param weather_code  心知天气API返回的天气代号,不同于上述自己设定的天气代号
 * @param weather_temperature 当地的温度
 */
static void Weather_Data_Deal(const char *location_name, const uint8_t weather_code, const int8_t weather_temperature)
{
    /*处理天气图标以及显示天气状况*/
    uint8_t i = 0;
    uint8_t j = 0;
    switch (weather_code)
    {
        case 0:
        case 1:
        case 2:
        case 3:
            /*晴天*/
            oled_weather.weather_code = WEATHER_QINGTIAN;
            OLED_ShowCHinese(96, 0, CHINESE_QING);
            OLED_ShowCHinese(112, 0, CHINESE_TIAN);
            break;
        case 4:
            /*多云*/
            oled_weather.weather_code = WEATHER_DUOYUN;
            OLED_ShowCHinese(96, 0, CHINESE_DUO);
            OLED_ShowCHinese(112, 0, CHINESE_YUN);
            break;
        case 9:
            /*阴*/
            oled_weather.weather_code = WEATHER_YINTIAN;
            OLED_ShowCHinese(96, 0, CHINESE_YIN);
            OLED_ShowCHinese(112, 0, CHINESE_TIAN);
            break;
        case 13:
            /*小雨*/
            oled_weather.weather_code = WEATHER_XIAOYU;
            OLED_ShowCHinese(96, 0, CHINESE_XIAO);
            OLED_ShowCHinese(112, 0, CHINESE_YU);
            break;
        case 14:
            /*中雨*/
            oled_weather.weather_code = WEATHER_ZHONGYU;
            OLED_ShowCHinese(96, 0, CHINESE_ZHONG);
            OLED_ShowCHinese(112, 0, CHINESE_YU);
            break;
        case 15:
            /*大雨*/
            oled_weather.weather_code = WEATHER_DAYU;
            OLED_ShowCHinese(96, 0, CHINESE_DA);
            OLED_ShowCHinese(112, 0, CHINESE_YU);
            break;
        case 16:
            /*暴雨*/
            oled_weather.weather_code = WEATHER_BAOYU;
            OLED_ShowCHinese(96, 0, CHINESE_BAO);
            OLED_ShowCHinese(112, 0, CHINESE_YU);
            break;
        case 30:
            /*雾*/
            oled_weather.weather_code = WEATHER_WU;
            OLED_ShowCHinese(96, 0, CHINESE_WU_4);
            OLED_ShowCHinese(112, 0, CHINESE_TIAN);
            break;
        case 31:
            /*霾*/
            oled_weather.weather_code = WEATHER_MAI;
            OLED_ShowCHinese(96, 0, CHINESE_MAI);
            OLED_ShowCHinese(112, 0, CHINESE_TIAN);
            break;
        default:
            break;
    }
    OLED_DrawBMP(0, 0, 48, 48, (uint8_t*)weather_logo[oled_weather.weather_code]);
    /*温度处理*/
    oled_weather.temperature = weather_temperature;
    OLED_Show_Temperature();
    /*地点处理*/
    for(i = 0, j = 0;i < strlen(compare_location); i += 2 * 3, j++)     /*注意utf-8编码中:汉字占三个字节*/
    {
        if(0 == strncmp(location_name, compare_location + i , 2 * 3))   /*这里同样也要注意,比较汉字长度为6*/
        {
            oled_weather.location_code =j;
        }
    }
    switch (oled_weather.location_code)
    {
    case BEIJING:
        OLED_ShowCHinese(48, 0, CHINESE_BEI);
        OLED_ShowCHinese(64, 0, CHINESE_JING);
        break;
    case NANJING:
        OLED_ShowCHinese(48, 0, CHINESE_NAN);
        OLED_ShowCHinese(64, 0, CHINESE_JING);
        break;
    case NANNING:
        OLED_ShowCHinese(48, 0, CHINESE_NAN);
        OLED_ShowCHinese(64, 0, CHINESE_NING);
        break;
    case QINZHOU:
        OLED_ShowCHinese(48, 0, CHINESE_QIN);
        OLED_ShowCHinese(64, 0, CHINESE_ZHOU);
        break;
    case WUZHOU:
        OLED_ShowCHinese(48, 0, CHINESE_WU_2);
        OLED_ShowCHinese(64, 0, CHINESE_ZHOU);
        break;
    default:
        /*后续可加入显示错误选项*/
        break;
    OLED_ShowChar(84, 0, ':', 16);
    }
}
/**
 * @brief 温度显示处理函数
 * 
 */
static void OLED_Show_Temperature(void)
{
    /*温度  纵轴坐标暂定选6*/
    /*大0*/
    OLED_DrawBMP(0, 6, 48, 16, (uint8_t*)clear_local);            /*清除温度区域内容*/
    uint8_t len = (abs(oled_weather.temperature) / 10)  == 0 ? 0 : 1;
    uint8_t pos = 4;
    uint8_t offset = 0;     
    if(oled_weather.temperature < 0)
    {
        offset = 0;
        /*负号显示*/
        if(len == 0)
             OLED_ShowChar(0 , 6, '-', 16);
        else
             OLED_ShowChar(0 + pos, 6, '-', 16);
    }
    if(len == 1)
    {
        OLED_ShowNum(8 + offset, 6, abs(oled_weather.temperature), len + 1, 16);
        OLED_ShowCHinese(24 + offset, 6, CHINESE_C);
    }
    else
    {
        OLED_ShowNum(8 + pos + offset, 6, abs(oled_weather.temperature), len + 1, 16);
        OLED_ShowCHinese(16 + offset + pos, 6, CHINESE_C);
    }
}

4、 完整程序链接

百度网盘链接:https://pan.baidu.com/s/1z4TatJjKScawEId91XgApQ
提取码:1234

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值