mirror of
https://github.com/JADE-Jerry/jcalendar.git
synced 2025-12-06 09:32:48 +08:00
Compare commits
15 Commits
v1.0.25
...
21dc3ff831
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21dc3ff831 | ||
|
|
6e3d9cd06d | ||
|
|
0865a9492a | ||
|
|
d085750073 | ||
|
|
64e5980d3d | ||
|
|
f631e8632a | ||
|
|
d1511d7035 | ||
|
|
2a65240c5a | ||
|
|
e58ad6aa75 | ||
|
|
dabe760a81 | ||
|
|
102b1b507f | ||
|
|
3cbdda3c60 | ||
|
|
a8a60e8286 | ||
|
|
bf390dada2 | ||
|
|
5856a84b8d |
40
README.md
40
README.md
@@ -2,6 +2,7 @@
|
||||
墨水屏日历,采用三色4.2寸墨水屏,展示基本月历信息,支持农历、公共假期、倒计日、天气(实时天气、每日天气)展示。<br>
|
||||
项目以低难度、低成本的方式,方便爱好者实现属于自己的低功耗月历。<br>
|
||||
<img src="./assets/img/sample.jpg" width="60%"><br>
|
||||
<img src="./assets/img/sample2.jpg" width="60%"><br>
|
||||
Bilibili连接:https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
|
||||
注:固件仅供个人下载免费使用,禁止商用。
|
||||
|
||||
@@ -28,8 +29,11 @@ Bilibili连接:https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
|
||||
* 其他
|
||||
* 按钮->(PIN_14, GND)
|
||||
* LED->22(板载)
|
||||
* 电池ADC->32
|
||||
2. 三色墨水屏排线插入时注意针脚方向,屏幕排线和驱动板排线1号针脚均是悬空,注意对齐。
|
||||
3. 电池接口需要是ph2.0,且注意正负极(开发板上有标注),如果电池的正负极反了,可以用镊子调整电池插头。
|
||||
1. 可选:电池电压检测电路,需要通过两个相同的大电阻(建议500K以上)串联,通过分压进行检测电池电压。如图
|
||||
<img src="./assets/img/battery_adc.png" width="30%"><br>
|
||||
4. 烧录固件<br>
|
||||
使用ESP32的烧录工具flash_download_tool烧录固件. [Flash Download Tools](https://www.espressif.com/en/support/download/other-tools?keys=flash+download+tools)
|
||||
1. 选择烧录的文件和烧录地址(bootloader.bin与partitions.bin烧录过一次后,就不需要重复烧录了)
|
||||
@@ -44,8 +48,8 @@ Bilibili连接:https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
|
||||
|
||||
## Button Operation Guide:
|
||||
1. **单点**
|
||||
如果处在休眠中,唤醒系统,并强制刷新月历。
|
||||
如果处在运行中,强制刷新日历。
|
||||
如果处在休眠中,唤醒系统,并刷新月历。
|
||||
如果处在运行中,强制刷新/切换日历。
|
||||
如果处在配置中,不做任何处理。
|
||||
2. **双击**
|
||||
系统正常运行中,双击进入系统配置。(并强制停止WIFI相关操作,如获取天气。)
|
||||
@@ -79,6 +83,12 @@ Bilibili连接:https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
|
||||
* 日期Tag:<br>
|
||||
1. 输入格式,yyyyMMddx,yyyy为年,每年显示设为0000,MM为月份,每月显示设为00,dd为日期,x为tag的图标(a:书签;b:金钱;c:笑脸;d:警告)。例如:00000015b,每年每月15日旁边显示$符号;00000312a,每年3月12日,显示书签符号。
|
||||
2. 最多可以设置三个tag,中间以分号隔开。例如:00000015b;00000312a
|
||||
* 课程表:<br>
|
||||
输入格式,分多段,每段用“;”隔开。
|
||||
1. 课程数段。"000",三位数字,分别代表上午、下午、晚上课程数,不可省略。如:“430”,代表上午4课,下午三课,晚上无课。
|
||||
2. 每日课程(多段)。首位为星期数,后接当日的课程,以“,”分隔。如:“二,数学,语文,英语,体育,音乐,德法,”,表示周二的课程为数学、语文...等。
|
||||
3. 样例: 430;一,语文,数学,体育,美术,科学,综合;二,数学,语文,英语,体育,音乐,德法;三,数学,英语,科学,语文,体育,书法,音乐;四,语文,数学,信息,劳动,德法,体育;五,英语,语文,数学,美术,心理,体育;
|
||||
4. 注意:分隔用的“,”“;”,必须为英文符号,不可用中文符号。
|
||||
* 保存配置后,系统自动重启。
|
||||
3. Update. OTA升级
|
||||
此项需要在浏览器内完成,通过ip地址访问配置页面,然后进入Update,选择固件文件后上传,等待。刷新完成后,页面会有成功提示。
|
||||
@@ -97,7 +107,7 @@ A: 观察22针脚的LED是否点亮,有闪烁说明固件已经刷入,如果
|
||||
3. Q: 可以支持哪种屏?<br>
|
||||
A: 仅4.2寸三色屏(目前仅支持黑白红,将来支持黑白黄)。
|
||||
4. Q: 我使用的屏应该烧录哪个固件?<br>
|
||||
A: 从经验上预估,SES的拆机屏选z21,比较新一些的选z98,如果是非常老的屏,选z15。实在不行的话,三个固件都刷一遍试试。<br>
|
||||
A: 从经验上预估,大部分比较新一些的选z98,SES的拆机屏选z21,如果是非常老的屏,选z15。实在不行的话,三个固件都刷一遍试试。<br>
|
||||
明确的丝印对应如下:<br>
|
||||
|丝印|固件|
|
||||
|-----|-----|
|
||||
@@ -108,13 +118,29 @@ A: 从经验上预估,SES的拆机屏选z21,比较新一些的选z98,如
|
||||
A: 1. 检查USB线连接是否正常。 <br>2. app的串口下拉框里是否检测到COM口。 <br>3. 把其他串口工具关闭,防止占用COM口。
|
||||
6. Q: 刷新固件的过程中提示报错。<br>
|
||||
A: 1. 换根质量较好的或短一些的USB线,或换个USB口插入。 <br>2. 可以将刷新的波特率降低一点,如选择速率低一点的115200。
|
||||
7. Q: 刷新固件后,需要重新配置吗?
|
||||
7. Q: 刷新固件后,需要重新配置吗?<br>
|
||||
A: 仅刷新app固件后,配置是保留的,所以,无需重新配置。如果刷新了分区表partition.bin,会将esp的nvs区刷新,这时候需要重新配置。
|
||||
8. Q: 填入和风天气的API Key和位置ID后,没有成功获取天气信息。
|
||||
A: 3月1日后注册的和风天气账户有API Host限制,请下载1.0.25以后版本,在配置时配置API Host信息。
|
||||
8. Q: 填入和风天气的API Key和位置ID后,没有成功获取天气信息。<br>
|
||||
A: 2025年3月1日后注册的和风天气账户有API Host限制,请下载1.0.25以后版本,在配置时配置API Host信息。
|
||||
8. Q: 如何切换日历和课程表显示?<br>
|
||||
A: 在系统运行状态下(状态灯常亮),单击按键,稍等即可切换课程表的展示。
|
||||
|
||||
## Releases
|
||||
### 1.0.25 (未测试)
|
||||
### 1.1.3
|
||||
* Fix: some bugs.
|
||||
### 1.1.2
|
||||
* Refine: 功耗
|
||||
### 1.1.1
|
||||
* New Featue: 增加对电池的测量和显示。
|
||||
### 1.1.0
|
||||
* New Feature: 课程表。
|
||||
* Refine: 对针脚统一配置。
|
||||
* Fix: some bugs.
|
||||
### 1.0.27
|
||||
* Fix: 闰月错误。(bug from nongli lib)
|
||||
### 1.0.26
|
||||
* Refine: 显示当前为第**周。(国际标准(ISO 8601):以周一作为每周起始日)
|
||||
### 1.0.25
|
||||
* Fix: 和风天气api变更,需要输入API Host(在配置页面增加配置项)
|
||||
### 1.0.24
|
||||
* Refine: 配置页面location约束改为64长度,允许输入经纬度设置天气位置(e.g. 116.41,39.92)
|
||||
|
||||
BIN
assets/img/battery_adc.png
Normal file
BIN
assets/img/battery_adc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
assets/img/sample2.jpg
Normal file
BIN
assets/img/sample2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 462 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
@@ -8,6 +8,8 @@
|
||||
// !!!preferences key限制15字符
|
||||
#define PREF_SI_CAL_DATE "SI_CAL_DATE" // 屏幕当前显示的日期
|
||||
#define PREF_SI_WEEK_1ST "SI_WEEK_1ST" // 每周第一天,0: 周日(默认),1:周一
|
||||
#define PREF_SI_TYPE "SI_TYPE" // 屏幕显示类型
|
||||
|
||||
#define PREF_QWEATHER_HOST "QWEATHER_HOST" // QWEATHER HOST
|
||||
#define PREF_QWEATHER_KEY "QWEATHER_KEY" // QWEATHER KEY/TOKEN
|
||||
#define PREF_QWEATHER_TYPE "QWEATHER_TYPE" // 0: 每日天气,1: 实时天气
|
||||
@@ -15,6 +17,7 @@
|
||||
#define PREF_CD_DAY_DATE "CD_DAY_DATE" // 倒计日
|
||||
#define PREF_CD_DAY_LABLE "CD_DAY_LABLE" // 倒计日名称
|
||||
#define PREF_TAG_DAYS "TAG_DAYS" // tag day
|
||||
#define PREF_STUDY_SCHEDULE "STUDY_SCH" // 课程表
|
||||
|
||||
// 假期信息,tm年,假期日(int8),假期日(int8)...
|
||||
#define PREF_HOLIDAY "HOLIDAY"
|
||||
|
||||
6
include/battery.h
Normal file
6
include/battery.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef __BATTERY_H__
|
||||
#define __BATTERY_H__
|
||||
|
||||
int readBatteryVoltage();
|
||||
|
||||
#endif
|
||||
@@ -1,10 +1,6 @@
|
||||
#ifndef ___LED_H__
|
||||
#define ___LED_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define PIN_LED GPIO_NUM_22
|
||||
|
||||
void led_init();
|
||||
void led_fast();
|
||||
void led_slow();
|
||||
|
||||
@@ -10,4 +10,6 @@ void si_weather();
|
||||
int si_screen_status();
|
||||
void si_screen();
|
||||
|
||||
void print_status();
|
||||
void print_status();
|
||||
|
||||
void si_warning(const char* str);
|
||||
27
include/wiring.h
Normal file
27
include/wiring.h
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
#ifndef __WIRING_H__
|
||||
#define __WIRING_H__
|
||||
|
||||
#define LOLIN32_LITE
|
||||
|
||||
|
||||
#ifdef LOLIN32_LITE
|
||||
// Output PIN
|
||||
#define SPI_MOSI GPIO_NUM_23
|
||||
#define SPI_MISO GPIO_NUM_19 // Reserved
|
||||
#define SPI_SCK GPIO_NUM_18
|
||||
#define SPI_CS GPIO_NUM_5
|
||||
#define SPI_DC GPIO_NUM_17
|
||||
#define SPI_RST GPIO_NUM_16
|
||||
#define SPI_BUSY GPIO_NUM_4
|
||||
// I2C
|
||||
#define I2C_SDA GPIO_NUM_21
|
||||
#define I2C_SCL GPIO_NUM_22
|
||||
// Other PIN
|
||||
#define KEY_M GPIO_NUM_14 // 注意:由于此按键负责唤醒,因此需要选择支持RTC唤醒的PIN脚。
|
||||
#define PIN_LED_R GPIO_NUM_22
|
||||
|
||||
#define PIN_ADC GPIO_NUM_32 // ADC
|
||||
#endif
|
||||
|
||||
#endif
|
||||
46
lib/README
46
lib/README
@@ -1,46 +0,0 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
@@ -12,6 +12,8 @@
|
||||
description = Ink Screen Calendar
|
||||
default_envs =
|
||||
z21
|
||||
z15
|
||||
z98
|
||||
|
||||
[common]
|
||||
framework = arduino
|
||||
@@ -23,6 +25,7 @@ lib_deps =
|
||||
mathertel/OneButton@^2.6.1
|
||||
https://github.com/JADE-Jerry/nongli.git
|
||||
https://github.com/tignioj/ArduinoUZlib
|
||||
https://github.com/Wh1teRabbitHU/RX8010SJ
|
||||
|
||||
[env:z98]
|
||||
build_type = release
|
||||
@@ -33,8 +36,7 @@ lib_deps = ${common.lib_deps}
|
||||
upload_speed = 460800
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = min_spiffs.csv
|
||||
build_flags =
|
||||
-D SI_DRIVER=98
|
||||
build_flags = -D SI_DRIVER=98
|
||||
|
||||
[env:z21]
|
||||
build_type = release
|
||||
@@ -45,9 +47,7 @@ lib_deps = ${common.lib_deps}
|
||||
upload_speed = 460800
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = min_spiffs.csv
|
||||
build_flags =
|
||||
-D SI_DRIVER=21
|
||||
;extra_scripts = post:extra_script.py
|
||||
build_flags = -D SI_DRIVER=21
|
||||
|
||||
[env:z15]
|
||||
build_type = release
|
||||
@@ -58,8 +58,8 @@ lib_deps = ${common.lib_deps}
|
||||
upload_speed = 460800
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = min_spiffs.csv
|
||||
build_flags =
|
||||
-D SI_DRIVER=15
|
||||
build_flags = -D SI_DRIVER=15
|
||||
|
||||
|
||||
[env:esp32c3]
|
||||
platform = espressif32
|
||||
@@ -70,6 +70,6 @@ lib_deps = ${common.lib_deps}
|
||||
upload_speed = 460800
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = min_spiffs.csv
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1 ;开启USB Slave 功能
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1 ;开启CDC 下载功能宏
|
||||
build_flags = -D SI_DRIVER=98
|
||||
; -DARDUINO_USB_MODE=1
|
||||
; -DARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <ArduinoUZlib.h> // 解压gzip
|
||||
|
||||
|
||||
struct Weather {
|
||||
String time;
|
||||
int8_t temp;
|
||||
@@ -106,9 +105,10 @@ private:
|
||||
if (isGzip) {
|
||||
// gzip解压缩
|
||||
uint8_t* outBuf = NULL;
|
||||
size_t outLen = 0;
|
||||
uint32_t outLen = 0;
|
||||
ArduinoUZlib::decompress((uint8_t*)s.c_str(), (uint32_t)s.length(), outBuf, outLen);
|
||||
error = deserializeJson(doc, (char*)outBuf, outLen);
|
||||
free(outBuf);
|
||||
} else {
|
||||
error = deserializeJson(doc, s);
|
||||
}
|
||||
|
||||
35
src/battery.cpp
Normal file
35
src/battery.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
电压 (V) 近似剩余电量 状态说明
|
||||
4.20 100% 刚刚充满,充电器断开
|
||||
4.10 ~90% 电量非常充足
|
||||
4.00 ~80% 电量充足
|
||||
3.90 ~60% 中等电量
|
||||
3.80 ~50% 中等电量(接近标称电压)
|
||||
3.75 ~40% 电量偏低
|
||||
3.70 ~30% 标称电压点,但电量已不多
|
||||
3.65 ~20% 低电量
|
||||
3.50 ~10% 极低电量,应立即充电
|
||||
3.30 0% 放电截止电压,继续放电将损坏电池
|
||||
|
||||
充电截止电压 4.2
|
||||
放电截止电压 3.3
|
||||
*/
|
||||
#include "driver/adc.h"
|
||||
#include "esp_adc_cal.h"
|
||||
|
||||
/**
|
||||
* 获取电池电压(mV)
|
||||
*/
|
||||
int readBatteryVoltage() {
|
||||
esp_adc_cal_characteristics_t adc_chars;
|
||||
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_11db, ADC_WIDTH_BIT_12, 1100, &adc_chars);
|
||||
|
||||
static const adc1_channel_t channel = ADC1_CHANNEL_4; // GPIO32
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(channel, ADC_ATTEN_11db);
|
||||
int adc_val = adc1_get_raw(channel);
|
||||
int voltage = esp_adc_cal_raw_to_voltage(adc_val, &adc_chars);
|
||||
voltage *= 2;
|
||||
|
||||
return voltage;
|
||||
}
|
||||
31
src/led.cpp
31
src/led.cpp
@@ -1,13 +1,14 @@
|
||||
#include "led.h"
|
||||
|
||||
#define PIN_LED GPIO_NUM_22
|
||||
#include <Arduino.h>
|
||||
#include "wiring.h"
|
||||
|
||||
TaskHandle_t LED_HANDLER;
|
||||
int8_t BLINK_TYPE;
|
||||
|
||||
void led_init()
|
||||
{
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
pinMode(PIN_LED_R, OUTPUT);
|
||||
}
|
||||
|
||||
void task_led(void *param)
|
||||
@@ -17,42 +18,42 @@ void task_led(void *param)
|
||||
switch(BLINK_TYPE)
|
||||
{
|
||||
case 0:
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
break;
|
||||
case 1:
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
break;
|
||||
case 2:
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
break;
|
||||
case 3:
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
break;
|
||||
case 4:
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, LOW); // On
|
||||
digitalWrite(PIN_LED_R, LOW); // On
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
break;
|
||||
default:
|
||||
digitalWrite(PIN_LED, HIGH); // Off
|
||||
digitalWrite(PIN_LED_R, HIGH); // Off
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
150
src/main.cpp
150
src/main.cpp
@@ -3,7 +3,11 @@
|
||||
|
||||
#include <WiFiManager.h>
|
||||
|
||||
#include "OneButton.h"
|
||||
#include "esp_sleep.h"
|
||||
|
||||
#include <wiring.h>
|
||||
|
||||
#include "battery.h"
|
||||
|
||||
#include "led.h"
|
||||
#include "_sntp.h"
|
||||
@@ -13,8 +17,8 @@
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define PIN_BUTTON GPIO_NUM_14 // 注意:由于此按键负责唤醒,因此需要选择支持RTC唤醒的PIN脚。
|
||||
OneButton button(PIN_BUTTON, true);
|
||||
#include "OneButton.h"
|
||||
OneButton button(KEY_M, true);
|
||||
|
||||
void IRAM_ATTR checkTicks() {
|
||||
button.tick();
|
||||
@@ -31,6 +35,7 @@ WiFiManagerParameter para_cd_day_label("cd_day_label", "倒数日(4字以内
|
||||
WiFiManagerParameter para_cd_day_date("cd_day_date", "日期(yyyyMMdd)", "", 8, "pattern='\\d{8}'"); // 城市code
|
||||
WiFiManagerParameter para_tag_days("tag_days", "日期Tag(yyyyMMddx,详见README)", "", 30); // 日期Tag
|
||||
WiFiManagerParameter para_si_week_1st("si_week_1st", "每周起始(0:周日,1:周一)", "0", 2, "pattern='\\[0-1]{1}'"); // 每周第一天
|
||||
WiFiManagerParameter para_study_schedule("study_schedule", "课程表", "0", 4000, "pattern='\\[0-9]{3}[;]$'"); // 每周第一天
|
||||
|
||||
void print_wakeup_reason() {
|
||||
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
|
||||
@@ -39,8 +44,22 @@ void print_wakeup_reason() {
|
||||
Serial.println("Wakeup caused by external signal using RTC_IO");
|
||||
break;
|
||||
case ESP_SLEEP_WAKEUP_EXT1:
|
||||
{
|
||||
Serial.println("Wakeup caused by external signal using RTC_CNTL");
|
||||
uint64_t status = esp_sleep_get_ext1_wakeup_status();
|
||||
if (status == 0) {
|
||||
Serial.println(" *None of the configured pins woke us up");
|
||||
} else {
|
||||
Serial.print(" *Wakeup pin mask: ");
|
||||
Serial.printf("0x%016llX\r\n", status);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if ((status >> i) & 0x1) {
|
||||
Serial.printf(" - GPIO%d\r\n", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
Serial.println("Wakeup caused by timer");
|
||||
break;
|
||||
@@ -51,7 +70,7 @@ void print_wakeup_reason() {
|
||||
Serial.println("Wakeup caused by ULP program");
|
||||
break;
|
||||
default:
|
||||
Serial.printf("Wakeup was not caused by deep sleep.\n");
|
||||
Serial.printf("Wakeup was not caused by deep sleep.\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +89,7 @@ void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println(".");
|
||||
print_wakeup_reason();
|
||||
Serial.println("\r\n\r\n\r\n");
|
||||
Serial.println("\r\n\r\n");
|
||||
delay(10);
|
||||
|
||||
button.setClickMs(300);
|
||||
@@ -79,7 +98,7 @@ void setup() {
|
||||
button.attachDoubleClick(buttonDoubleClick, &button);
|
||||
// button.attachMultiClick()
|
||||
button.attachLongPressStop(buttonLongPressStop, &button);
|
||||
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), checkTicks, CHANGE);
|
||||
attachInterrupt(digitalPinToInterrupt(KEY_M), checkTicks, CHANGE);
|
||||
|
||||
Serial.printf("***********************\r\n");
|
||||
Serial.printf(" J-Calendar\r\n");
|
||||
@@ -88,8 +107,24 @@ void setup() {
|
||||
Serial.printf("Copyright © 2022-2025 JADE Software Co., Ltd. All Rights Reserved.\r\n\r\n");
|
||||
|
||||
led_init();
|
||||
led_fast();
|
||||
led_on();
|
||||
delay(1000);
|
||||
int voltage = readBatteryVoltage();
|
||||
Serial.printf("Battery: %d mV\r\n", voltage);
|
||||
if(voltage < 1000) {
|
||||
Serial.println("[WARN]无电池。");
|
||||
} else if(voltage < 3000) {
|
||||
Serial.println("[WARN]电量低于3v,系统休眠。");
|
||||
go_sleep();
|
||||
} else if (voltage < 3300) {
|
||||
// 低于3.3v,电池电量用尽,屏幕给警告,然后关机。
|
||||
Serial.println("[WARN]电量低于3.3v,警告并系统休眠。");
|
||||
si_warning("电量不足,请充电!");
|
||||
go_sleep();
|
||||
}
|
||||
|
||||
Serial.println("Wm begin...");
|
||||
led_fast();
|
||||
wm.setHostname("J-Calendar");
|
||||
wm.setEnableConfigPortal(false);
|
||||
wm.setConnectTimeout(10);
|
||||
@@ -158,10 +193,10 @@ void loop() {
|
||||
|
||||
// 未在配置状态,且屏幕刷新完成,进入休眠
|
||||
if (!wm.getConfigPortalActive() && si_screen_status() > 0) {
|
||||
if(_wifi_flag) {
|
||||
if (_wifi_flag) {
|
||||
go_sleep();
|
||||
}
|
||||
if(!_wifi_flag && millis() - _wifi_failed_millis > 10 * 1000) { // 如果wifi连接不成功,等待10秒休眠
|
||||
if (!_wifi_flag && millis() - _wifi_failed_millis > 10 * 1000) { // 如果wifi连接不成功,等待10秒休眠
|
||||
go_sleep();
|
||||
}
|
||||
}
|
||||
@@ -181,6 +216,11 @@ void buttonClick(void* oneButton) {
|
||||
Serial.println("In config status.");
|
||||
} else {
|
||||
Serial.println("Refresh screen manually.");
|
||||
Preferences pref;
|
||||
pref.begin(PREF_NAMESPACE);
|
||||
int _si_type = pref.getInt(PREF_SI_TYPE);
|
||||
pref.putInt(PREF_SI_TYPE, _si_type == 0 ? 1 : 0);
|
||||
pref.end();
|
||||
si_screen();
|
||||
}
|
||||
}
|
||||
@@ -196,6 +236,7 @@ void saveParamsCallback() {
|
||||
pref.putString(PREF_CD_DAY_DATE, para_cd_day_date.getValue());
|
||||
pref.putString(PREF_TAG_DAYS, para_tag_days.getValue());
|
||||
pref.putString(PREF_SI_WEEK_1ST, strcmp(para_si_week_1st.getValue(), "1") == 0 ? "1" : "0");
|
||||
pref.putString(PREF_STUDY_SCHEDULE, para_study_schedule.getValue());
|
||||
pref.end();
|
||||
|
||||
Serial.println("Params saved.");
|
||||
@@ -232,6 +273,7 @@ void buttonDoubleClick(void* oneButton) {
|
||||
String cddDate = pref.getString(PREF_CD_DAY_DATE);
|
||||
String tagDays = pref.getString(PREF_TAG_DAYS);
|
||||
String week1st = pref.getString(PREF_SI_WEEK_1ST, "0");
|
||||
String studySchedule = pref.getString(PREF_STUDY_SCHEDULE);
|
||||
pref.end();
|
||||
|
||||
para_qweather_host.setValue(qHost.c_str(), 64);
|
||||
@@ -242,6 +284,7 @@ void buttonDoubleClick(void* oneButton) {
|
||||
para_cd_day_date.setValue(cddDate.c_str(), 8);
|
||||
para_tag_days.setValue(tagDays.c_str(), 30);
|
||||
para_si_week_1st.setValue(week1st.c_str(), 1);
|
||||
para_study_schedule.setValue(studySchedule.c_str(), 4000);
|
||||
|
||||
wm.setTitle("J-Calendar");
|
||||
wm.addParameter(¶_si_week_1st);
|
||||
@@ -252,6 +295,7 @@ void buttonDoubleClick(void* oneButton) {
|
||||
wm.addParameter(¶_cd_day_label);
|
||||
wm.addParameter(¶_cd_day_date);
|
||||
wm.addParameter(¶_tag_days);
|
||||
wm.addParameter(¶_study_schedule);
|
||||
// std::vector<const char *> menu = {"wifi","wifinoscan","info","param","custom","close","sep","erase","update","restart","exit"};
|
||||
std::vector<const char*> menu = { "wifi","param","update","sep","info","restart","exit" };
|
||||
wm.setMenu(menu); // custom menu, pass vector
|
||||
@@ -268,7 +312,6 @@ void buttonDoubleClick(void* oneButton) {
|
||||
_idle_millis = millis();
|
||||
}
|
||||
|
||||
|
||||
// 重置系统,并重启
|
||||
void buttonLongPressStop(void* oneButton) {
|
||||
Serial.println("Button long press.");
|
||||
@@ -286,71 +329,62 @@ void buttonLongPressStop(void* oneButton) {
|
||||
#define TIMEOUT_TO_SLEEP 10 // seconds
|
||||
time_t blankTime = 0;
|
||||
void go_sleep() {
|
||||
// 设置唤醒时间为下个偶数整点。
|
||||
time_t now = time(NULL);
|
||||
struct tm tmNow = { 0 };
|
||||
// Serial.printf("Now: %ld -- %s\n", now, ctime(&now));
|
||||
localtime_r(&now, &tmNow); // 时间戳转化为本地时间结构
|
||||
|
||||
uint64_t p;
|
||||
// 根据配置情况来刷新,如果未配置qweather信息,则24小时刷新,否则每2小时刷新
|
||||
Preferences pref;
|
||||
pref.begin(PREF_NAMESPACE);
|
||||
String _qweather_key = pref.getString(PREF_QWEATHER_KEY, "");
|
||||
pref.end();
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm local;
|
||||
localtime_r(&now, &local);
|
||||
if (_qweather_key.length() == 0 || weather_type() == 0) { // 没有配置天气或者使用按日天气,则第二天刷新。
|
||||
Serial.println("Sleep to next day.");
|
||||
now += 3600 * 24;
|
||||
localtime_r(&now, &tmNow); // 将新时间转成tm
|
||||
// Serial.printf("Set1: %ld -- %s\n", now, ctime(&now));
|
||||
|
||||
struct tm tmNew = { 0 };
|
||||
tmNew.tm_year = tmNow.tm_year;
|
||||
tmNew.tm_mon = tmNow.tm_mon; // 月份从0开始
|
||||
tmNew.tm_mday = tmNow.tm_mday; // 日期
|
||||
tmNew.tm_hour = 0; // 小时
|
||||
tmNew.tm_min = 0; // 分钟
|
||||
tmNew.tm_sec = 10; // 秒, 防止离线时出现时间误差,所以,延后10s
|
||||
time_t set = mktime(&tmNew);
|
||||
|
||||
p = (uint64_t)(set - time(NULL));
|
||||
Serial.printf("Sleep time: %ld seconds\n", p);
|
||||
} else {
|
||||
if (tmNow.tm_hour % 2 == 0) { // 将时间推后两个小时,偶整点刷新。
|
||||
now += 7200;
|
||||
} else {
|
||||
now += 3600;
|
||||
// Sleep to next day
|
||||
int secondsToNextDay = (24 - local.tm_hour) * 3600 - local.tm_min * 60 - local.tm_sec;
|
||||
if (secondsToNextDay <= 0) {
|
||||
secondsToNextDay += 24 * 3600;
|
||||
}
|
||||
localtime_r(&now, &tmNow); // 将新时间转成tm
|
||||
// Serial.printf("Set1: %ld -- %s\n", now, ctime(&now));
|
||||
|
||||
struct tm tmNew = { 0 };
|
||||
tmNew.tm_year = tmNow.tm_year;
|
||||
tmNew.tm_mon = tmNow.tm_mon; // 月份从0开始
|
||||
tmNew.tm_mday = tmNow.tm_mday; // 日期
|
||||
tmNew.tm_hour = tmNow.tm_hour; // 小时
|
||||
tmNew.tm_min = 0; // 分钟
|
||||
tmNew.tm_sec = 10; // 秒, 防止离线时出现时间误差,所以,延后10s
|
||||
time_t set = mktime(&tmNew);
|
||||
|
||||
p = (uint64_t)(set - time(NULL));
|
||||
Serial.printf("Sleep time: %ld seconds\n", p);
|
||||
Serial.printf("Seconds to next day: %d seconds.\n", secondsToNextDay);
|
||||
p = (uint64_t)(secondsToNextDay);
|
||||
} else {
|
||||
// Sleep to next even hour.
|
||||
int secondsToNextHour = (60 - local.tm_min) * 60 - local.tm_sec;
|
||||
if (secondsToNextHour <= 0) {
|
||||
secondsToNextHour += 3600;
|
||||
}
|
||||
if ((local.tm_hour % 2) == 0) { // 如果是奇数点,则多睡1小时
|
||||
secondsToNextHour += 3600;
|
||||
}
|
||||
Serial.printf("Seconds to next even hour: %d seconds.\n", secondsToNextHour);
|
||||
p = (uint64_t)(secondsToNextHour);
|
||||
}
|
||||
p += 10; // 额外增加10秒,避免过早唤醒
|
||||
|
||||
esp_sleep_enable_timer_wakeup(p * (uint64_t)uS_TO_S_FACTOR);
|
||||
esp_sleep_enable_ext0_wakeup(PIN_BUTTON, 0);
|
||||
esp_sleep_enable_ext0_wakeup(KEY_M, LOW);
|
||||
|
||||
// 省电考虑,关闭RTC外设和存储器
|
||||
// esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // RTC IO, sensors and ULP, 注意:由于需要按键唤醒,所以不能关闭,否则会导致RTC_IO唤醒失败
|
||||
// esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // RTC IO, sensors and ULP, 注意:由于需要按键唤醒,所以不能关闭,否则会导致RTC_IO唤醒(ext0)失败
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); //
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
|
||||
|
||||
gpio_deep_sleep_hold_dis(); // 解除所有引脚的保持状态
|
||||
|
||||
// 省电考虑,重置gpio,平均每针脚能省8ua。
|
||||
gpio_reset_pin(PIN_LED); // 减小deep-sleep电流
|
||||
gpio_reset_pin(GPIO_NUM_5); // 减小deep-sleep电流
|
||||
gpio_reset_pin(GPIO_NUM_17); // 减小deep-sleep电流
|
||||
gpio_reset_pin(GPIO_NUM_16); // 减小deep-sleep电流
|
||||
gpio_reset_pin(GPIO_NUM_4); // 减小deep-sleep电流
|
||||
gpio_reset_pin(PIN_LED_R); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_CS); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_DC); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_RST); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_BUSY); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_MOSI); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_MISO); // 减小deep-sleep电流
|
||||
gpio_reset_pin(SPI_SCK); // 减小deep-sleep电流
|
||||
gpio_reset_pin(PIN_ADC); // 减小deep-sleep电流
|
||||
gpio_reset_pin(I2C_SDA); // 减小deep-sleep电流
|
||||
gpio_reset_pin(I2C_SCL); // 减小deep-sleep电流
|
||||
|
||||
delay(10);
|
||||
Serial.println("Deep sleep...");
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#include "screen_ink.h"
|
||||
|
||||
|
||||
#include <weather.h>
|
||||
#include <API.hpp>
|
||||
#include "holiday.h"
|
||||
#include "nongli.h"
|
||||
|
||||
#include "battery.h"
|
||||
int voltage;
|
||||
|
||||
#include <_preference.h>
|
||||
|
||||
#include <U8g2_for_Adafruit_GFX.h>
|
||||
@@ -14,10 +18,10 @@
|
||||
#include "font.h"
|
||||
#define ROTATION 0
|
||||
|
||||
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, GxEPD2_DRIVER_CLASS::HEIGHT> display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ 5, /*DC=D3*/ 17, /*RST=D4*/ 16, /*BUSY=D2*/ 4));
|
||||
#include "wiring.h"
|
||||
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, GxEPD2_DRIVER_CLASS::HEIGHT> display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ SPI_CS, /*DC=D3*/ SPI_DC, /*RST=D4*/ SPI_RST, /*BUSY=D2*/ SPI_BUSY));
|
||||
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
|
||||
|
||||
|
||||
#define FONT_TEXT u8g2_font_wqy16_t_gb2312 // 224825bytes,最大字库(天气描述中“霾”,只有此字库中有)
|
||||
#define FONT_SUB u8g2_font_wqy12_t_gb2312 // 次要字体,u8g2最小字体
|
||||
const String week_str[] = { "日", "一", "二", "三", "四", "五", "六" };
|
||||
@@ -41,6 +45,10 @@ const int jrLength = 11;
|
||||
const int jrDate[] = { 101, 214, 308, 312, 501, 504, 601, 701, 801, 910, 1001, 1224, 1225 };
|
||||
const String jrText[] = { "元旦", "情人节", "妇女节", "植树节", "劳动节", "青年节", "儿童节", "建党节", "建军节", "教师节", "国庆节", "平安夜", "圣诞节" };
|
||||
|
||||
int _si_type = 0;
|
||||
String _study_schedule;
|
||||
|
||||
|
||||
struct tm tmInfo = { 0 }; // 日历显示用的时间
|
||||
|
||||
struct
|
||||
@@ -67,6 +75,11 @@ struct
|
||||
int16_t cdDayX;
|
||||
int16_t cdDayY;
|
||||
|
||||
int16_t statusX;
|
||||
int16_t statusY;
|
||||
int16_t statusW;
|
||||
int16_t statusH;
|
||||
|
||||
int16_t weatherX;
|
||||
int16_t weatherY;
|
||||
int16_t weatherW;
|
||||
@@ -98,9 +111,9 @@ void init_cal_layout_size() {
|
||||
calLayout.weekX = 10;
|
||||
calLayout.weekY = calLayout.topH - 5;
|
||||
|
||||
calLayout.lunarYearX = calLayout.topX + calLayout.topW;
|
||||
calLayout.lunarYearX = calLayout.topX + calLayout.topW + 15;
|
||||
calLayout.lunarYearY = calLayout.yearY / 2;
|
||||
calLayout.lunarDayX = calLayout.topX + calLayout.topW;
|
||||
calLayout.lunarDayX = calLayout.topX + calLayout.topW + 15;
|
||||
calLayout.lunarDayY = calLayout.yearY;
|
||||
|
||||
calLayout.cdDayX = 0;
|
||||
@@ -111,6 +124,11 @@ void init_cal_layout_size() {
|
||||
calLayout.tW = 60;
|
||||
calLayout.tH = calLayout.topH / 2;
|
||||
|
||||
calLayout.statusX = 300;
|
||||
calLayout.statusY = 0;
|
||||
calLayout.statusW = display.width() - calLayout.weatherX;
|
||||
calLayout.statusH = 14;
|
||||
|
||||
calLayout.weatherX = 300;
|
||||
calLayout.weatherY = calLayout.topY;
|
||||
calLayout.weatherW = display.width() - calLayout.weatherX;
|
||||
@@ -129,7 +147,7 @@ void init_cal_layout_size() {
|
||||
calLayout.dayH = 44;
|
||||
}
|
||||
|
||||
void draw_cal_layout() {
|
||||
void draw_cal_header() {
|
||||
uint16_t color;
|
||||
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
@@ -139,19 +157,19 @@ void draw_cal_layout() {
|
||||
int16_t daysMagin = 4;
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if((i + _week_1st) % 7 == 0 || (i + _week_1st) % 7 == 6) {
|
||||
if ((i + _week_1st) % 7 == 0 || (i + _week_1st) % 7 == 6) {
|
||||
color = GxEPD_RED;
|
||||
} else {
|
||||
color = GxEPD_BLACK;
|
||||
}
|
||||
// header background
|
||||
if(i == 0) {
|
||||
display.fillRect(0, calLayout.headerY, (display.width() - 7 * calLayout.dayW)/2, calLayout.headerH, color);
|
||||
} else if(i == 6) {
|
||||
display.fillRect((display.width() + 7 * calLayout.dayW)/2, calLayout.headerY, (display.width() - 7 * calLayout.dayW)/2, calLayout.headerH, color);
|
||||
if (i == 0) {
|
||||
display.fillRect(0, calLayout.headerY, (display.width() - 7 * calLayout.dayW) / 2, calLayout.headerH, color);
|
||||
} else if (i == 6) {
|
||||
display.fillRect((display.width() + 7 * calLayout.dayW) / 2, calLayout.headerY, (display.width() - 7 * calLayout.dayW) / 2, calLayout.headerH, color);
|
||||
}
|
||||
display.fillRect((display.width() - 7 * calLayout.dayW)/2 + i * calLayout.dayW, calLayout.headerY, calLayout.dayW, calLayout.headerH, color);
|
||||
|
||||
display.fillRect((display.width() - 7 * calLayout.dayW) / 2 + i * calLayout.dayW, calLayout.headerY, calLayout.dayW, calLayout.headerH, color);
|
||||
|
||||
// header text
|
||||
u8g2Fonts.drawUTF8(calLayout.headerX + daysMagin + (calLayout.dayW - u8g2Fonts.getUTF8Width(week_str[i].c_str())) / 2 + i * calLayout.dayW, calLayout.headerY + calLayout.headerH - 3, week_str[(i + _week_1st) % 7].c_str());
|
||||
}
|
||||
@@ -196,14 +214,15 @@ void draw_cal_year(bool partial) {
|
||||
calLayout.lunarYearX = u8g2Fonts.getCursorX() + 15;
|
||||
calLayout.lunarDayX = u8g2Fonts.getCursorX() + 15;
|
||||
|
||||
// 星期几
|
||||
// 第几周
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
u8g2Fonts.setFontMode(1);
|
||||
u8g2Fonts.setFontDirection(0);
|
||||
u8g2Fonts.setForegroundColor(todayColor);
|
||||
u8g2Fonts.setCursor(calLayout.weekX, calLayout.weekY);
|
||||
u8g2Fonts.print("星期" + week_str[tmInfo.tm_wday]);
|
||||
|
||||
char week_num[8];
|
||||
strftime(week_num, sizeof(week_num), "%V", &tmInfo); // 国际标准(ISO 8601):以周一作为每周起始日
|
||||
u8g2Fonts.printf("第%s周", week_num);
|
||||
calLayout.cdDayX = u8g2Fonts.getCursorX(); // update cd day X;
|
||||
|
||||
// 今日农历年份,e.g. 乙巳年 蛇
|
||||
@@ -246,7 +265,9 @@ void draw_cal_days(bool partial) {
|
||||
totalDays = 31;
|
||||
}
|
||||
if (monthNum == 2) {
|
||||
if ((tmInfo.tm_year + 1900) == 0 && (tmInfo.tm_year + 1900) % 100 != 0) {
|
||||
if ((tmInfo.tm_year + 1900) % 4 == 0
|
||||
&& (tmInfo.tm_year + 1900) % 100 != 0
|
||||
|| (tmInfo.tm_year + 1900) % 400 == 0) {
|
||||
totalDays = 29; // 闰二月
|
||||
} else {
|
||||
totalDays = 28; // 二月
|
||||
@@ -288,7 +309,7 @@ void draw_cal_days(bool partial) {
|
||||
}
|
||||
pref.end();
|
||||
|
||||
if(_holiday.year != tmInfo.tm_year + 1900 || _holiday.month != tmInfo.tm_mon + 1) {
|
||||
if (_holiday.year != tmInfo.tm_year + 1900 || _holiday.month != tmInfo.tm_mon + 1) {
|
||||
_holiday = {};
|
||||
}
|
||||
|
||||
@@ -310,7 +331,7 @@ void draw_cal_days(bool partial) {
|
||||
} else {
|
||||
color = GxEPD_BLACK;
|
||||
}
|
||||
|
||||
|
||||
if (tmInfo.tm_year + 1900 == _holiday.year && tmInfo.tm_mon + 1 == _holiday.month) {
|
||||
uint8_t holidayIndex = 0;
|
||||
for (; holidayIndex < _holiday.length; holidayIndex++) {
|
||||
@@ -416,8 +437,8 @@ void draw_cal_days(bool partial) {
|
||||
|
||||
// 今日农历年份,e.g. 乙巳年 蛇
|
||||
// 如果农历月份小于公历月份,那么说明是上一年
|
||||
int tg = nl_tg(tmInfo.tm_year + 1900 - (lunarMon > (tmInfo.tm_mon + 1)? 1:0));
|
||||
int dz = nl_dz(tmInfo.tm_year + 1900 - (lunarMon > (tmInfo.tm_mon + 1)? 1:0));;
|
||||
int tg = nl_tg(tmInfo.tm_year + 1900 - (lunarMon > (tmInfo.tm_mon + 1) ? 1 : 0));
|
||||
int dz = nl_dz(tmInfo.tm_year + 1900 - (lunarMon > (tmInfo.tm_mon + 1) ? 1 : 0));;
|
||||
todayLunarYear = String(nl_tg_text[tg]) + String(nl_dz_text[dz]) + "年 " + String(nl_sx_text[dz]);
|
||||
|
||||
// 今日农历日期
|
||||
@@ -568,6 +589,7 @@ void draw_cd_day(String label, String date) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void draw_special_day() {
|
||||
String str = "Special Days!!!";
|
||||
|
||||
@@ -795,6 +817,154 @@ void draw_err(bool partial) {
|
||||
}
|
||||
}
|
||||
|
||||
void draw_status(bool partial) {
|
||||
if (partial) {
|
||||
display.setPartialWindow(calLayout.statusX, calLayout.statusY, calLayout.statusW, calLayout.statusH);
|
||||
display.firstPage();
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
}
|
||||
u8g2Fonts.setFontMode(1);
|
||||
u8g2Fonts.setFontDirection(0);
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
|
||||
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
|
||||
u8g2Fonts.setFont(u8g2_font_siji_t_6x10);
|
||||
|
||||
// 电池icon
|
||||
String iconStr = "";
|
||||
if(voltage >= 4100) { // 满电
|
||||
iconStr = "\ue24b";
|
||||
} else if (voltage >= 3900) { // 多电
|
||||
iconStr = "\ue249";
|
||||
} else if (voltage >= 3700) { // 中电量
|
||||
iconStr = "\ue247";
|
||||
} else if (voltage >= 3500) { // 低电量
|
||||
iconStr = "\ue245";
|
||||
} else { // 空
|
||||
iconStr = "\ue242";
|
||||
}
|
||||
u8g2Fonts.drawUTF8(400 - 12 - 4, 10, iconStr.c_str());
|
||||
|
||||
if (partial) {
|
||||
display.nextPage();
|
||||
}
|
||||
}
|
||||
|
||||
void drawStudySchedule() {
|
||||
int i = _study_schedule.substring(0, 3).toInt();
|
||||
Serial.printf("%d\r\n", i);
|
||||
int morningClassCount = i / 100;
|
||||
int eveningClassCount = (i % 100) / 10;
|
||||
int nightClassCount = i % 10;
|
||||
int segmentCount = (morningClassCount > 0 ? 1 : 0) + (eveningClassCount > 0 ? 1 : 0) + (nightClassCount > 0 ? 1 : 0);
|
||||
String daysClassStr[7];
|
||||
int pos_begin = 4;
|
||||
int daysCount = 0;
|
||||
do {
|
||||
int pos_end = _study_schedule.indexOf(";", pos_begin);
|
||||
if (pos_end < 0) break;
|
||||
|
||||
String str = _study_schedule.substring(pos_begin, pos_end);
|
||||
str.trim();
|
||||
daysClassStr[daysCount++] = str;
|
||||
// Serial.printf("%d : %d : %s\r\n", daysCount, pos_end, str.c_str());
|
||||
pos_begin = pos_end + 1;
|
||||
} while (true);
|
||||
|
||||
// 计算Cell尺寸
|
||||
int cellHeight = (display.height() - calLayout.topH - calLayout.headerH - segmentCount * 2) / (morningClassCount + eveningClassCount + nightClassCount);
|
||||
int cellWidth = display.width() / daysCount;
|
||||
int marginLeft = (display.width() - cellWidth * daysCount) / 2;
|
||||
|
||||
// cell line
|
||||
display.drawFastHLine(0, calLayout.topH, display.width(), GxEPD_RED);
|
||||
display.drawFastHLine(0, calLayout.topH - 1, display.width(), GxEPD_RED);
|
||||
display.drawFastHLine(0, calLayout.topH + calLayout.headerH - 1, display.width(), GxEPD_RED);
|
||||
display.drawFastHLine(0, calLayout.topH + calLayout.headerH, display.width(), GxEPD_RED);
|
||||
int segmentIndex = 0;
|
||||
uint16_t max_y = calLayout.topH + calLayout.headerH; // 水平分割线
|
||||
for (int x = 1; x <= morningClassCount; x++) {
|
||||
max_y += cellHeight;
|
||||
display.drawFastHLine(0, max_y, display.width(), GxEPD_BLACK);
|
||||
}
|
||||
if (eveningClassCount > 0) {
|
||||
max_y += 2;
|
||||
display.drawFastHLine(0, max_y, display.width(), GxEPD_BLACK);
|
||||
}
|
||||
for (int x = 1; x <= eveningClassCount; x++) {
|
||||
max_y += cellHeight;
|
||||
display.drawFastHLine(0, max_y, display.width(), GxEPD_BLACK);
|
||||
}
|
||||
if (nightClassCount > 0) {
|
||||
max_y += 2;
|
||||
display.drawFastHLine(0, max_y, display.width(), GxEPD_BLACK);
|
||||
}
|
||||
for (int x = 1; x <= nightClassCount; x++) {
|
||||
max_y += cellHeight;
|
||||
display.drawFastHLine(0, max_y, display.width(), GxEPD_BLACK);
|
||||
}
|
||||
for (int x = 1; x < daysCount; x++) { // 垂直分割线
|
||||
display.drawFastVLine(cellWidth * x, calLayout.headerH + calLayout.topH, max_y - calLayout.headerH - calLayout.topH, GxEPD_BLACK);
|
||||
}
|
||||
|
||||
for (int x = 0; x < daysCount; x++) {
|
||||
// Serial.println(daysClassStr[x].c_str());
|
||||
int pos_begin = 0;
|
||||
int yStep = 0;
|
||||
int segmentIndex = 0;
|
||||
|
||||
|
||||
uint16_t fontColor = GxEPD_BLACK;
|
||||
do {
|
||||
int pos_end = daysClassStr[x].indexOf(",", pos_begin);
|
||||
String ss = daysClassStr[x].substring(pos_begin, pos_end);
|
||||
ss.trim();
|
||||
// Serial.printf("%s\r\n", ss.c_str());
|
||||
if (yStep == 0) {
|
||||
if (week_str[tmInfo.tm_wday] == ss) {
|
||||
display.fillRect(cellWidth * x + marginLeft, calLayout.topH, cellWidth, calLayout.headerH, GxEPD_RED);
|
||||
fontColor = GxEPD_RED;
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_RED);
|
||||
u8g2Fonts.setForegroundColor(GxEPD_WHITE);
|
||||
} else {
|
||||
fontColor = GxEPD_BLACK;
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
|
||||
u8g2Fonts.setForegroundColor(fontColor);
|
||||
}
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
u8g2Fonts.drawUTF8(cellWidth * x + marginLeft + (cellWidth - u8g2Fonts.getUTF8Width(ss.c_str())) / 2, calLayout.topH + calLayout.headerH - 4, ss.c_str());
|
||||
} else if (yStep > 0) {
|
||||
if (yStep == morningClassCount + 1 || yStep == (morningClassCount + eveningClassCount + 1)) {
|
||||
segmentIndex++;
|
||||
}
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
|
||||
u8g2Fonts.setForegroundColor(fontColor);
|
||||
int fontHeight = 12;
|
||||
if (cellHeight < 18) {
|
||||
u8g2Fonts.setFont(FONT_SUB);
|
||||
fontHeight = 12;
|
||||
} else {
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
fontHeight = 16;
|
||||
}
|
||||
u8g2Fonts.drawUTF8(cellWidth * x + marginLeft + (cellWidth - u8g2Fonts.getUTF8Width(ss.c_str())) / 2, calLayout.topH + calLayout.headerH + cellHeight * yStep - 1 - (cellHeight - 1 - fontHeight) / 2 + 2 * segmentIndex, ss.c_str());
|
||||
}
|
||||
|
||||
if (pos_end < 0) break;
|
||||
pos_begin = pos_end + 1;
|
||||
yStep++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// 显示当前周几, 农历年月不显示
|
||||
todayLunarYear = "";
|
||||
todayLunarDay = "";
|
||||
uint16_t color = (tmInfo.tm_wday == 0 || tmInfo.tm_wday == 6) ? GxEPD_RED : GxEPD_BLACK;
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
|
||||
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
u8g2Fonts.drawUTF8(calLayout.lunarDayX, calLayout.lunarDayY, ("星期" + week_str[tmInfo.tm_wday]).c_str());
|
||||
}
|
||||
|
||||
///////////// Calendar //////////////
|
||||
/**
|
||||
* 处理日历信息
|
||||
@@ -809,6 +979,10 @@ void si_calendar() {
|
||||
_cd_day_date = pref.getString(PREF_CD_DAY_DATE);
|
||||
_tag_days_str = pref.getString(PREF_TAG_DAYS);
|
||||
_week_1st = pref.getString(PREF_SI_WEEK_1ST, "0").toInt();
|
||||
_study_schedule = pref.getString(PREF_STUDY_SCHEDULE);
|
||||
_si_type = pref.getInt(PREF_SI_TYPE);
|
||||
if (_study_schedule.isEmpty()) _si_type = 0;
|
||||
|
||||
pref.end();
|
||||
|
||||
time_t now = 0;
|
||||
@@ -836,8 +1010,7 @@ void si_calendar() {
|
||||
time_t set = mktime(&tmInfo);
|
||||
timeval tv;
|
||||
tv.tv_sec = set;
|
||||
settimeofday(&tv, nullptr);
|
||||
isSetOK = true;
|
||||
isSetOK = (settimeofday(&tv, nullptr) == 0);
|
||||
Serial.println("WARN: Set system time by api time.");
|
||||
} else {
|
||||
Serial.println("ERR: Fail to format api time.");
|
||||
@@ -874,27 +1047,38 @@ int si_calendar_status() {
|
||||
void task_screen(void* param) {
|
||||
Serial.println("[Task] screen update begin...");
|
||||
|
||||
voltage = readBatteryVoltage();
|
||||
|
||||
delay(100);
|
||||
|
||||
display.init(115200); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能
|
||||
display.setRotation(ROTATION); // 设置屏幕旋转1和3是横向 0和2是纵向
|
||||
u8g2Fonts.begin(display);
|
||||
|
||||
init_cal_layout_size();
|
||||
display.setFullWindow();
|
||||
display.firstPage();
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
draw_cal_layout();
|
||||
draw_cal_days(false);
|
||||
draw_cal_year(false);
|
||||
do {
|
||||
if (_si_type == 1) {
|
||||
drawStudySchedule();
|
||||
} else {
|
||||
draw_cal_days(false);
|
||||
draw_cal_header();
|
||||
}
|
||||
|
||||
// 倒计日
|
||||
draw_cd_day(_cd_day_label, _cd_day_date);
|
||||
draw_cal_year(false);
|
||||
|
||||
if (weather_status() == 1) {
|
||||
draw_weather(false);
|
||||
} else if (weather_status() == 2) {
|
||||
draw_err(false);
|
||||
}
|
||||
// 倒计日
|
||||
draw_cd_day(_cd_day_label, _cd_day_date);
|
||||
|
||||
display.display();
|
||||
if (weather_status() == 1) {
|
||||
draw_weather(false);
|
||||
}
|
||||
if (voltage > 1000 && voltage < 4300) {
|
||||
draw_status(false);
|
||||
}
|
||||
} while (display.nextPage());
|
||||
|
||||
int32_t _calendar_date = (tmInfo.tm_year + 1900) * 10000 + (tmInfo.tm_mon + 1) * 100 + tmInfo.tm_mday;
|
||||
|
||||
@@ -904,6 +1088,7 @@ void task_screen(void* param) {
|
||||
pref.end();
|
||||
|
||||
display.powerOff(); // !!!important!!!, 关闭屏幕,否则会多0.5ma的空载电流(全屏刷新的话会自动关闭,局部刷新必须手动关闭)
|
||||
display.hibernate();
|
||||
Serial.println("[Task] screen update end...");
|
||||
|
||||
_screen_status = 1;
|
||||
@@ -935,4 +1120,41 @@ void print_status() {
|
||||
Serial.printf("Weather: %d\n", weather_status());
|
||||
Serial.printf("Calendar: %d\n", si_calendar_status());
|
||||
Serial.printf("Screen: %d\n", si_screen_status());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void si_warning(const char* str) {
|
||||
Serial.println("Screen warning...");
|
||||
display.init(115200); // 串口使能 初始化完全刷新使能 复位时间 ret上拉使能
|
||||
display.setRotation(ROTATION); // 设置屏幕旋转1和3是横向 0和2是纵向
|
||||
u8g2Fonts.begin(display);
|
||||
|
||||
display.setFullWindow();
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
do {
|
||||
u8g2Fonts.setFontMode(1);
|
||||
u8g2Fonts.setFontDirection(0);
|
||||
u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
|
||||
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
|
||||
|
||||
u8g2Fonts.setFont(u8g2_font_open_iconic_all_4x_t);
|
||||
int space = 8;
|
||||
int w = u8g2Fonts.getUTF8Width("\u0118") + space;
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
w += u8g2Fonts.getUTF8Width(str);
|
||||
|
||||
u8g2Fonts.setForegroundColor(GxEPD_RED);
|
||||
u8g2Fonts.setFont(u8g2_font_open_iconic_all_4x_t);
|
||||
u8g2Fonts.setCursor((display.width() - w) / 2, (display.height() + 32) / 2);
|
||||
u8g2Fonts.print("\u0118");
|
||||
|
||||
u8g2Fonts.setForegroundColor(GxEPD_BLACK);
|
||||
u8g2Fonts.setCursor(u8g2Fonts.getCursorX() + space, u8g2Fonts.getCursorY() - 5);
|
||||
u8g2Fonts.setFont(FONT_TEXT);
|
||||
u8g2Fonts.print(str);
|
||||
} while (display.nextPage());
|
||||
|
||||
display.powerOff();
|
||||
display.hibernate();
|
||||
}
|
||||
Reference in New Issue
Block a user